hookforge/sighook

GitHub: hookforge/sighook

一个基于信号处理机制的多平台运行时指令插桩与内联挂钩 Rust 库。

Stars: 37 | Forks: 2

# sighook [![crates.io](https://img.shields.io/crates/v/sighook.svg)](https://crates.io/crates/sighook) [![docs.rs](https://docs.rs/sighook/badge.svg)](https://docs.rs/sighook) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/dd58374507094234.svg)](https://github.com/YinMo19/sighook/actions/workflows/ci.yml) [![license](https://img.shields.io/crates/l/sighook.svg)](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, 动态二进制插桩, 可视化界面, 回调函数, 底层开发, 指令修补, 汇编, 网络流量审计, 运行时插桩, 逆向工程, 通知系统, 陷阱指令