grloper/Wraith
GitHub: grloper/Wraith
Wraith 是一个基于 syscall 来源验证的 Rust 运行时安全传感器,通过检测行为不变量违规来实时发现零日漏洞利用,无需依赖签名或 payload 特征。
Stars: 1 | Forks: 0
# Wraith
**通过 syscall 来源验证进行无签名运行时漏洞利用检测。**
Wraith 是一个底层的 Rust 安全传感器,它能回答大多数工具无法回答的问题:*这个进程现在正在被漏洞利用吗?* —— 而无需事先了解漏洞或 payload。这使其对**零日漏洞和 N-day 漏洞同样有效**,因为它关注的是*漏洞利用的行为*,而不是*特定 bug 的身份*。

floor: info|warn|high|critical (default warn)
```
## 主动响应(强制执行)
默认情况下,Wraith 只负责*检测* —— 它绝不会触碰被追踪进程。但由于它停留在 syscall 入口暂停点,此时此刻它正站在违规 syscall 尚未执行的那一瞬间,因此它也可以*阻止*攻击:
```
# Neutralise the offending syscall in place — the injected code's execve/connect
# returns an error and never takes effect; the process lives on so you can watch
# what it does next.
wraith run --block -- ./target
# Terminate the whole traced tree the instant exploitation is confirmed, before
# the offending syscall executes.
wraith run --kill -- ./target
```
两者都仅在判定为 **CRITICAL** 时触发 —— 即注入代码发出*敏感* syscall,或者构成了关联的漏洞利用链 —— 因此 HIGH/WARN 级别的异常(如 JIT 页面、孤立的 RWX 映射)绝不会触发强制执行。`--block` 会将入口暂停处的 syscall 号覆盖,从而使内核跳过该调用并返回 `-ENOSYS`;而 `--kill` 则会向每一个被追踪的线程组发送 `SIGKILL`。
### 信任 JIT
语言运行时(Node.js、JVM、.NET、浏览器)会从匿名可执行页中执行 JIT 编译的代码,并在编译过程中将可写页翻转为可执行页 —— 这种行为在 syscall 层面看起来就像是 payload 的部署。当你明确知道运行时将其代码放置在何处时,将该范围交给 Wraith,它就会将该范围内的来源和 W^X 视为合法:
```
# Trust one or more JIT arenas (repeat --trust-region as needed).
wraith run --trust-region 7f2a10000000-7f2a14000000 -- ./node-service
```
信任机制应用于具体的内存地址 —— syscall 的执行位置和 `mprotect` 的目标页面 —— 因此遵守 W^X 原则(映射为 RW,写入,再 `mprotect` 为 RX)的 JIT 将被完全豁免,而在其他地方的*直接* RWX 分配仍然会被标记。
## 同时扫描多个进程
`run` 和 `attach` 用于监视单个进程树。而 `scan` 则可以一次性附加到一整套已经在运行的进程上 —— 通过名称/cmdline 子字符串进行选择,或者获取你有权追踪的所有进程:
```
# Attach to every process whose name or command line contains "nginx"
# (repeat --match to widen the net); follows the children they spawn too.
sudo wraith scan --match nginx --match redis
# Attach to every process we're allowed to trace (heavy — see below).
sudo wraith scan --all --min high
```
单个追踪器会通过一个统一的回收循环来驱动所有这些进程,并且强制执行(`--block`/`--kill`)和 JSON 流的工作方式与处理单个目标时完全相同。需要注意以下几点:
- **需要权限。** 附加到不属于你的进程需要 `CAP_SYS_PTRACE`(以 root 身份运行);你无法附加的进程将被跳过,这不会导致致命错误。
- **存在性能损耗。** 每个被追踪的进程都要承担每个 syscall 产生两次暂停的 `ptrace` 开销,因此在繁忙的主机上使用 `--all` 代价很高 —— 建议优先使用 `--match`。
全系统、近乎零开销的监控是 eBPF 后端的任务(见下文)。
- **默认为非破坏性。** 与 `run` 不同,`scan`/`attach` *不会*设置 `PTRACE_O_EXITKILL`:停止 Wraith 后,所有被扫描的进程仍会继续运行。
- **仅限附加后的线程。** 与 `attach` 一样,在 Wraith 附加之前就已经存在的同辈线程不会被自动捕获(见下文)。
- **绝不追踪自身。** `scan` 会排除自身的进程及其整个祖先链(启动它的 shell/终端),因此宽泛的 `--match` 不会意外附加到启动它的工具上并导致其卡死。
### 实时仪表盘 (`--ui`)
在任何模式下添加 `--ui`,即可显示全屏的终端仪表盘,而不是滚动的日志 —— 本 README 顶部的截图正是扫描流程下的该界面:
```
sudo wraith scan --ui --match nginx
```
每一行代表一个被追踪的进程,包含实时的 syscall/事件计数器和带有颜色编码的判定结果(`clean` -> `suspicious` -> `EXPLOITATION`),上方是最新的检测信息流,底部状态栏显示汇总判定。它会在每次检测到事件时重绘,平时则以约 20 fps 的帧率刷新,在退出或按下 Ctrl-C 时能干净地恢复终端,并且依然兼容 `--json`(事件流会写入 UI 之下的输出端中)。该仪表盘是纯手工编写的 ANSI 代码实现的 —— 不依赖任何 TUI 库 —— 从而使该传感器的供应链保持仅依赖 `nix` + `libc`。
## 架构
特意设计得小巧、易于审计且依赖极简 —— 供他人运行的传感器应该具有你能做到的最精简的供应链。该引擎仅链接 `nix` 和 `libc`;JSON 也是手动生成的。
```
src/
├─ maps.rs parse /proc//maps into typed regions
├─ provenance.rs classify an instruction/stack pointer against the map
├─ syscalls.rs the syscall table Wraith cares about
├─ detect.rs the invariants + the exploitation-chain correlator
├─ event.rs detection events + their JSONL form
├─ engine.rs the transport-agnostic detection core (Backend trait, Engine)
├─ tracer.rs the ptrace Backend (spawn/attach/scan, thread-following, enforcement)
├─ ui.rs the live terminal dashboard (--ui), hand-rolled ANSI
└─ bin/
├─ wraith.rs the CLI sensor
├─ benign.rs false-positive control target
├─ benign_threads.rs multithreaded false-positive control
├─ shellcode_sim.rs exploitation-behaviour simulator
└─ mt_shellcode_sim.rs exploitation from a worker thread
```
追踪器在热路径上除了不可避免的 `getregs` 之外,不会发出任何自己的 syscall,并且仅当内存操作可能导致其发生变化时,才会重新读取 `/proc//maps`。
**传输无关的核心。** 检测逻辑通过 `Backend` trait 与传输层分离。`ptrace` 追踪器是第一个后端:它将每次 syscall 入口捕获为中立的寄存器快照,并将其交给共享的 `Engine`,该 `Engine` 负责管理缓存的内存映射、进程统计信息、检测器以及“在 CRITICAL 时执行强制策略” —— 并且它本身从不发出 `ptrace` 调用。
相同的 `Engine` 可以原封不动地驱动任何传输层,因此未来计划中的 eBPF 后端(见下文)可以复用整个检测模型,仅仅替换获取 syscall 暂停点的方式。
**线程跟踪。** 真实的目标 —— 网络守护进程、请求处理器、模糊测试框架 —— 都是多线程的,而漏洞利用可能会从任何线程触发。Wraith 会跟踪目标产生的每一个 `clone`/`fork`/`vfork`,并检查*所有*这些线程发出的 syscall。共享地址空间的线程会共享同一个缓存的内存映射和同一个漏洞利用链累加器,因此在一个线程上部署、从另一个线程触发的 payload 依然会被关联成单一的判定结果 —— 而不同的进程则保持各自独立的状态。
## 限制与路线图
Wraith 是一个诚实的研究原型,拥有清晰的生产化路径;它并不标榜自己是一个已经完善的 EDR。
- **`ptrace` 开销。** 每个 syscall 产生两次暂停的模式适用于高价值目标(网络守护进程、解析器、模糊测试目标),而不适合整个系统。通往生产环境的路径是在 **eBPF** 上运行相同的逻辑(`tracepoint/raw_syscalls` + 页面来源映射表)以实现近乎零的开销 —— 检测模型在设计上就是传输无关的,现在被置于 `Backend` trait (`src/engine.rs`) 之后,这样 eBPF 后端就可以插入到 `ptrace` 后端旁边,而无需改动检测逻辑。(eBPF 是进行观察而不是暂停,因此它将作为低开销的*观察*后端;`ptrace` 仍然用于无损捕获和需要被追踪进程在 syscall 入口处保持暂停的 `--block`/`--kill` 功能。)
- **附加与已存在的线程。** `wraith run` 和 `wraith attach` 会跟踪目标在开始追踪*之后*产生的每一个线程和子进程(通过 `PTRACE_O_TRACECLONE`/`FORK`/`VFORK`)。当附加到一个已经在运行的多线程进程时,只有那些在附加之后克隆的线程会被自动捕获;捕获所有已存在的同辈线程是一项小型的后续工作。
- **从不离开合法代码的纯 ROP。** 如果攻击者只重用现有的 `.text` 且从不部署新的可执行内存,就不会触发来源规则 —— 这正是存在栈切换启发式算法的原因,也是为什么返回地址验证和 shadow stack 被列在了路线图上。
- **合法的 JIT**(浏览器、JVM、.NET)会从匿名可执行页运行代码;因此默认情况下 `AnonExec` 被设为 WARN,`AnonExec` 可以通过 `--jit-critical` 调高严重级别,而已知的 JIT 内存区可以通过 `--trust-region` 直接豁免。
- **覆盖率与成本。** `scan --match`/`--all` 可以同时监视多个进程,但每个被追踪的进程都要承担每个 syscall 产生两次暂停的 `ptrace` 开销,因此这适用于少数几个高价值目标,而不是繁忙的整个系统。近乎开销、监视一切的监控任务是 eBPF 后端的工作(见下文),而不是 `ptrace` 引擎应该尝试的。
路线图:eBPF 后端(近乎零开销,全系统) · 返回地址/shadow stack 检查 · ROP 链长度启发式算法 · 每线程栈跟踪 · 附加时捕获已存在的线程 · 每进程行为基线分析 · 更丰富的 JIT 策略(自动学习运行时的内存区,而不是手动提供范围)。
## 构建与测试
```
cargo build --release
cargo test # 42 unit + 10 end-to-end tests
cargo clippy --all-targets
./demo.sh # side-by-side benign vs. exploitation run
```
端到端测试会在 `benign`、`benign-threads`、`shellcode-sim` 和 `mt-shellcode-sim` 二进制文件上驱动真实的 ptrace 引擎 —— 其中包括从工作线程发起漏洞利用,以测试线程跟踪功能。如果在无法使用 `ptrace` 的环境下,它们会自动跳过。
## 许可证
MIT —— 详见 [LICENSE](LICENSE)。
wraith scan --ui --match netd —— 每个进程占一行,带有实时的 syscall/事件计数器,并在注入代码发出 syscall 的瞬间给出相关的漏洞利用判定。
标签:Rust, 可视化界面, 时序数据库, 网络流量审计, 运行时防护, 通知系统, 零日漏洞防御