AI 工具 17 min read

REA 實測混淆 JS:小恐龍速度規則全挖出,381KB 大檔卻撞上 2 萬節點上限

照 IG 影片的小恐龍題目,我把自寫的恐龍遊戲用 esbuild 壓縮混淆成 552 bytes 交給 REA 6.8.0,7 秒產出語意圖,初速 6、每幀 +0.001、上限 13 全對上原始碼。但不給證據 Claude 也答得出來,而同一段程式埋進 381KB bundle 時,REA 的語意圖撞到每檔 20,000 節點上限、常數節點 0 個——它的價值在可追溯的證據,大檔要先看 truncated 再下結論。

REA 實測混淆 JS:小恐龍速度規則全挖出,381KB 大檔卻撞上 2 萬節點上限
本文目錄 · 7
懶人包
  • 我照 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。

所以分工是這樣:

混淆過的 bundle

REA 靜態解析
產出語意圖

證據 JSON
常數、欄位、呼叫關係

agent 讀證據
回答為什麼

名稱被混淆成 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: 6n.p初速 6,函式 p✅
ACCELERATION: 0.001n.c每幀 +0.001,函式 e✅
MAX_SPEED: 13n.r上限 13,函式 e✅
GRAVITY 0.6、JUMP_VELOCITY -10n.m、n.u判定與速度無關✅
7000 幀是 (13 − 6) ÷ 0.001 算出來的,數學上成立。

🔍 不給 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 兩次 REA 分析的終端機輸出對照:小檔列出速度常數,大檔語意圖停在 20000 節點且沒有任何常數
項目552 bytes 小檔381.6 KB 大檔
分析時間7.28 秒51.12 秒
證據 JSON317,644 bytes42,259,172 bytes
語意圖節點/關係166/17420,000/14,879
coverage.truncatedFalseTrue
常數(literal)節點60
萃取出的速度規則3 個常數全對什麼都沒有
大檔的語意圖剛好停在 20,000 個節點,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 明列只能分析你有權限的軟體
我自己的結論:REA 的價值在「證據有出處、限制寫得清楚」,不在「比模型更會讀程式」。用它之前,先想清楚你要的是答案還是證據。

🧱 實測踩坑紀錄

  • Node 版本警告:rea-agents 6.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 讀證據時,證據內容會送到模型供應商。

📚 延伸資源

想自己重做:照上面第 1 到第 4 步,用你自己寫的小遊戲或公司有權限的前端 bundle,跑完先看 coverage.truncated 再看結果。
author
陳
陳彥彤

AI 工程師 · AI 顧問。Java 後端 8 年、AI 工程師 2 年。AI 內訓 · AI 導入顧問 · 前後端與雲端培訓。

support

覺得文章有用可以到 GitHub 給個 star,或是透過信箱聊聊 AI 內訓、AI 導入顧問或前後端 / 雲端培訓。