Skip to content

Commit 96e7f2f

Browse files
committed
Add Wraith: signature-free runtime exploitation detector
Wraith detects exploitation in progress by verifying the provenance of every system call a monitored process makes, rather than matching known vulnerabilities or payloads. When a memory-corruption exploit finally issues a syscall, the process is in a state benign code never produces — a syscall from injected memory, a W^X page, or a pivoted stack — so the technique catches zero-days and n-days alike by behaviour. Engine (links only nix + libc): - maps.rs parse /proc/<pid>/maps into typed regions - provenance.rs classify instruction/stack pointer against the map - syscalls.rs the security-relevant syscall table - detect.rs the invariants + an exploitation-chain correlator - event.rs detection events with hand-rolled JSONL output - tracer.rs the ptrace engine (spawn/attach, syscall loop) - bin/wraith.rs the CLI sensor (run/attach, severity gating, exit codes) Ships a benign false-positive control and a self-contained exploitation-behaviour simulator; 28 unit + 4 end-to-end tests, clippy clean under -D warnings, plus CI and a demo script. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KENq7TfeeKSdusxR7iAWEa
1 parent 9b66306 commit 96e7f2f

18 files changed

Lines changed: 2245 additions & 1 deletion

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
jobs:
9+
build-test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Install Rust
14+
run: rustup toolchain install stable --profile minimal --component clippy
15+
- name: Build
16+
run: cargo build --all-targets --verbose
17+
- name: Clippy
18+
run: cargo clippy --all-targets -- -D warnings
19+
- name: Test
20+
# The end-to-end tests need ptrace; they self-skip where it is
21+
# unavailable, so the suite stays green on constrained runners.
22+
run: cargo test --verbose

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
**/*.rs.bk
3+
*.jsonl

Cargo.lock

Lines changed: 47 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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[package]
2+
name = "wraith"
3+
version = "0.1.0"
4+
edition = "2021"
5+
rust-version = "1.74"
6+
description = "Signature-free runtime exploitation detection via syscall provenance verification"
7+
license = "MIT"
8+
repository = "https://github.com/grloper/cyber-rust"
9+
readme = "README.md"
10+
keywords = ["security", "edr", "exploit", "ptrace", "detection"]
11+
categories = ["command-line-utilities"]
12+
13+
[[bin]]
14+
name = "wraith"
15+
path = "src/bin/wraith.rs"
16+
17+
# Self-contained targets used by the test-suite and for live demos.
18+
# `benign` never triggers a detection; `shellcode-sim` stages and executes
19+
# code from an RWX anonymous mapping the way a real exploit payload does.
20+
[[bin]]
21+
name = "benign"
22+
path = "src/bin/benign.rs"
23+
24+
[[bin]]
25+
name = "shellcode-sim"
26+
path = "src/bin/shellcode_sim.rs"
27+
28+
[dependencies]
29+
nix = { version = "0.29", features = ["ptrace", "process", "signal"] }
30+
libc = "0.2"
31+
32+
[profile.release]
33+
lto = true
34+
codegen-units = 1
35+
panic = "abort"
36+
strip = true

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 grloper, pandaadir05
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,185 @@
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).

demo.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
# Side-by-side demonstration: Wraith stays silent on a benign program and
3+
# catches the payload simulator executing a syscall from injected memory.
4+
set -euo pipefail
5+
6+
cd "$(dirname "$0")"
7+
8+
echo "==> building (release)"
9+
cargo build --release --quiet
10+
11+
WRAITH=./target/release/wraith
12+
BENIGN=./target/release/benign
13+
SIM=./target/release/shellcode-sim
14+
15+
echo
16+
echo "============================================================"
17+
echo " 1/2 BENIGN target — expect: clean, exit 0"
18+
echo "============================================================"
19+
set +e
20+
"$WRAITH" run --min info -- "$BENIGN"
21+
echo " -> wraith exit code: $?"
22+
set -e
23+
24+
echo
25+
echo "============================================================"
26+
echo " 2/2 SHELLCODE-SIM target — expect: EXPLOITATION DETECTED, exit 3"
27+
echo "============================================================"
28+
set +e
29+
"$WRAITH" run -- "$SIM"
30+
code=$?
31+
echo " -> wraith exit code: $code"
32+
set -e
33+
34+
echo
35+
if [ "$code" -eq 3 ]; then
36+
echo "Demo OK: benign was clean; injected-code execution was detected and correlated."
37+
else
38+
echo "Demo WARNING: expected exit 3 from the simulator run (got $code)."
39+
fi

src/bin/benign.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! A deliberately ordinary program. It performs a spread of everyday syscalls —
2+
//! including sensitive ones like `socket` — but always from legitimate,
3+
//! file-backed code. Wraith must report it as clean. Used by the test-suite as
4+
//! the false-positive control and handy for a live "nothing fires" demo.
5+
6+
use std::io::Read;
7+
8+
fn main() {
9+
println!("benign: starting normal work");
10+
11+
// Filesystem: open + read a file (open/read/close from libc — legit code).
12+
if let Ok(mut f) = std::fs::File::open("/proc/self/status") {
13+
let mut buf = String::new();
14+
let _ = f.read_to_string(&mut buf);
15+
println!("benign: read {} bytes from /proc/self/status", buf.len());
16+
}
17+
18+
// Network: create and immediately close a socket. `socket` is a sensitive
19+
// syscall, but issued from legitimate code, so it must NOT be flagged
20+
// unless the operator explicitly asks for --audit-sensitive.
21+
unsafe {
22+
let fd = libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0);
23+
if fd >= 0 {
24+
libc::close(fd);
25+
println!("benign: opened and closed a socket from legitimate code");
26+
}
27+
}
28+
29+
// A little compute so the process lives long enough to be worth tracing.
30+
let mut acc: u64 = 0;
31+
for i in 0..100_000u64 {
32+
acc = acc.wrapping_add(i.wrapping_mul(2654435761));
33+
}
34+
println!("benign: done ({acc:#x})");
35+
}

0 commit comments

Comments
 (0)