一个 Rust + eBPF 文件监控组件,如何在加密发生前 200ms 拦下它
凌晨 3:14,一台业务服务器的文件写入速率,从平时的 200 次/秒,瞬间飙到 8000 次/秒。 再往下看,目标文件名清一色在被改成
.locked、.crypt。 勒索病毒。真来了。 但那天它没得逞——在第 131 个文件时,进程被强制杀掉了。
做安全的同事都知道,勒索病毒永远在凌晨来。人少、反应慢、备份窗口还没跑完。

那天凌晨 3:14,监控大屏上一条曲线突然垂直起飞。某台核心业务服务器的文件写入速率,从平时的 200 次/秒,瞬间飙到 8000 次/秒。
再往下看,更吓人:这些写入操作,目标文件名清一色在被改成 .locked、.crypt 这类后缀。
勒索病毒。没想到真的遇到了。
但这次,没让它得逞。在它加密到第 131 个文件时,进程被强制杀掉了。整个服务器,只丢了 131 个临时文件,全部可从备份恢复。
拦住它的,不是杀毒软件,不是 HIDS,是一段我们跑在内核里的 Rust 代码。
先说为什么绝大多数方案在勒索病毒面前形同虚设。
杀毒软件:滞后。 依赖特征库。新变种换个加密算法,特征库没更新,等于裸奔。等厂商出了新规则,你的文件已经没了。杀毒软件在更新,勒索病毒也在更改。道高一尺,魔高一丈。
用户态文件监控(inotify / fanotify):可绕过。 这些机制在用户空间工作。恶意程序只要提权或走非常规路径,就能绕开。更别说 inotify 在高并发下会丢事件——你以为是安静的,其实是它没看到。
核心问题:监控点在用户空间,攻击也在用户空间。 你在同一个层面,对方想绕你就绕你。
要赢,得升维——进内核。
eBPF 是 Linux 内核的一项技术,允许你在内核里安全地运行一段沙箱程序,挂载到各种"钩子"上:系统调用、内核函数、网络栈、甚至安全模块。
关键是:它看到的,是操作系统最底层的真实行为。 任何进程,无论怎么提权、怎么伪装,只要它读文件、写文件、删文件,就得走系统调用——而 eBPF 就挂在系统调用上。
绕不开。
最初我们为了过等保三,选择 ebfp 做了一个内部的服务端防篡改监控。其于这个实践,我们决定继续采用 ebpf。
于是我们挂了这几个钩子:
sys_enter_openat —— 任何打开/创建文件的操作sys_enter_write —— 任何写操作(同时拿到写入字节数)sys_enter_rename —— 任何重命名(勒索病毒最爱改后缀名)sys_enter_unlink —— 任何删除(很多勒索会先删原文件再写加密版)每个文件操作,无论来自哪个进程,都会触发我们的 eBPF 程序,把一条事件塞进内核缓冲区,传给用户空间的 Rust agent。
这个组件有两部分:内核里的 eBPF 程序,和用户空间的监控 agent。两者我们都用 Rust 写。
内核部分用 Aya 框架。 Aya 是一个纯 Rust 的 eBPF 工具链,不需要依赖 libbpf 或 clang,直接把 Rust 编译成 eBPF 字节码。代码长这样(简化版):
use aya_ebpf::programs::TracePoint;
use aya_ebpf::macros::tracepoint;
use aya_ebpf::maps::PerfEventArray;
use aya_ebpf::cty::c_ulong;
#[map]
static mut EVENTS: PerfEventArray<FileEvent> =
PerfEventArray::with_max_entries(1024, 0);
#[tracepoint(name = "sys_enter_write")]
pub fn sys_enter_write(ctx: TracePointContext) -> u32 {
let pid = unsafe { bpf_get_current_pid_tgid() >> 32 };
let comm = unsafe { bpf_get_current_comm(&mut [0u8; 16]) };
// 取出本次写入的字节数、文件名,组装成事件
let event = FileEvent { pid, op: OP_WRITE, bytes, comm };
unsafe { EVENTS.output(&ctx, &event, 0); }
0
}用户空间部分也用 Rust。 原因有三点:
eBPF 负责"看见",真正的判断在用户空间的 Rust agent 里。
核心思路是行为异常检测,不是特征匹配。我们不看"这是不是已知病毒",而是看"这个进程的行为正不正常"。
对每个进程,我们维护一组滑动窗口计数:
进程 P 在最近 5 秒内:
- 文件写入次数:8732 次 ← 正常进程通常不超过几百
- 重命名次数:8011 次 ← 且目标后缀是 .locked/.crypt
- 删除原文件次数:6500 次 ← 加密前先删源文件是典型特征触发规则(举例):
那天的病毒,在第 3 秒就触发了红色规则:写入 8732 次 + 改名 8011 次 + 后缀 .locked。
发现不等于拦住。真正的价值在处置。
我们的 agent 触发红色规则后做了三件事,按优先级:
1. 杀进程。 立刻 kill -9 那个 PID。内核里的 eBPF 同时下发一条 LSM 策略,直接拒绝该进程后续的 open/write 系统调用——双保险。
2. 快照隔离。 把受影响目录标记为只读,防止二次写入。
3. 告警 + 取证。 把进程链(parent PID、命令行、加载的库)打包发到安全平台,供事后溯源。
从 eBPF 捕获第一条异常,到进程被杀,整个过程 < 200 毫秒。病毒只来得及动 131 个文件。
传统 HIDS(主机入侵检测)大多也是用户态轮询 + 规则引擎,但有两个硬伤:
一是采样。 为了性能,它不可能监控每一个 syscall,只能抽样。勒索病毒最开始那几百次写入,很可能被抽样漏掉。
二是路径。 它在 VFS 层之上,恶意程序用 unlink + 直接写 raw device、或者走内存映射 mmap 写文件,它能看见的就不全。
eBPF 在内核 syscall 入口,全量、不可绕过。我们这套组件在压测下,每秒处理 12 万次文件事件,CPU 占用 < 3%。近乎零感。
那 131 个文件,第二天全部从备份恢复,业务零中断。
事后复盘,攻击链上其实有很多点可以拦——弱密码、过期账号、没打的补丁。但最后真正挡住它的,是内核里那段 Rust 代码。
安全这件事,没有银弹。但有一件事是确定的:监控点必须比攻击点更底层。 攻击者只要还在用系统调用,你就得把眼睛装进内核里。
目前我们利用 Rust + eBPF,把这件事做得既快又稳的最佳组合。相比 HIDS,我们没有成熟的商业化产品,但这个小股入帮我解决了信息安全的一个大问题。
文中架构基于真实 eBPF 技术方案设计;Aya 为实际可用的纯 Rust eBPF 框架。