MCPcopy Index your code
hub / github.com/Pilfer/hermes_rs

github.com/Pilfer/hermes_rs @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
246 symbols 512 edges 47 files 20 documented · 8%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

hermes_rs

A nearly dependency-free disassembler and assembler for the Hermes bytecode, written in Rust.

Note: Still a WIP - A PR is always welcome. The API is subject to change as I iterate over use cases and improve the design.

For the sake transparency, the current dependencies are:

  • sha1
  • This is required for generating the footer hash.
  • serde - Optional
  • So you can serialize/deserialize in your app
  • specta - Optional
  • To generate TS types for tauri or wasm use cases
  • specta-util - Optional
  • Same as above

A special thanks to P1sec for digging through the Hermes git repo, pulling all of the BytecodeDef files out, and tagging them. This made writing this tool much easier.


Bytecode Studio

Bytecode Studio

I created and launched Bytecode Studio - a comprehensive tool built for reverse engineering, analyzing, disassembling, and decompiling React Native applications and Hermes binaries.

If you're using hermes_rs or any other tool for static analysis for an engagement or want a more comprehensive analysis and workflow, I'd use Bytecode Studio!


Supported HBC Versions

HBC Version Disassembler (Binary) Assembler (Textual) Assembler Decompiler
76
89
90
93
94
95
96

A couple of features are missing currently, as they're low priority for me at the moment.

  • Regular Expression deserialization and serialization*
  • Debug Info deserialization and serialization*

* Supports u8 buffer for manual population


Known Issues & Weirdness

Slow(er) Performance on Large Files

Chances are you need to compile the program that uses this crate with the --release flag and run the binary directly.

HBC File Size Differences

The size of a binary produced with hermes_rs is going to be a bit larger than one compiled directly with hermes. This is primarily due to the fact that hermes does some string merging to keep bundles down.

Example: If car and racecar are both strings within the app, only racecar will be written. Two StringTableEntry objects are created: Entry { offset: 0, length: 7 } and Entry { offset: 4, length: 3 }.

As of right now, hermes_rs does not implement this functionality. It's pretty literal when it comes to strings, and doesn't try to be clever or save space.


Project Goals

  • Full coverage for all public HBC versions
  • The ability to inject code stubs directly into the .hbc file for instrumentation
  • Textual HBC assembly
  • Eventually a halfway decent decompiler, but that may be another project that uses this one as a base
Potential Use cases
  • Find which functions reference specific strings
  • Generate frida hooks for mobile implementations
  • hermes loader -> hook loading the package -> feed to hermes_rs -> patch code for bidirectional communication or even just logging
  • Writing fuzzers

Features

  • Disassemble Hermes Bytecode (HBC)
  • Assemble Hermes Bytecode (HBC)
  • Type-safe instruction building across multiple versions of HBC
  • The ability to reduce binary size by only enabling certain versions of HBC
  • Utilities
  • Dump Bytecode
    • cargo run --bin bytecode index.android.bundle
  • Dump Strings
    • cargo run --bin strings index.android.bundle

Installation

To add hermes_rs to your project, simply run:

cargo add hermes_rs

  • Specific HBC Versions - enable any of ["v89","v90","v93","v94","v95", "v96"]
  • Default: ["v94","v95", "v96"]
  • Serde Support (Optional) - enable the serde feature
  • Generate TS Types (Optional) - enable the specta feature
  • cargo run --bin gen_ts --features specta will output *.d.ts to ./ts.
  • Note: Only one HBC version can be used at a time for this due to a limitiation in specta.

Usage

CLI Binaries

Dump bytecode

Dumps the entire applications bytecode - definitely direct it to a file since it's probably going to be huge.

cargo run --bin bytecode ./path/to/file/index.android.bundle > bytecode_ouput.txt

Dump strings

Dumps all string definitions

cargo run --bin strings ./path/to/file/index.android.bundle > strings_output.txt

Dump Metro bundler modules

Most React Native applications use the Metro bundler as a pre-Hermes compilation step. This utility finds the bundler function and where it's used, then outputs JavaScript-like representation of calls to it.

cargo run --bin modules ./path/to/file/index.android.bundle > output.txt

Example output:

// Function "$FUNC_73" being registered as a Metro module with a moduleId of 0
__d(73, 0, [1, 2, 5, 392, 2483, 1927, 1783, 698, 1348, 525, 2234] /* Arr IDX: 0 */);

// Function "$FUNC_79" being registered as a Metro module with a moduleId of 1
__d(79, 1, [...new Array(0)] /* Arr IDX: 0 */);

// Function "$FUNC_81" being registered as a Metro module with a moduleId of 2
__d(81, 2, [3] /* Arr IDX: 45 */);

// Function "$FUNC_82" being registered as a Metro module with a moduleId of 3
__d(82, 3, [4] /* Arr IDX: 50 */);

// Function "$FUNC_133" being registered as a Metro module with a moduleId of 4
__d(133, 4, [...new Array(0)] /* Arr IDX: 0 */);

// Function "$FUNC_137" being registered as a Metro module with a moduleId of 5
__d(137, 5, [6, 7, 8, 52, 191, 291, 292, 247, 277, 297, 298, 300, 301, 303, 310, 315, 316, 188, 319, 253, 320, 256, 287, 322, 323, 293, 325, 192, 328, 200, 331, 207, 212, 330, 83, 249, 288, 332, 143, 213, 334, 337, 351, 354, 347, 357, 359, 361, 362, 60, 242, 86, 308, 364, 223, 261, 262, 366, 369, 144, 126, 125, 370, 372, 59, 374, 376, 377, 379, 58, 44, 380, 29, 24, 382, 385, 386, 387, 389, 9, 390, 153, 30, 48, 67, 77, 164, 307, 64, 197, 391, 281] /* Arr IDX: 55 */);

Dump array values

Parses and dumps the contents of the Array storage buffer within the bundle in a JavaScript-like representation.

cargo run --bin arrays ./path/to/file/index.android.bundle > output.txt

Example Output:

const arr_53614 = ["Macintosh", "MacIntel", "MacPPC", "Mac68K"];
const arr_53621 = ["iPhone", "iPad", "iPod"];
const arr_53630 = ["Win32", "Win64", "Windows", "WinCE"];
const arr_53632 = ["get"];
const arr_53637 = ["head", "options"];
const arr_53640 = ["put"];
const arr_53642 = ["delete"];
const arr_53659 = ["ETIMEDOUT", "ECONNRESET", "EADDRINUSE", "ESOCKETTIMEDOUT", "ECONNREFUSED", "EPIPE", "EHOSTUNREACH", "EAI_AGAIN"];
const arr_53719 = ["ENOTFOUND", "ENETUNREACH", "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL", "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE", "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE", "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID", "CERT_HAS_EXPIRED", "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED", "ERROR_IN_CERT_NOT_BEFORE_FIELD", "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD", "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM", "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "CERT_CHAIN_TOO_LONG", "CERT_REVOKED", "INVALID_CA", "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE", "CERT_UNTRUSTED", "CERT_REJECTED"];
const arr_53728 = ["log", "debug", "info", "warn"];
const arr_53730 = ["error"];
const arr_53732 = ["type"];

Dump object keys and values

Dump the raw object keys and values from an application bundle.

Note: The object keys and values aren't joined with this utility, as they're correlated by individual indexes with the NewObjectWithBuffer or NewObjectWithBufferLong instructions. To see proper Object key/value pairs, use the Dump Bytecode utility. When you disassemble the binary with this utility, it'll print out the full object next to the two instructions above in the output.

cargo run --bin objects ./path/to/file/index.android.bundle > output.txt

Example Output:

const objkeys_7 = ["trace", "info", "warn"];
const objkeys_9 = ["error"];
const objkeys_12 = ["value", "enumerable"];
const objkeys_16 = ["message", "title", "color"];
const objkeys_19 = ["channelId"];
const objkeys_22 = ["value", "enumerable"];
const objkeys_27 = ["configurable", "writable"];
....
const objvals_17 = [0, 1, 2, 3];
const objvals_18 = [true];
const objvals_19 = [false];
const objvals_24 = ["You have new messages", "Kustomer Support"];
const objvals_25 = [null];

Generate r2 Script

Special thanks to AbhiTheModder for this one!

Generate a r2 script for a hermes binary to take advantage of all of the amazing features r2 has to offer.

cargo run --bin r2_script ./path/to/file/index.android.bundle > my_script.r2

Example Output:

# R2 script for Hermes bytecode file: ./index.android.bundle
# Generated by hermes_rs

# Hermes File Version: 84
# File Size: 5471568 bytes
# Function Count: 27835

f hbc.header=0x0
f hbc.header.size=0x537d50

# Function flags
'f func.hermes.global 32 0x1cb368
'f func.hermes.func_1 16 0x1dd8b4
'f func.hermes.clear 16 0x1dd9b1
'f func.hermes.define 16 0x1dd9ce
'f func.hermes.metroRequire 16 0x1dda2b

... <snip>

# String flags
'f str.API_STATUS_0 10 0x28
'f str.ActionCreator_1 13 0x17a
'f str.Actions_2 7 0x1c9
'f str.Animated_3 8 0x2ac
'f str.d_4 1 0x2b3

... <snip>

API

Reading File Header

let filename = "./input_data/index.android.bundle";

let f = File::open(filename).expect("no file found");
let mut reader = io::BufReader::new(f);

let mut hermes_file = HermesFile::deserialize(&mut reader);

println!("{:?}", hermes_file.header);

Output:

HermesHeader {
  magic: 2240826417119764422,
  version: 94,
  sha1: [20, 178, 139, 133, 105, 198, 134, 29, 58, 101, 194, 248, 210, 173, 84, 79, 162, 174, 43, 205],
  file_length: 11059884,
  global_code_index: 0,
  function_count: 54483,
  string_kind_count: 3,
  identifier_count: 35878,
  string_count: 65091,
  overflow_string_count: 425,
  string_storage_size: 2238216,
  big_int_count: 0,
  big_int_storage_size: 0,
  reg_exp_count: 448,
  reg_exp_storage_size: 49719,
  array_buffer_size: 132510,
  obj_key_buffer_size: 43517,
  obj_value_buffer_size: 137207,
  segment_id: 0,
  cjs_module_count: 0,
  cjs_module_offset: 0,
  function_source_count: 1361,
  debug_info_offset: 11059836,
  options: BytecodeOptions {
    static_builtins: false,
    cjs_modules_statically_resolved: false,
    has_async: false,
    flags: false
  },
}

Reading Strings

println!("Strings: {:?}", hermes_file.get_strings());

Output:

Strings: ["$$typeof", "type", "API", "isArray", "Array", ... ]

Reading Function Headers

```rust for func in hermes_file.function_headers { println!("{:?}", func); }

// Prints the following: Small(SmallFunctionHeader { offset: 252641, param_count: 3, byte_size: 63, func_name: 5168, info_offset: 842244, frame_size: 16, env_size: 0, highest_read_cache_index: 1, highest_write_cache_index: 0, flags: FunctionHeaderFlag { prohibit_invoke: ProhibitNone, strict_mode: false, has_exception_handler: false, has_debug_info: false, overflowed: false }, exception_handlers: [], debug_info: None }) Small(LargeFunctionHeader { offset: 252704, param_count: 2, byte_size: 41, func_name: 3756, info_offset: 842244, frame_size: 14, env_size: 0, highest_read_cache_index: 1, highest_write_cache_index: 0, flags: FunctionHeaderFlag { prohibit_invoke:

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Method 136
Function 53
Class 44
Enum 8
Interface 5

Languages

Rust99%
TypeScript1%

Modules by API surface

src/hermes/function_header.rs35 symbols
src/hermes/hermes_file/reader.rs34 symbols
src/hermes/types.rs21 symbols
src/hermes/hermes_file/writer.rs18 symbols
src/hermes/mod.rs14 symbols
src/hermes/hermes_file/builder.rs11 symbols
src/hermes/encode.rs11 symbols
src/hermes/debug_info.rs11 symbols
src/hermes/decode.rs10 symbols
src/hermes/string_kind.rs9 symbols
src/hermes/header.rs7 symbols
src/hermes/cjs_module.rs6 symbols

For agents

$ claude mcp add hermes_rs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page