爆強!將 exe 文件反編譯成 Python 腳本!

大家好,我是早起🧐

最近遇到了一個問題,自己打包好的 exe 文件還在,但是 Python 源文件不知什麼時候被誤刪了。現在想改動一下功能,重寫 Python 腳本工程量也太大了,怎麼辦?

今天我將教大家如何反編譯 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 文件。

首先我們需要從 exe 文件中抽取出其中的 pyc 文件:

抽取 exe 中的 pyc 文件

抽取 pyinstaller 打包的 exe 中的 pyc 文件,提取 pyc 文件有兩種方法:

  1. 通過 pyinstxtractor.py 腳本提取 pyc 文件

  2. 通過 pyi-archive_viewer 工具提取 pyc 文件

腳本提取 pyc 文件

pyinstxtractor.py 腳本可以在 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後綴的文件夾:

對兩種打包方式產生的 exe 提取出的文件結構稍有區別:

工具提取 pyc 文件

pyi-archive_viewer 是 PyInstaller 自己提供的工具,它可以直接提取打包結果 exe 中的 pyc 文件。

詳細介紹可參考官方文檔:ttps://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 <name>: open embedded archive name
X <name>: extract name
Q: quit

然後可以提取出指定需要提取的文件:

要提取其他被導入的 pyc 文件,則需要先打開PYZ-00.pyz

很顯然,使用 PyInstaller 的 pyi-archive_viewer 工具操作起來比較麻煩,一次只能提取一個文件,遇到子模塊還需執行一次打開操作。

所以後面我也只使用 pyinstxtractor.py 腳本來提取 pyc 文件。

反編譯 pyc 文件爲 py 腳本

有很多對 pyc 文件進行解密的網站,例如:

不過我們直接使用 uncompyle6 庫進行解碼,使用 pip 可以直接安裝:

pip install uncompyle6

uncompyle6 可以反編譯. pyc 後綴結尾的文件,兩種命令形式:

  1. uncompyle6 xxx.pyc>xxx.py

  2. 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) 的錯誤。

所以我們需要先對提取出的內容人工修改後綴:

運行入口 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 進制編輯的文本編輯器查看一探究竟,這裏我使用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 文件反編譯

考慮再反編譯導入的其他依賴文件:

先用UltraEdit32打開查看一下:

可以看到對於非入口運行的 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()

完整代碼下載見文末。

這樣我們只需將 Python 腳本、exe 文件和pyinstxtractor.py腳本文件 放置到同一文件夾下,運行我們的 Python 腳本。即可反編譯 exe。

可以看到已經完美的反編譯出 exe 其中的 Python 腳本:

好了,相信大家已經明白了反編譯的原理。那麼既然是攻防,如何防止自己打包的 exe 被反編譯呢?

如何防止 exe 被反編譯呢

只需在打包命令後面加上--key命令即可,例如文章開頭的命令可以更換爲:

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

123456是你用來加密的密鑰,可以隨意更換。

該加密參數依賴 tinyaes,可以通過以下命令安裝:

pip install tinyaes

打包後再次執行反編譯:

exe_file = r"D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe"
uncompyle_exe(exe_file, True)

結果只有入口腳本反編譯成功,被依賴的腳本均被加密,無法直接被反編譯:

可以看到抽取的中間結果變成了.pyc.encrypted格式,無法直接被反編譯:

可以看到,常規手段就無法直接反編譯了。

這個時候還想反編譯就需要底層的逆向分析研究了,或者 pyinstaller 的源碼完整研究一遍,瞭解其加密處理的機制,看看有沒有破解的可能。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/mUU-KYOnxKibue-MxTfDfw