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:
-
uncompyle6 xxx.pyc>xxx.py
-
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()