|
1 | | -# cyber-rust |
| 1 | +# Wraith |
| 2 | + |
| 3 | +**Signature-free runtime exploitation detection via syscall provenance verification.** |
| 4 | + |
| 5 | +Wraith is a low-level Rust security sensor that answers a question most tools |
| 6 | +can't: *is this process being exploited right now?* — without knowing the |
| 7 | +vulnerability or the payload in advance. That makes it effective against |
| 8 | +**zero-days and n-days alike**, because it keys on the *behaviour of |
| 9 | +exploitation*, not the *identity of the bug*. |
| 10 | + |
| 11 | +> Built as the runtime-defence companion to [`ghost`](https://github.com/pandaadir05/ghost). |
| 12 | +> `ghost` finds weaknesses; `wraith` catches them being used. |
| 13 | +
|
| 14 | +--- |
| 15 | + |
| 16 | +## The idea |
| 17 | + |
| 18 | +Defensive tooling usually answers one of two questions: |
| 19 | + |
| 20 | +| Question | Needs to know | Blind to | |
| 21 | +|---|---|---| |
| 22 | +| *Does this code contain a known bug?* (SAST/scanners) | the vulnerability | zero-days | |
| 23 | +| *Do these bytes match known-bad?* (AV/YARA) | the payload | novel payloads | |
| 24 | + |
| 25 | +Wraith answers a **third** question — *is this process executing in a state |
| 26 | +that only exploitation produces?* — and it needs to know neither the bug nor |
| 27 | +the payload. |
| 28 | + |
| 29 | +The observation behind it: **every** memory-corruption exploit, no matter the |
| 30 | +root cause (stack overflow, UAF, type confusion, an unknown zero-day), |
| 31 | +eventually converges on the same visible act. To accomplish anything — spawn a |
| 32 | +shell, open a socket, read a secret — the attacker must issue **system calls**. |
| 33 | +And at the instant those syscalls happen, the process is in a state that |
| 34 | +legitimate execution *never* produces. |
| 35 | + |
| 36 | +Wraith attaches to a process with `ptrace`, stops at the entry of every |
| 37 | +syscall, and checks a handful of invariants that hold for all benign programs: |
| 38 | + |
| 39 | +1. **Provenance** — a `syscall` instruction only ever runs from a *file-backed |
| 40 | + executable page* (the program's own `.text`, a shared library, or the kernel |
| 41 | + vDSO). Shellcode injected into the heap, stack, or an anonymous page |
| 42 | + **breaks this**. |
| 43 | +2. **W^X** — no benign program needs a page that is writable *and* executable, |
| 44 | + nor to flip a writable page to executable. Payload staging **breaks this**. |
| 45 | +3. **Stack integrity** — at syscall time the stack pointer lives inside a real |
| 46 | + stack, never the heap or a file image. A ROP stack pivot **breaks this**. |
| 47 | + |
| 48 | +Because these are invariants of *legitimate behaviour* rather than signatures |
| 49 | +of *specific attacks*, any violation is evidence of exploitation regardless of |
| 50 | +how the attacker got there. |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Quickstart |
| 55 | + |
| 56 | +```bash |
| 57 | +cargo build --release |
| 58 | + |
| 59 | +# Monitor a program you launch: |
| 60 | +./target/release/wraith run -- /usr/bin/some-service --flags |
| 61 | + |
| 62 | +# Monitor a process that's already running: |
| 63 | +sudo ./target/release/wraith attach 4242 |
| 64 | + |
| 65 | +# Emit machine-readable events for your SIEM/pipeline: |
| 66 | +./target/release/wraith run --json events.jsonl -- ./target |
| 67 | +``` |
| 68 | + |
| 69 | +Wraith exits `0` when clean, `1` on suspicious (HIGH) activity, and `3` when it |
| 70 | +detects exploitation (CRITICAL) — so it drops straight into CI and fuzzing |
| 71 | +harnesses as a behavioural oracle. |
| 72 | + |
| 73 | +### See it work |
| 74 | + |
| 75 | +The repo ships two self-contained targets (no real exploit required): |
| 76 | + |
| 77 | +```console |
| 78 | +$ wraith run --min info -- ./target/release/benign |
| 79 | +wraith: monitoring `./target/release/benign` (provenance mode) |
| 80 | +benign: ... normal work ... |
| 81 | +wraith: 81 syscalls, 0 event(s); verdict: clean # <- false-positive control |
| 82 | + |
| 83 | +$ wraith run -- ./target/release/shellcode-sim |
| 84 | + HIGH pid=6586 wx_violation mmap @ 0x7fa8ad52534a `mmap` requests writable+executable memory — classic shellcode staging |
| 85 | +CRITICAL pid=6586 foreign_origin_syscall socket @ 0x7fa8ad696011 [anon] sensitive syscall `socket` issued from wx-violation memory — injected code is now acting |
| 86 | +CRITICAL pid=6586 exploitation_chain socket @ 0x7fa8ad696011 [correlated] EXPLOITATION CHAIN: executable payload staged (W^X) -> sensitive syscall from injected code |
| 87 | +wraith: 69 syscalls, 3 event(s); verdict: EXPLOITATION DETECTED |
| 88 | +``` |
| 89 | + |
| 90 | +`shellcode-sim` stages an RWX page, writes a payload into it, and issues a |
| 91 | +syscall from that page — the exact tail end of a real exploit — and Wraith |
| 92 | +catches every stage and correlates them into one verdict. |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## What it detects |
| 97 | + |
| 98 | +| Event | Severity | Meaning | |
| 99 | +|---|---|---| |
| 100 | +| `foreign_origin_syscall` | HIGH / CRITICAL | A syscall issued from non-code memory (heap/stack/anon/RWX). CRITICAL when the syscall is *sensitive* (execve, connect, ptrace, …). | |
| 101 | +| `wx_violation` | HIGH | `mmap`/`mprotect` requesting writable **and** executable memory. | |
| 102 | +| `wx_transition` | HIGH | A writable page being flipped to executable — payload staging. | |
| 103 | +| `stack_pivot` | HIGH | Stack pointer sitting in the heap or a file image at syscall time — a ROP indicator. | |
| 104 | +| `crash` | HIGH | Target took SIGSEGV/SIGILL/SIGBUS/SIGABRT — often a *failed* exploit worth investigating. | |
| 105 | +| `exploitation_chain` | CRITICAL | Multiple primitives correlated into a single high-confidence verdict. | |
| 106 | +| `sensitive_call` | INFO | Audit breadcrumb (with `--audit-sensitive`): a sensitive syscall from legitimate code. | |
| 107 | + |
| 108 | +Tuning: |
| 109 | + |
| 110 | +``` |
| 111 | +--jit-critical treat anonymous-exec pages as HIGH (targets that never JIT) |
| 112 | +--no-stack-pivot disable the ROP stack-pivot heuristic |
| 113 | +--audit-sensitive log sensitive syscalls from legitimate code too |
| 114 | +--min <sev> floor: info|warn|high|critical (default warn) |
| 115 | +``` |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## Architecture |
| 120 | + |
| 121 | +Small, auditable, and dependency-light on purpose — a sensor others run should |
| 122 | +carry the smallest supply chain you can manage. The engine links only `nix` and |
| 123 | +`libc`; JSON is emitted by hand. |
| 124 | + |
| 125 | +``` |
| 126 | + src/ |
| 127 | + ├─ maps.rs parse /proc/<pid>/maps into typed regions |
| 128 | + ├─ provenance.rs classify an instruction/stack pointer against the map |
| 129 | + ├─ syscalls.rs the syscall table Wraith cares about |
| 130 | + ├─ detect.rs the invariants + the exploitation-chain correlator |
| 131 | + ├─ event.rs detection events + their JSONL form |
| 132 | + ├─ tracer.rs the ptrace engine (spawn/attach, syscall loop) |
| 133 | + └─ bin/ |
| 134 | + ├─ wraith.rs the CLI sensor |
| 135 | + ├─ benign.rs false-positive control target |
| 136 | + └─ shellcode_sim.rs exploitation-behaviour simulator |
| 137 | +``` |
| 138 | + |
| 139 | +The tracer adds no syscall of its own on the hot path beyond the unavoidable |
| 140 | +`getregs`, and re-reads `/proc/<pid>/maps` only when a memory operation could |
| 141 | +have changed it. |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +## Limitations & roadmap |
| 146 | + |
| 147 | +Wraith is an honest research prototype with a clear production path; it does not |
| 148 | +claim to be a finished EDR. |
| 149 | + |
| 150 | +- **`ptrace` overhead.** Two stops per syscall suits high-value targets |
| 151 | + (network daemons, parsers, fuzz targets), not the whole system. The |
| 152 | + production path is the same logic on **eBPF** (`tracepoint/raw_syscalls` + |
| 153 | + a page-provenance map) for near-zero overhead — the detection model is |
| 154 | + transport-agnostic by design. |
| 155 | +- **Multithreading.** The current engine focuses on single-threaded targets; |
| 156 | + full `PTRACE_O_TRACECLONE` thread-following and per-thread stack tracking is |
| 157 | + the next milestone. |
| 158 | +- **Pure-ROP that never leaves legit code.** An attacker who only reuses |
| 159 | + existing `.text` and never stages new executable memory won't trip the |
| 160 | + provenance rule — that's what the stack-pivot heuristic is for, and why |
| 161 | + return-address validation and a shadow stack are on the roadmap. |
| 162 | +- **Legitimate JIT** (browsers, JVMs, .NET) runs code from anonymous |
| 163 | + executable pages; hence `AnonExec` is WARN by default and configurable. |
| 164 | + |
| 165 | +Roadmap: eBPF backend · thread-following · return-address/shadow-stack checks · |
| 166 | +ROP-chain length heuristics · per-process behavioural baselining · a policy DSL |
| 167 | +for allow-listing legitimate JIT regions. |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## Building & testing |
| 172 | + |
| 173 | +```bash |
| 174 | +cargo build --release |
| 175 | +cargo test # 28 unit + 4 end-to-end tests |
| 176 | +cargo clippy --all-targets |
| 177 | +./demo.sh # side-by-side benign vs. exploitation run |
| 178 | +``` |
| 179 | + |
| 180 | +The end-to-end tests drive the real ptrace engine over the `benign` and |
| 181 | +`shellcode-sim` binaries; they self-skip where `ptrace` is unavailable. |
| 182 | + |
| 183 | +## License |
| 184 | + |
| 185 | +MIT — see [LICENSE](LICENSE). |
0 commit comments