使用Python標準庫對.pyc進行反編譯的全過程
在生產環(huán)境中,我們經常只能拿到 Python 的編譯文件(.pyc),而沒有原始 .py 源碼。
本文記錄一次完整的從 .pyc 到可讀源碼的實踐過程,并整理成可復用的“知識庫條目”,后續(xù)你可以對其他 .pyc 復用同樣的方法。
環(huán)境說明(模擬場景):
- 解釋器:CPython 3.x
- 目標文件:module_a.pyc(某業(yè)務模塊的編譯文件)
- 工具:只使用 Python 標準庫 dis、marshal(無第三方反編譯器依賴)
一、整體思路概覽
面對一個 .pyc 文件,我們的目標是:
- 從
.pyc里拿到 Code 對象(Python 的內部字節(jié)碼表示); - 用
dis把字節(jié)碼反匯編成可讀的文本指令(生成your_module_dis.txt之類的文本); - 基于該文本里的指令列表,手工/半自動還原出邏輯等價的 Python 源碼(得到
recovered_module.py這樣的還原版源碼); - 通過對比運行效果,驗證還原代碼與原模塊行為一致。
本文重點記錄第 ①~③ 步的細節(jié)和踩坑點,方便后續(xù)你作為“知識庫”查閱。
二、CPython.pyc文件的基本結構
了解一點 .pyc 結構有助于理解為什么要先 f.read(16):
- 前 16 字節(jié)是頭部(header),包含:
- 魔數(magic number)
- 標志位(flags)
- 時間戳 / 源文件哈希
- 源碼大小等
- 后面才是真正的
code object數據,通過marshal模塊進行序列化。
所以大致結構是:
[ 16 字節(jié)頭部 ] + [ marshal.dump(code_object) 的二進制數據 ]
只要我們跳過前 16 字節(jié),再用 marshal.load 讀取,就能拿回一個可以被 dis 反匯編的 code 對象。
三、用dis + marshal導出字節(jié)碼到文本(示例腳本)
核心導出腳本如下(示例腳本 disassemble_example.py):
import dis, marshal, types
# 示例:從業(yè)務模塊 module_a.pyc 中讀取字節(jié)碼
with open(r"D:\demo_project\bin\module_a.pyc", "rb") as f:
f.read(16) # 跳過頭部
code = marshal.load(f)
with open("your_module_dis.txt", "w", encoding="utf-8") as out:
dis.dis(code, file=out)
關鍵點說明:
f.read(16):跳過.pyc頭 16 字節(jié),只保留真正的字節(jié)碼部分;marshal.load(f):從剩余二進制流中反序列化出一個code對象;dis.dis(code, file=out):對這個頂層code對象做反匯編,結果寫入your_module_dis.txt。
執(zhí)行完成后,會在同目錄下生成一個比較長的 your_module_dis.txt,里面是類似這樣的內容(下面是完全虛構的模擬輸出片段,用于說明格式與思路):
0 0 RESUME 0
2 2 LOAD_CONST 0 (0)
4 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (json)
8 STORE_NAME 0 (json)
3 10 LOAD_CONST 0 (0)
12 LOAD_CONST 1 (None)
14 IMPORT_NAME 1 (re)
16 STORE_NAME 1 (re)
...
66 74 LOAD_CONST 6 (<code object extract_main_syms at 0x..., file "/app/demo_project/module_a.py", line 19>)
104 MAKE_FUNCTION 0
106 STORE_NAME 15 (extract_main_syms)
從這種輸出格式中,可以看出:
- 模塊會先導入若干依賴模塊;
- 然后依次把若干
code object綁定為函數。
這些信息就是后續(xù)還原源碼時用于搭建結構的“骨架”,這里只是虛構的示例格式,不對應任何真實業(yè)務代碼。
四、分析導出的文本:從字節(jié)碼骨架還原模塊結構
在導出的文本一開始,你一般會看到類似這樣的模式(同樣是虛構的模擬片段):
11 74 LOAD_CONST 0 (0)
76 LOAD_CONST 2 (('log_info', 'log_error'))
78 IMPORT_NAME 9 (core)
80 IMPORT_FROM 10 (log_info)
82 STORE_NAME 10 (log_info)
84 IMPORT_FROM 11 (log_error)
86 STORE_NAME 11 (log_error)
88 POP_TOP
13 90 LOAD_CONST 3 ('DEMO_CODE')
92 STORE_GLOBAL 12 (code_str)
15 94 LOAD_CONST 4 ('https://api.example.com/i')
96 STORE_NAME 13 (service_url)
16 98 LOAD_CONST 5 (False)
100 STORE_NAME 14 (debug_mode)
結合經驗,可以直接還原為類似下面的偽代碼形式(示例依然是虛構的,注意其中的配置項和值都只是演示用):
from core import log_error, log_info code_str: str = "DEMO_CODE" ICD_service_url: str = "https://api.example.com" debug_mode: bool = False
還原模塊級結構的一般步驟:
- 先關注所有
IMPORT_NAME/IMPORT_FROM:恢復頂層import語句; - 找所有
STORE_NAME+ 常量字符串:通常是模塊級常量配置,如code_str/ URL / debug 開關; - 找
LOAD_CONST (<code object ...>) + MAKE_FUNCTION + STORE_NAME:- 可以直接得到函數名:
STORE_NAME 15 (extract_main_syms)→def extract_main_syms(...): - 然后在
Disassembly of <code object extract_main_syms ...>下面繼續(xù)分析函數內部邏輯。
- 可以直接得到函數名:
到這里為止,我們已經能搭出一個大致的模塊輪廓。
五、函數內部:從字節(jié)碼反推高層邏輯(虛構示例)
拿某個函數(例如 extract_main_syms)舉例,在導出文本中它的反匯編開頭可能類似(示意片段,完全虛構):
Disassembly of <code object extract_main_syms at 0x..., file "/app/demo_project/module_a.py", line 19>:
19 0 RESUME 0
20 2 BUILD_LIST 0
4 STORE_FAST 2 (result_syms)
21 6 LOAD_FAST 0 (input_data)
8 LOAD_CONST 1 ('record')
10 BINARY_SUBSCR
20 LOAD_CONST 2 ('main_field')
22 BINARY_SUBSCR
32 STORE_FAST 3 (main_text)
22 34 LOAD_FAST 0 (input_data)
36 LOAD_CONST 1 ('record')
38 BINARY_SUBSCR
48 LOAD_CONST 3 ('history_field')
50 BINARY_SUBSCR
60 STORE_FAST 4 (history_text)
根據變量名,可以還原出類似這樣的偽代碼(同樣是虛構示例):
def extract_main_syms(input_data: Dict[str, Any]) -> List[str]:
result_syms: List[str] = []
record = input_data.get("record", {}) or {}
history_text: str = record.get("history_field", "") or ""
...
通用還原技巧:
BUILD_LIST 0 + STORE_FAST (xxx)→xxx = [];LOAD_FAST ... BINARY_SUBSCR多連串 → 多級下標 /dict取值;- 頻繁出現的字符串常量(中文問診要點、病史名稱)→ 可以直接在源碼里用同樣的文本補回去;
CALL_FUNCTION/ `CALL_METHOD`` 結合函數名、參數個數,大多數時候可以明確調用意圖。
通過這一套操作,可以逐步把若干核心函數(例如 extract_main_syms 等)都還原為結構清晰、類型標注完善的 Python 源碼(在你自己的還原文件中,例如 recovered_module.py)。
六、把反匯編結果固化為“知識庫”:還原源碼的實現策略
在一次完整的實踐中,我們可以在一個還原文件(例如 recovered_module.py)中遵循下面幾個原則:
- 保持對外行為一致:函數名、參數、返回結構盡量和原模塊保持兼容;
- 邏輯等價優(yōu)先,而非逐指令對齊:我們關注的是“相同輸入 → 相同輸出”,不追求一模一樣的實現寫法;
- 適當增加類型標注和注釋:方便后續(xù)維護和閱讀,特別是復雜的業(yè)務流程;
- 抽出可配置項:如
code_str、ICD_service_url、日期限制date_limite等統(tǒng)一放在模塊頂部。
例如,頂層配置在還原后被集中到了模塊開頭(這里用模擬數據舉例):
code_str: str = "DEMO_CODE" ICD_service_url: str = "https://api.example.com/icd" debug_mode: bool = False
對于某些主流程函數,可以按照原字節(jié)碼中調用順序,清晰劃出若干步驟,例如:
- 權限或授權有效期檢查;
- 輸入參數解析及預處理;
- 關鍵信息抽取、實體標注或特征工程;
- 業(yè)務規(guī)則判斷、打分與過濾;
- 結果歸組、加權及 Top-N 輸出。
這些結構在源碼層面被“語義化”之后,不再只是 LOAD_CONST / JUMP_FORWARD 這樣的指令堆,而是清晰的業(yè)務流程。
七、實踐經驗與踩坑記錄
反編譯 .pyc 到可維護源碼,過程中有一些值得記錄的點(下面都以虛構示例來說明思路):
1)優(yōu)先搞清楚“輸入/輸出”約定
- 先從接口調用方(如外部服務或其它模塊)入手,反向推函數參數含義,比直接看字節(jié)碼更有效。
2)對復雜 if/for 結構,不要硬記指令,先畫流程
- 可以根據
JUMP_IF_FALSE_OR_POP、POP_JUMP_FORWARD_IF_FALSE之類的跳轉位置,畫一張小流程圖,再還原成if/elif/else。
3)保持一個“對照文件”
- 建議始終保留一對文件:例如
your_module_dis.txt作為原始反匯編結果,recovered_module.py是還原后的源碼,兩邊對照非常重要。
4)不要迷信自動反編譯工具
- 對于邏輯復雜、包含大量中文業(yè)務規(guī)則的代碼,自動反編譯經常給出難以閱讀的結果,
手工+半自動(基于dis的文本)更適合做“可維護的重構”。
八、如何復用這套方法處理其他.pyc
當你以后再遇到一個新的 .pyc,可以按下面的模板操作(完全和具體業(yè)務無關,只關注技術流程):
準備一個類似前文的 disassemble_example.py,只改文件路徑:
import dis, marshal
with open(r"你的目標文件.pyc", "rb") as f:
f.read(16)
code = marshal.load(f)
with open("your_module_dis.txt", "w", encoding="utf-8") as out:
dis.dis(code, file=out)
打開生成的 your_module_dis.txt:
- 先定位所有
IMPORT_NAME/MAKE_FUNCTION + STORE_NAME,搭好模塊骨架; - 再逐個函數分析
Disassembly of <code object ...>部分。
在新建的 .py 文件中,還原可讀源碼:
- 先實現外部接口簽名;
- 再根據字節(jié)碼實現內部邏輯;
- 最后寫一些測試用例,對比行為。
把還原心得也整理到類似本篇這樣的 markdown 中
- 方便你自己未來查閱,也方便團隊里其他同事快速接手。
九、小結
- 使用 Python 標準庫的
marshal + dis,可以在不依賴第三方庫的前提下,從.pyc中獲取字節(jié)碼并反匯編到文本; - 基于反匯編文本(如
your_module_dis.txt),可以逐步還原出結構清晰的源碼文件(例如recovered_module.py),作為長期維護版本; - 將整個流程和經驗沉淀成 markdown 知識庫(本文)后,可以非常方便地復用于今后所有
.pyc分析與還原工作。
只要掌握了這一套流程,你在面對“只有 .pyc、沒有源碼”的場景時,就不會再那么無助,而是有一套穩(wěn)定可復用的反編譯+重構方法 論。
以上就是使用Python標準庫對.pyc進行反編譯的全過程的詳細內容,更多關于Python標準庫對.pyc反編譯的資料請關注腳本之家其它相關文章!
相關文章
Python利用GDAL模塊實現讀取柵格數據并對指定數據加以篩選掩膜
這篇文章主要為大家詳細介紹了如何基于Python語言中gdal模塊,對遙感影像數據進行柵格讀取與計算,同時基于QA波段對像元加以篩選、掩膜的操作,需要的可以參考一下2023-02-02
python GUI庫圖形界面開發(fā)之PyQt5滾動條控件QScrollBar詳細使用方法與實例
這篇文章主要介紹了python GUI庫圖形界面開發(fā)之PyQt5滾動條控件QScrollBar詳細使用方法與實例,需要的朋友可以參考下2020-03-03

