Decompile exe file into Python source code

Keywords: Python

The commands used to package into a single file are:

pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data="h.ico;/"

The commands used to package into folders are:

pyinstaller -w --icon=h.ico auto_organize_gui.py --add-data="h.ico;."

No matter which packaging method, an exe file will be left.

one   Extract pyc files from exe

Extract the pyc file through the pyinstxtracker.py script, which is downloaded in Python exe unpacker of github project at:

https://github.com/countercept/Python-exe-unpacker

After downloading the project, copy the pyinstxtracker.py script file to a directory at the same level as exe.

Then enter cmd in the directory where exe is located to execute:

Python pyinstxtractor.py auto_organize_gui.exe

After execution, you get the exe file name plus_ Folder with extracted suffix

two   Decompile pyc file into py script

use   uncompyle6   The library can be decoded and installed directly using pip:

pip install uncompyle6

uncompyle6 can decompile files ending in. pyc suffix in two command forms:

  1. uncompyle6 xxx.pyc>xxx.py

  2. uncompyle6 -o xxx.py xxx.pyc

The pyc file extracted from pyinstaller cannot be decompiled directly. The entry runs a 16 byte class   magic   and   The timestamp was removed.

If you decompile directly, for example, execute   uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pyc

The following error will be reported: ImportError: Unknown magic number 227 in auto_organize_gui.exe_extracted\auto_organize_gui.pyc

Using a text editor that supports hexadecimal editing, you can see that the first 16 bytes have been removed, and the first four bytes are magic. These four bytes will change with the system and Python version and must be consistent. The last four bytes, including timestamp and some other information, can be filled in at will. The header byte of the "struct" file in the same directory is a kind of pyc file, which can be used to supplement. After selecting the first 16 bytes to insert, you only need to replace the first 4 bytes with magic in the current environment, and then execute:

uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pyc>auto_organize_gui.py

You can successfully compile the entry file.

Dependency files are similar. Their pyc files start from 12 bytes and are missing 4 bytes. Here we select the 13th byte and insert four bytes. Then execute:

uncompyle6 auto_organize_gui.exe_extracted/PYZ-00.pyz_extracted/auto_organize.pyc > auto_organize.py

Successfully decompile the dependent files.

If an exe needs to be decompiled and the Python script has less than 3 files, we can all operate it manually.

However, if an exe involves dozens or even hundreds of Python scripts that need to be decompiled, the workload of manual operation is too huge. We consider implementing the above process in Python to achieve the effect of batch decompilation.

Extract pyc from exe

import os
import sys
import pyinstxtractor

exe_file = r"D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe"
sys.argv = ['pyinstxtractor', exe_file]
pyinstxtractor.main()
#  Restore current directory location
os.chdir("..")
[*] Processing D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 37
[*] Length of package: 9491710 bytes
[*] Found 984 files in CArchive
[*] Beginning extraction...please standby
[*] Found 157 files in PYZ archive
[*] Successfully extracted pyinstaller archive: D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe

You can now use a Python decompiler on the pyc files within the extracted directory

Preprocessing pyc file repair check head

def find_main(pyc_dir):
    for pyc_file in os.listdir(pyc_dir):
        if not pyc_file.startswith("pyi-") and pyc_file.endswith("manifest"):
            main_file = pyc_file.replace(".exe.manifest", "")
            result = f"{pyc_dir}/{main_file}"
            if os.path.exists(result):
                return main_file

pyc_dir = os.path.basename(exe_file)+"_extracted"
main_file = find_main(pyc_dir)
main_file

Read the first 4 bytes of the pyc file extracted from the pyz directory as the benchmark:

pyz_dir = f"{pyc_dir}/PYZ-00.pyz_extracted"
for pyc_file in os.listdir(pyz_dir):
    if pyc_file.endswith(".pyc"):
        file = f"{pyz_dir}/{pyc_file}"
        break
with open(file, "rb") as f:
    head = f.read(4)
list(map(hex, head))
['0x42', '0xd', '0xd', '0xa']

Calibration entry class:

import shutil
if os.path.exists("pycfile_tmp"):
    shutil.rmtree("pycfile_tmp")
os.mkdir("pycfile_tmp")
main_file_result = f"pycfile_tmp/{main_file}.pyc"
with open(f"{pyc_dir}/{main_file}", "rb") as read, open(main_file_result, "wb") as write:
    write.write(head)
    write.write(b"\0"*12)
    write.write(read.read())

Calibration subclass:

pyz_dir = f"{pyc_dir}/PYZ-00.pyz_extracted"
for pyc_file in os.listdir(pyz_dir):
    pyc_file_src = f"{pyz_dir}/{pyc_file}"
    pyc_file_dest = f"pycfile_tmp/{pyc_file}"
    print(pyc_file_src, pyc_file_dest)
    with open(pyc_file_src, "rb") as read, open(pyc_file_dest, "wb") as write:
        write.write(read.read(12))
        write.write(b"\0"*4)
        write.write(read.read())

Start Decompilation

from uncompyle6.bin import uncompile

if not os.path.exists("py_result"):
    os.mkdir("py_result")
for pyc_file in os.listdir("pycfile_tmp"):
    sys.argv = ['uncompyle6', '-o',
                f'py_result/{pyc_file[:-1]}', f'pycfile_tmp/{pyc_file}']
    uncompile.main_bin()

Posted by vijdev on Tue, 23 Nov 2021 18:29:31 -0800