2 个版本
0.1.1 | 2024 年 4 月 17 日 |
---|---|
0.1.0 | 2024 年 4 月 17 日 |
#180 in 调试
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 -> 修补代码
用于双向通信或仅用于日志记录
- 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
实现了一个具有 deserialize
和 serialize
方法的特质。
你可以为你的版本重命名导入以缩短代码,但完全展开可能看起来像这样
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 资源
我大量借鉴了以下项目和资源来开发此包。
- 官方文档:https://hermesengine.dev/
- hermes-dec 反汇编器/反编译器
- hbctool:https://github.com/bongtrop/hbctool
- hasmer(过时):https://github.com/lucasbaizer2/hasmer
开发
支持 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,
};
最后,将feature
(v100 = []
)添加到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();
}