隨筆

逆向工程需要更大的模型嗎?

作者 N0zoM1z0 · 2026 年 10 月 13 日

要點

  • 模型能力會影響 agent 能提出哪些假設。 後來的工作解決了早期嘗試未能解決的問題。
  • 熟悉的編譯器為專用模型提供了具體的學習對象。 有些精確匹配依賴細小但可以複用的原始碼模式。
  • 也要公平評估程式碼倉庫裡的知識。 較小的模型配合良好的檢索,可能已經掌握了足以推進工作的知識。
  • 訓練模型選擇下一個有價值的實驗,同時檢驗它使用的驗證器。 回饋是否有用,取決於驗證器究竟檢查了什麼。
  • 結合時間和成本,評估透過驗證的重建成果。 這是我希望從整個 agent 工作流程中獲得的結果。

更強的模型改變了什麼

從 2026 年 8 月開始,我一直在探索:如果給 agent 訪問程式的能力,以及驗證自身工作的手段,它能獨立完成多少逆向工作?我的經驗主要來自遊戲重建,尤其是東方系列。隨著我更換模型,程式碼倉庫記錄了每次工作還原出的內容。

這些工作很大一部分追求精確重建:用固定版本的歷史工具鏈編譯我們維護的原始碼,檢查它能否復現目標函式。當後來的模型解決了早期工作卡住的問題,我們可以從原始碼變化中看出,是什麼促成了這次進展。

臨時值需要同時保持有效

TH09 中一個攻擊函式的大部分邏輯已經還原,但編譯器對臨時結果的儲存安排仍然與原版不同。剩下的問題在於:這些結果的生命週期應該多長,編譯器何時可以複用它們的儲存空間?

GPT-6-Sol 的一次工作記錄表明,嘗試幾種常見的原始碼寫法後,差異仍未消除。後來,GPT-6.1-Sol 重新研究這個問題,提出了一個有價值的假設:關鍵可能在於這些臨時值的生命週期。

這個函式會產生臨時結果,再把它們複製到動畫的位置資料中。在 C++ 中,臨時結果會一直存在,直到使用它的表示式執行完畢。把實際操作放進同一個表示式,就能讓這些結果同時存活,編譯器也就不能為它們全部複用同一塊儲存空間。

其中一個結果仍然與稍後用於碰撞檢查的值共享儲存。給那個值單獨的生命週期後,最後一處重疊也消失了。經過幾次有針對性的實驗,一次乾淨的重新構建確認,整個函式都已匹配。

我感興趣的是,發現與驗證如何共同推進工作。生命週期的假設給 agent 指明瞭新方向;編譯器的回饋又幫助它逐步調整原始碼,直到找到匹配的寫法。

換一種控制流結構

TH09 的另一個函式說明了,新方向為什麼重要。這次的難點是多個分支如何共享末尾的操作。GPT-6.1-Sol 之前的工作已經讓函式的一些部分匹配,但分支匯合的方式仍與原版不同。

GPT-6-Astra 改寫了外圍的控制流圖,讓主要路徑匯入共同的最終更新與返回。在這種原始碼結構下,編譯器合併分支的方式與原版一致。整個函式實現了匹配,另一次獨立的重新構建也確認了這一結果。

換一種表達方式,開啟了更有價值的搜尋空間。 這兩個例子讓我認真看待通用模型的能力:agent 可能花很長時間嘗試局部變化,才找到一種更好的整體寫法。

不過,後來的工作也繼承了之前的原始碼和失敗記錄。這些積累本身就是優勢的一部分。歷史記錄讓我們可以檢查發現及其驗證過程;要區分模型能力和繼承經驗各自帶來的幫助,還需要對照實驗。

這正是我覺得有趣的地方。有些進展來自新的表達方式,剩下的大量工作則是在熟悉的編譯器上試驗不同寫法。其中有多少需要廣泛的推理能力?又有多少可以透過反覆接觸這個編譯器和這些程式碼來學會?

為什麼專用模型可能做得很好?

東方系列給了我們一個具體的理由,去期待經驗積累能發揮作用。大量遊戲程式碼出自同一個作者 ZUN,因此,把反覆出現的程式設計習慣作為初始假設是合理的。程式碼倉庫也提供了實際例子:相似的迴圈和輔助函式組織方式,曾為其他遊戲的重建提供假設。

把編譯器也考慮進去,這些原始碼模式就更加具體了。在固定版本和選項下,原始碼的小變化可能穩定地產生不同的機器碼。其中一些規律藏得很深。

以 TH095 為例,連局部變數的名字,都可能改變 VC7.1 為它安排的儲存位置。只移動宣告並不能解決佈局問題,但先前重建的一個背景函式揭示了會影響編譯器選擇的命名模式。這為重建 Supervisor::LoadDat提供了更有希望的假設。

把這個模式用於表示檔案大小的變數,不增加額外儲存就得到了所需佈局。隨後,整個函式實現了匹配。

這類細節非常具體,通用模型第一次接觸時未必知道。而針對這個編譯器訓練的專用模型,可以接觸許多類似案例,學會哪些原始碼變化值得優先嚐試。

當我們已經理解函式行為,只剩最後的匹配差異時,這個機會尤其明顯。此時的問題是:這個編譯器會把哪一種普通寫法編譯成眼前的指令?專用訓練可能讓模型更高效地提出這些寫法,而最終由目標程式決定哪些假設成立。

但這些模式必須保留適用的上下文。TH08 使用的 Visual C++ 版本早於 TH095 和 TH09。TH04 用的是另一種 DOS 時代的編譯器,其玩家邏輯重建需要不同的假設,既涉及原始碼寫法,也涉及編譯後的程式碼如何連結成可執行檔案。同一作者為我們提供了先驗,具體目標則告訴我們它是否適用。區分這些情況,也是專用模型需要學會的事。

這些經驗應該存在哪裡?

在決定透過訓練讓模型學會這些模式之前,我會先看看程式碼倉庫裡已有的知識。在上一篇文章中,我寫過:倉庫正在成為專案的工作記憶。這裡儲存著對編譯器的認識,也儲存著相關原始碼和失敗實驗,讓這些經驗有據可查。

檢索這些記錄後,agent 可以從已知結果出發,看看我們是如何得到它的,也可以避開在相同條件下已經失敗的方案。模型的參數沒有改變,工作的起點卻提高了。

重建 TH09 的敵人生成函式時,GPT-6-Luna-max 的一次工作從 TH08 對應的原始碼中找到了假設。之前的寫法提示了迴圈該如何組織,以及內部失敗該如何處理。TH09 自身的證據補上了遊戲特有的細節,乾淨的重新構建確認了整個函式的匹配。

“模型不知道這個編譯器特性”和“模型不會利用它”,是兩個不同的問題。 檢索可能解決前者,但把知識變成有價值的候選原始碼,仍然需要能力。如果候選方案失敗了,模型還得判斷這次結果對下一次嘗試意味著什麼。知道相關模式,只是這個過程的起點。

所以,在任何對照實驗中,我都會加入一個具備良好檢索能力的小型通用模型。給它相同的經驗庫,才能看清訓練究竟增加了什麼:更好的候選方案、更好的回饋後決策,還是兩者都有?

學習選擇下一個有價值的實驗

回饋之後如何決策,是我最想研究的部分。在 agent 驅動的重建中,模型提出解釋,再用程式來驗證。即使候選方案失敗了,結果也可能縮小剩餘的可能性,提示下一步更值得嘗試什麼。

再看前面的臨時結果例子。最終原始碼展示了成功的寫法,但得到它的過程包含更多資訊。下面的每次實驗,都幫助我們確定了還有什麼需要改變。

實驗 結果告訴了我們什麼
嘗試另一種複製結果的方式 複製程式碼改變了,但函式仍有差異。
直接使用回傳值 複製方式與原版一致,但臨時儲存佈局仍有差異。
讓臨時結果同時保持有效 它們得到了各自的儲存空間,但其中一個仍與稍後用於碰撞檢查的值重疊。
讓碰撞檢查的值擁有獨立儲存 乾淨的重新構建確認了整個函式的匹配。

選自前面攻擊函式的重建實驗,按每次得到的認識整理。

儲存這個過程的訓練記錄,比單獨儲存最終原始碼能教會模型更多東西。它把每個改動、觀察到的結果和下一步決策聯絡起來。無效的嘗試也值得保留:後來的 agent 可以據此知道,在這個上下文中,哪些假設已經檢驗過。

我希望模型學會如何利用回饋。 結果沒有變化,可能意味著該重新考慮這個改動;差異發生變化,則可能指向另一個原因。當局部修改不再提供有效資訊時,agent 應該可以重新考慮原始碼的整體表達,或者回頭檢查證據。

學會這些選擇,需要自主性。有時,更有價值的下一步是檢查另一個呼叫者,而不是再修改一次原始碼並編譯。如果強制按照固定順序改寫,就會剝奪這種選擇,也失去一部分我們希望模型學會的行為。

為什麼逆向工程值得用來探索強化學習

範圍明確的重建任務,為研究這些選擇提供了一個實際環境。目標和編譯器版本固定之後,許多原始碼實驗都能快速給出可重複的回饋。agent 可以編譯候選原始碼、檢查差異,再根據觀察選擇下一步。

強化學習可以利用這些回饋,訓練模型的決策過程。候選原始碼透過明確定義的檢查,就給出了一個具體成果;而嘗試的過程,讓我們可以看到實現它花了多少功夫。

已有研究支援這個方向。LLM4Decompile研究專門用於反編譯的模型。AutoDecompiler利用編譯和執行回饋,訓練多輪改進能力。這些研究關注的是功能還原。對於我們的工作,我會以它們為起點,研究一個相關任務:找到能復現歷史編譯器輸出的原始碼。

不過,要得到這樣一個定義明確的任務,有時得先確定函式負責哪些資源,或者設計新的方法觀察程式執行時的行為。在 TH08 中,一次碰撞差異最終追溯到原版處理浮點計算的方式。agent 必須先弄清楚該測量什麼,才能驗證有價值的修正。

這提示了能力全面的模型的另一個作用:先把問題變得可驗證。驗證手段建立起來後,我們才可以研究,學出來的搜尋策略能走多遠。

當驗證器出錯時

一旦用某項檢查來訓練模型,它的覆蓋範圍就會成為學習環境的一部分。我把這種參考標準或驗證器稱為 oracle。即使它能給出可重複的答案,我們也必須知道,這個答案覆蓋了什麼。

TH08 展示了檢查範圍之外可能藏著多少問題。一些重建函式使用了錯誤的浮點常量,卻仍然透過了精確比較。比較器會先調整重定位地址,再檢查指令;但只有明確登記過的常量,才會進一步檢查地址指向的值。其他常量引用,即使值沒被檢查,也可能透過。

為了補上這個漏洞,一次更全面的審計在已經透過檢查的函式中找到了錯誤常量。有一個計算中,重建原始碼加的是 1.0f,而原版加的是 32.0f。

如果用舊檢查的結果作為獎勵,模型可能學會生成錯誤的重建原始碼。 我們沒有用這個驗證器訓練 RL 模型;實際觀察到的是檢查本身的漏洞。但如果把它用作獎勵,同一個盲點也會進入學習環境。

功能測試也可能存在類似盲點。針對 LLM 反編譯器的差分模糊測試研究發現,在已有測試之外,程式行為仍可能不同。因此,理解“測試透過”時,必須看它實際覆蓋了哪些情況;理解“指令匹配”時,也必須看它檢查了哪些位元組和資料。

在把任何一種檢查用作獎勵之前,我都會用刻意構造的錯誤來測試它。我還會把最終驗收機制放在候選原始碼可修改的範圍之外,讓模型透過正確重建來滿足檢查。否則,直接複製目標程式的位元組陣列,也可能騙過簡單的評分,卻沒有還原出有用的原始碼。

改進模型和改進它的驗證器,屬於同一個研究問題。 模型越擅長最佳化我們給它的回饋,我們就越需要理解回饋本身。

值得做的對照實驗

具備這些檢查之後,我會從一組固定的 VC7.1 任務開始比較。先為強通用模型和小型通用模型建立基線,再給小模型加入倉庫知識,觀察檢索帶來的變化。接著比較監督訓練的專用模型,以及在此基礎上進行 RL 訓練的版本,看看訓練增加了什麼。

為了讓比較有意義,每個模型都應獲得相同的目標證據和工具。專門比較訓練效果時,還要固定基礎模型和可用知識。重複實驗有助於區分可靠的搜尋策略與偶然成功的候選方案。

我還會區分兩種起點:從指令開始重建函式,以及在已有原始碼中消除最後幾處差異。專用模型可能很擅長後者,而通用模型承擔了到達這個階段的大部分工作。分別評估,才能看清這種分工。

兩類任務都需要真正未見過的測試案例。東方系列的相關遊戲會共享引擎程式碼,只留出某個遊戲,仍可能讓幾乎相同的函式進入訓練集。我會改為按相關原始碼家族留出測試集,並把它們透過驗證的答案同時排除在訓練資料和檢索庫之外。

在這些案例上,要統計的是完整、獨立驗證過的匹配成果。給每個模型相同的候選數量或編譯次數預算,可以比較搜尋品質。記錄總耗時和總成本,包括編譯,才能知道這種品質是否轉化成了高效的工作流程。訓練成本也要單獨計算。

兩種角度都重要。一個便宜的提議,如果導致大量無效編譯,整個工作流程仍可能很貴。反過來,較慢的模型如果更早找到有價值的實驗,也可能更划算。對照實驗應當反映這些成本。

只統計透過驗收的結果,也能幫助我們合理看待中間進展。TH09 的一個錄影儲存候選方案看起來很有希望,因為編譯後函式的大小符合預期,但完整比較仍發現大量差異。編譯器改變指令排列,也可能讓候選原始碼看起來更接近,卻沒有解決根本問題。這些診斷可以指導下一步,但若要獎勵“變得更接近”,我會先對照完整驗收檢查,確認這種獎勵有效。

我想看這些結果呈現出什麼規律。如果專用模型在熟悉的編譯器上表現很好,換了工具鏈後,這種優勢還在嗎?當雙方都擁有良好的檢索能力時,訓練還能增加多少價值?這些答案能幫助我們決定,每類模型最適合用在哪裡。

這意味著怎樣的 agent?

如果不同模型在不同階段各有所長,一種有價值的安排可能是:讓能力全面的 agent 可以呼叫專用模型。當任務變成一種熟悉的編譯器搜尋時,它可以請專用模型提出候選方案。如果結果開始說不通,通用 agent 可以回頭檢查目標證據,或者調查測量本身。

在這樣的安排中,專用模型成為 agent 隨著調查進展可以選擇的工具。agent 仍然擁有改變方向的自由,而這正是前面那些突破發生的重要條件。

這自然延續了上一篇文章中談到的工業化轉變。能夠開展更多實驗,會改變我們組織工作的方式,也讓我們有理由追問:哪些決策需要全面的判斷,哪些可以透過反覆積累某項具體技藝來改善?

目前,最強的通用模型解決了我過去沒能解決的問題,因此在我的工作流程中佔據了一席之地。正是這些成功,讓我想更仔細地研究過程:有多少依賴模型能力的廣度?又有多少可以交給專門學習這類重建的模型?

這些程式碼倉庫讓我們有機會驗證這個問題。 我想利用這些記錄建立對照實驗,看看我們能學到什麼。

證據與模型歸屬

詳細證據記錄包含歷史比較結果與實驗背景。

這些例子來自連結中的提交中記錄的歷史函式級比較。整款遊戲的執行時檢查屬於另一個里程碑。相關原始碼與匹配記錄核查於 2026 年 10 月 13 日。

TH09 中的標籤 gpt-dots 對應 GPT-6-Astra。gpt-web 會隨當時使用的模型變化,因此僅憑這個標籤無法確定某次執行的具體模型。其他引用提交中的明確模型名,提供了對應的歸屬記錄;這些歷史記錄並沒有統一的單次執行成本或投入統計。

文中提出的專用模型與 RL 對照,屬於未來實驗。專案歷史為這些想法提供了依據;本文尚未評估經過訓練的專用模型。

頂部