1996년 게임 DX-Ball의 실행 파일에는 소리의 좌우 균형을 정하는 함수가 있다. 원본 소스 대신 실행 파일에서 출발한 에이전트는 명령어를 읽고, 호출하는 쪽을 따라가 입력이 화면 좌표라는 것을 확인하고, 메모리에서 상수를 읽어 x × 1.5625 − 500이라는 식을 복원했다. REA 공식 사례에 따르면 이 함수를 C로 다시 쓴 코드는 원본 x86 함수와 비교한 3,205개 케이스를 통과했고, 고정한 VC4.0 툴체인의 컴파일 결과도 재배치 처리와 참조 상수 확인을 거쳐 원본의 63바이트 함수와 일치했다고 보고했다. 게임 전체가 아니라 함수 하나의 결과다.
REA(Reverse Engineer Anything)는 이런 조사를 코딩 에이전트에 연결하는 오픈소스다. 에이전트가 실행 파일이나 앱 패키지에서 단서를 찾고, 필요한 코드를 다시 읽고, 구현과 테스트로 이어 간다. 기능 하나를 새로 만드는 비용보다 이미 있는 기능을 이해하는 비용이 줄면, 개발과 제품 경쟁에서 따질 것이 달라질 수 있다.
REA 설치 방법: 에이전트에 연결하고 등록 상태 확인하기
공식 요구 조건은 Node.js 22 계열 22.19 이상, 24 계열 24.11 이상 또는 26 이상과 npm이다. Linux에서 JavaScript 분석을 하려면 procps 호환 ps도 PATH에서 실행돼야 한다. 버전을 확인한 뒤 공식 setup을 실행한다.
node --version
npm --version
npx rea-agents setupsetup은 연결할 에이전트(Claude Code, Codex, Cursor, Gemini CLI 등)를 고르게 하고, 바뀔 설정 파일 경로와 MCP 등록 내용을 보여 준다. 내용을 읽고 승인하면 REA 서버 등록과 그 버전에 맞는 조사 지침이 함께 설치되고, 기존 설정은 백업된다. MCP는 에이전트가 외부 도구를 호출하는 연결 규약이다. 끝나면 선택한 에이전트를 다시 시작한다. Hopper 설치는 별도 선택이며 JavaScript 앱 분석에는 필요하지 않다.
Codex를 골랐다면 다음 명령으로 등록과 지침을 검사할 수 있다. 이번 검사에서 선택한 Codex 등록과 지침은 healthy: true였고, Hopper·Ghidra·IDA가 없는 전체 환경은 environment_healthy: false였다. 두 값은 가리키는 범위가 달라서, 이 상태에서도 아래의 JavaScript 정적 분석과 objdump 호출은 성공했다.
npx -y [email protected] doctor --client codex --skill --json터미널에서 자주 쓸 계획이면 전역 설치 후 도움말로 명령 등록을 확인한다.
npm install --global rea-agents
rea --help설치한 뒤에는 기능 하나를 물어본다
공식 README가 제시하는 첫 요청은 다음 한 문장이다. Notes 앱과 검색 기능은 자기가 조사하려는 대상과 기능으로 바꾸면 된다.
Understand how search works in the Notes app, show me the evidence, and build a similar feature for my project.이 요청에는 세 단계가 들어 있다. 앱이 어떻게 동작하는지 이해하고, 근거를 보여 주고, 내 프로젝트에 맞게 구현한다. 첫 질문은 구현까지 한 번에 맡기기보다 앞의 두 단계로 좁히는 편이 확인하기 쉽다. 예를 들어 "검색어가 어느 프로세스로 넘어가고 어디에서 걸러지는지, 코드 위치와 함께 보여 줘"처럼 기능 하나와 확인 범위를 정하면 에이전트가 읽어야 할 파일이 줄고, 돌려받은 근거를 직접 열어 대조할 수 있다.
에이전트 없이 터미널에서도 같은 분석을 호출할 수 있다. 압축을 푼 JavaScript/Electron 앱 폴더나 ASAR를 대상으로 한다.
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json결과에는 모듈, import, Electron 경계(preload, IPC 등)와 각각의 근거가 들어 있다. 이 글의 재현 기록은 버전을 6.5.0으로 고정했고, 전체 샘플 파일과 명령은 끝의 부록에 두었다.
소스가 없는데 어떻게 기능을 알아낼까
앱을 실행하려면 동작에 필요한 코드가 배포물에 들어 있어야 한다. JavaScript·Electron 앱에는 압축되거나 이름이 짧아진 코드가 남고, 네이티브 실행 파일에는 CPU 명령, 문자열, 함수 호출 같은 단서가 남는다. 웹 서비스의 서버 내부까지 이 파일에 들어 있는 것은 아니지만, 적어도 로컬에서 실행하는 부분은 조사할 출발점이 있다.
이 단서를 읽는 기술은 새로 생긴 것이 아니다. Hopper·Ghidra·IDA는 이미 기계어를 분석하고 의사코드나 호출 관계를 보여 준다. REA가 바꾸는 것은 그 도구를 사용하는 방식이다. 에이전트가 MCP로 분석을 요청하면 REA가 도구를 연결하고, 코드 위치·명령·참조·확인하지 못한 부분을 정해진 결과 형식으로 돌려준다. 에이전트는 그 근거로 다음 질문을 고른다. 한 번의 답으로 끝내지 않고 조사와 검증을 반복할 수 있게 한 것이다.
공식 Notion 사례에서는 그 과정이 실제 기능 하나에 연결된다. 공개된 개발 저장소 대신 데스크톱 앱 패키지를 입력으로, 복사 요청이 화면에서 시스템 클립보드까지 어떻게 넘어가는지 추적한다.
첫 단계는 위치 찾기다. 압축된 preload 번들에서 REA가 analyze_javascript_application으로 페이지에 노출된 API(__electronApi)와 IPC invoke 호출을 찾고, 둘 다 번들의 줄과 열 범위를 붙여 돌려준다. 에이전트는 그 범위를 열어 clipboard 멤버를 찾고, 호출부가 notion:clipboard:write라는 채널 이름을 넘긴다는 단서를 얻는다. 다음은 메인 프로세스에서 같은 채널을 받는 코드를 찾을 차례다.
다음은 가설 검증이다. 전송 쪽 invokerInMain은 래퍼여서 이름만으로는 무엇을 하는지 알 수 없다. 에이전트가 래퍼를 풀면 ipcRenderer.invoke가 나오고, 메인 번들에서 같은 채널을 찾으면 또 다른 래퍼 addListener 뒤에 ipcMain.handle이 있다. 수신부는 isNotionWebContents(event.sender)로 보낸 쪽을 확인한 뒤에만 clipboard.write(data)를 호출한다. 채널 이름을 안다고 같은 기능이 생기는 것은 아니라는 점도 이 코드에서 읽힌다. 가설이 맞는지는 이름이 일치하는지가 아니라 호출이 끝까지 이어지는지로 판단한다.

구현 단계에서는 이렇게 모은 근거가 요구 사양 역할을 한다. 같은 사례의 후반부에는 Notion이 텍스트와 HTML을 보내면서 블록 구조는 로컬에 따로 보관하고, 복사한 HTML에 ID를 넣어 둘을 연결한다는 설명이 이어진다. 이런 데이터 형식까지 확인되면 에이전트가 "내 프로젝트의 복사 기능"을 작성할 때 참고할 구체적인 사양이 생긴다. 구현이 맞는지는 REA가 아니라 내가 작성한 테스트가 판정한다.
같은 구조를 직접 만든 작은 Electron 형태 샘플로 재현해 봤다. 실제 실행한 REA 6.5.0 결과에는 앱 그래프가 노드 30개와 연결 43개로 나왔고, preload.js 3행의 invoke와 main.js 4행의 handle이 같은 notes:search 채널로 연결됐다. 아래 화면은 그 JSON 발췌에 한글 주석을 붙인 것이다.

REA의 각 항목에는 근거의 종류가 붙는다. ipcMain.handle 등록 구문은 state: observed, authority: ast-static-analysis로 기록됐고, 래퍼를 따라간 호출 관계에는 state: inferred, authority: static-relationship-inference가 붙었다. observed는 소스 구문을 관찰했다는 뜻이고 앱을 실행해 호출을 목격했다는 뜻이 아니다. 이 구분이 있어서 에이전트가 가설을 세울 때 확인된 사실과 추정을 나눠 다룰 수 있다.
샘플의 검색 함수도 따로 실행했다. 검색 함수에 backup을 넣으면 두 항목이 나오는지 Node.js로 확인했다(부록 명령). Electron 화면과 IPC 전체를 실행한 테스트는 아니다.
실행 파일에서는 기계어를 따라간다
JavaScript는 배포물에 텍스트가 남아 있지만 기계어 프로그램은 분석 도구가 있어야 읽을 수 있다. REA 문서는 네이티브 분석에 Hopper, Ghidra, IDA 중 하나를 쓰고, 오프라인 역어셈블에는 호출자가 준비한 GNU objdump나 Rizin도 지원한다고 설명한다. 공식 README에는 Hopper에서 macOS의 /usr/bin/true를 열어 REA가 분석 브리지를 실행하는 화면이 있다. 아래 화면은 공식 문서의 별도 환경 예시이며 이번 Linux 환경에서 실행한 결과가 아니다.

이번 Linux 환경에는 GNU objdump가 있어 REA로 역어셈블 결과를 얻었다. 입력은 1만 원 이상이면 금액에 9할을 곱하는 직접 작성한 C 프로그램이다(discount.c는 부록). 실행하면 10800이 출력되고, REA 증거에는 provider.id: gnu.objdump, 종료 코드 0, 분석 전후 파일이 바뀌지 않았다는 기록이 남았다. 명령열에는 0x270f, 십진수로 9999와의 비교가 있고 그 이하면 할인 계산을 건너뛴다. 소스의 "10000 이상" 조건이 기계어에서는 "9999 이하면 건너뜀"으로 뒤집혀 나타난다.

이 실습의 GNU objdump 출력은 명령열과 실행 기록이다. Hopper나 Ghidra의 의사코드 생성은 이번에 실행하지 않았다. 문서상 프로세스 관찰은 Linux와 macOS의 네이티브 PTY를, GDB 통합은 Linux를 대상으로 하므로 환경에 따라 쓸 수 있는 분석 수단이 다르다.
기능을 알아내는 비용이 내려가면
소스 없이 앱의 기능을 알아내는 일은 예전에도 가능했다. 달라지는 것은 시작 비용이다. 미니파이된 번들에서 진입점을 찾고, 래퍼를 풀고, 호출이 어디로 이어지는지 추적하고, 확인한 내용을 기록하는 작업이 사람의 오랜 집중 시간이었다. REA는 이 중 위치 찾기와 호출 관계 추적, 근거 기록을 에이전트가 반복 호출할 수 있는 도구로 만들었다. 공식 DX-Ball 사례는 이 흐름이 끝까지 간 예다. 사례에 따르면 에이전트는 analyze_function으로 0x406400 함수를 열었다. 처음 나온 의사코드는 입력도 없이 __ftol()만 호출하는 모양이었지만, 명령어에는 스택의 정수를 읽는 FILD와 부동소수점 연산이 있었다. 이 차이가 더 파고들 이유가 됐다. 호출하는 0x411f40에서는 타일 좌표에 30을 곱하고 20을 더해 넘기는 것이 보였고(20 + 30 × tile_x), read_bytes로 읽은 값은 1.5625, 500.0, 1.0이었다. 이를 합치면 (x × 1.5625 − 500) × 배율이고, 배율이 1이면 왼쪽 끝 −500, 가운데 0, 오른쪽 끝 500이 나온다. 재구성한 C는 화면 좌표 0부터 640까지 모든 정수를 배율 다섯 가지(0, 0.5, 1, 20, −1)로 원본과 비교했다. 3,205개는 여기서 나온 숫자다. 이 수치와 과정은 공식 사례의 보고이며 이 글에서 재현한 것이 아니다.
기능을 이해하는 데 드는 비용이 낮아지면 제품 간 경쟁도 달라질 수 있다. 기능 하나의 동작 방식(요청이 어떤 경로를 거치고, 어떤 데이터 형식을 쓰는지)을 아는 데 드는 시간이 줄면, 그 기능을 가지는 것만으로 생기던 차별성은 약해질 수 있다. 반대로 Notion 사례에서 본 것처럼 기능은 그 앱의 데이터 구조, 서버 쪽 처리, 계정 권한과 맞물려 있다. 복사 채널의 이름과 수신부 구조를 알아도 블록 데이터가 어디에 저장되고 어떻게 동기화되는지는 별개의 조사 대상이다.
경쟁 제품을 살펴보는 일만 빨라지는 것은 아니다. 원래 개발자가 떠난 사내 프로그램의 동작을 정리하거나, 지원이 끝난 소프트웨어를 유지하거나, 파일 형식과 연동 방식을 이해해 다른 도구로 옮기는 일에도 같은 조사 과정이 필요하다. REA 같은 도구가 이 진입 비용을 낮춘다면, 기능 하나를 이유로 특정 앱에 머물러야 했던 선택도 바뀔 여지가 생긴다. 얼마나 빨라지는지는 대상과 분석 도구에 따라 달라지지만, 조사 과정을 에이전트의 작업 안으로 가져왔다는 점은 구체적이다.
판매하는 쪽에서는 기능 목록만으로 차이를 유지하기가 더 어려워질 수 있다. 반면 사용자 데이터, 파일 호환성, 연동 생태계, 운영 지원과 신뢰는 코드 경로를 읽는 것만으로 옮겨 오지 않는다. 기능의 구현 원리를 알아내는 일이 쉬워질수록 제품의 가치는 그 기능을 둘러싼 요소로 더 넓게 평가받을 수 있다.
분석이 끝난 뒤 남는 작업도 많다. 복사 기능의 사양을 알아냈다 해도 내 프로젝트의 구조에 맞춰 구현하고, 보안 확인과 오류 처리를 붙이고, 원본의 이용 약관과 저작권 범위를 살피고, 테스트로 동작을 증명해야 한다. 따라서 결과의 observed와 inferred를 구분해 읽어야 다음에 무엇을 시험할지 정할 수 있다.
어떤 프로그램을 조사할 수 있나
분석 대상은 자작 소프트웨어나 분석 허가를 받은 배포물에서 고른다. REA의 분석은 로컬에서 이뤄지지만 결과를 받는 에이전트의 모델 제공사에는 출력이 전달될 수 있어, 업무 데이터와 코드를 보내도 되는 범위를 먼저 정한다. 기능 재구현으로 넘어가기 전에 원본 소프트웨어의 이용 조건과 Hopper·IDA 같은 상용 분석 도구의 라이선스도 확인한다.
보조 자료로 참고한 티보의 해설에서 소개한 ArtCraft는 REA와 별도 프로젝트다.
선택: 평소 설정을 건드리지 않는 격리 실습
기존 에이전트 설정을 바꾸지 않고 setup과 doctor를 시험해 보려면 HOME과 설정 경로를 임시 폴더로 분리한다. 이번 확인에서도 Codex 등록은 임시 HOME 안에만 만들었다. 아래는 Linux·macOS 셸 예시이며, 환경 변수 접두사를 두 명령 모두에 유지해야 한다.
LAB=$(mktemp -d)
mkdir -p "$LAB/home" "$LAB/config"
HOME="$LAB/home" XDG_CONFIG_HOME="$LAB/config" \
CODEX_HOME="$LAB/home/.codex" \
npx -y [email protected] setup --client codex --yes --json
HOME="$LAB/home" XDG_CONFIG_HOME="$LAB/config" \
CODEX_HOME="$LAB/home/.codex" \
npx -y [email protected] doctor --client codex --skill --json--yes는 이 예제에서 명시한 Codex 설정 변경을 승인한다. 일상적인 설치에서는 대화형 setup의 변경 계획을 읽고 승인하면 된다. HOME 분리는 설정 위치를 나누는 방법이고 파일 접근이나 네트워크를 막는 보안 샌드박스는 아니다.
부록: 샘플 파일 6개와 재현 명령
아래 여섯 파일을 한 폴더(예: sample-app)에 같은 이름으로 저장한다. Electron 패키지를 설치하거나 GUI를 띄우지 않아도 정적 분석은 실행된다.
package.json
{"name":"rea-owned-notes-fixture","version":"1.0.0","main":"main.js","devDependencies":{"electron":"^35.0.0"},"license":"MIT"}main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
const { searchNotes } = require('./search');
ipcMain.handle('notes:search', (_event, query) => searchNotes(query));
app.whenReady().then(() => {
const window = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true } });
window.loadFile('index.html');
});preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('notes', {
search: (query) => ipcRenderer.invoke('notes:search', query)
});search.js
const notes = ['Backup plan', 'Release checklist', 'Backup restore test'];
exports.searchNotes = (query) => notes.filter(note => note.toLowerCase().includes(query.toLowerCase()));index.html
<!doctype html><html lang="en"><meta charset="utf-8"><title>Owned notes fixture</title><input id="query"><ul id="results"></ul><script src="renderer.js"></script></html>renderer.js
document.querySelector('#query').addEventListener('input', async (event) => {
const matches = await window.notes.search(event.target.value);
document.querySelector('#results').replaceChildren(...matches.map(text => {
const item = document.createElement('li'); item.textContent = text; return item;
}));
});분석 대상 폴더의 절대 경로로 JSON 증거를 저장한다.
# /absolute/path/to/sample-app을 저장 폴더의 절대 경로로 바꾼다
npx -y [email protected] analyze-javascript-application \
/absolute/path/to/sample-app --json > app-evidence.json샘플 폴더에서 검색 함수만 따로 확인한다. backup과 일치하는 두 항목이 나온다.
node -e "const assert=require('node:assert/strict'); const {searchNotes}=require('./search'); assert.deepEqual(searchNotes('backup'),['Backup plan','Backup restore test']); console.log(searchNotes('backup'))"네이티브 확인용 discount.c를 저장하고 컴파일한 뒤 REA의 objdump 경로로 분석한다. 실행 출력은 10800이다.
#include <stdio.h>
int discounted_price(int price) { return price >= 10000 ? price * 9 / 10 : price; }
int main(void) { printf("%d\n", discounted_price(12000)); return 0; }gcc -O0 -g discount.c -o discount
npx -y [email protected] inspect-with-objdump \
./discount disassemble --json > native-evidence.json
./discount출처: REA 공식 저장소, rea.tools, 설치 문서, 외부 분석 도구 연동, Notion 공식 사례, DX-Ball 공식 사례. 보조 출처: 티보, 「“어도비 포토샵 망하겠는데?” 프로그램을 컨닝하는 오픈소스 출시」.
