Communitygithub.com

leanrea: 빌드된 소프트웨어 리버스 엔지니어링

로컬 CLI 도구만으로 배포된 소프트웨어를 리버스 엔지니어링합니다. Electron/JS 번들, Mach-O/ELF/PE 네이티브 바이너리, Go/Rust/.NET/Flutter/WASM, APK, SQLite, pcap, 펌웨어 및 웹사이트를 다룹니다. 기능 분석, 버전/의존성 추출, 빌드 비교 또는 기능 재구현에 사용합니다.

leanrea: 빌드된 소프트웨어 리버스 엔지니어링란 무엇인가요?

이 스킬은 AI 에이전트(예: Claude Code, Cursor)가 별도의 MCP 서버 없이 일반적인 로컬 CLI 도구만으로 배포된 소프트웨어 바이너리를 효율적으로 분석할 수 있도록 설계되었습니다. Electron 앱, Mach-O/ELF/PE 실행 파일, Go/Rust/.NET/Flutter/WASM 바이너리, APK, SQLite 데이터베이스, pcap 파일, 펌웨어 이미지 및 웹사이트 등 다양한 대상을 지원하며, 각 대상에 대해 가장 빠른 명령어 한 줄을 제공합니다. 핵심 원칙은 '증거 기반 추론'입니다. 최소한의 도구 호출(보통 2~4회)로 필수 정보를 추출하고, 모든 주장을 파일 내에서 발견된 증거와 연결하여 '보임(seen)', '추론됨(inferred)', '알 수 없음(unknown)'으로 명확히 구분합니다. `file` 명령어로 대상 유형을 식별한 후 가장 저렴한 인벤토리 명령(문자열, 임포트)을 먼저 실행하고, 필요할 때만 역어셈블하거나 코드를 읽습니다. 이 스킬은 버전 비교, 기능 재구현, 보안 분석 등에 유용합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/aicodeking/leanrea/tree/HEAD/leanrea

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

leanrea: 빠른 길(fast path)

이 도구들은 이미 알고 있습니다. 이 파일은 각 대상에 대한 가장 빠른 명령어와 함정만 나열합니다.

규칙

  • 사본에서 작업하세요 (cp -c는 APFS에서 즉시 복사). 각 추출 결과는 $TMPDIR/re/<이름> 아래에 새 디렉토리를 만들어 넣으세요. 이미 추출된 결과가 있으면 재사용하세요.
  • 먼저 file -b T와 매직 바이트로 유형을 판별하세요. 그 다음 가장 저렴한 인벤토리 명령을 실행하세요. 그래야만 디컴파일하거나 코드를 읽으세요.
  • rg는 숨김 파일과 무시된 파일을 건너뜁니다. 번들러는 코드를 숨깁니다(Electron: .webpack/, .vite/). 추출된 트리는 항상 rg -uu로 검색하세요.
  • 크거나 난독화된 파일을 cat하지 마세요. 잘라서 보세요: rg -o '.{0,200}NEEDLE.{0,200}' F | head. 역어셈블리는 한 번 파일로 덤프한 후 그 파일을 grep하세요.
  • 독립적인 프로브는 하나의 셸 호출로 묶어 ;와 echo '--- name' 마커로 구분하세요(따옴표 주의: zsh는 ==x를 확장함). 모든 출력은 head나 -m으로 제한하세요.
  • 증거에 기반하여 답변하세요. 답변에서 각 주장을 보임(seen: 파일:오프셋 또는 심볼/문자열 인용) 또는 추론됨(inferred)으로 표시하세요. 찾지 못한 사실은 알 수 없음(unknown)이지 "없음"이 아닙니다. 정적 분석은 실행을 관찰한 것이 아닙니다.
  • 도구가 없다고 막히지 마세요. 같은 행의 대체 도구를 사용하거나, 사용자가 허용하면 brew install <x>를 사용하세요.
  • 턴(turn)보다 바이트가 더 중요합니다. 2~4회의 도구 호출을 목표로 하세요. 한 번은 배치 리컨(recon) 호출, 그리고 한두 번의 대상 슬라이스, 마지막으로 답변입니다. 요청된 모든 항목에 대한 증거가 확보되면 중단하세요. 이미 본 것을 다시 확인하지 마세요.

첫 번째 동작: 각각 한 번 호출, 붙여넣고 실행

네이티브 (Mach-O):

T=./bin; D=$TMPDIR/re/$(basename $T); mkdir -p $D; file $T; lipo -info $T; otool -L $T; echo '--- EXPORTS'; nm -gU $T | head -150; echo '--- IMPORTS'; nm -u $T | rg -v '^__Z|napi_' | tr '\n' ' '; echo; echo '--- STRINGS'; strings -a -n 6 $T | rg -v '^[_$.]' | head -400; otool -tV $T > $D/dis.txt; echo '--- FUNCS'; rg '^[_a-zA-Z].*:$' $D/dis.txt | rg -v '^_?_?Z?N?St3' | head -150

그런 다음 한 함수 읽기: awk '/^FUNC_LABEL:$/{p=1;next} p&&/^[_a-zA-Z].*:$/{exit} p' $D/dis.txt | rg 'bl|b\.|cmp|literal|mov\s+w2'.

Electron:

A=./app.asar; X=$TMPDIR/re/asar; rm -rf $X; npx -y @electron/asar extract $A $X && jq '{name,version,main}' $X/package.json; find $X -path '*/node_modules' -prune -o -type f \( -name '*.js' -o -name '*.node' \) -print | head -60; ls ${A}.unpacked/node_modules 2>/dev/null; rg -uu -l --glob '!node_modules' 'exposeInMainWorld' $X

그런 다음 번들 전체에서 니들(needle) 슬라이스: rg -uu -o --glob '!node_modules' '.{0,250}NEEDLE.{0,250}' $X | head -20.

Go: go version -m T 하나로 대부분의 질문에 답할 수 있습니다.

라우터

대상 (감지)가장 빠른 경로
Electron 앱 (*.app/Contents/Resources/app.asar)npx -y @electron/asar extract app.asar X (app.asar.unpacked/에 .node 애드온도 있음). 진입점: jq .main X/package.json. 표면: `rg -n -o '.{0,120}(ipcMain.(handle
웹 번들 / 난독화 JSsourceMappingURL을 찾으세요. 맵에 sourcesContent가 있으면 소스를 덤프: jq -r '.sources[]'. webpack의 경우 모듈 id를 grep한 후 적중 지점(hit) 근처를 슬라이스하세요. 예쁘게 출력: npx -y prettier --parser babel F > F.pretty.js, 그런 다음 라인 범위로 읽으세요.
Hermes / bytenode / V8 스냅샷Hermes 매직은 c6 1f bc 03입니다 (index.android.bundle): hermes-dec / hbc-decompiler 사용. .jsc(bytenode)는 디컴파일 불가: 문자열 및 런타임 훅 사용.
Mach-O (file: Mach-O)lipo -info (먼저 thin: lipo -thin arm64 -o T1 T). 그런 다음: otool -L (라이브러리); xcrun dyld_info -exports -imports; nm -gU (익스포트) / nm -u (임포트 = 기능 맵); strings -a -n 6; otool -l | rg -A3 'LC_(RPATH|BUILD_VERSION)'; codesign -d --entitlements :- T; otool -ov (ObjC 클래스/메서드); xcrun swift-demangle --compact.
네이티브 코드 로직otool -tV T > $D/dis.txt (또는 llvm-objdump -d --no-show-raw-insn -C). 함수 찾기: rg -n '^_?.*Name.*:$'. 호출/문자열 추적 읽기: awk '/^FUNC:/{p=1} p&&/^[_a-zA-Z].*:$/&&!/FUNC/{exit} p' dis.txt | rg 'bl|call|literal|cmp'. otool은 문자열 리터럴과 스텁(stub)을 인라인으로 레이블링하므로 대부분 충분합니다. 진정한 pseudocode가 필요할 때만: Ghidra analyzeHeadless $D p -import T -postScript DecompileAll.java -deleteProject 또는 r2 -qc 'aaa; pdc @ sym.X' T.
ELF / PEreadelf -hlSW, readelf -dW (NEEDED), nm -D --defined-only, objdump -d -M intel. PE: objdump -x, pefile/wrestool (리소스). checksec: pwn checksec.
Go (__go_buildinfo, .go.buildinfo)go version -m T는 한 번 호출로 툴체인, 경로, 모듈, 의존성 및 빌드 플래그를 제공합니다. Go 없을 때: strings -a T | rg -m1 -A200 'Go buildinf'. 심볼은 제거 후에도 남아 있음: go tool nm T | rg main\..
Rust의존성(cargo-auditable): cargo audit bin T, 또는 .dep-v0 / __DATA,.dep-v0 추출 후 zlib-inflate. 컴파일러: strings -a T | rg -o '/rustc/[0-9a-f]{40}' | sort -u. 경로에서 크레이트: strings -a T | rg -o '[a-z0-9_-]+-[0-9]+\.[0-9]+\.[0-9]+/src' | sort -u.
.NET (file: Mono/.Net)ilspycmd -p -o X T.dll (설치: dotnet tool install -g ilspycmd), 그런 다음 rg. P/Invoke: rg DllImport X. NativeAOT/IL2CPP는 네이티브: 네이티브 행 사용.
Android APKjadx -d X --show-bad-code a.apk 그런 다음 rg. 리소스/매니페스트만: apktool d --no-src -o X a.apk. 빠른 확인: unzip -l a.apk. lib/*/libflutter.so는 Flutter (Dart AOT: blutter); libhermes.so는 React Native (Hermes 행).
Flutter 스냅샷 해시libapp.so에서 매직 f5 f5 dc dc를 찾으세요; +20 오프셋의 32자리 16진수 문자는 스냅샷 해시입니다. 이를 Dart 버전에 매핑하세요.
WASMwasm-objdump -x, wasm2wat, wasm-decompile (WABT).
SQLiteDB를 복사하고, -wal 파일이 있으면 함께 복사하세요. sqlite3 'file:C?mode=ro' '.schema', 그런 다음 SELECT * FROM t LIMIT 20.
plist / nib / carplutil -p F. Info.plist는 번들 ID, URL 스킴 및 개인정보 문자열을 포함합니다. 에셋 카탈로그: assetutil --info Assets.car.
pcap / HARtshark -r C -q -z conv,tcp; tshark -r C -T fields -e dns.qry.name -e tls.handshake.extensions_server_name -e http.request.full_uri | sort -u. HAR: jq -r '.log.entries[].request | .method+" "+.url'.
펌웨어binwalk -e F 또는 unblob -e X F. 그런 다음 추출된 각 바이너리를 이 표의 해당 행으로 라우팅하세요.
웹사이트curl -sL URL로 HTML 및 스크립트 src를 가져온 다음 JS를 가져와서 슬라이스하세요. 런타임 동작을 보려면 네트워크 로그가 있는 헤드리스 브라우저(Playwright page.on('request')) 또는 Chrome --remote-debugging-port를 사용하세요.
실행 중인 앱 / 런타임macOS: lldb -p PID, dtrace/fs_usage/log stream --predicate 'process=="X"'. Electron main: --inspect (RunAsNode/inspect 퓨즈가 허용하는 경우에만). Android: adb logcat, frida -U -f pkg -l hook.js.

속도 패턴

  • 문자열과 임포트부터 확인하세요. 작은 바이너리에서는 nm -u + strings + 익스포트 목록만으로 역어셈블리 없이 "무슨 일을 하는지"에 답할 수 있습니다.
  • 문자열을 사용하는 곳을 찾으려면: otool -tV의 "literal pool for" 주석에서 주소를 얻거나 rg -n 'literal pool for: "X'를 사용하세요. 그런 다음 그 줄 위에 있는 포함 함수 레이블을 찾으세요.
  • bl 대상의 C++ 이름은 이미 프로토콜을 알려줍니다(예: getpeereid, write, std::hash). 모든 명령어가 아니라 호출 시퀀스를 읽으세요.
  • JS의 경우 네이티브/JS 경계에서 안쪽으로 들어가세요: 네이티브 호출 → 호출자 클래스 → IPC 등록 → 렌더러/프리로드 순서.
  • 버전 비교: 코드를 비교하기 전에 인벤토리(nm, 모듈 목록, go version -m)를 먼저 diff하세요.
  • 재구현: 최소한의 재구현을 작성한 다음 원본과 동일한 입출력으로 테스트하세요.

설치 (요청 시에만)

brew install jadx apktool radare2 binwalk wabt wireshark ghidra · npm i -g @electron/asar · dotnet tool install -g ilspycmd · cargo install cargo-audit.

관련 스킬