詳解如何將Python可執(zhí)行文件(.exe)反編譯為Python腳本
前言
將 Python 可執(zhí)行文件(.exe)反編譯為 Python 腳本是一項有趣的技術挑戰(zhàn),可以幫助我們理解程序的工作原理,以及可能包含的邏輯和算法。雖然反編譯不是一項簡單的任務,并且對于使用各種保護措施的程序可能無效,但對于一般情況下的 Python 可執(zhí)行文件,我們可以嘗試使用一些工具來進行反編譯。
下面我們就來學習如何將 Python 可執(zhí)行文件(.exe)反編譯為 Python 腳本。
版本
Python 3.9
反編譯
反編譯是將已編譯的程序代碼還原為其原始源代碼的過程。在 Python 中,由于其解釋性質(zhì),通常沒有像編譯語言那樣生成的二進制文件,但是我們可以將 Python 腳本轉換為字節(jié)碼文件(.pyc),而 .exe 文件通常是由 pyinstaller、cx_Freeze 等工具編譯生成的。
Python 可執(zhí)行文件(.exe)反編譯
Python 可執(zhí)行文件(.exe)反編譯為 Python 腳本主要分為兩個步驟,(1)從 .exe 文件中提取 pyc 文件 (2)將 pyc 文件轉換為 Python 腳本。
打包一個簡單的 .exe 可執(zhí)行文件
# student.py
class 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.py
import time
from student import Student
if __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 構建可執(zhí)行 .exe
pyinstaller --onefile -p venv/Lib/site-packages .\print-student\main.py
提取 pyc 文件
使用腳本提取
pyi-archive_viewer 是 PyInstaller 自己提供的工具,它可以直接提取打包結果exe中的pyc文件。
詳細介紹可參考官方文檔:pyinstaller.readthedocs.io/en/stable/a…
# 使用 pyi-archive_viewer 查看文件并提取 > pyi-archive_viewer .\main.exe Options in 'main.exe' (PKG/CArchive): pyi-contents-directory _internal Contents 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 level O <name>: open embedded archive with given name // 打開包查看文件 X <name>: extract file with given name // 提取文件 S: list the contents of current archive again Q: quit ? x main Output filename? main.pyc ? o PYZ-00.pyz Contents 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 student Output filename? student.pyc ? ls U: go up one level O <name>: open embedded archive with given name X <name>: extract file with given name S: list the contents of current archive again Q: quit ? q
在上面的操作中,我們使用 pyi-archive_viewer 提取了 main.pyc、和 student.pyc 文件,當時大家可以很清楚的看到弊端,即需要一個一個手動提取,對于大項目這是十分麻煩的,推薦使用下面的工具提取。
使用工具提取
我們可以使用開源項目 Python-exe-unpacker 中的腳本 pyinstxtractor.py 腳本進行提取,地址:github.com/countercept/Python-exe-unpacker
\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.exe You can now use a python decompiler on the pyc files within the extracted directory


將 .pyc 文件轉換為 Python 腳本
入口運行類
對于從 pyinstaller 提取出來的 pyc 文件并不能直接反編譯,入口運行類共16字節(jié)的 magic 和 時間戳被去掉了。如果直接進行反編譯,例如執(zhí)行 uncompyle6 main.pyc,則會報出如下錯誤:
ImportError: Unknown magic number 227 in main.pyc
我們可以使用支持16進制編輯的文本編輯器進行處理,比如:UltraEdit32

可以看到前16個字節(jié)都被去掉了,其中前四個字節(jié)是magic,這四個字節(jié)會隨著系統(tǒng)和Python版本發(fā)生變化,需要保持一致。后四個字節(jié)包括時間戳和一些其他的信息,都可以隨意填寫。我們可以通過 UltraEdit32 向提取的文件添加回信息。
這里我寫了一個 python 腳本實現(xiàn)這個過程:
# 讀取從pyz目錄抽取的pyc文件的前4個字節(jié)作基準
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}"
break
with 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字節(jié)開始缺4個字節(jié)。

# 補全非入口類文件
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 uncompyle6 uncompyle6 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.py Unsupported 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,有興趣的朋友可以去試試。
在線工具
我們也可以使用一些在線工具進行解密,比如:ctfever.uniiem.com/tools/pyc-decompiler

可能遇到的問題
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.exe
You 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 參數(shù)來進行加密,例如:
pyinstaller --onefile -p venv/Lib/site-packages .\print-student\main.py --key '1234'
再次解壓,抽取的中間結果變?yōu)榱?.pyc.encrypted,無法正常反編譯。
思考
Bytecode encryption was removed in PyInstaller v6.0. Please remove your --key=xxx argument. For the rationale and alternatives see https://github.com/pyinstaller/pyinstaller/pull/6999
可以看到在 PyInstaller v6.0 加密參數(shù)已經(jīng)被廢棄,大家可以思考一下原因。
總結
反編譯 Python 可執(zhí)行文件可以幫助我們理解程序的工作原理和邏輯,但在實踐中可能會受到許多因素的限制。對于復雜的程序,反編譯可能只是了解其工作原理的第一步,可能需要進一步的分析和研究。最后,我們需要明白技術沒有好壞,需要謹守道德和法律的底線。
到此這篇關于詳解如何將Python可執(zhí)行文件(.exe)反編譯為Python腳本的文章就介紹到這了,更多相關Python可執(zhí)行文件反編譯為腳本內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Keras: model實現(xiàn)固定部分layer,訓練部分layer操作
這篇文章主要介紹了Keras: model實現(xiàn)固定部分layer,訓練部分layer操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06
Pytorch在dataloader類中設置shuffle的隨機數(shù)種子方式
今天小編就為大家分享一篇Pytorch在dataloader類中設置shuffle的隨機數(shù)種子方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01
使用keras實現(xiàn)densenet和Xception的模型融合
這篇文章主要介紹了使用keras實現(xiàn)densenet和Xception的模型融合,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05
分析Python中設計模式之Decorator裝飾器模式的要點
這篇文章主要介紹了Python中設計模式之Decorator裝飾器模式模式,文中詳細地講解了裝飾對象的相關加鎖問題,需要的朋友可以參考下2016-03-03

