blog

REA: cho agent dịch ngược phần mềm, rồi kiểm luôn cái oracle

REA cho coding agent đọc một chương trình không có source, xuống tới mã máy, rồi dựng lại tính năng đó bằng code của mình. Bản dựng lại đúng hay sai do oracle quyết định, và oracle cũng là code, cũng có bug.

Ngày đăng
AIAgentClaude CodeReverse engineeringMCP
Cửa sổ assembly bên trái đi qua cổng oracle ở giữa thành cửa sổ code mới bên phải, một bản sai bị chặn lại trước cổng

REA (Reverse Engineer Anything) cho coding agent đọc một chương trình không có source, xuống tới mã máy, rồi dựng lại tính năng đó bằng code của mình. Model đọc và viết lại khá giỏi. Thứ quyết định bản dựng lại có đúng hay không là oracle, phép kiểm so bản mới với bản gốc, và một oracle có điểm mù sẽ âm thầm duyệt hàng trăm hàm sai.

REA là MCP server cộng CLI, chạy phân tích ngay trên máy

REA của morluto, giấy phép MIT. Agent gọi nó qua MCP; cùng bộ tool đó cũng gọi được từ terminal. Đo ngày 29/10/2026, repo có 403.103 sao, bản mới nhất là rea-agents 6.8.0 ra ngày 25/10/2026. Phân tích chạy local, chương trình không bị upload đi đâu; agent chỉ thấy kết quả REA trả về, và chính sách dữ liệu của nhà cung cấp model áp cho phần đó.

REA đọc được các loại chương trình sau, mỗi loại cần một bộ công cụ khác:

LoạiCần thêm
JavaScript, app ElectronNode.js và npm
Chương trình native, gameHopper, Ghidra, IDA hoặc Kuna
Assembly .NET (metadata, lệnh CIL)không cần gì, chỉ đọc tĩnh
APK AndroidJADX chạy headless và một JDK đầy đủ
Website, file HARtrình duyệt họ Chrome
FirmwareBinwalk hoặc Unblob trên Linux
WASM, binary Go, bytecode EVM, crash Linux đã ghi lạiWABT, không cần cài Go, pwntools tuỳ loại

Phân tích tĩnh JavaScript và .NET chỉ đọc file, không chạy app. Client được hỗ trợ gồm Claude Code, Codex, Cursor, Gemini CLI, Antigravity, Windsurf, OpenCode, VS Code và tám client khác.

Cài REA vào Claude Code

Lệnh lấy từ trang Get started:

bash
# Node 22 (>=22.23.4), 24 (>=24.22.1) hoặc 26.12.1 trở lên
node -v

# nối REA vào agent: in kế hoạch, sao lưu config, hỏi trước khi sửa
npx rea-agents@latest setup

# khởi động lại agent rồi kiểm
npx -y rea-agents@latest doctor --client claude_code --json

Chỉ khi cần đọc chương trình native mới phải cài thêm Ghidra 12.1.x và một JDK 64-bit từ 21 trở lên:

bash
export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
export JAVA_HOME=/absolute/path/to/jdk-21
npx -y rea-agents@latest doctor --provider ghidra --json

Trên Windows x64, phần native mới ở mức thử nghiệm và chỉ đọc. Cập nhật REA bằng rea update. Muốn thử trước trên một app Electron có sẵn trên máy:

bash
npx -y rea-agents@latest analyze-javascript-application "/absolute/path/to/app" --json

App Electron trên macOS giữ code trong /Applications/<App>.app/Contents/Resources/app.asar.

Ba loại oracle: so byte, so hành vi, chạy thử cả chương trình

Oracle là bất kỳ phép kiểm nào so bản dựng lại với một tham chiếu mình tin. Có ba cấp. Oracle byte biên dịch bản mới rồi so với mã máy của bản gốc. Oracle hành vi đưa cùng một bộ input cho cả hai rồi so output. Oracle runtime build cả chương trình rồi dùng thử. Các ví dụ trên rea.tools và trong dự án Touhou ở mục sau dùng đủ ba cấp.

DX-Ball: 63 byte khớp từng byte

DX-Ball là game Windows năm 1996. REA lần theo một lệnh phát âm thanh tới hàm tính độ lệch trái phải của tiếng, agent viết lại hàm đó bằng C. Oracle hành vi chạy 3.205 ca thử trên mã x86 gốc, qua hết. Oracle byte biên dịch lại bằng đúng trình biên dịch VC4.0, cả 63 byte của hàm khớp với bản gốc.

Windows Calculator: vì sao 200 + 10% ra 220

REA lôi nhánh xử lý nút % ra khỏi Calculator đã cài trên máy:

text
0x180124949: CMP EAX, 0x5c
0x18012494c: JZ 0x180124aba
0x180124952: CMP EAX, 0x5b
0x180124955: JZ 0x180124aba
0x18012495b: MOV EDX, 0x64

Trong source công khai của Calculator, IDC_MUL là 92 (0x5c) và IDC_DIV là 91 (0x5b); còn 0x64 là 100. Ra được quy tắc: sau phép nhân hoặc chia, % lấy current / 100; sau phép cộng hoặc trừ, % lấy current * previous / 100. Vậy 10% sau 200 + là 20, kết quả 220. Phiên bản đọc là Windows Calculator 11.2508.4.0, REA 4.1.0.

Cùng trang có game khủng long của trình duyệt (bản dựng từ Chromium của wayou): tốc độ bắt đầu ở 6, mỗi lần update cộng 0,001, trần là 13. Họ gọi chính hàm update gốc để kiểm: sau 4.000 lần update tốc độ là 10,0, sau 10.000 lần là 13,0.

TH08: hằng số 0.0 thay cho 128.0 vẫn qua phép so byte

N0zoM1z0 dựng lại các game Touhou bằng agent và viết lại quá trình đó trong một bài trên blog của REA ngày 10/10/2026.

Ở TH08, một hàm đã qua phép so byte chính xác. Tới tháng 9, một bản port lên Switch báo lỗi tính năng tự hút item. Code dựng lại so sức mạnh của người chơi với 0.0, còn bản gốc so với 128.0, ngưỡng sức mạnh tối đa. Sức mạnh không bao giờ âm, nên điều kiện trong bản dựng lại gần như luôn đúng.

Lỗi lọt qua vì công cụ so sánh chỉnh địa chỉ trước khi so lệnh. Việc này cần thiết, vì bản build lại có thể đặt hằng số ở chỗ khác trong file. Nhưng nó không kiểm giá trị số thực nằm ở địa chỉ đó.

Bản sửa (PR #11, merge ngày 2/9/2026) có hai nửa: sửa source, và cho phép so sánh kiểm luôn byte thật của hằng số. Sau đó rà lại mọi thứ từng qua phép kiểm yếu: 1.548 chỗ tham chiếu hằng số thực, tìm thêm 12 hằng số sai trong năm hàm đã được duyệt. Bài học tác giả ghi lại nguyên văn là "Test the oracle too": thử những ca nó phải từ chối, và khi nó bỏ sót một bug thì xem lại mọi kết quả trước đó đã dựa vào nó.

TH095 xong trong khoảng 12 ngày nhờ repo làm bộ nhớ

TH08 là phần nối tiếp bản dựng lại công khai của nhóm GensokyoClub. Phần việc riêng của N0zoM1z0 bắt đầu ngày 13/8/2026; tới 19/8 sổ theo dõi đã có source cho đủ 1.107 hàm của game, ngày 24/8 có bản port Linux chơi được.

TH095 bắt đầu ngày 29/8 với không hàm nào được xác nhận. Ngày 7/9 có source cho đủ 697 hàm, ngày 8/9 có 696 hàm khớp chính xác, ngày 10/9 bản Windows i386 chơi được, khoảng 12 ngày sau khi khởi tạo. TH04, game DOS cho PC-98 gồm bốn chương trình chạy phối hợp, đã dựng lại được bản DOS, bản port 64-bit còn đang làm.

Game thứ hai nhanh hơn vì không bắt đầu từ số không: code, bằng chứng, các phép kiểm và bài học của TH08 nằm sẵn trong repo, kể cả phép kiểm đã sửa sau vụ 128.0. Bốn trong năm bài học tác giả rút ra:

  • Giao cho agent quyền tự điều tra: đặt mục tiêu và tiêu chí nghiệm thu, để agent chọn thử gì tiếp.
  • Dùng repo và Git làm bộ nhớ: hội thoại hết thì repo vẫn còn, phiên sau đọc checkpoint mà làm tiếp.
  • Để bằng chứng quyết định điều mình khẳng định; "chưa biết" là câu trả lời hợp lệ.
  • Kiểm cả oracle.

Phía con người thì chưa êm. GensokyoClub giờ phát triển kín, gọi các bản dịch ngược và port làm bằng AI là "grifters" lấy công của họ, và không nhận pull request chủ yếu do AI viết. N0zoM1z0 giữ lại lịch sử commit của nhóm trong bản nối tiếp.

Clean room: đọc hành vi của bản gốc, không đọc code của nó

Clean room là cách làm có từ đầu thập niên 1980. Một bên nghiên cứu bản gốc và viết ra nó làm gì. Bên kia chỉ được đọc bản mô tả đó để viết code, không bao giờ thấy code gốc. Compaq làm BIOS tương thích IBM PC theo cách này cho chiếc Compaq Portable, công bố tháng 11/1982.

Toà án Mỹ nhiều lần bảo vệ nửa "đọc". Năm 1992, vụ Sega v. Accolade kết luận tháo mã máy ra đọc để làm game tương thích là fair use. Năm 2000, vụ Sony v. Connectix cho phép giả lập PlayStation được làm sau khi nghiên cứu BIOS của Sony, vì sản phẩm cuối không chứa code nào của Sony.

Bộ app ArtCraft làm theo đúng tinh thần đó. Họ có 13 app viết từ đầu bằng Rust, giấy phép MIT hoặc Apache-2.0: PhotoCraft nhắm vào Photoshop, FilmCraft nhắm vào Premiere Pro, các app còn lại lo ảnh chụp, vector, motion graphics, dàn trang, thiết kế giao diện, PDF, văn bản, bảng tính, slide, âm thanh và CAD. Phần lớn đang ở trạng thái "In development", PhotoCraft và PdfCraft là "Early alpha". FilmCraft tự viết codec H.264, HEVC, ProRes và AAC từ đặc tả công khai.

Mục 2 trong AGENTS.md của FilmCraft, file mọi người và agent đóng góp đều phải theo, có tên "Clean-room code":

Never read, disassemble or copy anything inside Adobe application bundles; file names and listings only. Observe behaviour by using the app; never capture its Home screen, recent projects, account info or file browsers.

Oracle của FilmCraft vì thế là hành vi của Premiere quan sát từ bên ngoài, cộng ffmpeg/ffprobe chỉ dùng làm oracle kiểm thử bên ngoài, không bao giờ link vào hay đóng gói theo.

Luật Sở hữu trí tuệ Việt Nam không có điều khoản nào về dịch ngược

Đọc văn bản hợp nhất Luật Sở hữu trí tuệ đăng trên Công báo số 197 ngày 10/4/2026, đã gồm các lần sửa tới Luật 131/2025/QH15, không có điều nào nhắc tới "dịch ngược" hay "tương thích". Hai điều dính tới chương trình máy tính:

  • Điều 22 khoản 1 chỉ cho người dùng hợp pháp làm một bản sao dự phòng, không được chuyển cho người khác.
  • Điều 25 khoản 3 loại chương trình máy tính khỏi ngoại lệ tự sao chép để nghiên cứu, học tập ở khoản 1: "Việc sao chép quy định tại khoản 1 Điều này không áp dụng đối với tác phẩm kiến trúc, tác phẩm mỹ thuật, chương trình máy tính".

Mỹ có án lệ Sega và Connectix bảo vệ việc tháo ra đọc để làm sản phẩm tương thích; luật Việt Nam không có điều khoản tương ứng. Giấy phép mình đã bấm đồng ý khi cài app cũng là một lớp ràng buộc riêng. Phần này không phải tư vấn pháp lý; định làm sản phẩm thương mại từ kết quả dịch ngược thì hỏi luật sư trước khi viết dòng code đầu tiên.

REA tự vạch ranh giới trong DISCLAIMER

DISCLAIMER.md của REA ghi rõ công cụ dành cho nghiên cứu bảo mật, phân tích malware và sự cố, tìm lỗ hổng có công bố phối hợp, CTF và giáo dục, tương thích, bảo tồn phần mềm, debug hoặc audit phần mềm mình sở hữu hay được phép kiểm tra. Nó không giúp vượt DRM, kiểm tra bản quyền hay anti-cheat, không làm cheat game, không vượt xác thực, không sao chép phần mềm độc quyền hay tái tạo code và tài nguyên được bảo hộ.

Có một câu ít người nhắc: "Obtaining source-like output or an analysis report does not grant rights to the original program, its data, or the generated output." Code giống source mà REA sinh ra không cho mình quyền gì với chương trình gốc. Bản crack của IDA, Hopper hay JEB cũng không được REA chấp nhận; công cụ thương mại thì phải có license thật.

Vòng lặp hai phòng với Claude Code và /goal

Đem clean room vào agent thì tách làm hai session không chung thư mục, chỉ nối bằng một file SPEC.md.

Phòng A, người đọc: một session Claude Code có REA, điều tra và viết spec. Không gì khác ra khỏi phòng này.

text
Dùng REA điều tra [tính năng] trong [app] hoạt động thế nào. Gắn nhãn mỗi khẳng định
là quan sát, suy luận hay chưa biết, kèm bằng chứng. Sau đó viết SPEC.md: hành vi,
input, output, ca biên và những điểm chưa biết. Không code, không địa chỉ, không tên
hàm hay tài nguyên lấy từ bản gốc.

Phòng B, người dựng: một session mới ở thư mục khác, chỉ nhận SPEC.md, không bao giờ thấy phần phân tích. Phòng này chạy bằng /goal: Claude làm tiếp qua nhiều lượt cho tới khi điều kiện đạt. Sau mỗi lượt, một model nhỏ và nhanh đọc lại những gì session đã in ra để đánh giá điều kiện. Model đó không tự chạy lệnh hay đọc file, nên điều kiện phải là thứ output của test chứng minh được.

text
/goal mọi ca trong SPEC.md pass trong ./my-version, oracle từ chối cả 3 bản sai cài sẵn,
và không file nào ngoài ./my-version bị sửa. Hoặc dừng sau 25 lượt.

Tài liệu của /goal khuyên điều kiện có một trạng thái cuối đo được, một phép kiểm nói rõ (npm test exit 0), các ràng buộc không được phá, và một mệnh đề giới hạn số lượt. Cách dựng vòng lặp quanh /goal có ở bài loop engineering.

Cài sẵn ba bản sai để thử oracle

"3 bản sai cài sẵn" trong điều kiện trên là bài học TH08 biến thành thói quen: trước khi tin một lần chạy xanh, đưa cho oracle vài bản chắc chắn sai. Bản nào lọt qua thì oracle có điểm mù, và mọi kết quả xanh trước đó đều đáng ngờ.

Tôi viết lại quy tắc nút % ở trên thành một hàm, kèm ba bản sai: đổi một hằng số, đảo hai nhánh, và quên nhánh chia. Rồi cho hai oracle chấm: một oracle chỉ thử phép cộng như ví dụ 200 + 10% ai cũng nhắc, một oracle có ít nhất một ca cho mỗi phép tính.

js
// oracle.mjs — chạy: node oracle.mjs
export function percent(op, previous, current) {
  if (op === '*' || op === '/') return current / 100
  return (current * previous) / 100
}

const apply = (op, a, b) => ({ '+': a + b, '-': a - b, '*': a * b, '/': a / b })[op]
const calc = (fn) => (op, a, b) => apply(op, a, fn(op, a, b))

const mutants = {
  'hằng số 100 thành 10': (op, p, c) => (op === '*' || op === '/' ? c / 10 : (c * p) / 10),
  'đảo nhánh': (op, p, c) => (op === '*' || op === '/' ? (c * p) / 100 : c / 100),
  'quên nhánh ÷': (op, p, c) => (op === '*' ? c / 100 : (c * p) / 100),
}

const weak = [['+', 200, 10, 220], ['+', 50, 50, 75], ['+', 0, 10, 0]]
const strong = [...weak, ['-', 200, 10, 180], ['*', 200, 10, 20], ['/', 200, 10, 2000]]

const passes = (fn, cases) =>
  cases.every(([op, a, b, want]) => Math.abs(calc(fn)(op, a, b) - want) < 1e-9)

for (const [name, cases] of [['oracle yếu', weak], ['oracle đủ', strong]]) {
  console.log(`${name}: bản đúng ${passes(percent, cases) ? 'qua' : 'TRƯỢT'}`)
  for (const [m, fn] of Object.entries(mutants)) {
    console.log(`  ${m}: ${passes(fn, cases) ? 'LỌT (oracle mù)' : 'bị bắt'}`)
  }
}

Kết quả trên Node 22:

text
oracle yếu: bản đúng qua
  hằng số 100 thành 10: bị bắt
  đảo nhánh: bị bắt
  quên nhánh ÷: LỌT (oracle mù)
oracle đủ: bản đúng qua
  hằng số 100 thành 10: bị bắt
  đảo nhánh: bị bắt
  quên nhánh ÷: bị bắt

Oracle yếu bắt được hai lỗi lộ liễu và bỏ qua lỗi thứ ba, vì nó không có ca nào đi qua phép chia. Bản đúng qua cả hai oracle, nên chỉ chạy bản đúng thì không bao giờ thấy điểm mù đó; phải có một bản sai lọt qua.

Các con số trong strong tôi tính từ quy tắc REA rút ra, không phải đo trên Calculator thật. Muốn có oracle hành vi đúng nghĩa thì gõ cùng các phép tính đó vào Calculator trên Windows rồi so.

Trước khi tin một oracle:

  • Mỗi nhánh trong spec có ít nhất một ca thử đi qua nó.
  • Có ít nhất ba bản sai cài sẵn: đổi hằng số, đảo nhánh, lệch biên một đơn vị.
  • Oracle so cả giá trị, không chỉ hình dạng: byte của hằng số chứ không chỉ byte của lệnh.
  • Oracle bỏ sót một bug thì chạy lại mọi thứ từng qua nó.

Opus 5.5 cho việc đọc mã máy: ngữ cảnh dài, giá rẻ hơn, và chỗ nó từ chối

REA đưa cho agent bằng chứng thô: assembly, pseudocode, đồ thị lệnh gọi, tham chiếu chưa giải. Model phải biến chúng thành giải thích đúng, một spec người khác dựng được, và code qua được oracle. Việc kiểu này chạy lâu và ăn nhiều ngữ cảnh.

Opus 5.5 có cửa sổ ngữ cảnh 1M token, output tối đa 128K token, effort mặc định medium. Giá 4 USD / 20 USD cho mỗi triệu token input / output; đọc cache 0,20 USD mỗi triệu token, rẻ hơn 60% so với Opus 5.

Trong bài ra mắt, Anthropic cho Opus 5.5 và Fable 5.1 chuyển HAProxy từ C sang Rust. Cả hai bản viết lại đều qua gần hết bộ regression test của chính HAProxy; Opus 5.5 xong trong 9,5 giờ so với 12 giờ của Fable 5.1, chi phí thấp hơn 51%. Đó chính là bước dựng lại, với bộ test của dự án gốc làm oracle.

Gợi ý chia effort theo phòng: đọc và lập bản đồ để mặc định medium, viết spec để high vì mỗi lỗ hổng trong spec thành một bug ở phòng B, kiểm chứng và code tính toán con trỏ nhiều thì xhigh hoặc max.

Opus 5.5 có lớp bảo vệ an ninh mạng tương tự Fable 5.1: phần lớn tác vụ an ninh mạng bị chuyển sang Opus 4.8. Lần theo một tính năng trong app của mình, dựng lại một hàm game để hiểu nó thì không sao. Việc gì trông như phá lớp bảo vệ, viết exploit hay crack license thì nên chờ bị chuyển model hoặc bị từ chối; đó là cùng ranh giới REA vạch trong DISCLAIMER, nhìn từ phía model.

Chỗ REA không với tới

  • Code chạy trên server. Thứ chạy ở máy chủ của người ta chưa bao giờ được gửi về máy mình, REA không có gì để đọc.
  • Code dịch ngược không phải source gốc. Nó là bản dựng lại, nên mới cần oracle.
  • Binary C++ đã strip vẫn khó. Một app Electron và một file native không còn symbol là hai việc khác nhau.
  • Công cụ thương mại cần license thật. REA không kèm license IDA, Hopper hay JEB.

Bài này dựa trên tài liệu và các ví dụ REA tự công bố, cộng đoạn script oracle tôi chạy trên máy. Tôi chưa cho REA đọc app thật nào; lần thử đầu tiên nên là app ví dụ Notes của chính REA, rồi tới một app Electron mình dùng hằng ngày, và luôn bắt đầu bằng ba bản sai cài sẵn trước khi tin một lần chạy xanh.

Bài viết liên quan

tất cả bài viết →