将 exe 文件反编译为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文件。

1. 抽取exe中的pyc文件

通过 pyinstxtractor.py 脚本提取pyc文件,脚本在github项目 python-exe-unpacker 中下载,地址:

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

下载该项目后把其中的pyinstxtractor.py脚本文件复制到与exe同级的目录。

然后进入exe所在目录的cmd执行:

Python pyinstxtractor.py auto_organize_gui.exe

执行后便得到exe文件名加上_extracted后缀的文件夹

2. 反编译pyc文件为py脚本

使用 uncompyle6 库进行解码,使用pip可以直接安装:

pip install uncompyle6

uncompyle6可以反编译.pyc后缀结尾的文件,两种命令形式:

  1. uncompyle6 xxx.pyc>xxx.py

  2. uncompyle6 -o xxx.py xxx.pyc

对于从pyinstaller提取出来的pyc文件并不能直接反编译,入口运行类共16字节的 magic 和 时间戳被去掉了。

如果直接进行反编译,例如执行 uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pyc

会报出如下错误:ImportError: Unknown magic number 227 in auto_organize_gui.exe_extracted\auto_organize_gui.pyc

使用支持16进制编辑的文本编辑器查看,可以看到前16个字节都被去掉了,其中前四个字节是magic,这四个字节会随着系统和Python版本发生变化,必须一致。后四个字节包括时间戳和一些其他的信息,都可以随意填写。同目录下的“struct”文件的头字节是pyc文件的一种,可以利用这个文件来进行补齐。选择开头插入16个字节后,只需要替换前4个字节为当前环境下的magic,然后执行:

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

即可顺利编译入口文件。

依赖性文件与此类似,它们的pyc文件是从12字节开始缺4个字节。这里我们选择第13个字节再插入四个字节即可。然后再执行:

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

成功的反编译出依赖的文件。

如果一个exe需要被反编译的Python脚本只有3个以内的文件,我们都完全可以人工来操作。

但是假如一个exe涉及几十个甚至上百个Python脚本需要反编译的时候,人工操作未免工作量过于巨大,我们考虑将以上过程用Python实现,从而达到批量反编译的效果。

提取exe中的pyc

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

预处理pyc文件修护校验头

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()

  • 2
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值