Skip to content

Commit 8e5642f

Browse files
authored
Merge pull request #3 from grloper/claude/wraith-security-sensor-0pjajs
Add active enforcement and multi-process scanning to Wraith
2 parents b2311d5 + b4a08ab commit 8e5642f

7 files changed

Lines changed: 684 additions & 68 deletions

File tree

README.md

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ cargo build --release
6262
# Monitor a process that's already running:
6363
sudo ./target/release/wraith attach 4242
6464

65+
# Monitor every process matching a name at once:
66+
sudo ./target/release/wraith scan --match nginx
67+
6568
# Emit machine-readable events for your SIEM/pipeline:
6669
./target/release/wraith run --json events.jsonl -- ./target
6770
```
@@ -104,18 +107,96 @@ catches every stage and correlates them into one verdict.
104107
| `crash` | HIGH | Target took SIGSEGV/SIGILL/SIGBUS/SIGABRT — often a *failed* exploit worth investigating. |
105108
| `exploitation_chain` | CRITICAL | Multiple primitives correlated into a single high-confidence verdict. |
106109
| `sensitive_call` | INFO | Audit breadcrumb (with `--audit-sensitive`): a sensitive syscall from legitimate code. |
110+
| `blocked` | CRITICAL | Enforcement neutralised the offending syscall in place (`--block`). |
111+
| `killed` | CRITICAL | Enforcement killed the traced tree on confirmed exploitation (`--kill`). |
107112

108113
Tuning:
109114

110115
```
111116
--jit-critical treat anonymous-exec pages as HIGH (targets that never JIT)
117+
--trust-region A-B treat the hex range [A,B) as legitimate JIT (repeatable)
112118
--no-stack-pivot disable the ROP stack-pivot heuristic
113119
--audit-sensitive log sensitive syscalls from legitimate code too
114120
--min <sev> floor: info|warn|high|critical (default warn)
115121
```
116122

117123
---
118124

125+
## Active response (enforcement)
126+
127+
By default Wraith only *detects* — it never touches the tracee. Because it sits
128+
at the syscall-entry stop, though, it is standing at the one moment the
129+
offending syscall has not yet run, so it can also *stop* the attack:
130+
131+
```bash
132+
# Neutralise the offending syscall in place — the injected code's execve/connect
133+
# returns an error and never takes effect; the process lives on so you can watch
134+
# what it does next.
135+
wraith run --block -- ./target
136+
137+
# Terminate the whole traced tree the instant exploitation is confirmed, before
138+
# the offending syscall executes.
139+
wraith run --kill -- ./target
140+
```
141+
142+
Both fire only on a **CRITICAL** verdict — injected code issuing a *sensitive*
143+
syscall, or a correlated exploitation chain — so a HIGH/WARN anomaly (a JIT
144+
page, a lone RWX mapping) never trips enforcement. `--block` overwrites the
145+
syscall number at its entry stop so the kernel skips it and returns `-ENOSYS`;
146+
`--kill` sends `SIGKILL` to every traced thread-group.
147+
148+
### Trusting a JIT
149+
150+
Language runtimes (Node.js, the JVM, .NET, browsers) execute JIT-compiled code
151+
from anonymous executable pages and flip writable pages to executable as they
152+
compile — behaviour that looks, syscall-for-syscall, like payload staging. When
153+
you know where a runtime places its code, hand Wraith the range and it treats
154+
provenance and W^X inside it as legitimate:
155+
156+
```bash
157+
# Trust one or more JIT arenas (repeat --trust-region as needed).
158+
wraith run --trust-region 7f2a10000000-7f2a14000000 -- ./node-service
159+
```
160+
161+
Trust applies to concrete addresses — the syscall's execution site and an
162+
`mprotect` target page — so a JIT that respects W^X (map RW, write, `mprotect`
163+
RX) is fully exempted, while a *direct* RWX allocation elsewhere is still
164+
flagged.
165+
166+
---
167+
168+
## Scanning many processes at once
169+
170+
`run` and `attach` watch a single process tree. `scan` attaches to a whole set
171+
of already-running processes in one shot — select them by name/cmdline
172+
substring, or take everything you have permission to trace:
173+
174+
```bash
175+
# Attach to every process whose name or command line contains "nginx"
176+
# (repeat --match to widen the net); follows the children they spawn too.
177+
sudo wraith scan --match nginx --match redis
178+
179+
# Attach to every process we're allowed to trace (heavy — see below).
180+
sudo wraith scan --all --min high
181+
```
182+
183+
One tracer drives all of them through a single reap loop, and enforcement
184+
(`--block`/`--kill`) and the JSON stream work exactly as they do for a single
185+
target. Caveats worth knowing:
186+
187+
- **Needs privilege.** Attaching to a process you don't own requires
188+
`CAP_SYS_PTRACE` (run as root); processes you can't attach to are skipped, not
189+
fatal.
190+
- **It has a cost.** Every traced process pays the two-stops-per-syscall
191+
`ptrace` tax, so `--all` on a busy host is expensive — prefer `--match`.
192+
Whole-system, near-zero-overhead monitoring is the eBPF backend's job (below).
193+
- **Non-destructive by default.** Unlike `run`, `scan`/`attach` do *not* set
194+
`PTRACE_O_EXITKILL`: stopping Wraith leaves every scanned process running.
195+
- **Post-attach threads only.** As with `attach`, sibling threads that already
196+
existed before Wraith attached aren't picked up automatically (see below).
197+
198+
---
199+
119200
## Architecture
120201

121202
Small, auditable, and dependency-light on purpose — a sensor others run should
@@ -129,7 +210,7 @@ carry the smallest supply chain you can manage. The engine links only `nix` and
129210
├─ syscalls.rs the syscall table Wraith cares about
130211
├─ detect.rs the invariants + the exploitation-chain correlator
131212
├─ event.rs detection events + their JSONL form
132-
├─ tracer.rs the ptrace engine (spawn/attach, thread-following loop)
213+
├─ tracer.rs the ptrace engine (spawn/attach/scan, thread-following, enforcement)
133214
└─ bin/
134215
├─ wraith.rs the CLI sensor
135216
├─ benign.rs false-positive control target
@@ -173,20 +254,28 @@ claim to be a finished EDR.
173254
provenance rule — that's what the stack-pivot heuristic is for, and why
174255
return-address validation and a shadow stack are on the roadmap.
175256
- **Legitimate JIT** (browsers, JVMs, .NET) runs code from anonymous
176-
executable pages; hence `AnonExec` is WARN by default and configurable.
177-
178-
Roadmap: eBPF backend · return-address/shadow-stack checks · ROP-chain length
179-
heuristics · per-thread stack tracking · seizing pre-existing threads on attach
180-
· per-process behavioural baselining · a policy DSL for allow-listing
181-
legitimate JIT regions.
257+
executable pages; hence `AnonExec` is WARN by default, `AnonExec` can be
258+
raised with `--jit-critical`, and known JIT arenas can be exempted outright
259+
with `--trust-region`.
260+
- **Coverage vs. cost.** `scan --match`/`--all` can watch many processes at
261+
once, but every traced process pays the two-stops-per-syscall `ptrace` tax, so
262+
this suits a handful of high-value targets rather than a busy whole system.
263+
Near-zero-overhead, watch-everything monitoring is the eBPF backend's job (see
264+
below), not something the `ptrace` engine should attempt.
265+
266+
Roadmap: eBPF backend (near-zero-overhead, system-wide) · return-address/
267+
shadow-stack checks · ROP-chain length heuristics · per-thread stack tracking ·
268+
seizing pre-existing threads on attach · per-process behavioural baselining ·
269+
richer JIT policy (auto-learn a runtime's arenas rather than hand-supplied
270+
ranges).
182271

183272
---
184273

185274
## Building & testing
186275

187276
```bash
188277
cargo build --release
189-
cargo test # 28 unit + 6 end-to-end tests
278+
cargo test # 36 unit + 9 end-to-end tests
190279
cargo clippy --all-targets
191280
./demo.sh # side-by-side benign vs. exploitation run
192281
```

demo.sh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,27 @@ mt_code=$?
5353
echo " -> wraith exit code: $mt_code"
5454
set -e
5555

56+
echo
57+
echo "============================================================"
58+
echo " 5/5 ENFORCEMENT — same payload, but Wraith intervenes"
59+
echo "============================================================"
60+
echo "--- --block: the injected socket() is neutralised (returns -ENOSYS),"
61+
echo " the process survives so you can watch what it does next ---"
62+
set +e
63+
"$WRAITH" run --block -- "$SIM"
64+
echo " -> wraith exit code: $?"
65+
echo
66+
echo "--- --kill: the traced tree is SIGKILLed before the payload runs ---"
67+
"$WRAITH" run --kill -- "$SIM"
68+
echo " -> wraith exit code: $?"
69+
set -e
70+
5671
echo
5772
if [ "$code" -eq 3 ] && [ "$mt_code" -eq 3 ]; then
5873
echo "Demo OK: benign runs (single- and multi-threaded) were clean;"
5974
echo " injected-code execution was detected on the main thread AND"
60-
echo " on a worker thread, and correlated into an exploitation chain."
75+
echo " on a worker thread, correlated into an exploitation chain, and"
76+
echo " (in --block/--kill) stopped before the payload's syscall ran."
6177
else
6278
echo "Demo WARNING: expected exit 3 from both simulator runs (got $code and $mt_code)."
6379
fi

0 commit comments

Comments
 (0)