Skip to content

Commit f145dde

Browse files
committed
Initial zighook release
0 parents  commit f145dde

File tree

31 files changed

+2485
-0
lines changed

31 files changed

+2485
-0
lines changed

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
jobs:
9+
zig-macos-aarch64:
10+
runs-on: macos-14
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Setup Zig
17+
uses: mlugg/setup-zig@v2
18+
with:
19+
version: 0.15.2
20+
21+
- name: Zig fmt
22+
run: |
23+
zig fmt --check \
24+
build.zig \
25+
src/*.zig \
26+
src/arch/*.zig \
27+
src/platform/*.zig \
28+
examples/*/main.zig \
29+
examples/support/*.zig \
30+
examples/preload/*.zig
31+
32+
- name: Zig build
33+
run: zig build
34+
35+
- name: Zig test
36+
run: zig build test
37+
38+
- name: Build examples
39+
run: zig build examples
40+
41+
- name: Run preload smoke tests
42+
run: zig build preload-smoke

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.zig-cache/
2+
zig-out/
3+
root_test_bin
4+
*.o
5+
*.obj
6+
*.a
7+
*.so
8+
*.dylib.dSYM/
9+
*.exe
10+
.DS_Store

LICENSE

Lines changed: 338 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# zighook
2+
3+
`zighook` is a Zig rewrite of the sibling Rust `sighook` project.
4+
5+
Current scope:
6+
7+
- macOS
8+
- Apple Silicon / AArch64
9+
10+
Implemented APIs:
11+
12+
- `patchcode(address, opcode)`
13+
- `patch_bytes(allocator, address, bytes)`
14+
- `instrument(address, callback)`
15+
- `instrument_no_original(address, callback)`
16+
- `inline_hook(address, callback)`
17+
- `inline_hook_jump(address, replace_fn)`
18+
- `unhook(address)`
19+
- `original_opcode(address)`
20+
- `prepatched.instrument*`
21+
- `prepatched.inline_hook`
22+
- `prepatched.cache_original_opcode`
23+
24+
The current backend supports:
25+
26+
- direct instruction patching
27+
- trap-based instrumentation via `brk`
28+
- signal-based entry hooks
29+
- jump detours
30+
- constructor-based dylib payloads for `DYLD_INSERT_LIBRARIES` / later Mach-O insertion workflows
31+
32+
## Status
33+
34+
This repository currently targets the first backend slice only:
35+
36+
- `aarch64-apple-darwin`
37+
38+
It is usable for local experiments on Apple Silicon macOS, but it is not yet at full feature/platform parity with the Rust crate.
39+
40+
## Build
41+
42+
```bash
43+
zig build
44+
zig build test
45+
zig build examples
46+
```
47+
48+
## Preload Smoke Tests
49+
50+
The repository includes dylib payloads and a small C target to validate constructor-based preload hooking:
51+
52+
```bash
53+
zig build preload-smoke
54+
```
55+
56+
This builds and runs:
57+
58+
- `libzighook_payload_inline_hook_signal.dylib`
59+
- `libzighook_payload_inline_hook_jump.dylib`
60+
- `zighook_preload_target_add`
61+
62+
Manual preload usage after `zig build`:
63+
64+
```bash
65+
DYLD_INSERT_LIBRARIES=zig-out/lib/libzighook_payload_inline_hook_signal.dylib \
66+
TARGET_EXPECT=42 \
67+
zig-out/bin/zighook_preload_target_add
68+
```
69+
70+
```bash
71+
DYLD_INSERT_LIBRARIES=zig-out/lib/libzighook_payload_inline_hook_jump.dylib \
72+
TARGET_EXPECT=6 \
73+
zig-out/bin/zighook_preload_target_add
74+
```
75+
76+
## Examples
77+
78+
See:
79+
80+
- `examples/README.md`
81+
- `examples/preload/README.md`
82+
83+
## License
84+
85+
This repository follows the same license file as the Rust `sighook` repository.
86+
See `LICENSE`.

build.zig

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
const std = @import("std");
2+
3+
fn addExample(
4+
b: *std.Build,
5+
zighook_mod: *std.Build.Module,
6+
runtime_targets_mod: *std.Build.Module,
7+
target: std.Build.ResolvedTarget,
8+
optimize: std.builtin.OptimizeMode,
9+
name: []const u8,
10+
root_source_path: []const u8,
11+
runtime_targets_asm: std.Build.LazyPath,
12+
) *std.Build.Step.Compile {
13+
const exe = b.addExecutable(.{
14+
.name = name,
15+
.root_module = b.createModule(.{
16+
.root_source_file = b.path(root_source_path),
17+
.target = target,
18+
.optimize = optimize,
19+
.imports = &.{
20+
.{ .name = "zighook", .module = zighook_mod },
21+
.{ .name = "runtime_targets", .module = runtime_targets_mod },
22+
},
23+
.link_libc = true,
24+
}),
25+
});
26+
exe.root_module.addAssemblyFile(runtime_targets_asm);
27+
return exe;
28+
}
29+
30+
fn addPayloadLibrary(
31+
b: *std.Build,
32+
zighook_mod: *std.Build.Module,
33+
target: std.Build.ResolvedTarget,
34+
optimize: std.builtin.OptimizeMode,
35+
name: []const u8,
36+
root_source_path: []const u8,
37+
constructor_asm: std.Build.LazyPath,
38+
) *std.Build.Step.Compile {
39+
const dylib = b.addLibrary(.{
40+
.name = name,
41+
.linkage = .dynamic,
42+
.root_module = b.createModule(.{
43+
.root_source_file = b.path(root_source_path),
44+
.target = target,
45+
.optimize = optimize,
46+
.imports = &.{
47+
.{ .name = "zighook", .module = zighook_mod },
48+
},
49+
.link_libc = true,
50+
}),
51+
});
52+
dylib.root_module.addAssemblyFile(constructor_asm);
53+
return dylib;
54+
}
55+
56+
fn addPreloadTargetBuild(
57+
b: *std.Build,
58+
output_name: []const u8,
59+
source_path: []const u8,
60+
) struct { step: *std.Build.Step.Run, output: std.Build.LazyPath } {
61+
const cc = b.addSystemCommand(&.{
62+
"cc",
63+
"-O0",
64+
"-g",
65+
"-Wl,-export_dynamic",
66+
"-o",
67+
});
68+
const output = cc.addOutputFileArg(output_name);
69+
cc.addFileArg(b.path(source_path));
70+
return .{ .step = cc, .output = output };
71+
}
72+
73+
pub fn build(b: *std.Build) void {
74+
const target = b.standardTargetOptions(.{});
75+
const optimize = b.standardOptimizeOption(.{});
76+
const runtime_targets_asm = b.path("examples/support/runtime_targets_aarch64_macos.S");
77+
const preload_constructor_asm = b.path("examples/preload/constructor_aarch64_macos.S");
78+
79+
const mod = b.addModule("zighook", .{
80+
.root_source_file = b.path("src/root.zig"),
81+
.target = target,
82+
.optimize = optimize,
83+
.link_libc = true,
84+
});
85+
const runtime_targets_mod = b.createModule(.{
86+
.root_source_file = b.path("examples/support/runtime_targets.zig"),
87+
.target = target,
88+
.optimize = optimize,
89+
.link_libc = true,
90+
});
91+
92+
const exe = b.addExecutable(.{
93+
.name = "zighook",
94+
.root_module = b.createModule(.{
95+
.root_source_file = b.path("src/main.zig"),
96+
.target = target,
97+
.optimize = optimize,
98+
.imports = &.{
99+
.{ .name = "zighook", .module = mod },
100+
},
101+
.link_libc = true,
102+
}),
103+
});
104+
105+
b.installArtifact(exe);
106+
107+
const run_step = b.step("run", "Run the app");
108+
const run_cmd = b.addRunArtifact(exe);
109+
run_step.dependOn(&run_cmd.step);
110+
run_cmd.step.dependOn(b.getInstallStep());
111+
if (b.args) |args| {
112+
run_cmd.addArgs(args);
113+
}
114+
115+
const mod_tests = b.addTest(.{
116+
.root_module = mod,
117+
});
118+
mod_tests.root_module.addAssemblyFile(runtime_targets_asm);
119+
const run_mod_tests = b.addRunArtifact(mod_tests);
120+
121+
const exe_tests = b.addTest(.{
122+
.root_module = exe.root_module,
123+
});
124+
const run_exe_tests = b.addRunArtifact(exe_tests);
125+
const test_step = b.step("test", "Run tests");
126+
test_step.dependOn(&run_mod_tests.step);
127+
test_step.dependOn(&run_exe_tests.step);
128+
129+
const examples_step = b.step("examples", "Build all example executables");
130+
const example_specs = [_]struct { name: []const u8, path: []const u8 }{
131+
.{ .name = "example_patchcode_add_to_mul", .path = "examples/patchcode_add_to_mul/main.zig" },
132+
.{ .name = "example_instrument_with_original", .path = "examples/instrument_with_original/main.zig" },
133+
.{ .name = "example_instrument_no_original", .path = "examples/instrument_no_original/main.zig" },
134+
.{ .name = "example_inline_hook_signal", .path = "examples/inline_hook_signal/main.zig" },
135+
.{ .name = "example_inline_hook_jump", .path = "examples/inline_hook_jump/main.zig" },
136+
.{ .name = "example_instrument_unhook_restore", .path = "examples/instrument_unhook_restore/main.zig" },
137+
};
138+
139+
inline for (example_specs) |spec| {
140+
const example_exe = addExample(
141+
b,
142+
mod,
143+
runtime_targets_mod,
144+
target,
145+
optimize,
146+
spec.name,
147+
spec.path,
148+
runtime_targets_asm,
149+
);
150+
examples_step.dependOn(&example_exe.step);
151+
}
152+
153+
const payload_inline_hook_signal = addPayloadLibrary(
154+
b,
155+
mod,
156+
target,
157+
optimize,
158+
"zighook_payload_inline_hook_signal",
159+
"examples/preload/payload_inline_hook_signal.zig",
160+
preload_constructor_asm,
161+
);
162+
const payload_inline_hook_jump = addPayloadLibrary(
163+
b,
164+
mod,
165+
target,
166+
optimize,
167+
"zighook_payload_inline_hook_jump",
168+
"examples/preload/payload_inline_hook_jump.zig",
169+
preload_constructor_asm,
170+
);
171+
const preload_target = addPreloadTargetBuild(
172+
b,
173+
"zighook_preload_target_add",
174+
"examples/preload/target_add.c",
175+
);
176+
177+
b.installArtifact(payload_inline_hook_signal);
178+
b.installArtifact(payload_inline_hook_jump);
179+
b.getInstallStep().dependOn(&b.addInstallBinFile(preload_target.output, "zighook_preload_target_add").step);
180+
181+
examples_step.dependOn(&payload_inline_hook_signal.step);
182+
examples_step.dependOn(&payload_inline_hook_jump.step);
183+
examples_step.dependOn(&preload_target.step.step);
184+
185+
const preload_examples_step = b.step("preload-examples", "Build DYLD preload payload dylibs and the C smoke target");
186+
preload_examples_step.dependOn(&payload_inline_hook_signal.step);
187+
preload_examples_step.dependOn(&payload_inline_hook_jump.step);
188+
preload_examples_step.dependOn(&preload_target.step.step);
189+
190+
const smoke_signal = b.addSystemCommand(&.{ "env", "TARGET_EXPECT=42" });
191+
smoke_signal.addPrefixedFileArg("DYLD_INSERT_LIBRARIES=", payload_inline_hook_signal.getEmittedBin());
192+
smoke_signal.addFileArg(preload_target.output);
193+
194+
const smoke_jump = b.addSystemCommand(&.{ "env", "TARGET_EXPECT=6" });
195+
smoke_jump.addPrefixedFileArg("DYLD_INSERT_LIBRARIES=", payload_inline_hook_jump.getEmittedBin());
196+
smoke_jump.addFileArg(preload_target.output);
197+
198+
const preload_smoke_step = b.step("preload-smoke", "Run DYLD_INSERT_LIBRARIES smoke tests against a C target");
199+
preload_smoke_step.dependOn(&smoke_signal.step);
200+
preload_smoke_step.dependOn(&smoke_jump.step);
201+
}

0 commit comments

Comments
 (0)