hookforge/sighook
GitHub: hookforge/sighook
一个基于信号处理机制的多平台运行时指令插桩与内联挂钩 Rust 库。
Stars: 37 | Forks: 2
# sighook
[](https://crates.io/crates/sighook)
[](https://docs.rs/sighook)
[](https://github.com/YinMo19/sighook/actions/workflows/ci.yml)
[](https://spdx.org/licenses/LGPL-2.1-only.html)
`Sighook` 是一个专注于以下方面的运行时补丁库 (crate):
- 通过陷入指令 + 信号处理程序实现指令级插桩
- 函数入口处的内联拦截(支持近跳转和远跳转)
它专为底层实验、逆向工程和自定义运行时插桩工作流而设计。
## 功能特性
- `patchcode(address, opcode)` 用于指令补丁(在 x86_64 架构下,当补丁短于当前指令时,剩余字节将用 NOP 填充)
- `patch_bytes(address, bytes)` 用于多字节/原始补丁
- `patch_asm(address, asm)` 用于汇编后再打补丁(受特性门控;在 x86_64 架构下,当汇编后的字节短于当前指令时,将用 NOP 填充)
- `instrument(address, callback)` 用于陷入陷阱并随后执行原始操作码
- `instrument_no_original(address, callback)` 用于陷入陷阱并跳过原始操作码
- `prepatched::instrument*` / `prepatched::inline_hook` 用于离线陷入点(无运行时文本补丁;推荐用于 iOS 已签名文本页)
- `inline_hook(addr, callback)` 用于基于信号处理的函数入口挂钩
- `inline_hook_jump(addr, replace_fn)` 支持自动回退至远跳转
- `unhook(address)` 用于恢复字节并移除挂钩的运行时状态
- 回调中的回调上下文 (`HookContext`)
- 架构特定的回调上下文(`aarch64` 和 `x86_64` 布局),包括通过 `ctx.fpregs` 访问的 FP/SIMD 状态
## 平台支持
- `aarch64-apple-darwin`:完整的 API 支持(`patchcode` / `instrument` / `instrument_no_original` / `inline_hook` / `inline_hook_jump`)
- `x86_64-apple-darwin`:完整的 API 支持(`patchcode` / `instrument` / `instrument_no_original` / `inline_hook` / `inline_hook_jump`)
- `aarch64-apple-ios`:完整的 API 支持(`patchcode` / `instrument` / `instrument_no_original` / `prepatched::*` / `inline_hook` / `inline_hook_jump`)
- `aarch64-unknown-linux-gnu`:完整的 API 支持(`patchcode` / `instrument` / `instrument_no_original` / `inline_hook` / `inline_hook_jump`)
- `aarch64-linux-android`:完整的 API 支持(`patchcode` / `instrument` / `instrument_no_original` / `inline_hook` / `inline_hook_jump`)
- `x86_64-unknown-linux-gnu`:完整的 API 支持;CI 冒烟测试验证了 `patchcode` / `instrument` / `instrument_no_original` / `inline_hook` / `inline_hook_jump` 示例
- 单线程模型(内部状态使用 `static mut`)
`patch_asm` 当前可用于以下平台:
- `aarch64-apple-darwin`
- `x86_64-apple-darwin`
- `aarch64-unknown-linux-gnu`
- `x86_64-unknown-linux-gnu`
## 安装说明
```
[dependencies]
sighook = "0.10.0"
```
仅在需要时启用汇编字符串补丁支持:
```
[dependencies]
sighook = { version = "0.10.0", features = ["patch_asm"] }
```
`patch_asm` 会引入 `keystone-engine`,这是一个较重的依赖项。
## 快速开始
### 1) BRK 插桩(执行原始操作码)
```
use sighook::{instrument, HookContext};
extern "C" fn on_hit(_address: u64, ctx: *mut HookContext) {
let _ = ctx;
}
let target_instruction = 0x1000_0000_u64;
let _original = instrument(target_instruction, on_hit)?;
# Ok::<(), sighook::SigHookError>(())
```
### 0) 使用 asm 字符串打补丁(需要特性 `patch_asm`)
```
# #[cfg(feature = "patch_asm")]
# {
use sighook::patch_asm;
let target_instruction = 0x1000_0000_u64;
#[cfg(target_arch = "aarch64")]
let _original = patch_asm(target_instruction, "mul w0, w8, w9")?;
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
let _original = patch_asm(target_instruction, "imul %edx")?;
# }
# Ok::<(), sighook::SigHookError>(())
```
### 2) BRK 插桩(不执行原始操作码)
```
use sighook::{instrument_no_original, HookContext};
extern "C" fn replace_logic(_address: u64, ctx: *mut HookContext) {
let _ = ctx;
}
let target_instruction = 0x1000_0010_u64;
let _original = instrument_no_original(target_instruction, replace_logic)?;
# Ok::<(), sighook::SigHookError>(())
```
### 3) 基于信号的内联入口挂钩
```
use sighook::{inline_hook, HookContext};
extern "C" fn replacement(_address: u64, ctx: *mut HookContext) {
unsafe {
#[cfg(target_arch = "aarch64")]
{
(*ctx).regs.named.x0 = 42;
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
(*ctx).rax = 42;
}
}
}
let function_entry = 0x1000_1000_u64;
let _original = inline_hook(function_entry, replacement)?;
# Ok::<(), sighook::SigHookError>(())
```
### 4) 基于跳转的内联拦截
```
use sighook::inline_hook_jump;
extern "C" fn replacement() {}
let function_entry = 0x1000_1000_u64;
let replacement_addr = replacement as usize as u64;
let _original = inline_hook_jump(function_entry, replacement_addr)?;
# Ok::<(), sighook::SigHookError>(())
```
### 5) 解除挂钩并恢复
```
use sighook::{instrument, unhook, HookContext};
extern "C" fn on_hit(_address: u64, _ctx: *mut HookContext) {}
let target_instruction = 0x1000_0000_u64;
let _ = instrument(target_instruction, on_hit)?;
unhook(target_instruction)?;
# Ok::<(), sighook::SigHookError>(())
```
## 示例加载模型
这些示例是 `cdylib` 载荷,它们通过构造函数段自动运行挂钩安装逻辑:
- Apple 目标平台(macOS/iOS)使用 `__DATA,__mod_init_func` + dyld 预加载/注入流程
- Linux/Android 目标平台使用 `.init_array` + `LD_PRELOAD` 风格流程
当你的预加载库通过 `dlsym` 从目标可执行文件中解析符号时,在 Linux/Android 上编译目标可执行文件请带上 `-rdynamic` 参数。
## Android 指南 (`arm64-v8a`)
如果你的工作流程是“使用 `sighook` 构建一个挂钩载荷 `.so`,然后使用 `patchelf` 将其注入到现有的目标 `.so` 中”,请使用此模式:
1. 构建挂钩载荷
```
cargo build --release --target aarch64-linux-android
```
输出通常为:
`target/aarch64-linux-android/release/libsighook_payload.so`
2. 确保载荷中包含基于构造函数的初始化
- Android 使用 `.init_array` 构造函数流程。
- 将挂钩安装逻辑放入 init 函数中(与本仓库示例相同的模式)。
- 保持挂钩目标符号的稳定性(对于 AArch64 示例,建议使用显式的 patchpoint 符号,而不是硬编码的偏移量)。
3. 将载荷注入目标 `.so`
```
patchelf --add-needed libsighook_payload.so libtarget.so
```
验证 `DT_NEEDED`:
```
readelf -d libtarget.so | grep NEEDED
```
4. 将两个 `.so` 文件打包进 APK
- 将这两个文件放置在相同的 ABI 目录下:`lib/arm64-v8a/`。
- 保持 SONAME / 文件名与 `DT_NEEDED` 引用的一致。
5. 重新签名并安装 APK,然后进行验证
- 使用你自己的证书进行签名,在测试设备上安装,并检查 `logcat` 中的载荷初始化日志。
### Android 专项注意事项
- Android linker 命名空间规则可能会阻止意外的库路径/依赖项;请尽量保持载荷的依赖项最少。
- `patchelf` 不能绕过 SELinux、代码签名或应用沙盒边界。
- 对于应用级的预加载风格实验,Android 的 `wrap.sh`(适用于可调试应用)是另一种选择,但对于固定的目标库,`patchelf` 补丁通常更具确定性。
## Linux AArch64 补丁点注意事项
对于 AArch64 Linux 示例,基于 `calc` 的演示导出了一个专用的 `calc_add_insn` 符号并直接修补该符号。这避免了在工具链生成的函数布局中做出不可靠的固定偏移假设。
## iOS 预补丁要求
对于 iOS 挂钩工作流,`prepatched::*` 是主要的实现途径。
iOS 可执行页是经过代码签名的。在正常(非越狱)运行时环境中,向已签名的文本页写入陷入操作码通常会被拒绝,因此纯粹的运行时非侵入式补丁是不可靠的。为了在 iOS 上保持挂钩点有效,请在签名/分发之前离线预修补陷入指令(`brk`),然后在运行时通过 `prepatched::*` 注册回调。
- 在 iOS 上推荐使用 `prepatched::instrument_no_original(...)` 和 `prepatched::inline_hook(...)`。
- `prepatched::instrument(...)`(执行原始指令模式)在 `aarch64` 架构上需要原始操作码元数据;请使用 `prepatched::cache_original_opcode(...)` 进行预加载。
- `unhook(...)` 会移除 `prepatched::*` 的运行时状态,但不会重写已预修补的文本字节。
## API 注意事项
- `instrument(...)` 在需要时通过内部跳板代码执行原始指令。
- 在 `aarch64` 架构上,`instrument(...)` 为常见的位移 PC 相对指令预计算直接重放:`adr`、`adrp`、字面量 `ldr` / `ldrsw` / `prfm`、`b` / `bl` / `b.cond`、`cbz` / `cbnz` 以及 `tbz` / `tbnz`。
- 其他 `aarch64` 的 PC 相对形式在执行原始指令模式下仍不保证安全。对于这些补丁点,建议使用 `instrument_no_original(...)` 并在回调中模拟原始指令。
- 在 `x86_64` 架构上,仍然不支持 RIP 相对的执行原始指令补丁点(例如,使用 `[rip + disp]` 的 `lea` / `mov`)。
- `instrument_no_original(...)` 会跳过原始指令,除非回调改变了控制流寄存器(`pc`/`rip`)。
- `HookContext` 在 `ctx.fpregs` 中暴露 FP/SIMD 状态:
- `aarch64`:`fpregs.v[0..31]` 或 `fpregs.regs.named.v0..v31`,外加 `fpsr` / `fpcr`
- `x86_64`:`fpregs.st[0..7]` 或 `fpregs.st.named.st0..st7`,`fpregs.xmm[0..15]` 或 `fpregs.xmm.named.xmm0..xmm15`,外加 `mxcsr`;Linux 还映射了 `fpregs.ymm_hi[0..15]` 或 `fpregs.ymm_hi.named.ymm0_hi..ymm15_hi`
- `prepatched::*` API 假定该地址已离线完成了陷入补丁(`brk`/`int3`),并且在运行时不写入可执行页。
- `prepatched::instrument(...)` 需要原始操作码元数据来执行原始指令(在 `aarch64` 架构上,使用 `prepatched::cache_original_opcode(...)` 进行预加载)。
- `prepatched::instrument(...)` 执行原始指令模式目前在 `x86_64` 上不受支持;请使用 `prepatched::instrument_no_original(...)`。
- `inline_hook(...)` 会安装一个入口陷阱,并在回调保持 `pc`/`rip` 不变时默认返回给调用者。
- `inline_hook_jump(...)` 首先使用特定于架构的近跳转,然后回退到远跳转。
- `unhook(...)` 为运行时打补丁的挂钩恢复补丁字节;对于 `prepatched::*` 挂钩,它仅移除运行时状态。
## 安全说明
此 crate 会执行运行时代码修补和原始上下文变异。
- 确保目标地址是有效的运行时地址。
- 确保回调逻辑符合 ABI 预期。
- 请先在不重要的可执行文件上进行测试。
## 许可证
LGPL-2.1-only
标签:API Hook, API接口, Crate, DBI, Detour, Inline Hook, iOS, Rust, x86_64, 二进制分析, 云安全运维, 云资产清单, 信号处理, 内存修补, 内联Hook, 动态二进制插桩, 可视化界面, 回调函数, 底层开发, 指令修补, 汇编, 网络流量审计, 运行时插桩, 逆向工程, 通知系统, 陷阱指令