Skip to content

Commit 8a9d14d

Browse files
committed
feat: bootstrap ptracehook core with examples and CI
0 parents  commit 8a9d14d

17 files changed

Lines changed: 1882 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
jobs:
9+
rust-checks:
10+
runs-on: ubuntu-24.04
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Setup Rust
17+
uses: dtolnay/rust-toolchain@stable
18+
with:
19+
components: rustfmt, clippy
20+
21+
- name: Setup Rust cache
22+
uses: Swatinem/rust-cache@v2
23+
24+
- name: Cargo fmt
25+
run: cargo fmt --all -- --check
26+
27+
- name: Cargo check
28+
run: cargo check --all-targets
29+
30+
- name: Cargo clippy
31+
run: cargo clippy --all-targets -- -D warnings
32+
33+
- name: Cargo test (unit + integration)
34+
run: cargo test --all-targets
35+
36+
zigbuild-linux-targets:
37+
runs-on: ubuntu-24.04
38+
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@v4
42+
43+
- name: Setup Rust
44+
uses: dtolnay/rust-toolchain@stable
45+
46+
- name: Setup Zig
47+
uses: goto-bus-stop/setup-zig@v2
48+
with:
49+
version: 0.13.0
50+
51+
- name: Install cargo-zigbuild
52+
run: cargo install cargo-zigbuild --locked
53+
54+
- name: Zigbuild x86_64 Linux
55+
run: cargo zigbuild --target x86_64-unknown-linux-gnu
56+
57+
- name: Zigbuild aarch64 Linux
58+
run: cargo zigbuild --target aarch64-unknown-linux-gnu

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
examples/*/app

AGENT.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# ptracehook Agent Notes
2+
3+
Last updated: 2026-02-09
4+
5+
## 1) Project Positioning
6+
7+
- Crate: `ptracehook`
8+
- Status: **new scaffold / design-first**
9+
- Goal: build a dedicated out-of-process hook framework on top of `ptrace`, focusing first on Linux `x86_64`.
10+
- Relationship to `sighook`: **complementary, not replacement**.
11+
- `sighook`: in-process signal/trap patching (BRK/INT3 handler inside target process)
12+
- `ptracehook`: external tracer model (controller process drives target execution)
13+
14+
### Why separate crate
15+
16+
- Semantic boundary is clear: `sighook` means signal-based in-process hook.
17+
- Runtime model, failure modes, and API ergonomics differ significantly (`fork/attach/waitpid` loop vs in-process callback).
18+
- Keeping them separate reduces conceptual coupling and prevents platform-specific complexity leakage.
19+
20+
## 2) Ecosystem Survey (crates.io quick scan)
21+
22+
The following crates already exist and should be treated as references or potential building blocks:
23+
24+
- `pete` (`0.13.0`): friendly ptrace wrapper.
25+
- `ptrace-do` (`0.1.4`): featureful ptrace interaction library.
26+
- `ptrace` (`0.1.2`): low-level POSIX ptrace bindings.
27+
- `udbg` (`0.3.1`): broader debugging/memory-hacking framework.
28+
29+
### Gap assessment
30+
31+
There are ptrace crates, but there is no obvious crate with a `sighook`-like **hook-centric** API contract (breakpoint registration + callback action contract + deterministic hook event loop tuned for RE/CTF workflows).
32+
33+
Therefore, `ptracehook` is still justified as:
34+
35+
- a focused hook abstraction layer,
36+
- with explicit behavior contracts for breakpoint lifecycle,
37+
- and stable, compact API for scripted reverse workflows.
38+
39+
## 3) Scope and Non-goals (MVP)
40+
41+
### MVP scope (v0.1 -> v0.2)
42+
43+
- Linux `x86_64` only.
44+
- Spawn and attach modes.
45+
- Software breakpoint (`int3`) management.
46+
- Restore-original -> optional single-step -> reinsert flow.
47+
- Register read/write (`user_regs_struct`-aligned abstraction).
48+
- Remote memory read/write helpers.
49+
- Event loop with callback-based hook actions.
50+
51+
### Explicit non-goals (MVP)
52+
53+
- Windows/macOS backend.
54+
- Hardware breakpoint abstraction.
55+
- Full debugger feature parity (symbol server, DWARF stepping, source-level UI).
56+
- Thread-wide advanced scheduling policy beyond basic correctness.
57+
58+
## 4) Public API Draft (current scaffold)
59+
60+
Current scaffold exports these key types from `src/api.rs`:
61+
62+
- `SessionBuilder`
63+
- `spawn(path)`
64+
- `attach(pid)`
65+
- `arg(...)`, `args(...)`
66+
- `options(...)`
67+
- `build() -> Result<TraceSession, PtraceHookError>`
68+
- `TraceSession`
69+
- `add_breakpoint(spec, callback) -> Result<BreakpointId, ...>`
70+
- `remove_breakpoint(id) -> Result<(), ...>`
71+
- `run() -> Result<TraceExit, ...>`
72+
- `read_bytes(...)`, `write_bytes(...)`
73+
- `get_regs()`, `set_regs(...)`
74+
- `BreakpointSpec`
75+
- `address`, `mode`, `name`
76+
- `HookCallback` / `HookContext` / `HookAction`
77+
- `RegistersX86_64`
78+
79+
### Hook action contract
80+
81+
`HookAction` is designed to support deterministic control flow decisions:
82+
83+
- `Continue`
84+
- `ContinueWithSignal(i32)`
85+
- `SingleStepThenContinue`
86+
- `Detach`
87+
- `Kill`
88+
89+
### Breakpoint mode contract
90+
91+
- `ExecuteOriginal`: run original instruction (restore byte + single-step + reinsert trap).
92+
- `SkipOriginal`: callback fully controls state transition; default path skips original instruction semantics.
93+
94+
## 5) Internal Architecture Plan
95+
96+
Target module split (planned, not fully implemented):
97+
98+
- `src/lib.rs`: public exports and compile gates.
99+
- `src/session.rs` (or current `api.rs` split later): session lifecycle and state container.
100+
- `src/event_loop.rs`: wait/dispatch loop, signal pass-through rules.
101+
- `src/breakpoint.rs`: install/remove/reinsert breakpoint logic.
102+
- `src/memory.rs`: `PTRACE_PEEKDATA/POKEDATA` helpers with alignment-safe reads/writes.
103+
- `src/regs.rs`: register mapping and conversions.
104+
- `src/error.rs`: typed error model.
105+
106+
### State model requirements
107+
108+
At minimum, `TraceSession` should track:
109+
110+
- launch mode (`spawn` / `attach`),
111+
- active breakpoints map (`BreakpointId -> metadata`),
112+
- original byte cache (`address -> original opcode byte`),
113+
- per-breakpoint callback registry,
114+
- stepping state (`which breakpoint is in single-step recovery`).
115+
116+
## 6) Behavior Invariants (must keep)
117+
118+
- Every installed breakpoint stores original first byte before writing `0xCC`.
119+
- On trap hit:
120+
1. restore original byte,
121+
2. set `rip = rip - 1`,
122+
3. execute callback,
123+
4. follow callback action,
124+
5. if needed, single-step and reinsert trap.
125+
- Non-trap stop signals should be forwarded unless policy says otherwise.
126+
- `PTRACE_O_EXITKILL` should be enabled by default for safety.
127+
- Session teardown must attempt best-effort breakpoint restoration.
128+
129+
## 7) Error Model Guidelines
130+
131+
`PtraceHookError` should remain compact and user-facing:
132+
133+
- platform incompatibility,
134+
- invalid pid/address,
135+
- ptrace syscall failure (with operation context + errno),
136+
- internal state errors (missing breakpoint, invalid lifecycle),
137+
- not-implemented placeholders during scaffold stage.
138+
139+
Avoid leaking raw syscall details directly in API unless wrapped with actionable context.
140+
141+
## 8) Roadmap (Detailed Plan)
142+
143+
### Phase 0 — Scaffold (done in this commit)
144+
145+
- Create crate.
146+
- Draft public API shape.
147+
- Add `AGENT.md` contract and implementation roadmap.
148+
149+
### Phase 1 — Core runtime loop
150+
151+
- Implement `spawn` mode.
152+
- Implement `add_breakpoint` + internal table.
153+
- Implement trap dispatch loop with single-thread tracee assumption.
154+
- Implement `run()` for one tracee process.
155+
156+
### Phase 2 — Memory/regs surface
157+
158+
- Implement `read_bytes`/`write_bytes` with unaligned handling.
159+
- Implement register get/set mapping for Linux x86_64.
160+
- Validate callback-controlled register mutation.
161+
162+
### Phase 3 — Attach mode and resilience
163+
164+
- Implement `attach(pid)`.
165+
- Handle already-stopped tracees and signal forwarding.
166+
- Add robust detach/kill/cleanup paths.
167+
168+
### Phase 4 — API hardening
169+
170+
- Add richer `StopReason` reporting and optional observer hooks.
171+
- Add configurable policies (signal pass-through, auto-reinsert behavior).
172+
- Freeze MVP signatures for `0.2.x`.
173+
174+
### Phase 5 — Testing and examples
175+
176+
- Add integration demos:
177+
- dump-only hook,
178+
- bypass-style control-flow redirection,
179+
- oracle-style byte-by-byte solver flow.
180+
- Add regression tests for breakpoint restore and reinsert logic.
181+
182+
## 9) Validation Checklist
183+
184+
Run before merge/release:
185+
186+
```bash
187+
cargo fmt --all -- --check
188+
cargo check --all-targets
189+
cargo test
190+
cargo clippy --all-targets -- -D warnings
191+
```
192+
193+
Linux x86_64 runtime verification (manual during MVP):
194+
195+
- spawn + breakpoint hit,
196+
- attach + breakpoint hit,
197+
- callback register mutation observed,
198+
- non-trap signal pass-through,
199+
- teardown restores original byte.
200+
201+
## 10) Interop Guidance with sighook
202+
203+
- Keep crate boundaries explicit (`sighook` vs `ptracehook`).
204+
- Shared helper code should move to a separate utility crate only when duplication becomes meaningful.
205+
- Future optional umbrella crate (`hookkit` style) can re-export both backends, but backends remain independent.
206+
207+
## 11) Current Scaffold Limitations
208+
209+
- `TraceSession::run` and many runtime methods currently return `NotImplemented`.
210+
- The scaffold is intentionally API-first to stabilize contracts before low-level ptrace engine work.
211+
- Treat this crate as design baseline for the next implementation iteration.

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "ptracehook"
3+
version = "0.1.0"
4+
edition = "2024"
5+
rust-version = "1.85"
6+
description = "Out-of-process ptrace-based hook framework scaffold"
7+
license = "MIT"
8+
repository = "https://github.com/YinMo19/ptracehook"
9+
10+
[dependencies]
11+
libc = "0.2"
12+
13+
[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies]
14+
iced-x86 = { version = "1", default-features = false, features = ["decoder", "std"] }

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# ptracehook
2+
3+
`ptracehook` is a planned out-of-process runtime hook framework for Linux `x86_64` targets.
4+
5+
Unlike in-process signal/trap hook crates, `ptracehook` is designed for scenarios where preload-based injection is unavailable (for example, statically linked executables).
6+
7+
## Current status
8+
9+
- Public API surface is drafted and Linux runtime core is partially implemented.
10+
- Linux `x86_64` now includes:
11+
- spawn/attach session flow,
12+
- software breakpoint install/restore/reinsert loop,
13+
- callback-based hook dispatch,
14+
- register get/set,
15+
- remote memory read/write helpers.
16+
- Linux `aarch64` target is accepted at build level, but runtime backend remains TODO.
17+
- Detailed implementation plan and API contract are documented in `AGENT.md`.
18+
19+
## Scope
20+
21+
- Primary MVP target: `linux x86_64`
22+
- Primary runtime primitive: `ptrace` (`PTRACE_TRACEME` / `PTRACE_ATTACH` / breakpoint + single-step loop)
23+
24+
## Examples
25+
26+
- `examples/instrument_with_original`
27+
- `examples/instrument_no_original`
28+
29+
Example quick run (Linux `x86_64`):
30+
31+
```bash
32+
cc -O0 -fno-omit-frame-pointer -no-pie examples/instrument_with_original/target.c -o examples/instrument_with_original/app
33+
cargo run --example instrument_with_original
34+
```
35+
36+
## CI
37+
38+
CI workflow is at `.github/workflows/ci.yml` and currently includes:
39+
40+
- fmt/check/clippy/test on `ubuntu-24.04`
41+
- `cargo zigbuild` cross compile checks for:
42+
- `x86_64-unknown-linux-gnu`
43+
- `aarch64-unknown-linux-gnu`

examples/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# ptracehook examples
2+
3+
These examples use an external C target process and trace it with `ptracehook`.
4+
5+
Available examples:
6+
7+
- `instrument_with_original`
8+
- `instrument_no_original`
9+
10+
Each example folder contains:
11+
12+
- `target.c`: tracee program source
13+
- `main.rs`: tracer program (cargo example target)
14+
- `README.md`: run instructions
15+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# instrument_no_original
2+
3+
Demonstrates `BreakpointMode::SkipOriginal`.
4+
5+
The tracer breaks at `calc` entry, sets return register to `99`, and skips the original function body.
6+
Expected output:
7+
8+
```text
9+
calc(4, 5) = 99
10+
```
11+
12+
## Run
13+
14+
```bash
15+
cc -O0 -fno-omit-frame-pointer -no-pie examples/instrument_no_original/target.c -o examples/instrument_no_original/app
16+
cargo run --example instrument_no_original
17+
```

0 commit comments

Comments
 (0)