
Inspects, decompiles and structurally patches Hermes bytecode (HBC) from React Native apps, with a GDB-style REPL, CFG, decompiler and re-serializing patcher.
Hermes Reverse Engineering & Manipulation Interactive Toolkit.
hermit inspects, decompiles and structurally patches Hermes bytecode (HBC), the
format React Native apps ship their JavaScript in. Open an .hbc file, browse
its functions, read the decompiled JavaScript, edit instructions, strings and
literals in an interactive editor, and write back a file that the Hermes VM
still loads and runs.
Unlike tools that only analyse bytecode, or that patch it in place without changing sizes, hermit re-serializes the whole file. It can insert and delete instructions, relocate jumps and exception handlers, rebuild the string and literal tables, add new functions and recompute the SHA-1 footer. An unmodified file round-trips byte for byte.
.hbc file| Version | Role |
|---|---|
| 96 | Primary target. What shipping React Native apps use: RN 0.73 to 0.83, and RN 0.84+ with Hermes V1 disabled. |
| 94, 95 | Validated. The format is identical to 96, only the opcode tables differ. |
Apps built with Hermes V1 (the default from RN 0.84) emit HBC 98, which is not
supported yet. Format notes are in doc/hbc-format/.
Each release has archives for macOS and Linux, on arm64 and x86_64. No Python or compiler is needed:
| Archive | Content |
|---|---|
hermit-<version>-<platform>.tar.gz | The interactive REPL: ./hermit file.hbc. |
hermit-tools-<version>-<platform>.tar.gz | The standalone hbc_* command-line tools. |
SHA256SUMS | Checksums: shasum -a 256 -c SHA256SUMS. |
tar -xzf hermit-0.1.0-macos-arm64.tar.gz
./hermit-0.1.0-macos-arm64/hermit --version
The macOS binaries are not signed. After downloading, clear the quarantine flag
once: xattr -dr com.apple.quarantine hermit-0.1.0-macos-arm64. The Linux builds
need glibc 2.35 or newer. The cfg command still needs graph-easy installed.
Releases are produced by .github/workflows/release.yml when a v* tag is
pushed, using packaging/build-release.sh, which you can also run locally.
FetchContent unless they are already installed.graph-easy for the cfg
command (cpanm Graph::Easy), less as its pager.Run everything from this directory (the one containing CMakeLists.txt):
cmake -B build
cmake --build build -j
This builds the libhbc static library, the Python module _libhbc (in
build/bindings/), the command-line tools (in build/tools/) and the unit
tests. The REPL only needs the Python module:
cmake --build build --target _libhbc
The tools and the tests can be left out of a build, for example for a release:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DHERMIT_BUILD_TOOLS=OFF -DHERMIT_BUILD_TESTS=OFF
Rebuild with the same command after every change to core/ or bindings/.
macOS: if linking fails with an error about a malformed SDK, point the build at a complete SDK, for example
SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk cmake --build build.
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
The module _libhbc is not installed into the virtualenv. Add its build
directory to PYTHONPATH whenever you run hermit:
export PYTHONPATH=$PWD/build/bindings:$PYTHONPATH
.hbc fileA React Native app ships its bundle as a plain file:
| Platform | Location inside the package |
|---|---|
Android (.apk, a zip) | assets/index.android.bundle |
iOS (.ipa, a zip) | Payload/<App>.app/main.jsbundle |
If the file starts with the bytes C6 1F BC 03 C1 03 19 1F, it is Hermes
bytecode. The REPL only opens files with the .hbc extension, so rename it:
unzip -p app.apk assets/index.android.bundle > app.hbc
xxd app.hbc | head -1 # c61f bc03 c103 191f ...
Only analyse apps you are allowed to study, for example your own, or open-source apps for research.
To get small, controlled inputs, compile JavaScript or TypeScript with a Hermes
compiler that emits HBC 96 (for example hermesc from react-native 0.73 to
0.83, under node_modules/react-native/sdks/hermesc/):
hermes -emit-binary -g0 -out hello.hbc hello.js # -g0: no debug info
The seeder/ package automates this for the sample programs in
seeder/samples/ and writes the results to 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 writes <name>_g0.hbc without debug info, --debug writes
<name>_g.hbc with full debug info. .ts samples need tsc on your PATH;
the compiler is taken from --hermes (default hermes). seeder/build-real-bundle.sh
builds a real open-source React Native app into an .hbc.
export PYTHONPATH=$PWD/build/bindings:$PYTHONPATH
.venv/bin/python -m cli.main app.hbc
Without a file argument the REPL starts empty; use open <file.hbc>. The
prompt shows the HBC version and the file name, with a + when there are
unsaved changes: [hbc v96 | app+] >. History is kept in ~/.hermit_history
and commands are tab-completed.
> info
> strings --identifiers filter:fetch
> functions
> disasm func:12
> decompile func:isPremium
> xrefs string:"api.example.com"
> edit func:isPremium
> patch patched.hbc
Functions are addressed by index or name (func:12, func:isPremium), strings
by index or value (string:42, string:"hello"), literal sites by index
(array:7).