
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 모듈은 virtualenv에 설치되지 않습니다. 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
컴파일러로 JavaScript 또는 TypeScript를 컴파일하세요(예: react-native 0.73에서
0.83까지의 hermesc, node_modules/react-native/sdks/hermesc/ 아래에 위치):
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를 누르면 대상(target)을 선택할 수 있습니다. |
Ctrl+K | 현재 명령어를 삭제합니다. |
R | 현재 라인을 복원합니다. |
Ctrl+Z / Ctrl+R | 실행 취소 / 다시 실행. |
Ctrl+X | 저장하고 종료합니다 (Y는 적용, N은 폐기, Esc는 취소). |
동일 크기 편집은 제자리에서 이어 붙입니다(splice). 명령어를 삽입하거나 삭제하면 구조적 직렬화기를 거치며, 이는 필요할 때 짧은 점프를 긴 점프로 완화하고, 예외 처리기 범위를 재매핑하며, 이동한 모든 오프셋을 다시 씁니다.
문자열 편집기 (edit string:): 한 줄 편집기입니다. 같은 길이의 값은 제자리에 기록되며, 다른 길이의 경우 새 바이트를 문자열 저장소에 추가하고 항목을 다시 가리키며, 이는 저장소를 공유하는 문자열도 분리합니다. Ctrl+N은 새 문자열 추가로 전환합니다. 식별자는 해시 테이블에 연결되어 있으므로 읽기 전용입니다.
리터럴 편집기 (edit array:): Up과 Down으로 요소를 선택하고 Enter를 눌러 다시 입력합니다. Ctrl+T는 타입을 변경하며(null, true, false 포함), a는 요소 또는 키-값 쌍을 추가하고 d는 마지막 것을 제거합니다.
공유 바이트코드. Metro와 Hermes는 동일한 함수 본문을 한 번만 저장하므로, 여러 함수가 같은 바이트를 가리킬 수 있습니다. 편집기는 이에 대해 경고하고 편집을 모든 함수에 적용할지 묻습니다. 하나만 편집하는 것은 지원되지 않습니다.