grloper/Wraith

GitHub: grloper/Wraith

Wraith 是一个基于 syscall 来源验证的 Rust 运行时安全传感器,通过检测行为不变量违规来实时发现零日漏洞利用,无需依赖签名或 payload 特征。

Stars: 1 | Forks: 0

# Wraith **通过 syscall 来源验证进行无签名运行时漏洞利用检测。** Wraith 是一个底层的 Rust 安全传感器,它能回答大多数工具无法回答的问题:*这个进程现在正在被漏洞利用吗?* —— 而无需事先了解漏洞或 payload。这使其对**零日漏洞和 N-day 漏洞同样有效**,因为它关注的是*漏洞利用的行为*,而不是*特定 bug 的身份*。 ![Wraith 的实时扫描仪表盘:三个正常的工作进程和一个在漏洞利用过程中被抓取的进程](https://static.pigsec.cn/wp-content/uploads/repos/cas/c6/c6c52a7008dabbde5b85c898ea1a81402003b744f7cb110aa3b42582bdf5da55.svg)

wraith scan --ui --match netd —— 每个进程占一行,带有实时的 syscall/事件计数器,并在注入代码发出 syscall 的瞬间给出相关的漏洞利用判定。

## 核心理念 防御性工具通常会回答以下两个问题之一: | 问题 | 需要知道 | 盲区 | |---|---|---| | *此代码是否包含已知 bug?* (SAST/扫描器) | 漏洞 | 零日漏洞 | | *这些字节是否匹配已知的恶意特征?* (AV/YARA) | payload | 未知 payload | Wraith 回答了**第三**个问题 —— *这个进程是否正处于只有漏洞利用才会导致的执行状态中?* —— 并且它既不需要知道 bug,也不需要知道 payload。 其背后的观察结果是:**每一个**内存破坏型漏洞利用,无论根本原因是什么(栈溢出、UAF、类型混淆、未知的零日漏洞),最终都会收敛到同一个可见的行为上。为了实现任何目的 —— 生成 shell、打开 socket、读取机密 —— 攻击者都必须发出 **syscall**。而在这些 syscall 发生的瞬间,进程处于合法执行*永远不会*产生的状态。 Wraith 通过 `ptrace` 附加到进程上,在每个 syscall 的入口处停止,并检查少量对所有良性程序都成立的不变量: 1. **来源** —— `syscall` 指令只会从*文件映射的可执行页*(程序自身的 `.text` 段、共享库或内核 vDSO)中运行。注入到堆、栈或匿名页中的 Shellcode **会破坏这一点**。 2. **W^X** —— 没有良性程序需要既可写*又*可执行的页面,也不需要将可写页翻转为可执行。Payload 的部署**会破坏这一点**。 3. **栈完整性** —— 在 syscall 发生时,栈指针位于真实的栈中,而绝不在堆或文件镜像中。ROP 的栈切换**会破坏这一点**。 由于这些是*合法行为*的不变量,而不是*特定攻击*的签名,因此无论攻击者是如何达到这种状态的,任何违规都是漏洞利用的证据。 ## 快速开始 ``` cargo build --release # Monitor a program you launch: ./target/release/wraith run -- /usr/bin/some-service --flags # Monitor a process that's already running: sudo ./target/release/wraith attach 4242 # Monitor every process matching a name at once: sudo ./target/release/wraith scan --match nginx # Emit machine-readable events for your SIEM/pipeline: ./target/release/wraith run --json events.jsonl -- ./target ``` Wraith 在状态正常时退出码为 `0`,在检测到可疑 (HIGH) 活动时退出码为 `1`,在检测到漏洞利用 (CRITICAL) 时退出码为 `3` —— 因此它可以直接作为行为预言机无缝接入 CI 和模糊测试框架。 ### 查看运行效果 本仓库自带了两个独立的测试目标(不需要真实的漏洞利用): ``` $ wraith run --min info -- ./target/release/benign wraith: monitoring `./target/release/benign` (provenance mode) benign: ... normal work ... wraith: 81 syscalls, 0 event(s); verdict: clean # <- false-positive control $ wraith run -- ./target/release/shellcode-sim HIGH pid=6586 wx_violation mmap @ 0x7fa8ad52534a `mmap` requests writable+executable memory — classic shellcode staging CRITICAL pid=6586 foreign_origin_syscall socket @ 0x7fa8ad696011 [anon] sensitive syscall `socket` issued from wx-violation memory — injected code is now acting CRITICAL pid=6586 exploitation_chain socket @ 0x7fa8ad696011 [correlated] EXPLOITATION CHAIN: executable payload staged (W^X) -> sensitive syscall from injected code wraith: 69 syscalls, 3 event(s); verdict: EXPLOITATION DETECTED ``` `shellcode-sim` 会部署一个 RWX 页面,将 payload 写入其中,并从该页面发出 syscall —— 这正是真实漏洞利用的最后一步 —— 而 Wraith 会捕获每一个阶段,并将它们关联成一个判定结果。 ## 检测内容 | 事件 | 严重程度 | 含义 | |---|---|---| | `foreign_origin_syscall` | HIGH / CRITICAL | 从非代码内存(堆/栈/匿名页/RWX页)发出的 syscall。当该 syscall 为*敏感*操作时为 CRITICAL —— 包括程序执行(`execve`、`memfd_create`)、网络通信/数据外泄(`connect`、`sendto`)、进程篡改(`ptrace`、`process_vm_writev`)、防御规避(`seccomp`、`bpf`、`prctl`)或容器逃逸(`setns`、`unshare`)等。 | | `wx_violation` | HIGH | `mmap`/`mprotect` 请求可写**且**可执行的内存。 | | `wx_transition` | HIGH | 可写页被翻转为可执行页 —— payload 部署。 | | `stack_pivot` | HIGH | 发生 syscall 时栈指针位于堆或文件镜像中 —— ROP 攻击的标志。 | | `crash` | HIGH | 目标进程触发 SIGSEGV/SIGILL/SIGBUS/SIGABRT —— 通常是值得调查的*失败*漏洞利用。 | | `exploitation_chain` | CRITICAL | 多个攻击基元被关联为单一的、高可信度的判定。 | | `sensitive_call` | INFO | 审计记录(使用 `--audit-sensitive`):来自合法代码的敏感 syscall。 | | `blocked` | CRITICAL | 阻断模式(`--block`)在原处中和了违规的 syscall。 | | `killed` | CRITICAL | 阻断模式(`--kill`)在确认发生漏洞利用时,终止了被追踪的进程树。 | 参数调优: ``` --jit-critical treat anonymous-exec pages as HIGH (targets that never JIT) --trust-region A-B treat the hex range [A,B) as legitimate JIT (repeatable) --ui live full-screen dashboard instead of the log stream --no-stack-pivot disable the ROP stack-pivot heuristic --audit-sensitive log sensitive syscalls from legitimate code too --max-targets N (scan) attach to at most N matching processes --min 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)。
标签:Rust, 可视化界面, 时序数据库, 网络流量审计, 运行时防护, 通知系统, 零日漏洞防御