extremecoders-re / pyinstxtractor

PyInstaller Extractor
GNU General Public License v3.0
2.81k stars 604 forks source link

Any way to replace a file? #60

Open escequi opened 1 year ago

escequi commented 1 year ago

Ok so basically the program i'm trying to mess with is from 3.9, using pyinstaller 4.8, i managed to extract it just fine, but since i can't decompile the files anyways, i just wanted to replace the cacert.pem from certifi to use my own fiddler signed cert, is there any way i can just replace that file on the .exe or any way to recompile the .exe with it replaced? (straight up running pyinstaller main.pyc throw me errors)

extremecoders-re commented 1 year ago

pyinstxtractor doesn't support replacing files. I've a tool which supports replace & repacking pyinstaller exe files but its not open source at the moment and requires more improvements.

If you're familiar with internal pyinstaller format you can replace the file manually.

The cacert.pem will be stored in the CArchive and likely zlib compressed. You can search and replace the zlib compressed original cacert bytes with your patched & compressed cacert using a hex editor. Also ensure the patched bytes are smaller or equal in size to the original.

Szustarol commented 1 year ago

I have the same problem and have followed your advice, with the following source:

import zlib

target_file = 'installed.exe'
source_cert_file = 'cacert.pem'
replacement_cert_file = 'cert.pem'

with open(target_file, 'rb') as f:
    file_data = f.read()

for compression_level in range(-1, 10):
    # zlib compress source 
    with open(source_cert_file, 'rb') as f:
        source_zlib_data = zlib.compress(f.read(), level=compression_level)

    pos = file_data.find(source_zlib_data)

    if pos != -1:
        print("Compression level:", compression_level)
        print("Found position:", pos)
        break

print("Compressing at level", compression_level)

with open(replacement_cert_file, 'rb') as f:
    target_zlib_data = zlib.compress(f.read(), level=compression_level)

print("Source len: ", len(source_zlib_data), "replacement len: ", len(target_zlib_data))

assert len(source_zlib_data) >= len(target_zlib_data)

print(target_zlib_data)

target_zlib_data = target_zlib_data + b'\0'*(len(source_zlib_data)-len(target_zlib_data))

output_data = file_data.replace(source_zlib_data, target_zlib_data)

with open("replaced_" + target_file, 'wb') as f:
    f.write(output_data)

As You can see, I have padded the compressed zlib with binary zeros. If i don't pad, I end up with [9584] Failed to extract LIBBZ2.dll: decompression resulted in return code -3! when trying to run the code, and when I do pad, the software doesn't seem to register the file, and indeed when I use pyinxtractor on the source file I end up with a certifi folder with the cacert.pem file inside, but after replacing the binary content, the whole folder is gone when extracted with pyinxtractor. So simple replacement of compressed zlib doesn't really help.

Edit: the folder doesn't show up if I pad with space (' '), and pyinxtractor throws assert len(data) == entry.uncmprsdDataSize failed when i pad with b'\0'.

To no avail I have also tried using compression level 0 and just appending newlines until the compressed size was equal:

while len(target_zlib_data) != len(source_zlib_data):
    if len(target_zlib_data) < len(source_zlib_data):
        target_zlib_source += "\n".encode('utf-8') * (len(source_zlib_data)-len(target_zlib_data))
        target_zlib_data = zlib.compress(target_zlib_source, level=0)
    if len(target_zlib_data) > len(source_zlib_data):
        target_zlib_source = target_zlib_source[:-1]
        target_zlib_data = zlib.compress(target_zlib_source, level=0)

I am sure my problem is no longer related to this software, but It would be great if You could further elaborate on this subject.

extremecoders-re commented 1 year ago

@Szustarol Hi, added a wiki article which describes the process of replacing a file in the CArchive. https://github.com/extremecoders-re/pyinstxtractor/wiki/Replacing-files-inside-the-CArchive

Szustarol commented 1 year ago

Thank you so much for this article! Not only does it solve an important problem but is actually very insightful of pysintaller and the extractor itself.

escequi commented 1 year ago

@Szustarol Hi, added a wiki article which describes the process of replacing a file in the CArchive. https://github.com/extremecoders-re/pyinstxtractor/wiki/Replacing-files-inside-the-CArchive

thank you, that's very helpful

sparklive commented 1 year ago

Hi,

is there a way to replace a file in PYZarchive?

extremecoders-re commented 1 year ago

is there a way to replace a file in PYZarchive?

It is possible. The PYZArchive has its own table of contents (toc) which is stored as a marshalled list. The toc stores the filename, offset and size of the items. The individual items in the PYZArchive are Zlib Compressed.

To replace a file, its the same as overwriting the original bytes with the new data. Both the original and new data are Zlib Compressed. If the size of compressed new data is exactly the same as original, no further changes need to be done.

If the new size is less than the original, the toc also needs to be updated with the new size. There will be some extra bytes at the end but that is fine and will be ignored.

If the new size is more than original, it will require further invasive modifications. Like shifting all the succeeding files and also updating the toc with the new offset for all files that are affected.

sparklive commented 1 year ago

is there a way to replace a file in PYZarchive?

It is possible. The PYZArchive has its own table of contents (toc) which is stored as a marshalled list. The toc stores the filename, offset and size of the items. The individual items in the PYZArchive are Zlib Compressed.

To replace a file, its the same as overwriting the original bytes with the new data. Both the original and new data are Zlib Compressed. If the size of compressed new data is exactly the same as original, no further changes need to be done.

If the new size is less than the original, the toc also needs to be updated with the new size. There will be some extra bytes at the end but that is fine and will be ignored.

If the new size is more than original, it will require further invasive modifications. Like shifting all the succeeding files and also updating the toc with the new offset for all files that are affected.

I appreciate your answer very much. I know some golang. I can make some references from your golang project, right?

extremecoders-re commented 1 year ago

@sparklive You can. I will also add a wiki article later about replacing files in the PYZArchive.

sparklive commented 1 year ago

@sparklive You can. I will also add a wiki article later about replacing files in the PYZArchive.

looking forward to your article

Serg4y commented 1 year ago

Hello!Good job! And how to replace *.pyc file (main.pyc)

in1nit1t commented 8 months ago

Please try pyipx, hope it helps you

qteyrqer commented 1 month ago

@sparklive You can. I will also add a wiki article later about replacing files in the PYZArchive.

Have you finished the article?