zighook is a Zig rewrite of the sibling Rust sighook project.
Current scope:
- macOS
- iOS
- Linux
- Android
- Windows
- AArch64 / ARM64
- x86_64 (macOS / Linux / Windows)
Implemented APIs:
instrument(address, callback)instrument_no_original(address, callback)inline_hook(address, callback)unhook(address)patch_bytes(address, bytes)original_instruction(address)cache_original_instruction(address, bytes)original_opcode(address)prepatched.instrument*prepatched.inline_hookprepatched.cache_original_instructionprepatched.cache_original_opcode
The current backends support:
- trap-based instrumentation via
brk(AArch64) orint3(x86_64) - signal/exception-based entry hooks on AArch64 and x86_64
- strict execute-original replay for common AArch64 PC-relative instructions
- Zydis-backed x86_64 instruction decoding plus trampoline replay
- public callback access to AArch64 FP/SIMD state (
fpregs.v[i],fpregs.named.v0..v31,fpsr,fpcr) - public callback access to x86_64 XMM state (
fpregs.xmm[i],fpregs.named.xmm0..xmm15,mxcsr) - constructor-based payloads for both Mach-O (
__mod_init_func) and ELF (.init_array)
Implemented AArch64 platform backends:
aarch64-macosaarch64-iosaarch64-linuxaarch64-linux-androidat the code/backend level via the Linux-family signal pathaarch64-windowsat the code/backend level via the Windows exception path
Implemented x86_64 platform backends:
x86_64-macosx86_64-linuxx86_64-windows
Verification status:
- macOS AArch64: runtime-tested locally and in CI
- Linux AArch64: runtime-tested in CI
- iOS AArch64: cross-compiled core dylib and Mach-O payload locally
- Android AArch64: compiled core/payload objects against a local NDK sysroot
- Linux x86_64: runtime-tested in CI
- macOS x86_64: core library and example payload cross-compiled
- Windows x86_64: core library cross-compiled
- Windows AArch64: core library cross-compiled
Windows support is currently build-validated but not yet covered by a runtime smoke test.
x86_64 replay coverage:
inline_hook(...): supportedprepatched.inline_hook(...): supportedinstrument_no_original(...): supported with automatic instruction-length decoding for runtime-installed hooksinstrument(...): supported through Zydis-backed decode + trampoline replay- direct calls and direct jumps are replayed through synthetic absolute trampoline control transfers
- RIP-relative indirect calls and jumps are replayed through near trampoline allocation plus relocated memory displacements
- plain RIP-relative memory instructions such as
mov foo(%rip), %eaxare replayed through relocated disp32 patching when a near trampoline page is available - conditional branches are replayed through a local branch stub plus absolute taken/fallthrough jumps
- stack-pointer-based indirect calls such as
call *(%rsp)are replayed through a synthetic push-plus-jump trampoline sequence - unsupported x86_64 execute-original cases still fail early with
error.ReplayUnsupported- interrupt / syscall / system instructions
- RIP-relative relocations that cannot be represented from the allocated trampoline address
Deployment model by platform:
- macOS: runtime patching or prepatched trap sites, usually with
DYLD_INSERT_LIBRARIES - Linux: runtime patching or prepatched trap sites, usually with
LD_PRELOADorpatchelf - iOS: recommended prepatched trap sites plus inserted dylib + re-sign
- Android: Linux-family backend plus sidecar
.so, typically loaded via patched ELF metadata / app packaging - Windows: explicit sidecar DLL load, or an external injector / launcher that loads the hook DLL before patch-point use
Current execute-original replay whitelist for PC-relative AArch64 instructions:
adradrpldr (literal)intowNldr (literal)intoxNldr (literal)intosNldr (literal)intodNldr (literal)intoqNldrsw (literal)prfm (literal)bblb.condcbz/cbnztbz/tbnz
Unsupported execute-original cases fail at hook-install time instead of silently falling back to unsafe trampoline replay.
zig build
zig build testbuild.zig intentionally builds only the library and the test suite.
Examples are not wired into the root build because each example directory is
treated as a standalone mini-project with its own:
README.mdtarget.chook.zig
As a library package, the repository also intentionally has no src/main.zig.
Every example is built directly from inside its own directory with plain shell
commands. This keeps the example layout easy to read, keeps the injected hook
library self-contained, and avoids hiding the real build steps behind root
build.zig glue.
Common pattern:
cd examples/inline_hook_signal
cc -arch arm64 -O3 -DNDEBUG -Wl,-export_dynamic -o target target.c
zig build-lib -dynamic -OReleaseFast -femit-bin=hook.dylib \
--dep zighook \
-Mroot=hook.zig \
-Mzighook=../../src/root.zig \
-lc
DYLD_INSERT_LIBRARIES=$PWD/hook.dylib ./targetWhen you build a hook payload for x86_64 directly with zig build-lib, fetch
the pinned zydis-zig package once and then compile its bridge C file:
(cd ../.. && zig build --fetch)
ZYDIS_BRIDGE_C="$(../../scripts/zydis-package-path.sh bridge-c)"
zig build-lib -dynamic -OReleaseFast -femit-bin=hook.so \
"$ZYDIS_BRIDGE_C" \
--dep zighook \
-Mroot=hook.zig \
-Mzighook=../../src/root.zig \
-lcFor Linux, iOS, and Android deployment flows, see:
docs/platform-workflows.md
Available examples:
inline_hook_signal: function-entry trap hook, expected outputresult=42instrument_with_original: trap one instruction and replay it, expected outputresult=42instrument_no_original: trap one instruction and replace it, expected outputresult=99instrument_unhook_restore: install, unhook, and verify restoration, expected outputhooked=123thenrestored=5prepatched_inline_hook: register a trap point that already containsbrk, expected outputresult=77
CI runs these exact per-directory build commands and compares exact stdout. The Linux x86_64 CI smoke now runs all five shipped examples.
See:
docs/platform-workflows.mdexamples/README.md- each
examples/*/README.md
The public integration guide lives directly in src/root.zig. Each exported
function is documented with behavior, installation rules, resume semantics, and
small code examples so callers can integrate the library without reading
internal backend code.
For deployment-oriented notes, see docs/platform-workflows.md.
This repository follows the same license file as the Rust sighook repository.
See LICENSE.