今天我将教大家如何反编译exe文件,即将自己或别人写好的exe,还原成Python源码。以最近写Python一键自动整理归类文件为例进行演示,运行所需的代码和文件都会在文末提供给大家。pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data="h.ico;/"
pyinstaller -w --icon=h.ico auto_organize_gui.py --add-data="h.ico;."
首先我们需要从exe文件中抽取出其中的pyc文件:抽取pyinstaller打包的exe中的pyc文件,提取pyc文件有两种方法:- 通过 pyinstxtractor.py 脚本提取pyc文件
- 通过 pyi-archive_viewer 工具提取pyc文件
pyinstxtractor.py 脚本可以在github项目 python-exe-unpacker 中下载,地址:https://github.com/countercept/Python-exe-unpacker下载该项目后把其中的pyinstxtractor.py脚本文件复制到与exe同级的目录。Python pyinstxtractor.py auto_organize_gui.exe
执行后便得到exe文件名加上_extracted后缀的文件夹:对两种打包方式产生的exe提取出的文件结构稍有区别:pyi-archive_viewer是PyInstaller自己提供的工具,它可以直接提取打包结果exe中的pyc文件。https://pyinstaller.readthedocs.io/en/stable/advanced-topics.html#using-pyi-archive-viewer执行pyi-archive_viewer [filename]即可查看 exe 内部的文件结构:pyi-archive_viewer auto_organize.exe
U: go Up one level
O : open embedded archive name
X : extract name
Q: quit
要提取其他被导入的pyc文件,则需要先打开PYZ-00.pyz:很显然,使用PyInstaller的pyi-archive_viewer 工具操作起来比较麻烦,一次只能提取一个文件,遇到子模块还需执行一次打开操作。所以后面我也只使用pyinstxtractor.py 脚本来提取pyc文件。不过我们直接使用 uncompyle6 库进行解码,使用pip可以直接安装:uncompyle6可以反编译.pyc后缀结尾的文件,两种命令形式:- uncompyle6 xxx.pyc>xxx.py
- uncompyle6 -o xxx.py xxx.pyc
uncompyle6 auto_organize.cpython-37.pyc>auto_organize.py
执行后便直接将.pyc文件反编译成Python脚本了:对于不是pyc后缀结尾的文件,使用uncompyle6反编译时会报出 must point to a Python source that can be compiled, or Python bytecode (.pyc, .pyo) 的错误。对于从pyinstaller提取出来的pyc文件并不能直接反编译,入口运行类共16字节的 magic 和 时间戳被去掉了。uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pycImportError: Unknown magic number 227 in auto_organize_gui.exe_extracted\auto_organize_gui.pyc
使用支持16进制编辑的文本编辑器查看一探究竟,这里我使用UltraEdit32:分别打开正常情况下编译出的pyc和从pyinstaller提取出来的pyc文件进行对比:可以看到前16个字节都被去掉了,其中前四个字节是magic,这四个字节会随着系统和Python版本发生变化,必须一致。后四个字节包括时间戳和一些其他的信息,都可以随意填写。我们先通过UltraEdit32向pyinstaller提取的文件添加头信息:选择开头插入16个字节后,只需要替换前4个字节为当前环境下的magic:uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pyc>auto_organize_gui.py
可以看到对于非入口运行的pyc文件是从12字节开始缺4个字节。
uncompyle6 auto_organize_gui.exe_extracted/PYZ-00.pyz_extracted/auto_organize.pyc > auto_organize.py
如果一个exe需要被反编译的Python脚本只有3个以内的文件,我们都完全可以人工来操作。但是假如一个exe涉及几十个甚至上百个Python脚本需要反编译的时候,人工操作未免工作量过于巨大,我们考虑将以上过程用Python实现,从而达到批量反编译的效果。
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()
# 恢复当前目录位置
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
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
读取从pyz目录抽取的pyc文件的前4个字节作基准: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']
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())
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())
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()
这样我们只需将Python脚本、exe文件和pyinstxtractor.py脚本文件 放置到同一文件夹下,运行我们的Python脚本。即可反编译exe。可以看到已经完美的反编译出exe其中的Python脚本:
好了,相信大家已经明白了反编译的原理。那么既然是攻防,如何防止自己打包的exe被反编译呢?只需在打包命令后面加上--key命令即可,例如文章开头的命令可以更换为:pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data="h.ico;/" --key 123456
该加密参数依赖tinyaes,可以通过以下命令安装:exe_file = r"D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe"
uncompyle_exe(exe_file, True)
结果只有入口脚本反编译成功,被依赖的脚本均被加密,无法直接被反编译:可以看到抽取的中间结果变成了.pyc.encrypted格式,无法直接被反编译:这个时候还想反编译就需要底层的逆向分析研究了,或者pyinstaller的源码完整研究一遍,了解其加密处理的机制,看看有没有破解的可能。如果大家想测试exe反编译,手头又没有合适的文件,在后台对话框回复
反编译即可获取。https://blog.csdn.net/as604049322
点这里👇关注我,记得标星哦~