Skip to content

Instantly share code, notes, and snippets.

@dfirfpi
Last active May 4, 2024 08:17
Show Gist options
  • Save dfirfpi/113ff71274a97b489dfd to your computer and use it in GitHub Desktop.
Save dfirfpi/113ff71274a97b489dfd to your computer and use it in GitHub Desktop.
Windows 10 Prefetch (native) Decompress
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2015, Francesco "dfirfpi" Picasso <francesco.picasso@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Python Windows-only utility to decompress MAM compressed files."""
import binascii
import ctypes
import struct
import sys
def tohex(val, nbits):
"""Utility to convert (signed) integer to hex."""
return hex((val + (1 << nbits)) % (1 << nbits))
def main():
"""Utility core."""
if len(sys.argv) != 3:
sys.exit('Missing params [win10compressed.pf] [win10decompressed.pf]')
NULL = ctypes.POINTER(ctypes.c_uint)()
SIZE_T = ctypes.c_uint
DWORD = ctypes.c_uint32
USHORT = ctypes.c_uint16
UCHAR = ctypes.c_ubyte
ULONG = ctypes.c_uint32
# You must have at least Windows 8, or it should fail.
try:
RtlDecompressBufferEx = ctypes.windll.ntdll.RtlDecompressBufferEx
except AttributeError:
sys.exit('You must have Windows with version >=8.')
RtlGetCompressionWorkSpaceSize = \
ctypes.windll.ntdll.RtlGetCompressionWorkSpaceSize
with open(sys.argv[1], 'rb') as fin:
header = fin.read(8)
compressed = fin.read()
signature, decompressed_size = struct.unpack('<LL', header)
calgo = (signature & 0x0F000000) >> 24
crcck = (signature & 0xF0000000) >> 28
magic = signature & 0x00FFFFFF
if magic != 0x004d414d :
sys.exit('Wrong signature... wrong file?')
if crcck:
# I could have used RtlComputeCrc32.
file_crc = struct.unpack('<L', compressed[:4])[0]
crc = binascii.crc32(header)
crc = binascii.crc32(struct.pack('<L',0), crc)
compressed = compressed[4:]
crc = binascii.crc32(compressed, crc)
if crc != file_crc:
sys.exit('Wrong file CRC {0:x} - {1:x}!'.format(crc, file_crc))
compressed_size = len(compressed)
ntCompressBufferWorkSpaceSize = ULONG()
ntCompressFragmentWorkSpaceSize = ULONG()
ntstatus = RtlGetCompressionWorkSpaceSize(USHORT(calgo),
ctypes.byref(ntCompressBufferWorkSpaceSize),
ctypes.byref(ntCompressFragmentWorkSpaceSize))
if ntstatus:
sys.exit('Cannot get workspace size, err: {}'.format(
tohex(ntstatus, 32)))
ntCompressed = (UCHAR * compressed_size).from_buffer_copy(compressed)
ntDecompressed = (UCHAR * decompressed_size)()
ntFinalUncompressedSize = ULONG()
ntWorkspace = (UCHAR * ntCompressFragmentWorkSpaceSize.value)()
ntstatus = RtlDecompressBufferEx(
USHORT(calgo),
ctypes.byref(ntDecompressed),
ULONG(decompressed_size),
ctypes.byref(ntCompressed),
ULONG(compressed_size),
ctypes.byref(ntFinalUncompressedSize),
ctypes.byref(ntWorkspace))
if ntstatus:
sys.exit('Decompression failed, err: {}'.format(
tohex(ntstatus, 32)))
if ntFinalUncompressedSize.value != decompressed_size:
print 'Decompressed with a different size than original!'
with open(sys.argv[2], 'wb') as fout:
fout.write(bytearray(ntDecompressed))
print 'Lucky man, you have your prefetch file ready to be parsed!'
if __name__ == "__main__":
main()
@DJP89
Copy link

DJP89 commented Dec 4, 2020

So Im trying to decompress a .png that for some reason has MAM encoding, or appears to. I keep getting errors with the script, but did a sanity check with a file from c:\windows\prefetch and that worked. The specific error code is: 0xc0000242L

@dfirfpi
Copy link
Author

dfirfpi commented Dec 28, 2020

@DJP89 don't know about the PNG, most likely you will need to adjust the script.

@toomanydavids11
Copy link

toomanydavids11 commented Apr 23, 2022

Hi @dfirfpi, I'm doing some prefetch research as part of some postgrad study and found your super helpful tool. Thank you!

I had a minor issue getting it running on Python 3.10.4. To get it working I just modified the code to enclose the print commands at row 104 and 109 with parenthesis as per below.

    if ntFinalUncompressedSize.value != decompressed_size:
        print ('Decompressed with a different size than original!')

    with open(sys.argv[2], 'wb') as fout:
        fout.write(bytearray(ntDecompressed))

    print ('Lucky man, you have your prefetch file ready to be parsed!')

@synner88
Copy link

synner88 commented May 22, 2023

Running Python 3.9.16 on Cygwin 3.4.6 on Win10.0.19045.2965 x64.

To accommodate the possibility of running non-Windows-native Python and load the ntdll.dll library. I modified the try ... RtlDecompressBufferEx block as follows:

try:
	RtlDecompressBufferEx = ctypes.windll.ntdll.RtlDecompressBufferEx
	RtlGetCompressionWorkSpaceSize = \
		ctypes.windll.ntdll.RtlGetCompressionWorkSpaceSize
except AttributeError:
	#sys.exit('You must have Windows with version >=8.')

	# Handle running non-Windows-native Python.
	try:
		ntdll =  ctypes.CDLL("ntdll.dll")
	except Exception as e:
		print(type(e).__name__ + ": " + str(e), file=sys.stderr)
		exit(1)
	else:
		RtlDecompressBufferEx = ntdll.RtlDecompressBufferEx
		RtlGetCompressionWorkSpaceSize = ntdll.RtlGetCompressionWorkSpaceSize

But when I get to the actual RtlDecompressBufferEx call:

ntstatus = RtlDecompressBufferEx(
	USHORT(calgo),
	ctypes.byref(ntDecompressed),
	ULONG(decompressed_size),
	ctypes.byref(ntCompressed),
	ULONG(compressed_size),
	ctypes.byref(ntFinalUncompressedSize),
	ctypes.byref(ntWorkspace))

if ntstatus:
	raise ValueError('Decompression failed, err: {}'.\
		format(tohex(ntstatus, 32)))

I get ValueError: Decompression failed, err: 0xc0000242 which is STATUS_BAD_COMPRESSION_BUFFER.
I tried different ctype calls & type modifications with no luck.

I also tried running it via native-Windpws Python 3.11 from the MS Store.
Exactly same error code.
I recon either the input or ouput buffer is incorrectly cast / passed to RtlDecompressBufferEx but I lack skill / knowledge to debug WinNT API + Python ctypes...

Any ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment