
React Native 앱의 Hermes 바이트코드(HBC)를 검사, 디컴파일하고 구조적으로 패치하며, GDB 스타일 REPL, CFG, 디컴파일러 및 재직렬화 패처를 제공합니다.
Hermes 리버스 엔지니어링 및 조작 인터랙티브 툴킷.
hermit은 React Native 앱이 JavaScript를 담아 배포하는 형식인 Hermes 바이트코드(HBC)를 검사하고, 디컴파일하며, 구조적으로 패치합니다. .hbc 파일을 열고, 그 함수들을 탐색하고, 디컴파일된 JavaScript를 읽고, 인터랙티브 편집기에서 명령어·문자열·리터럴을 편집한 뒤, Hermes VM이 여전히 로드하고 실행할 수 있는 파일로 다시 씁니다.
바이트코드를 분석만 하거나, 크기를 바꾸지 않고 제자리에서 패치하는 도구들과 달리, hermit은 파일 전체를 다시 직렬화합니다. 명령어를 삽입·삭제하고, 점프와 예외 핸들러를 재배치하고, 문자열 및 리터럴 테이블을 재구성하고, 새 함수를 추가하고, SHA-1 푸터를 재계산할 수 있습니다. 수정하지 않은 파일은 바이트 단위로 그대로 왕복됩니다.
| 버전 | 역할 |
|---|---|
| 96 | 주요 대상. 배포되는 React Native 앱이 사용하는 버전: RN 0.73~0.83, 그리고 Hermes V1이 비활성화된 RN 0.84+. |
| 94, 95 | 검증됨. 형식은 96과 동일하며 opcode 테이블만 다릅니다. |
Hermes V1(RN 0.84부터 기본)로 빌드된 앱은 HBC 98을 생성하며, 이는 아직 지원되지 않습니다. 형식 관련 노트는 doc/hbc-format/에 있습니다.
각 릴리스에는 macOS와 Linux용, arm64와 x86_64용 아카이브가 있습니다. Python이나 컴파일러가 필요하지 않습니다:
| 아카이브 | 내용 |
|---|---|
hermit-<version>-<platform>.tar.gz | 인터랙티브 REPL: ./hermit file.hbc. |
hermit-tools-<version>-<platform>.tar.gz | 독립 실행형 hbc_* 커맨드라인 도구. |
SHA256SUMS | 체크섬: shasum -a 256 -c SHA256SUMS. |
tar -xzf hermit-0.1.0-macos-arm64.tar.gz
./hermit-0.1.0-macos-arm64/hermit --version
macOS 바이너리는 서명되지 않았습니다. 다운로드 후 격리 플래그를 한 번 해제하세요: xattr -dr com.apple.quarantine hermit-0.1.0-macos-arm64. Linux 빌드는 glibc 2.35 이상이 필요합니다. cfg 명령은 여전히 graph-easy가 설치되어 있어야 합니다.
릴리스는 v* 태그가 푸시될 때 .github/workflows/release.yml에 의해 생성되며, packaging/build-release.sh를 사용합니다. 이 스크립트는 로컬에서도 실행할 수 있습니다.
FetchContent로 가져옵니다.cfg 명령을 위한 graph-easy(cpanm Graph::Easy), 그리고 그 페이저로 less.이 디렉터리(CMakeLists.txt가 있는 디렉터리)에서 모든 것을 실행하세요:
cmake -B build
cmake --build build -j
이 명령은 libhbc 정적 라이브러리, Python 모듈 _libhbc(build/bindings/에), 커맨드라인 도구(build/tools/에), 그리고 단위 테스트를 빌드합니다. REPL은 Python 모듈만 필요합니다:
cmake --build build --target _libhbc
도구와 테스트는 빌드에서 제외할 수 있습니다. 예를 들어 릴리스의 경우:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DHERMIT_BUILD_TOOLS=OFF -DHERMIT_BUILD_TESTS=OFF
core/ 또는 bindings/를 변경할 때마다 같은 명령으로 다시 빌드하세요.
macOS: 잘못된 SDK에 관한 오류로 링크가 실패하면, 빌드가 완전한 SDK를 가리키도록 하세요. 예를 들어
SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk cmake --build build.
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
모듈 _libhbc는 가상환경에 설치되지 않습니다. hermit을 실행할 때마다 그 빌드 디렉터리를 PYTHONPATH에 추가하세요:
export PYTHONPATH=$PWD/build/bindings:$PYTHONPATH
.hbc 파일 구하기React Native 앱은 번들을 일반 파일로 담아 배포합니다:
| 플랫폼 | 패키지 내 위치 |
|---|---|
Android (.apk, zip) | assets/index.android.bundle |
iOS (.ipa, zip) | Payload/<App>.app/main.jsbundle |
파일이 바이트 C6 1F BC 03 C1 03 19 1F로 시작하면 Hermes 바이트코드입니다. REPL은 .hbc 확장자를 가진 파일만 열기 때문에 이름을 바꾸세요:
unzip -p app.apk assets/index.android.bundle > app.hbc
xxd app.hbc | head -1 # c61f bc03 c103 191f ...
연구가 허용된 앱만 분석하세요. 예를 들어 본인 소유의 앱이나 연구용 오픈소스 앱이 있습니다.
작고 통제된 입력을 얻으려면, HBC 96을 생성하는 Hermes 컴파일러(예: react-native 0.73~0.83의 hermesc, node_modules/react-native/sdks/hermesc/ 아래)로 JavaScript 또는 TypeScript를 컴파일하세요:
hermes -emit-binary -g0 -out hello.hbc hello.js # -g0: no debug info
seeder/ 패키지는 seeder/samples/의 샘플 프로그램에 대해 이를 자동화하고 결과를 seeder/corpus/에 씁니다:
.venv/bin/python -m seeder.compile # all samples
.venv/bin/python -m seeder.compile --g0 seeder/samples/loop_sum.js
.venv/bin/python -m seeder.compile --debug seeder/samples/greeting.js
--g0은 디버그 정보 없이 <name>_g0.hbc를 쓰고, --debug는 전체 디버그 정보와 함께 <name>_g.hbc를 씁니다. .ts 샘플은 PATH에 tsc가 필요합니다. 컴파일러는 --hermes에서 가져옵니다(기본값 hermes). seeder/build-real-bundle.sh는 실제 오픈소스 React Native 앱을 .hbc로 빌드합니다.
export PYTHONPATH=$PWD/build/bindings:$PYTHONPATH
.venv/bin/python -m cli.main app.hbc
파일 인자 없이 REPL은 빈 상태로 시작합니다. open <file.hbc>를 사용하세요. 프롬프트는 HBC 버전과 파일 이름을 표시하며, 저장되지 않은 변경이 있으면 +가 붙습니다: [hbc v96 | app+] >. 기록은 ~/.hermit_history에 보관되고 명령은 탭 완성됩니다.
> info
> strings --identifiers filter:fetch
> functions
> disasm func:12
> decompile func:isPremium
> xrefs string:"api.example.com"
> edit func:isPremium
> patch patched.hbc
함수는 인덱스나 이름으로(func:12, func:isPremium), 문자열은 인덱스나 값으로(string:42, string:"hello"), 리터럴 사이트는 인덱스로(array:7) 지정합니다.
| 명령 | 하는 일 |
|---|---|
open <file.hbc> | 파일을 엽니다. |
info | 헤더 요약: 버전, 크기, 개수, 전역 함수, 소스 해시. |
strings [--identifiers|--strings] [filter:<text>] | 종류별 문자열 테이블. |
arrays [--arrays|--objects] [filter:<text>] [func:<N|name>] | 디코딩된 배열 및 객체 리터럴 사이트. |
functions | 매개변수, 레지스터, 플래그가 있는 모든 함수; Metro 모듈 id. |
disasm func:<N|name> | 소스 라인이 있는 디스어셈블리. s는 디버그 소스 테이블을 토글합니다. |
src [func:<N|name>] | 모든 함수 또는 한 함수의 디버그 소스 테이블. |
cfg func:<N|name> [--compact=true|false] | graph-easy로 렌더링된 제어 흐름 그래프. |
xrefs | 문자열 상호 참조 요약. |
xrefs string:<value|N> | 문자열을 사용하는 모든 명령어. |
xrefs func:<N|name> [--callers] [--callees] | 함수 주변의 호출 그래프. |
decompile func:<N|name> | 디컴파일된 JavaScript. |
decompile all [--output=<dir>] | 모든 함수를 .js 파일로 디컴파일. |
edit func:<N|name|new> | 바이트코드 편집기; func:new는 새 함수를 만듭니다. |
edit string:<idx|value|new> | 문자열 편집기; string:new는 문자열을 추가합니다. |
edit array:<N> | 배열 및 객체 리터럴 편집기. |
patch [file.hbc] | 편집된 이미지를 새 파일에 씁니다. |
help, exit | 도움말 및 종료. |
긴 목록은 스크롤 가능한 뷰어에서 열립니다: 화살표 키, PgUp/PgDn, 맨 위와 맨 아래로 g/G, 입력하면서 검색하려면 /, 검색을 지우려면 Esc, 나가려면 q.
편집은 메모리에서 이루어집니다. edit ...는 전체 화면 편집기를 엽니다. Ctrl+X 후 Y로 확인하면 무결성 검사(파일을 다시 파싱하여 예상한 것과 비교) 후 변경 사항이 메모리 내 이미지에 적용됩니다. patch 전까지는 디스크에 아무것도 쓰이지 않으며, patch는 원본 파일을 절대 덮어쓰지 않습니다.
명령어 편집기(edit func:):
| 키 | 동작 |
|---|---|
| Left / Right | 니모닉과 피연산자 사이를 이동합니다. |
| Up / Down, PgUp / PgDn | 명령어 사이를 이동합니다. |
Enter | 선택한 토큰을 편집합니다. 니모닉은 같은 피연산자 시그니처를 가진 임의의 opcode가 될 수 있으며, 피연산자는 그 타입에 대해 검증됩니다. |
o / O | 아래 / 위에 명령어를 삽입합니다(opcode 선택기). 점프의 경우, 주소 피연산자에서 Enter를 누르면 대상을 선택할 수 있습니다. |
Ctrl+K | 현재 명령어를 삭제합니다. |
R | 현재 줄을 복원합니다. |
Ctrl+Z / Ctrl+R | 실행 취소 / 다시 실행. |
Ctrl+X | 저장하고 종료합니다(Y 적용, N 버림, Esc 취소). |
같은 크기의 편집은 제자리에 이어 붙입니다. 명령어를 삽입하거나 삭제하면 구조적 직렬화기를 거치며, 필요할 때 짧은 점프를 긴 점프로 완화하고, 예외 핸들러 범위를 재매핑하며, 이동한 모든 오프셋을 다시 씁니다.
문자열 편집기(edit string:): 한 줄 편집기입니다. 같은 길이의 값은 제자리에 쓰이고, 다른 길이는 새 바이트를 문자열 저장소에 덧붙이고 항목을 다시 가리키며, 이는 저장소를 공유하는 문자열들도 분리합니다. Ctrl+N은 새 문자열 추가로 전환합니다. 식별자는 해시 테이블에 묶여 있기 때문에 읽기 전용입니다.
리터럴 편집기(edit array:): Up과 Down으로 요소를 선택하고 Enter를 눌러 다시 입력합니다. Ctrl+T는 그 타입을 변경하고(null, true, false 포함), a는 요소나 키-값 쌍을 덧붙이고 d는 마지막 것을 제거합니다.
공유 바이트코드. Metro와 Hermes는 동일한 함수 본문을 한 번만 저장하므로, 여러 함수가 같은 바이트를 가리킬 수 있습니다. 편집기는 이에 대해 경고하고 모든 함수에 편집을 적용할지 묻습니다. 하나만 편집하는 것은 지원되지 않습니다.
일반적인 세션은 앱 내 검사를 패치하고 같은 버전의 Hermes VM으로 결과를 실행합니다: