Sighook is a runtime patching crate focused on:
- instruction-level instrumentation via trap instruction + signal handler
- function-entry inline detours (near and far jump)
It is designed for low-level experimentation, reverse engineering, and custom runtime instrumentation workflows.
patchcode(address, opcode)for instruction patching (x86_64 pads remaining bytes with NOPs when patch is shorter than current instruction)patch_bytes(address, bytes)for multi-byte/raw patchingpatch_asm(address, asm)for assembling then patching (feature-gated; x86_64 pads with NOPs when assembled bytes are shorter than current instruction)instrument(address, callback)to trap and then execute original opcodeinstrument_no_original(address, callback)to trap and skip original opcodeprepatched::instrument*/prepatched::inline_hookfor offline trap points (no runtime text patch; recommended for iOS signed text pages)inline_hook(addr, callback)for signal-based function-entry hooksinline_hook_jump(addr, replace_fn)with automatic far-jump fallbackunhook(address)to restore bytes and remove hook runtime state- zero-copy context remap (
HookContext) in callbacks - architecture-specific callback context (
aarch64andx86_64layouts)
aarch64-apple-darwin: full API support (patchcode/instrument/instrument_no_original/inline_hook/inline_hook_jump)x86_64-apple-darwin: full API support (patchcode/instrument/instrument_no_original/inline_hook/inline_hook_jump)aarch64-apple-ios: full API support (patchcode/instrument/instrument_no_original/prepatched::*/inline_hook/inline_hook_jump)aarch64-unknown-linux-gnu: full API support (patchcode/instrument/instrument_no_original/inline_hook/inline_hook_jump)aarch64-linux-android: full API support (patchcode/instrument/instrument_no_original/inline_hook/inline_hook_jump)x86_64-unknown-linux-gnu: full API support; CI smoke validatespatchcode/instrument/instrument_no_original/inline_hook/inline_hook_jumpexamples- single-thread model (
static mutinternal state)
patch_asm is currently available on:
aarch64-apple-darwinx86_64-apple-darwinaarch64-unknown-linux-gnux86_64-unknown-linux-gnu
[dependencies]
sighook = "0.9.1"Enable assembly-string patching support only when needed:
[dependencies]
sighook = { version = "0.9.1", features = ["patch_asm"] }patch_asm pulls keystone-engine, which is a heavier dependency.
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>(())# #[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>(())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>(())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>(())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>(())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>(())The examples are cdylib payloads that auto-run hook install logic via constructor sections:
- Apple targets (macOS/iOS) use
__DATA,__mod_init_func+ dyld preload/injection flow - Linux/Android targets use
.init_array+LD_PRELOAD-style flow
When your preload library resolves symbols from the target executable via dlsym, compile the target executable with -rdynamic on Linux/Android.
For authorized security research and your own binaries only.
If your workflow is “build a hook payload .so with sighook, then inject it into an existing target .so with patchelf”, use this pattern:
- Build hook payload
cargo build --release --target aarch64-linux-androidThe output is usually:
target/aarch64-linux-android/release/libsighook_payload.so
- Ensure constructor-based init in payload
- Android uses
.init_arrayconstructor flow. - Put hook-install logic in an init function (same pattern as this repo examples).
- Keep hook target symbols stable (for AArch64 examples, prefer explicit patchpoint symbols instead of hardcoded offsets).
- Inject payload into target
.so
patchelf --add-needed libsighook_payload.so libtarget.soVerify DT_NEEDED:
readelf -d libtarget.so | grep NEEDED- Package both
.sofiles into APK
- Place both files under the same ABI directory:
lib/arm64-v8a/. - Keep SONAME / filenames consistent with what
DT_NEEDEDreferences.
- Re-sign and install APK, then verify
- Sign with your own cert, install on test device, and inspect
logcatfor payload init logs.
- Android linker namespace rules may block unexpected library paths/dependencies; keep payload dependencies minimal.
patchelfdoes not bypass SELinux, code-signing, or app sandbox boundaries.- For app-level preload-style experiments, Android
wrap.sh(debuggable app) is another option, butpatchelfpatching is usually more deterministic for fixed target libs.
For AArch64 Linux examples, calc-based demos export a dedicated calc_add_insn symbol and patch that symbol directly. This avoids brittle fixed-offset assumptions in toolchain-generated function layout.
For iOS hook workflows, prepatched::* is the primary path.
iOS executable pages are code-signed. In normal (non-jailbreak) runtime environments, writing trap opcodes into signed text pages is usually rejected, so purely runtime non-invasive patching is not reliable. To keep hook points valid on iOS, pre-patch trap instructions (brk) offline before signing/distribution, then register callbacks at runtime through prepatched::*.
- Prefer
prepatched::instrument_no_original(...)andprepatched::inline_hook(...)on iOS. prepatched::instrument(...)(execute-original mode) requires original opcode metadata onaarch64; preload it withprepatched::cache_original_opcode(...).unhook(...)removes runtime state forprepatched::*, but does not rewrite the prepatched text bytes.
instrument(...)executes original instruction through an internal trampoline.instrument(...)should not be used for PC-relative patch points (for example:aarch64adr/adrp, orx86_64RIP-relativelea/mov).instrument_no_original(...)skips original instruction unless callback changes control-flow register (pc/rip). For PC-relative patch points, prefer this API and emulate the instruction in callback.prepatched::*APIs assume the address is already trap-patched offline (brk/int3) and do not write executable pages at runtime.prepatched::instrument(...)needs original opcode metadata to execute original instruction (onaarch64, preload withprepatched::cache_original_opcode(...)).prepatched::instrument(...)execute-original mode is currently unsupported onx86_64; useprepatched::instrument_no_original(...).inline_hook(...)installs an entry trap and returns to caller by default when callback leavespc/ripunchanged.inline_hook_jump(...)uses architecture-specific near jump first, then far-jump fallback.unhook(...)restores patch bytes for runtime-patched hooks; forprepatched::*hooks it removes runtime state only.
This crate performs runtime code patching and raw context mutation.
- Ensure target addresses are valid runtime addresses.
- Ensure callback logic preserves ABI expectations.
- Test on disposable binaries first.
LGPL-2.1-only