Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any way to replace a file? #60

Open
escequi opened this issue Nov 23, 2022 · 13 comments
Open

Any way to replace a file? #60

escequi opened this issue Nov 23, 2022 · 13 comments

Comments

@escequi
Copy link

escequi commented Nov 23, 2022

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
Copy link
Owner

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
Copy link

Szustarol commented Dec 3, 2022

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
Copy link
Owner

@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
Copy link

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
Copy link
Author

escequi commented Dec 6, 2022

@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
Copy link

Hi,

is there a way to replace a file in PYZarchive?

@extremecoders-re
Copy link
Owner

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
Copy link

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
Copy link
Owner

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

@sparklive
Copy link

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

looking forward to your article

@Serg4y
Copy link

Serg4y commented Jul 12, 2023

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

@in1nit1t
Copy link

in1nit1t commented Jan 9, 2024

Please try pyipx, hope it helps you

@qteyrqer
Copy link

qteyrqer commented Aug 2, 2024

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

Have you finished the article?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants