龙空技术网

Python exe 文件反编译为 Python 脚本

不秃头程序员 434

前言:

眼前姐妹们对“编译pyd”都比较着重,小伙伴们都想要学习一些“编译pyd”的相关内容。那么小编也在网络上网罗了一些对于“编译pyd””的相关资讯,希望小伙伴们能喜欢,看官们快快来学习一下吧!

前言Python 可执行文件(.exe)反编译为 Python 脚本是一项有趣的技术挑战,可以帮助我们理解程序的工作原理,以及可能包含的逻辑和算法。虽然反编译不是一项简单的任务,并且对于使用各种保护措施的程序可能无效,但对于一般情况下的 Python 可执行文件,我们可以尝试使用一些工具来进行反编译。下面我们就来学习如何将 Python 可执行文件(.exe)反编译为 Python 脚本。版本Python 3.9反编译反编译是将已编译的程序代码还原为其原始源代码的过程。在 Python 中,由于其解释性质,通常没有像编译语言那样生成的二进制文件,但是我们可以将 Python 脚本转换为字节码文件(.pyc),而 .exe 文件通常是由 pyinstaller、cx_Freeze 等工具编译生成的。Python 可执行文件(.exe)反编译Python 可执行文件(.exe)反编译为 Python 脚本主要分为两个步骤,(1)从 .exe 文件中提取 pyc 文件 (2)将 pyc 文件转换为 Python 脚本。打包一个简单的 .exe 可执行文件

# student.pyclass Student:    def __init__(self, name, age, gender):        self.name = name        self.age = age        self.gender = gender    def get_name(self):        return self.name    def get_age(self):        return self.age    def get_gender(self):        return self.gender    def set_name(self, name):        self.name = name    def set_age(self, age):        self.age = age    def set_gender(self, gender):        self.gender = gender    def display_info(self):        print("Name:", self.name)        print("Age:", self.age)        print("Gender:", self.gender)# main.pyimport timefrom student import Studentif __name__ == "__main__":    # Create a student object    student1 = Student("Alice", 20, "Female")    # Display student information    student1.display_info()    # Update student information    student1.set_age(21)    student1.display_info()    time.sleep(10)# 使用 pyinstaller 构建可执行 .exepyinstaller --onefile   -p venv/Lib/site-packages .\print-student\main.py
提取 pyc 文件

使用脚本提取

pyi-archive_viewerPyInstaller 自己提供的工具,它可以直接提取打包结果exe中的pyc文件。详细介绍可参考官方文档:

# 使用 pyi-archive_viewer 查看文件并提取> pyi-archive_viewer .\main.exeOptions in 'main.exe' (PKG/CArchive): pyi-contents-directory _internalContents of 'main.exe' (PKG/CArchive): position, length, uncompressed_length, is_compressed, typecode, name 0, 199, 269, 1, 'm', 'struct' 199, 2008, 3700, 1, 'm', 'pyimod01_archive' 2207, 7671, 17413, 1, 'm', 'pyimod02_importers' 9878, 1760, 4029, 1, 'm', 'pyimod03_ctypes' 11638, 644, 1074, 1, 'm', 'pyimod04_pywin32' 12282, 603, 851, 1, 's', 'pyiboot01_bootstrap' 12885, 229, 295, 1, 's', 'main'...... 4721057, 408332, 1123832, 1, 'b', 'unicodedata.pyd' 5129389, 702999, 702999, 0, 'z', 'PYZ-00.pyz'?U: go up one levelO <name>: open embedded archive with given name // 打开包查看文件X <name>: extract file with given name // 提取文件S: list the contents of current archive againQ: quit? x main        Output filename? main.pyc? o PYZ-00.pyzContents of 'PYZ-00.pyz' (PYZ): is_package, position, length, name 0, 17, 2647, '_compat_pickle'...... 0, 543553, 531, 'student' 0, 544084, 19733, 'subprocess' 0, 563817, 27425, 'tarfile' 0, 591242, 5936, 'textwrap' 0, 597178, 15612, 'threading' 0, 612790, 1398, 'token' 0, 614188, 8969, 'tokenize' 0, 623157, 6659, 'tracemalloc' 0, 629816, 27711, 'typing' 1, 657527, 70, 'urllib' 0, 657597, 13861, 'urllib.parse' 0, 671458, 2188, 'uu' 0, 673646, 26812, 'zipfile'? x studentOutput filename? student.pyc? lsU: go up one levelO <name>: open embedded archive with given nameX <name>: extract file with given nameS: list the contents of current archive againQ: quit? q
在上面的操作中,我们使用 pyi-archive_viewer 提取了 main.pyc、和 student.pyc 文件,当时大家可以很清楚的看到弊端,即需要一个一个手动提取,对于大项目这是十分麻烦的,推荐使用下面的工具提取。

使用工具提取

我们可以使用开源项目 Python-exe-unpacker 中的脚本 pyinstxtractor.py 脚本进行提取,地址:

\print-student> Python pyinstxtractor.py .\main.exe                                            DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses  import imp[*] Processing .\main.exe[*] Pyinstaller version: 2.1+[*] Python version: 309[*] Length of package: 5835756 bytes[*] Found 59 files in CArchive[*] Beginning extraction...please standby[*] Found 81 files in PYZ archive[*] Successfully extracted pyinstaller archive: .\main.exeYou can now use a python decompiler on the pyc files within the extracted directory

解压后文件

解压后文件

将 .pyc 文件转换为 Python 脚本

入口运行类

对于从 pyinstaller 提取出来的 pyc 文件并不能直接反编译,入口运行类共16字节的 magic 和 时间戳被去掉了。如果直接进行反编译,例如执行 uncompyle6 main.pyc,则会报出如下错误:

ImportError: Unknown magic number 227 in main.pyc
我们可以使用支持16进制编辑的文本编辑器进行处理,比如:UltraEdit32

差异对比

可以看到前16个字节都被去掉了,其中前四个字节是magic,这四个字节会随着系统和Python版本发生变化,需要保持一致。后四个字节包括时间戳和一些其他的信息,都可以随意填写。我们可以通过 UltraEdit32 向提取的文件添加回信息。这里我写了一个 python 脚本实现这个过程:

// 读取从pyz目录抽取的pyc文件的前4个字节作基准pyz_dir = "./main.exe_extracted/PYZ-00.pyz_extracted"for pyc_file in os.listdir(pyz_dir):    if pyc_file.endswith(".pyc"):        file = f"{pyz_dir}/{pyc_file}"        breakwith open(file, "rb") as f:    head = f.read(4)// 补全入口类文件if os.path.exists("pycfile_tmp"):    shutil.rmtree("pycfile_tmp")os.mkdir("pycfile_tmp")main_file_result = "pycfile_tmp/main.pyc"with open("./main.exe_extracted/main.pyc", "rb") as read, open(main_file_result, "wb") as write:    write.write(head)    write.write(b"\0" * 12)    write.write(read.read())

非入口运行类

对于非入口运行的pyc文件从12字节开始缺4个字节。

差异对比

# 补全非入口类文件pyz_dir = "main.exe_extracted/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())

转换补全后的 pyc 文件

uncompyle6 反编译

pip install uncompyle6uncompyle6 xxx.pyc>xxx.py如:uncompyle6 .\pycfile_tmp\main.pyc# uncompyle6 version 3.9.0# Python bytecode version base 3.9.0 (3425)# Decompiled from: Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)]# Embedded file name: main.pyUnsupported Python version, 3.9.0, for decompilation# Unsupported bytecode in file .\pycfile_tmp\main.pyc# Unsupported Python version, 3.9.0, for decompilation
由于我使用的是 3.9.0 版本,uncompyle6 不再支持 decompilation,有兴趣的朋友可以去试试。

在线工具

我们也可以使用一些在线工具进行解密,比如:

反编译结果

可能遇到的问题

PYZ-00.pyz_extracted 文件为空

构建 .exe 文件 Python 版本和解压包时使用的版本不一致,比如我使用 Python 2.7 进行解包:

>Python .\pyinstxtractor.py .\main.exe[*] Processing .\main.exe[*] Pyinstaller version: 2.1+[*] Python version: 312[*] Length of package: 7675728 bytes[*] Found 60 files in CArchive[*] Beginning extraction...please standby[!] Warning: The script is running in a different python version than the one used to build the executable    Run this script in Python312 to prevent extraction errors(if any) during unmarshalling[!] Unmarshalling FAILED. Cannot extract PYZ-00.pyz. Extracting remaining files.[*] Successfully extracted pyinstaller archive: .\main.exeYou can now use a python decompiler on the pyc files within the extracted directory# 查看解压后的文件\print-student\main.exe_extracted\PYZ-00.pyz_extracted> ls\print-student\main.exe_extracted\PYZ-00.pyz_extracted>
如何防止exe被反编译我们可以在打包命令后面添加 --key 参数来进行加密,例如:
 pyinstaller --onefile   -p venv/Lib/site-packages .\print-student\main.py --key '1234'
再次解压,抽取的中间结果变为了 .pyc.encrypted,无法正常反编译。思考Bytecode encryption was removed in PyInstaller v6.0. Please remove your --key=xxx argument. For the rationale and alternatives see 可以看到在 PyInstaller v6.0 加密参数已经被废弃,大家可以思考一下原因。总结反编译 Python 可执行文件可以帮助我们理解程序的工作原理和逻辑,但在实践中可能会受到许多因素的限制。对于复杂的程序,反编译可能只是了解其工作原理的第一步,可能需要进一步的分析和研究。最后,我们需要明白技术没有好坏,需要谨守道德和法律的底线。

标签: #编译pyd