如果有什麼想回饋的(如對文章或部落格的感想),除了留言以外也能填表單跟我說:表單連結。若是對更多 JavaScript 知識有興趣,歡迎參考我的新書《JavaScript 重修就好》

從逆向工程重新認識 AI 的強大

之前寫過一篇感謝 AI 讓我這外行人也能做簡單的逆向工程,描述了我怎麼結合 AI agent 跟 ghidra MCP,去逆向一個 Golang binary(stripped),就算結果有點小錯誤,但整體方向都是對的。

過了快兩個月,這中間我拿 AI 去逆向了更多東西,更多我以為 AI 逆不出來的東西,但 AI 狠狠地打了我的臉,我才是無知的那個。

這篇記錄一下 AI 能做到的事情,最後聊聊這件事讓我對 AI 的看法有了怎樣的改變。

精選案例

底下的案例沒有特別講的話,都是 Android 應用程式。

案例一:Cocos2d 遊戲

AI 把 apk 拆開後,用 jadx 還原出 Java,發現遊戲邏輯不在裡面。

觀察一下發現是用 Cocos2d 寫的,assets 底下有大量加密過的 JavaScript 與 JSON 文件,還有個 libcocos2djs.so

下一步把 libcocos2djs.so 裡面的符號解析出來,確實看到一些加解密函數,而這個 so 有 35 MB 它覺得太大,所以沒有整包解開,而是選擇從解密的函式開始逆向,識別出加密演算法是 Blowfish。

接著追了一下誰 call 了設置 key 的函式,把那一段反編譯後還原出了 key,在原始程式碼中是用字串拼接的方式一個一個拼上去的:

std::string key;
key.push_back('7');  // MOV W8, 0x37
key.push_back('2');  // MOV W8, 0x32
key.push_back('c');  // MOV W8, 0x63
key.push_back('d');  // MOV W8, 0x64
....(共 32 個字元)

之所以特別講這個,是因為 AI 在嘗試這個方法之前,先在 code 裡面掃過一次,看有沒有長得很像 key 的字串,但發現沒有。做到這步 AI 就知道因為 key 是一個一個加上去的,所以在 code 裡不連續,沒辦法直接掃出來。

再來用 Python 寫了個腳本去解密所有資源,最後就拿到了 JavaScript,還原出遊戲邏輯以及 client 端配置。

這個案例主要是走純靜態分析,AI 光用靜態分析就找到加解密函式以及設置 key 的地方,再反編譯回推出 key 的內容,把加密過的遊戲資源解開。

案例二:另一個 Cocos2d 遊戲

一樣先拆開先 jadx,發現 dex 有加了個殼,不過這個遊戲也觀察出來是 Cocos2d,所以 dex 先放一旁,先看 libcocos2djs.so,從裡面找到一個疑似的 key。

但因為這個 key 解不了加密的 jsc,所以換別條路,用 Unicorn Engine 去模擬執行這個 so,不過沒進展,跑一跑卡住。

靜態分析跑不出來,改採動態分析,安裝了 Android 模擬器以及 frida,去 hook 多個參數,Memory.scanSyncfopen 都失敗了,後來也去找了 libcocos2djs.so 導出的函數,發現 xxtea_decrypt 是 public 的,就去 hook xxtea_decrypt

把遊戲跑起來之後,就拿到了正確的 key,並且還原出了那些加密後的 jsc 檔案,同樣含有遊戲邏輯與配置。

跑到這邊之後因為遊戲已經還原,就停止了,我想繼續測試他的能力,於是跟他説:「那你試著脫脫看那個殼吧」,結果那個殼的保護滿弱,跑起來之後 frida-dexdump 一下就把 dex 都 dump 出來了。

這個案例靜態分析跑不通改採動態分析加上 hook,並且還順便脫了一個殼(雖然殼滿弱的就是了)。

案例三:Unity 遊戲

APK 拆開之後有 libil2cpp、libunity 跟 libxlua 三個 so 檔案,猜測核心邏輯在 Lua 層。之後先用 Il2CppDumper 把 global-metadata 拆開,解出一些 C# 檔案跟 DLL,用 ILSpy 反編譯之後發現都是 class,實作是空的(似乎 IL2CPP 本來就是這樣?)

用 jadx 拆 Java,也都是一些 SDK,沒有遊戲邏輯,於是把心力放在找出 Lua 檔案。

用 UnityPy 掃所有 asset bundle 的 TextAsset,只找到 4 個 lua 腳本,都不是遊戲核心邏輯。從剛剛拿的 C# 程式碼中找到一些字串,發現遊戲有熱更新系統,用裡面翻到的 URL 跟 AES key 與 IV 嘗試去下載,發現都回 404 載不下來。

後來轉向再回去 asset bundle 找,找到一個 bundle 裡面有 3000 個 TextAsset(第一次沒檢查到),從檔名確認是加密的 Lua 腳本。

接著觀察發現這些檔案有許多前 6 個 byte 一樣,猜測是 Lua 5.3 編譯後的 bytecode 開頭,用 XOR 反推回 key 發現解不開。然後又嘗試了幾種不同的加密法,各種 AES 的模式,還是解不開。

再來去反編譯 libil2cpp,看到解密的過程是用 XOR 沒錯,然後密鑰是 6 個 byte 不斷重複。在 C# 裡面跟 global-metadata 用靜態的方式都找不到,到這邊卡關,由我介入。

我就問他說:「你動態分析會不會比較快?」

AI 給了兩個方案,一個 Frida hook,一個 Unicorn 模擬,我選後者,結果 AI 在寫腳本的時候,神來一筆地用別的方法破解了。他說他觀察那些文件,發現有一半前 54 個 bytes 都一樣,而且是 6 個 bytes 不斷重複,c3 70 43 22 34 a6

如果 key 是 6 個 bytes 重複 XOR,那密文重複代表明文也重複,什麼 Lua 文件開頭會有這麼多完全相同的字元?

AI 大膽的猜測:「註解」,Lua 的註解是 -- 開頭,很多框架的習慣是開頭放 ----- 一段長長的註解當分隔線。然後他就以這個假設為基礎, XOR 了一下拿到 key,再去解密 Lua 腳本,發現全部解開了,解出來直接是可讀的原始碼。

這個案例的關鍵在於 AI 是懂觀察的,而且會運用許多手段試著去解密,底下是 AI 在解這個案例時的流程,可以看到中間嘗試過許多方法但都走不通,不通就換下一個方法:

XAPK 解包
  ↓
Il2CppDumper + ILSpy + JADX  ← 標準流程,沒什麼問題
  ↓
UnityPy 掃描 → 只有 4 個工具腳本  ← 以為核心邏輯在服務器
  ↓
找 AppConfig → 拿到 CDN 地址和 AES Key
  ↓
嘗試下載 → 全部 404  ← 死胡同
  ↓
重新檢查 AssetBundle → 找到加密文件!
  ↓
觀察密文 → 發現 6 bytes 重複模式
  ↓
❌ 錯誤假設:Lua bytecode → 推導出錯誤的密鑰流
  ↓
❌ 嘗試 AES-CBC/ECB/OFB/CTR/CFB → 全不匹配
❌ 嘗試 RC4 → 不匹配
❌ 嘗試 .NET Random(10K seeds)→ 不匹配
  ↓
逆向 libil2cpp.so → 確認是簡單重複 XOR(不是流密碼)
  ↓
❌ 嘗試靜態找 ENCRYPT_BYTES → 走不通
❌ 嘗試 metadata defaultValues → 走不通
❌ 嘗試 ELF GOT/RELA → 太深
  ↓
 回頭看密文特徵 → 1500 個文件共享 62 bytes 前綴
  ↓
💡 假設明文是 '-'(0x2d) 重複 → XOR 得到 key → 解密成功!

案例四:另一個 Unity 遊戲

跟上一個拆開來類似,一樣發現是 Unity + Lua,但這次解密方法比較簡單,把 C# 的 dump 出來之後,用 encypt 當關鍵字去找,直接在裡面找到 LuaEncryption 的 key,以及自定義的 asset bundle offset,要跳過前 12 個 bytes。

接下來就跳過 12 個 bytes,然後用 key 去 XOR,得到了 Lua 的 bytecode(這次就是 bytecode 了不是原始碼),拼接起來變成一個大的 asset。

接下來 AI 寫了個 Python 腳本:

  1. 跳過前 12 bytes 自定義頭部
  2. 掃描所有 UnityFS\x00 標記的位置
  3. 按偏移量切割成獨立的 bundle
  4. 對每個 bundle 用 UnityPy 解析
  5. 提取所有 .lua.bytes 文件
  6. 對每個文件用 key 去 XOR 解密

就拿到 7000 多個 Lua JIT bytecode,然後全部丟到 ljd 去解回來,拿到可讀性高許多的 Lua 原始碼,總共 1000 萬行。

這案例跟第一個類似,靜態分析就全部搞定了,直接找到加密的 key 以及模式,把資源全部都解回來。

案例五:混淆過的 App

這個就是一般常見的稍微做過保護的 App,Java 層做了混淆讓你不容易還原,然後加解密相關邏輯都放 so 檔案裡面用 JNI 去使用,在送出 request 時會經過一些加密外加 signature,只要破解不了演算法就沒辦法離開 App 使用。

AI 拆開來發現混淆過後,自己寫了個反混淆的腳本,把幾個核心的 class 名稱還原了出來,接著開始逆向 so 那一段,基於 capstone + lief 反組譯 arm64-v8a 版本,還原為 pseudo-C。

有了這些程式碼之後就能進一步分析,最後把裡面的加解密演算法外加 key 都還原了出來。

所以混淆歸混淆,AI 可以經過觀察後得出一些方法想辦法還原。就算沒有還原,AI 讀混淆過後的程式碼也比人厲害得多。

案例六:加殼過的銀行等級 App

上面這些試完之後,我決定來挑戰大魔王:銀行等級的 App。

銀行通常對於資安的要求比較嚴格,所以鐵定會有一堆加解密、混淆與加殼,還有各種防 root 與防 hook 機制,因此「銀行等級」指的是類似的規格。

目標很明確,就是要能做到在 root 過的模擬器上打開 App,並且可以 hook 看到請求內容,做到這些代表裡面的保護機制都被繞過了。

這個銀行等級的 App 是加了俗稱的商業殼(意思就是某一間公司特別做的殼,這種商業方案的殼都滿貴的,一年可能要幾十萬台幣),主要邏輯都在一個 so 檔裡面。

AI 先用 objdump -h 看 section headers,發現兩個非標準 section,然後 .text 裡面一半以上都是加密的沒辦法反組譯,.rodata 也是完全加密,但從其他線索中已經推斷出是哪間的殼。

接著 AI 開始試著把 so 的殼先脫掉,先亂試了幾個方法都失敗,例如說想要暴力破解 key 之類的。

幾種方法都失敗後,開始先解一些能解出來的地方,試了幾個方法後知道了殼的運作,成功脫殼。

脫殼之後,就看得到裡面有什麼東西以及保護措施了,在 native 層有這些保護措施:

  • 反注入偵測:掃描 /proc/self/maps 尋找 frida-agentfrida-gadget 等記憶體映射
  • 執行緒名稱掃描:讀取 /proc/self/task/*/comm 尋找 pool-frida 等 Frida 特徵執行緒
  • 反 debug:ptrace(PTRACE_TRACEME) 自我附加,阻止 debugger
  • 字串比對:透過 strstrstrncmp 等函式在記憶體中搜尋 Frida 關鍵字
  • SO 加殼:.text 段加密,runtime 動態解密,無法靜態分析

在 Java 層則有這些:

  • Root 偵測:File.exists() 檢查 su、magisk、supersu 等路徑
  • 模擬器偵測
  • SystemProperties 偵測
  • anti-debug
  • SSL Pinning

繞過方式是搶先一步去 hook 各種方法,讓它都偵測不到,然後 native 層會把結果傳給 Java,在那邊去 patch 也行。既然都知道有哪些偵測手段了,就可以根據這些手段去做相對應的處理。

至於 Java 層的混淆,AI 觀察之後寫了個 1000 行的 Python 腳本根據各種規則去把字串還原,得到可讀性高的字串。

總之呢,最後成功了,App 打得開,可以 hook 到請求內容,保護措施全部都被繞過了。

案例七:加殼過的遊戲

自從知道 AI 也能脫殼以後,我就覺得說不定沒什麼是 AI 解不開的了,於是再找一個加殼過的遊戲。

這個遊戲的難度就比之前高了,他一樣是用了某個商業殼,而且做了雙層加密,他的 dex 先用一個 so 加密,然後這個 so 再用另一個 so 加密,而這個入口點的 so 本身又加了殼,不讓你破解。

我試了一天讓 AI 去試,最後都沒試出什麼結果,就算網路上有找到其他已經脫殼過的文章,他還是沒有完全試出來,so 的殼沒有脫掉,碰到了一些障礙。

但是呢,我讓 AI 換個方向之後,他發現這 Unity 遊戲其實主要邏輯放在 Lua,而 Lua 的資源雖然有加密,但是觀察加密過的 hex 之後,很快就觀察到是什麼模式,然後弄著弄著就解開了。

所以雖然 Java 層的那些程式碼沒有完全解開,殼也沒有脫掉,但總之核心的遊戲邏輯是拿到了。

這樣應該也算是成功吧?再給 AI 更久的時間、更多參考資料以及更多好用的工具,要把那個殼脫掉我認為也是辦得到的。

我的 AI 使用方式與花費

我用的是 Cursor 搭配 Claude Opus 4.6 high thinking,沒有裝任何 skill,而且 prompt 也很簡單:

xxx 資料夾底下有個 apk,把它逆向還原,要還原成原始碼

中途如果他碰到一些東西卡住,我會給他一些指示,例如說碰到加殼的時候:

他怎麼做到無法靜態分析的,怎麼個加密法,什麼時候會解密?你試試看能不能脫殼解開

有時候會給一些更明確的指示:

我們先 plan 一下之後要做的事,我待會要去睡了

  1. .so 還原成 C
  2. 查看 apk 還有什麼其他保護,該怎麼破解
  3. java 從混淆過的程式碼還原,至少要知道邏輯,或是觀察哪些 pattern 知道是 android 自己的 lib

主要就這幾個任務,我想要你對這個 apk 的保護方式瞭若指掌,彷彿有 source code 一樣,然後想出破解方法

我權限都開給他了,所有工具都是他自己裝的。他通常會先嘗試靜態分析,當他卡很久的時候,我就會讓他切換到動態分析,裝 Android 模擬器搭配 Frida hook。

我會觀察 AI 在做什麼,如果我覺得他太偏離方向,我會主動中斷並給他建議(但滿少發生的)。在 AI 做完之後,我會讓他總結一下他做了什麼,卡在哪裡,又是怎麼解決的。

在逆向了許多 app 之後,我會把這些經驗總結成 skill,下次速度就能更快。

每個 App 逆向還原的時間不一定,但大多數都在 30 分鐘左右,花的 token 沒有詳細計算,但總之在 Cursor 的計費下,逆向一個 App 花不到 5 塊美金。

不過我也有用 Claude Code 試了一下,有一個 Unity 的遊戲花了 4000 萬個 token,換算成錢大約 27 塊美金。

因此,比較公正的說法,如果純看 token 用量來說,我猜平均落在 30 塊美金上下。至於為什麼用 Cursor 會這麼便宜,我也不知道,明明是相同的模型。

AI 的不足之處

雖然說能解的都解開了,但有些只是你以為他有解開,其實根本沒做好。

舉個例子,遊戲的 APK 解開後他通常會用一些工具把 DLL 檔案拿出來,然後還原成 C#。通常我給的指示是「還原出原始碼」,但有時候他只有還原到 interface,只有方法的定義跟參數,並沒有實作的邏輯。

接著就是容易被 AI 騙的地方了,就算他只有 interface,也能根據這些命名跟結構自己猜運作邏輯,所以你讓他寫一份報告去分析有哪些東西,他也能寫得頭頭是道。

若是沒有再去追問細節,你會以為他真的還原出原始碼了,但其實不然。

這是個非常需要小心的地方,我之後每次都會追問他:「所以你有拿到原始碼了嗎?看一下登入系統的實作吧,要能看到實作才算數」,逼迫他再做更深一點,把我想要的東西還原出來。

這個確認的過程是很重要的,少了這一步就不完整了,就會被 AI 欺騙。反之,若是有好好確認,AI 的產出一定會讓你滿意。

一些心得

原來是我限制了 AI

我之前對 AI 的認知是:「逆向一些小東西絕對沒問題,但應該沒辦法脫殼吧」,但後來 AI 打我臉,跟我說我錯了。

那時我才意識到,我才是 AI 的限制器。

我明明沒有試過,但卻覺得 AI 辦不到。以前在軟體開發時我也會有類似的想法,某些工作之所以自己來,是因為我覺得 AI 辦不到。例如說需要同時改多個專案啦,或者是某個比較大的功能,需要對整體架構都很了解,我就會覺得 AI 辦不到,不如我自己來。

但後來我開始把越來越多 task 交給 AI,才發現大部分他都辦得到,再次印證了我才是 AI 的限制器。難怪之前有人說在某些領域,不懂的人用 AI 反而用的比較好,因為你不會先去假設 AI 做不到就不給他,而是什麼都讓他去是,做不到再說。

講回 AI 逆向這件事情,我看了 AI 逆向這麼多個 app 的流程,發現本質上都是一樣的,那就是做各種嘗試並觀察輸出,再從輸出中去改善,或是換個方法做事。

例如說 Frida hook 好了,如果 hook 失敗,他會根據 error log 去改。若是 hook 以後 app 自己關掉,他會更改 hook 的時機,或者是去測到底哪一段被偵測到,接著做出改動。

或許,只要你能給 AI 一個讓他發揮的環境,並確保他能看到足夠的 log 而且驗證對錯,再給他夠多的時間,沒有什麼是逆向不出來的。我後來也試了 desktop app,試了 wasm,都是可以的。

就算是加殼,只要讓 AI 去觀察去追蹤,就像人類逆向的流程那樣,他也能慢慢觀察、動手,再根據結果去調整,然後再試一次,再試無數次,直到成功為止。

雖然說我原本就知道 AI 強,但其實沒想到已經強到這種地步。如同我以前寫過的,逆向工程我會一點點皮毛,但到了 native 那一層就完全不行了,而 AI 已經超越了我的能力,做出了那些我做不到的事,甚至做出了「我以為他做不到」的事。

經歷過這次探索之後,我對 AI 的能力徹底改觀了,並且對「AI 取代軟體工程師」這件事情有了更多的思考。假設我上面說的那段「沒有什麼是逆向不出來的」是對的,那這是否也能運用在軟體開發上?如果 AI 可以自己規劃、寫程式、測試並驗證結果,那是不是讓他一直跑,可以做出一個完整的應用程式,而且品質還很好?

這個話題我們留到之後聊吧,還有其他角度可以一起探討。

攻防的平衡

只要是 client 端的東西,都是沒有秘密的。

混淆也好,加殼也好,都只是延緩被還原的時間,只要付出的時間夠多,你所有程式碼都是在 client 跑的,因此所有東西都能還原。

因此,許多防禦手法都建立在「增加難度延長破解時間」這點上面。我們都知道 client 沒有秘密,但有些在 client 端的東西,我們還是想盡量守護,讓還原的難度變高。

所以守方把程式碼混淆,讓變數變成一堆難以閱讀的文字,甚至是把常數加密,要執行才能解開,也是不想讓你這麼容易看到明文。加殼也是一樣,不希望這麼容易看到裡面到底在跑什麼,而 anti-debug 也是不希望你 root,不希望你 hook,不希望你 debug,所以試著去偵測各種逆向工具是否存在。

而攻方就是花時間去反向推回你原本的邏輯,靠著經驗加速,知道這個模式看起來是 AES,這個看起來是 XOR,這個模式以前出現過,應該怎麼破解等等。只要守方稍微調整一下,就算只是一點,最後產出來的東西也可能完全不同,攻方需要從頭再來一次。

但是 AI 逆向變得這麼強以後,立場會不會開始反過來?攻方花這麼多時間弄出來的殼,結果 AI 一個小時就脫掉了。想了這麼多混淆的辦法,自己實作了一套新的演算法,結果 AI 看一看就寫了個反向腳本,全部組裝回去。

若是守方要跟上,或許需要用魔法對付魔法,用 AI 來加殼,每次加殼都換一種全新的方法或是模式,而且不是簡單的更換,是讓 AI 讓它變得更複雜,才能確保攻方需要從頭開始。

但儘管如此,在 AI 面前,會不會又只花了一兩小時就解開了?我不知道。

總結

我不是什麼逆向工程專家,因此對於 AI 在這領域的影響不多加評論。但至少對我這個不太會逆向的人來說,AI 已經幾乎完全滿足我對逆向工程的需求,桌面應用可以逆,APK 可以逆,WASM 可以逆,殼可以脫,加密可以解。

對於 binary 的逆向,雖然有時需要我協助打開 Ghidra,幫他裝好 Ghidra MCP,但這都是小事。

經過此次一役,見識到了 AI 的能力之後,我已經臣服於 AI。

有沒有什麼是 AI 解不開的?或許有,像我上面提的案例七其實就沒解開,他只是換個方式拿到我想要的東西,並沒有把 APP 全部都還原。但除了這個以外,他每一個都確實解開了(話說我很好奇 AI 能不能解開 HybridCLR 的商業殼,但我還沒碰過用這個商業版本的)。

有沒有可能 AI 逆向被我講得好像很強,實際上在專業人士眼中沒這麼厲害?也有這個可能,畢竟我拆過的東西,看雪(中國有名的逆向社群)那邊的大大們都拆的出來,甚至有些東西已經被人拆過且分享了心得,AI 有參考到。

但總之,這篇文章的初衷是想記錄一下我體驗完 AI 逆向工程的心得,就三個字:「太神啦!」,這次體驗完,徹底影響我對 AI 能力的看法。

而且不是「AI 輔助逆向工程」,是全 AI 逆向工程,工具他自己裝,分析他自己分析,反組譯他自己反,我只會在旁邊靠北說:「你應該解得開吧,再試試」。

從 Coupang 的個資外洩談內部威脅、金鑰管理與 JWT

評論