#bytecode #hermes #disassembler #assembly #instructions #binary #version

bin+lib hermes_rs

为 Hermes 字节码提供的无依赖反汇编器和汇编器

2 个版本

0.1.1 2024 年 4 月 17 日
0.1.0 2024 年 4 月 17 日

#180 in 调试

MIT 许可证

125KB
2.5K SLoC

hermes-rs

使用 Rust 编写的为 Hermes 字节码提供的无依赖反汇编器和汇编器。

特别感谢 P1sec 挖掘 Hermes git 仓库,提取所有 BytecodeDef 文件并标记它们。这使得编写此工具变得容易得多。

支持的 HBC 版本

HBC 版本 反汇编器 (二进制) 汇编器 (文本) 汇编器 反编译器
89
90
93
94
95

项目目标

  • 对所有公共 HBC 版本的全覆盖
  • 能够直接将代码占位符注入到 .hbc 文件中进行测试
  • 文本 HBC 汇编
潜在用途
  • 查找引用特定字符串的函数
  • 为移动实现生成 frida 钩子
    • hermes 加载程序 -> 加载包的钩子 -> 传给 hermes-rs -> 修补代码
      用于双向通信或仅用于日志记录
  • 编写模糊测试

功能

  • bLaZiNgLy FaSt
  • 导出字符串
  • 导出字节码
  • 分步编码指令
  • 通过仅启用某些 HBC 版本来减小二进制大小

安装

cargo添加 hermes_rs

使用方法

读取文件头

let f = File::open("./test_file.hbc").expect("no file found");
let mut reader = io::BufReader::new(f);
let header: HermesHeader = HermesStruct::deserialize(&mut reader);

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

输出

{
  magic: 2240826417119764422, 
  version: 94, 
  sha1: [ 13, 37, 133, 71, 337, 17, 182, 139, 155, 223, 133, 7, 132, 109, 21, 96, 3, 12, 19, 56], 
  file_length: 1102, 
  global_code_index: 0, 
  function_count: 3, 
  string_kind_count: 2, 
  identifier_count: 5, 
  string_count: 14, 
  overflow_string_count: 0, 
  string_storage_size: 88, 
  big_int_count: 0, 
  big_int_storage_size: 0, 
  reg_exp_count: 0, 
  reg_exp_storage_size: 0, 
  array_buffer_size: 0, 
  obj_key_buffer_size: 0, 
  obj_value_buffer_size: 0, 
  segment_id: 0, 
  cjs_module_count: 0, 
  function_source_count: 0, 
  debug_info_offset: 628, 
  options: BytecodeOptions { 
    static_builtins: false, 
    cjs_modules_statically_resolved: false, 
    has_async: false, 
    flags: false
  },
  function_headers: [
    SmallFunctionHeader { 
      offset: 348, 
      param_count: 1, 
      byte_size: 69, 
      func_name: 5, 
      info_offset: 576, 
      frame_size: 15,
      env_size: 2,
      highest_read_cache_index: 2,
      highest_write_cache_index: 0,
      flags: FunctionHeaderFlag {
        prohibit_invoke: ProhibitNone, 
        strict_mode: false, 
        has_exception_handler: true, 
        has_debug_info: true, overflowed: false
        }
    }, 
    // ...
  ], 
  string_kinds: [
    StringKindEntry { count: 9, kind: String }, 
    StringKindEntry { count: 5, kind: Identifier }
  ],
  string_storage: [
    SmallStringTableEntry { is_utf_16: false, offset: 0, length: 4}, 
    // ...
  ],
  string_storage_bytes: [ 119, 101, 101, ... ], 
  overflow_string_storage: []
}

读取函数头

header.function_headers.iter().for_each(|fh| {
  println!("function header: {:?}", fh);
});

// Prints the following:
// function header: SmallFunctionHeader { ... } }
// ...

解析字节码

header.parse_bytecode(&mut reader);

// By default, prints the following. It is assumed that the end user will 
// bring their own functionality to play with the instructions as-needed

/*
Function<foo>(1 params, 1 registers, 0 symbols):
  LoadConstString  r0,  "bar"
  AsyncBreakCheck
  Ret  r0
*/        

编码指令

编码指令很简单 - 每个 Instruction 实现了一个具有 deserializeserialize 方法的特质。

你可以为你的版本重命名导入以缩短代码,但完全展开可能看起来像这样

let load_const_string = hermes::v94::Instruction::LoadConstString(hermes::v94::LoadConstString {
  op: hermes::v94::str_to_op("LoadConstString"),
  r0: 0,
  p0: 2,
});

let async_break_check = hermes::v94::Instruction::AsyncBreakCheck(hermes::v94::AsyncBreakCheck {
  op: hermes::v94::str_to_op("AsyncBreakCheck"),
});

let ret = hermes::v94::Instruction::Ret(hermes::v94::Ret {
    op: hermes::v94::str_to_op("Ret"),
    r0: 0,
});

let instructions = vec![load_const_string, async_break_check, ret];

let mut writer = Vec::new();
for instr in instructions {
    instr.serialize(&mut writer);
}

// Make sure the encoded bytes are valid
assert!(writer == vec![115, 0, 2, 0, 98, 92, 0], "Bytecode is incorrect!");

使用特定 HBC 版本

想要使用 Hermes 字节码的特定版本并减小你的二进制大小?

在 Cargo.toml 中,找到 hermes_rs 依赖项,并选择你想要在应用程序中使用哪个 HBC 版本。

示例

[dependencies]
hermes_rs = { features = ["v89", "v90", "v93", "v94", "v95"] }

Hermes 资源

我大量借鉴了以下项目和资源来开发此包。


开发

支持 Hermes 的新版本

./def_versions/_gen_macros.js中有一个脚本,它会读取并解析作为第一个参数传递给它的字节码定义文件,并输出一个包含宏体的文件,以支持更新的指令。

# How I generated them  

cd ./def_versions

node _gen_macros.js 89.def > ../src/hermes/v89/mod.rs
node _gen_macros.js 90.def > ../src/hermes/v90/mod.rs
node _gen_macros.js 93.def > ../src/hermes/v93/mod.rs
node _gen_macros.js 94.def > ../src/hermes/v94/mod.rs
node _gen_macros.js 95.def > ../src/hermes/v95/mod.rs

以下是一个假设的v100版本的示例

node _gen_macros.js v100.def

它输出

use crate::hermes;

build_instructions!(
  ... instructions here
);

从这里开始,您将添加一个新的目录和mod.rs文件来存储这个版本(./src/hermes/v100/mod.rs),并将脚本的输出粘贴到其中。

这可能(并且很可能)是build.rs过程。

创建此文件后,打开./src/hermes/mod.rs,导航到指令模块的导入并添加导入,然后使用新版本填充指令枚举+特质+其他函数的match语句。您可能需要依赖编译器来报告缺少的match分支 - 虽然只有几个。

随着代码库的演变,您可能需要在不同的match中添加分支。

#[macro_use]
#[cfg(feature = "v100")]
pub mod v100;

// ...

pub enum Instruction {
  // ...
  #[cfg(feature = "v100")]
  V100(v100::Instruction),
}

// ...

impl Instruction {
  // implement the methods of the trait
  fn display(&self, _hermes: &HermesHeader) -> String{
      match self {
        // ...
        #[cfg(feature = "v100")]
        Instruction::V100(instruction) => instruction.display(_hermes),
      }
  }

  fn size(&self) -> usize {
      match self {
          // ...
          #[cfg(feature = "v100")]
          Instruction::V100(instruction) => instruction.size(),
      }
  }
}


// ...

// In parse_bytecode there's currently a match statement that will also need to be populated...
let ins_obj: Option<Instruction> = match self.version {
  #[cfg(feature = "v89")]
  89 => Some(Instruction::V89(v89::Instruction::deserialize(&mut r_cursor, op))),
  #[cfg(feature = "v90")]
  90 => Some(Instruction::V90(v90::Instruction::deserialize(&mut r_cursor, op))),
  #[cfg(feature = "v93")]
  93 => Some(Instruction::V93(v93::Instruction::deserialize(&mut r_cursor, op))),
  #[cfg(feature = "v94")]
  94 => Some(Instruction::V94(v94::Instruction::deserialize(&mut r_cursor, op))),
  #[cfg(feature = "v95")]
  95 => Some(Instruction::V95(v95::Instruction::deserialize(&mut r_cursor, op))),
  _ => None,
};

最后,将featurev100 = [])添加到Cargo.toml。


待办事项

  • 将头结构拆分为自己的文件
  • 为FunctionHeader/SmallFunctionHeader添加正确的逻辑
  • 异常定义内容
    • 这个代码的质量相当差 - 可能以后会回过头来修改
  • 调试信息定义内容
  • 添加注释
  • 文档
  • Serializer实现
结构 反序列化 序列化 大小
HermesHeader
SmallFunctionHeader
StringKindEntry
SmallStringTableEntry
OverflowStringTableEntry
BigIntTableEntry
BytecodeOptions
DebugInfoOffsets
DebugInfoHeader
DebugFileRegion
ExceptionHandlerInfo
RegExpTableEntry
FunctionHeaderFlag
  • 以正确的顺序解析
// From official Hermes source code  
void visitBytecodeSegmentsInOrder(Visitor &visitor) {
  visitor.visitFunctionHeaders();
  visitor.visitStringKinds();
  visitor.visitIdentifierHashes();
  visitor.visitSmallStringTable();
  visitor.visitOverflowStringTable();
  visitor.visitStringStorage();
  visitor.visitArrayBuffer();
  visitor.visitObjectKeyBuffer();
  visitor.visitObjectValueBuffer();
  visitor.visitBigIntTable();
  visitor.visitBigIntStorage();
  visitor.visitRegExpTable();
  visitor.visitRegExpStorage();
  visitor.visitCJSModuleTable();
  visitor.visitFunctionSourceTable();
}

没有运行时依赖

功能