懶人包
- 我照 IG 影片的小恐龍題目重做一次:自己寫恐龍遊戲,用 esbuild 壓縮並把設定名稱混淆成單一字母,交給 REA 6.8.0 分析。
- 552 bytes 的混淆 bundle,REA 7.28 秒跑完,語意圖裡就有「常數 → 設定欄位 → 讀它的函式」這條線,初速 6、每幀 +0.001、上限 13 全部對上原始碼。
- 對照組:同一個 552 bytes bundle 不給 REA 證據,直接貼給 Claude,也答出一樣的三個數字。小檔不需要 REA。
- 把同一段遊戲埋進 381.6 KB 的 bundle,REA 跑 51 秒、吐 42 MB 證據,語意圖撞到每檔 20,000 節點上限,常數節點 0 個,自動萃取找不到規則。
- REA 會在輸出裡標
truncated: true,並明講「缺的事實不能當成不存在」。大檔要先切小或改用其他查法,不能只看「沒找到」就下結論。
YuNo 老師 10 月 24 日在 Instagram 發了一支 REA 的 Reel,示範「問它小恐龍為什麼越跑越快,它直接找到速度規則」。REA 是 morluto 開源的逆向工程 MCP,我 2026-10-30 用 gh 查到 410,117 星。
REA 是什麼、怎麼裝、能拆哪些東西,我在 REA 是什麼?AI 逆向工程 MCP 實測拆 Electron App 寫過。這篇只回答一個問題:把程式碼混淆之後,REA 到底能不能把「越跑越快」的規則挖回來?檔案變大又會怎樣?
📌 REA 怎麼「看懂」混淆過的 JavaScript?
REA 不執行目標程式,也不直接給答案。它把 JavaScript 當純文字解析成 AST,再建一張語意關係圖(semantic graph),節點是函式、綁定、屬性欄位、常數,關係是 calls、reads-property、defines 這類事實。每條關係都帶來源檔案路徑和檔案的 sha256。
所以分工是這樣:
名稱被混淆成 n.c、n.r 沒關係,因為圖記錄的是「哪個常數寫進哪個欄位、哪個函式讀了那個欄位」。這條線不靠名字。
🦖 我怎麼重現小恐龍實驗?
1. 寫一個迷你恐龍遊戲
速度設定照 Chrome 離線小恐龍的數字:初速 6、每幀加 0.001、上限 13。這三個值可以在 wayou/t-rex-runner 這份從 Chromium 抽出來的原始碼對到(SPEED: 6、ACCELERATION: 0.001、MAX_SPEED: 13)。
// src/config.js
export const RUNNER_CONFIG = {
INITIAL_SPEED: 6,
ACCELERATION: 0.001,
MAX_SPEED: 13,
GRAVITY: 0.6,
JUMP_VELOCITY: -10,
GAP_COEFFICIENT: 0.6,
};
// src/physics.js
export function nextSpeed(speed) {
if (speed < RUNNER_CONFIG.MAX_SPEED) {
return Math.min(RUNNER_CONFIG.MAX_SPEED, speed + RUNNER_CONFIG.ACCELERATION);
}
return speed;
}
game.js 每次 tick 呼叫 nextSpeed,main.js 用 requestAnimationFrame 每幀跑一次。
2. 壓縮加混淆
用 esbuild 0.28.2 打包壓縮,再用 --mangle-props 把設定名稱和狀態欄位改成單一字母:
esbuild src/main.js --bundle --minify \
--mangle-props='^([A-Z_]+|speed|distance|trex|vy|frames)$' \
--outfile=app-hard/dist/bundle.min.js
產出 552 bytes。INITIAL_SPEED 變成 p,ACCELERATION 變成 c,MAX_SPEED 變成 r:
(()=>{var n={p:6,c:.001,r:13,m:.6,u:-10,a:.6};function e(t){return t<n.r?Math.min(n.r,t+n.c):t}function i(t){let o=t.n+n.m;return{...t,n:o,y:Math.min(0,t.y+o)}}function p(){return{e:n.p,o:0,t:{y:0,n:0},i:0}}function c(t){let o=e(t.e);return{...t,e:o,o:t.o+o,t:i(t.t),i:t.i+1}}function m(t){return t.t.y===0?{...t,t:{...t.t,n:n.u}}:t}var r=p();document.addEventListener("keydown",t=>{t.code==="Space"&&(r=m(r))});function u(){r=c(r),document.getElementById("score").textContent=Math.floor(r.o/40),requestAnimationFrame(u)}requestAnimationFrame(u);})();
app-hard/ 資料夾裡另外放一個 index.html 和 package.json,讓它看起來像一個網頁 App。
3. 跑 REA
REA 用的是 npm 上的 rea-agents 6.8.0,我裝在獨立資料夾,沒跑 setup,所以不會動到 Claude Code 的全域 MCP 設定:
npm install rea-agents
./node_modules/.bin/rea analyze-javascript-application ./app-hard --json > out.json
exit 0,7.28 秒(含 Node 啟動),out.json 317,644 bytes。輸出裡的 limitations 寫明「bundle bootstrap code was never executed」,也就是全程沒執行遊戲。
4. 從證據萃取速度規則
317 KB 的 JSON 人看不完,我寫了一支 46 行的 Python,沿著 literal --defines--> 欄位 --reads-property--> 函式 這條關係走,列出每個函式讀了哪些常數:
REA semantic graph: 166 nodes / 174 relations
function e: c=0.001 (read x1), r=13 (read x2)
function i: m=0.6 (read x1)
function m: u=-10 (read x1)
function p: p=6 (read x1)
e 讀了 0.001 和 13(13 讀兩次,一次比較、一次 Math.min),p 讀了 6。GAP_COEFFICIENT 的 0.6 還留在設定物件裡,但沒有任何函式讀它(obstacleGap 沒被呼叫,打包時被拿掉了),所以它不在清單上。
5. 交給 Claude 解讀
把上面的輸出和 bundle 一起貼給 claude -p,問「這隻恐龍為什麼越跑越快,給出初速、每幀增量、上限和證據位置」。它的回答(節錄):
- 初速 6:p() 讀 n.p=6,當作初始速度 e。
- 每幀 +0.001:requestAnimationFrame(u) 每幀呼叫 c(),c() 再呼叫 e(t.e),
也就是 Math.min(n.r, t+n.c),其中 n.c=0.001。
- 上限 13:e() 裡的 n.r=13,先判斷 t<n.r 再加,並用 Math.min 封頂。
依此推算,大約 7000 幀後到達上限(60fps 約 117 秒)。
m=0.6(i())是重力,u=-10(m())是跳躍初速,兩者都和加速無關。
| 原始碼 | 混淆後 | REA+Claude 還原 | 結果 |
|---|---|---|---|
INITIAL_SPEED: 6 | n.p | 初速 6,函式 p | ✅ |
ACCELERATION: 0.001 | n.c | 每幀 +0.001,函式 e | ✅ |
MAX_SPEED: 13 | n.r | 上限 13,函式 e | ✅ |
GRAVITY 0.6、JUMP_VELOCITY -10 | n.m、n.u | 判定與速度無關 | ✅ |
🔍 不給 REA 證據,Claude 答得出來嗎?
答得出來。我用同一個 552 bytes bundle 開一個對照組,prompt 拿掉 REA 證據,只留 bundle 和同樣的問題。Claude 的回答:
結論:速度從 6 開始,每幀加 0.001,最高到 13。
- 初速 6:p() 建立初始狀態時設 e:n.p,而 n.p=6。
- 每幀 +0.001:u() 透過 requestAnimationFrame 每幀呼叫一次 c(),
c() 再用 e(t.e) 算出新速度,加上 n.c=.001。
- 上限 13:e() 只在速度低於 n.r=13 時才加,並用 Math.min(n.r, …) 夾住。
三個數字、三個函式位置,跟有 REA 的版本一樣。
這個結果讓我把問題換個問法:552 bytes 的程式,模型一眼就讀完了,REA 在這裡只是多了一份可以引用的證據。它的差別在可追溯性:每個結論都能對回語意圖裡一條帶 sha256 的關係,而不是「模型說的」。要把答案寫進報告、或讓另一個 agent 複查,這份證據有用;只是想知道答案,直接貼給模型就夠了。
⚠️ 381 KB 大檔會怎樣?我踩到 2 萬節點上限
REA 該發揮的地方是「模型一次讀不完」的大檔。我把同一段恐龍遊戲跟 openpgp 一起打包,模擬一個真實網站的 bundle:
# src/main-big.js:import './main.js',再 import openpgp 當雜訊
esbuild src/main-big.js --bundle --minify --platform=browser \
--mangle-props='^(INITIAL_SPEED|ACCELERATION|MAX_SPEED|GRAVITY|JUMP_VELOCITY|GAP_COEFFICIENT|speed|distance|trex|vy|frames)$' \
--outfile=app-big/dist/bundle.min.js
產出 381.6 KB(390,787 bytes),恐龍的設定物件 tr={L:6,O:.001,M:13,j:.6,J:-10,_:.6} 落在檔案第 103 個 byte。
| 項目 | 552 bytes 小檔 | 381.6 KB 大檔 |
|---|---|---|
| 分析時間 | 7.28 秒 | 51.12 秒 |
| 證據 JSON | 317,644 bytes | 42,259,172 bytes |
| 語意圖節點/關係 | 166/174 | 20,000/14,879 |
coverage.truncated | False | True |
| 常數(literal)節點 | 6 | 0 |
| 萃取出的速度規則 | 3 個常數全對 | 什麼都沒有 |
coverage.limits 裡寫著 max_semantic_nodes_per_file = 20000。保留下來的節點以 call-site(7,320)、binding(4,534)為主,常數節點一個都沒有,我那支沿著常數走的 Python 自然什麼都找不到。設定物件明明在檔案最前面,也沒有被留下。
REA 這裡做得對的地方是它沒有假裝完整。輸出同時寫了:
coverage.status = partial, truncated = true
"Graph coverage is incomplete; unavailable facts remain explicit and must not be read as absence."
意思是「圖裡沒有」不等於「程式裡沒有」。agent 如果只看萃取結果是空的,就回答「找不到加速規則」,那是 agent 讀錯證據,不是 REA 說錯。
trace-javascript-semantics 這類追查指令讀的是同一張語意圖,我看了它的原始碼,輸入就是 analyze-javascript-application 的證據,所以在被截斷的圖上追,一樣追不到已經被丟掉的節點。
🧭 什麼時候該用 REA 讀混淆 JavaScript?
| 情況 | 建議 |
|---|---|
| 幾 KB 以內的單一檔案,只想知道答案 | 直接貼給模型讀,不用 REA |
| 小到中型檔案,答案要寫進報告或讓別人複查 | 用 REA,留著證據 JSON 當出處 |
| 單檔幾百 KB 的大 bundle | 先看輸出的 coverage.truncated,是 True 就把 bundle 拆小、或先用 grep 找到目標區段再分析 |
| Electron App、多個檔案、IPC 呼叫鏈 | REA 的強項,見上一篇的 Electron 實測 |
| 別人的商業軟體 | 先確認授權,REA 的 DISCLAIMER 明列只能分析你有權限的軟體 |
🧱 實測踩坑紀錄
- Node 版本警告:
rea-agents6.8.0 的engines寫^22.23.4 || ^24.22.1 || >=26.12.1,我的機器是 24.11.1。npm install出 EBADENGINE 警告,但analyze-javascript-application兩次都 exit 0。正式用建議先升 Node。 - npx 並行會壞快取:前一輪實驗同時跑兩個
npx rea-agents,npm 回 ECOMPROMISED。改成在獨立資料夾npm install後直接呼叫node_modules/.bin/rea就沒事。 - 證據很肥:552 bytes 換 317 KB,381.6 KB 換 42 MB。不要整份塞給模型,用程式或 REA 的
inspect-analysis-view挑片段。 - 只做靜態分析:REA 全程不執行 bundle,
limitations也寫明靜態關係不代表執行時一定發生。速度規則這種寫死的常數沒問題,要看執行時的值得另外跑起來量。 - 分析在本機,解讀在雲端:REA 不上傳目標檔案,但 agent 讀證據時,證據內容會送到模型供應商。
📚 延伸資源
- REA 原始碼與 README:morluto/rea(MIT)
- npm 套件:rea-agents
- Chrome 小恐龍抽出版原始碼:wayou/t-rex-runner
- 本次影片來源:YuNo 老師的 Instagram Reel
- REA 安裝與 Electron 實測:REA 是什麼?AI 逆向工程 MCP 實測拆 Electron App
coverage.truncated 再看結果。