这次我们来看一个很有意思的 Linux 安全项目:用 eBPF 与 IMA LSM 组合,在内核最高特权上下文里做一个“蹩脚”的 ring-0 玩具反病毒原型。这个项目不是一个能直接部署的生产级杀毒软件,它更像一个安全教学实验:把 Linux 安全模块钩子、eBPF 虚拟机、内核完整性度量架构串在一起,验证“文件在 open / exec 时能不能被内核直接拦下来”。
项目的核心价值在于它把两个容易被忽视的内核能力组合起来了:eBPF LSM 允许用户态加载一个运行在内核的安全策略程序,在文件打开或程序执行时返回拒绝码;IMA 则负责对文件内容做哈希度量,给用户态守护进程提供完整的数据基础。两者结合后,一个原本需要在用户态高频扫描磁盘的杀毒动作,就可以变成“内核在关键系统调用路径上预判 + 用户态策略引擎兜底”。
从门槛看,这个项目需要 Linux 5.7+ 内核、开启 BPF LSM、有 root 权限,编译环境要准备 clang / LLVM、libbpf、bpftool。它不消耗显存,主要关注 CPU、固定内存和 BPF map 开销。下面这篇文章会把原理、环境准备、最小 eBPF LSM 拦截程序、IMA 策略配置、功能验证和常见坑完整地过一遍。
如果你关心 eBPF 安全编程、LSM hook 拦截文件访问、IMA 完整性度量,或者想给自己做一个基于哈希 / 路径黑名单的本地文件执行保护层,这篇文章可以直接收藏。
1. 核心能力速览
这个项目不是常规的图形化杀毒软件,而是一个偏内核编程和安全机制的实验原型。下面这张表先帮读者把关键规格过一遍。
| 能力项 | 说明 |
|---|---|
| 项目定位 | 教学型 / 实验型的 ring-0 玩具反病毒原型 |
| 技术栈 | eBPF + LSM + IMA + 用户态策略守护进程 |
| 内核要求 | Linux 5.7+,开启 CONFIG_BPF_LSM,建议开启 CONFIG_DEBUG_INFO_BTF |
| 主要功能 | 文件打开拦截、可执行文件路径黑名单、IMA 文件哈希度量、策略批量下发 |
| 运行方式 | 命令行编译加载,bpftool / libbpf 管理 BPF 程序与 map |
| 是否支持 API | 无内置 HTTP 服务;BPF map 可以作为策略写入接口,可扩展成 Unix Socket / REST |
| 是否支持批量任务 | 支持向 BPF map 批量写入路径 / 哈希策略,后续可对接威胁情报 |
| 硬件要求 | 普通 x86_64 服务器即可,无 GPU 要求 |
| 资源占用 | 核心开销在 BPF 程序执行和 map 查询,固定内存很小,具体以本机测试为准 |
| 适合场景 | eBPF 安全编程学习、LSM hook 实验、主机文件访问控制原型 |
从这张表能看到,项目最大的吸引力不在“杀毒效果”,而在“用内核原语重新实现一次文件访问控制”。理解这套流程之后,再去学 LSM、eBPF、IMA 都会顺很多。
2. 它是怎么“杀毒”的:eBPF + IMA + LSM 的流程
2.1 eBPF 在反病毒场景中能做什么
传统杀毒软件通常依赖用户态进程扫磁盘、扫内存、hook 系统调用。这种方式实现简单,但存在两个问题:一是用户态进程可能被恶意程序提前结束;二是每次扫描都有路径竞争,文件从“扫描完成”到“真正执行”之间可能被替换。eBPF 则不同,它可以把安全策略直接放进内核执行路径里,文件在 open / mmap / exec 的关键节点就会被检查,不存在用户态进程被杀后策略失效的问题。
eBPF 在安全场景里有三种常见用法:
- 用 tracepoint / kprobe 追踪系统调用和内核函数,做监控告警。
- 用 LSM hook 做强制访问控制,直接阻止某个操作。
- 用 XDP 在网络包入口做流量过滤,拦截恶意连接。
这个项目选择的是第二种,也就是把 BPF 程序挂到 LSM hook 上,让内核在文件打开时调用我们的“安全策略”。这是 Linux 5.7+ 引入的BPF_PROG_TYPE_LSM能力,也是整个项目最核心的技术点。
2.2 IMA 负责什么
IMA 的全称是 Integrity Measurement Architecture,它是 Linux 内核自带的完整性度量架构。它会在文件被读取、执行、打开等事件发生时,对文件内容计算哈希,并把结果记录到内核的度量列表里。
IMA 通常有几种工作模式:
- measure:只测量文件哈希,记录到
/sys/kernel/security/ima/ascii_runtime_measurements。 - appraise:对文件进行校验,需要文件带有签名或扩展属性,校验失败直接拒绝访问。
- audit:把度量结果写入审计日志,用于事后分析。
在普通的 eBPF LSM 拦截程序里,我们很难快速计算文件的 SHA256,因为 BPF 程序复杂度有限,能用的 helper 也不多。IMA 的价值就在这里:它把“文件内容哈希计算”这件重活从 BPF 程序里拆出来,交给内核既有的度量机制完成。用户态守护进程只需要读取 IMA 度量列表,就能拿到文件哈希,然后决定是否把这个文件加入黑名单。
2.3 整体检测流程
把 eBPF LSM 和 IMA 组合起来,一个简化版的检测流程是这样:
- 用户态守护进程启动,连接 BPF map。
- 用户态通过 IMA 度量列表或外部威胁情报,获得一批恶意文件哈希。
- 用户态根据哈希反查文件路径,把路径写入 BPF map 的 deny 名单。
- BPF LSM 程序挂在
file_open或bprm_checkhook 上,每次文件被打开或程序被执行时,读取文件路径。 - BPF 程序在 deny map 中查找路径,匹配成功则返回
-EPERM,内核直接拒绝这次操作。 - IMA 继续度量文件内容,如果同一个文件换了路径再次出现,用户态又能通过新的 IMA 记录把它加到 deny map 中。
这个流程当然很粗糙,路径可以改,硬链接也能绕过,恶意程序甚至可以直接替换为同路径文件。但它完整演示了“内核拦截 + 度量反馈 + 策略下发”的闭环,这也是这个“蹩脚”项目最有教学价值的部分。
3. 适用场景与使用边界
这个项目适合以下几类读者:
- 正在学习 eBPF,想找一个不只是“统计包数量”的真实应用场景。
- 对 Linux LSM 安全模块感兴趣,想通过 BPF 动态加载安全策略。
- 需要做主机安全原型验证,比如某个目录只允许白名单文件执行。
- 想把 IMA 度量数据接入自己的策略引擎。
它不适合替代生产环境的杀毒软件。从防御能力看,路径黑名单非常容易绕过;从性能看,给所有文件 open 都加 BPF LSM 检查,在高并发场景会带来额外延迟;从稳定性看,LSM hook 如果写错返回值,可能影响整个系统的文件访问。因此这个项目更适合在内网测试机、容器实验环境或安全研究平台里运行。
另外,必须强调合规边界。
- 这类 eBPF 安全能力只能用于防御和教学。
- 不要尝试用它绕过任何安全产品、隐藏进程或干扰 EDR 检测。
- 如果要在多人共用的环境里做实验,必须获得授权。
- 涉及用户文件、日志、进程数据时,需要遵守隐私和数据合规要求。
- 如果需要处理真实恶意样本,建议在隔离的虚拟机或专用沙箱里进行。
4. 环境准备:内核、工具链、权限
4.1 内核要求
eBPF LSM 对内核版本和配置有硬性要求。推荐使用 Debian 12、Ubuntu 22.04+ 这类较新的发行版,内核版本一般在 5.15+ 或 6.x,基本满足要求。开始之前先确认三件事。
uname -r然后检查内核编译选项:
zgrep BPF_LSM /proc/config.gz 2>/dev/null || grep BPF_LSM /boot/config-$(uname -r)正常情况下要能看到:
CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_LSM=y CONFIG_IMA=y CONFIG_DEBUG_INFO_BTF=y接着检查当前 LSM 列表里是否包含 bpf:
cat /sys/kernel/security/lsm如果输出里没有bpf,可以通过内核启动参数追加lsm=bpf,或者在编译内核时调整配置。这里要注意,LSM 列表和启动参数因发行版而异,修改后需要重启才能生效。
4.2 安装工具链
编译 BPF 程序需要 clang / LLVM,运行和管理 BPF 程序需要 libbpf 和 bpftool。
# Ubuntu / Debian 示例 sudo apt update sudo apt install -y clang llvm libbpf-dev bpftool linux-tools-common linux-tools-generic如果发行版自带的 bpftool 版本太旧,也可以从内核源码编译。先确认当前使用的 clang 版本:
clang --version建议 clang 版本在 14 以上,太低的话对 BPF CO-RE 的支持不完整。
4.3 准备 vmlinux.h
CO-RE 是 eBPF 现代开发方式的基础,它依赖内核的 BTF 信息。我们可以用 bpftool 从内核导出 vmlinux.h:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h这个文件会把内核里面的结构体定义导出来,之后在 BPF C 代码里直接#include "vmlinux.h"即可,不需要手动匹配内核头文件版本。
5. 写一个最小 eBPF LSM 拦截程序
5.1 BPF 侧代码:文件打开拦截
下面是一个最小示例,挂在file_openLSM hook 上。它读取文件路径,在 deny map 里查一下,如果命中就返回-EPERM,拒绝打开文件。
这段代码属于教学示例,实际使用时需要根据目标内核的 struct 字段和 hook 签名做适配。
// av_policy.bpf.c #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_core_read.h> char LICENSE[] SEC("license") = "GPL"; #define MAX_PATH 256 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1024); __type(key, char[MAX_PATH]); __type(value, __u32); } deny_path_map SEC(".maps"); SEC("lsm.s/file_open") int BPF_PROG(av_file_open, struct file *file) { char path[MAX_PATH] = {}; __u32 *value; long ret; ret = bpf_d_path(&file->f_path, path, sizeof(path)); if (ret < 0) { return 0; } bpf_printk("av_policy: file_open %s\n", path); value = bpf_map_lookup_elem(&deny_path_map, path); if (value && *value == 1) { return -EPERM; } return 0; }代码里有几个关键点:
lsm.s中的.s表示这是一个 sleepable BPF 程序,因为bpf_d_path这个 helper 在部分 LSM hook 中需要睡眠上下文。bpf_d_path用于把内核的struct path转成可读的字符串路径。bpf_map_lookup_elem在 deny map 中查找路径,map 的值 1 表示禁止。- 返回
-EPERM会直接让内核拒绝本次文件打开操作。
注意:不同内核版本的file_openhook 参数写法可能不同。如果在编译或加载阶段报参数错误,优先检查vmlinux.h中对应的 LSM hook 签名。
5.2 编译与加载
先编译出 BPF 字节码对象文件:
clang -O2 -g -Wall -target bpf -D__TARGET_ARCH_x86 -c av_policy.bpf.c -o av_policy.bpf.o编译完成后,用 bpftool 加载并自动 attach 到 LSM hook:
# 确保 bpf 文件系统已挂载 sudo mount -t bpf bpf /sys/fs/bpf/ # 加载并自动 attach sudo bpftool prog load av_policy.bpf.o /sys/fs/bpf/av_policy autoattach加载后查看 BPF 程序列表,确认av_policy存在:
sudo bpftool prog show再查看对应 hook 是否 attach 成功:
sudo bpftool prog show pinned /sys/fs/bpf/av_policy如果当前内核不支持autoattach,也可以先加载再手动 attach:
sudo bpftool prog load av_policy.bpf.o /sys/fs/bpf/av_policy sudo bpftool prog attach pinned /sys/fs/bpf/av_policy lsm file_open5.3 向 deny map 写入路径
BPF map 加载后并不一定自动出现在/sys/fs/bpf,需要先找到 map id,然后 pin 出来。
sudo bpftool map show找到deny_path_map对应的 id 后,pin 到固定路径:
sudo bpftool map pin id <map_id> /sys/fs/bpf/deny_path_map之后就可以通过 bpftool 更新 map。这里用一个简单的 Python 脚本把字符串路径转换成 256 字节的 key,因为 BPF hash map 的 key 大小是固定的char[MAX_PATH]。
#!/usr/bin/env python3 import subprocess import sys map_path = "/sys/fs/bpf/deny_path_map" path = sys.argv[1].encode() # 补零到 256 字节,与 BPF map 定义保持一致 key = path + b'\x00' * (256 - len(path)) key_bytes = ' '.join(str(b) for b in key) cmd = [ "bpftool", "map", "update", "pinned", map_path, "key", *key_bytes.split(), "value", "1", "0", "0", "0" ] subprocess.run(cmd, check=True) print(f"deny path added: {sys.argv[1]}")使用示例:
python3 add_deny_path.py /tmp/av_test/malware此时再尝试打开/tmp/av_test/malware,系统会返回Operation not permitted。
5.4 验证拦截效果
新建一个测试文件,然后直接执行它:
mkdir -p /tmp/av_test echo '#!/bin/bash' > /tmp/av_test/malware echo 'echo "malware executed"' >> /tmp/av_test/malware chmod +x /tmp/av_test/malware /tmp/av_test/malware如果还没加入 deny map,这个脚本会正常输出。加入 deny map 后再执行:
/tmp/av_test/malware预期输出是权限错误:
bash: /tmp/av_test/malware: Operation not permitted同时可以在 trace 里看到 BPF 打印的日志:
sudo cat /sys/kernel/debug/tracing/trace_pipe如果日志里有av_policy: file_open /tmp/av_test/malware,说明 BPF 程序确实被调用了。
6. 接入 IMA:让内核帮忙计算文件哈希
单纯靠路径黑名单很容易被绕过,比如把文件复制到另一个路径。IMA 可以补充“内容哈希”这个维度:文件内容变了,哈希就会变,即使路径相同,用户态也可以识别出来。这里用 IMA 的 measure 模式来做演示。
6.1 启用 IMA 并配置策略
首先挂载 securityfs:
sudo mount -t securityfs securityfs /sys/kernel/security查看 IMA 是否可用:
ls /sys/kernel/security/ima/部分发行版默认没有启用 IMA,启动时必须加内核参数:
ima_policy=1修改 GRUB 或直接在启动命令行里追加该参数,重启后 IMA 策略目录才会支持写入。
写入两条最基本的策略,分别度量程序执行和只读打开:
echo "measure func=BPRM_CHECK" > /sys/kernel/security/ima/policy echo "measure func=FILE_OPEN mask=MAY_READ" >> /sys/kernel/security/ima/policy注意,IMA 策略在很多版本中只能追加,不能删除,也不能覆盖已写入的规则。写策略时如果提示Device or resource busy,说明之前已经有策略被加载。
6.2 查看 IMA 度量结果
执行上面新建的/tmp/av_test/malware脚本后,查看 IMA 度量列表:
sudo tail -n 20 /sys/kernel/security/ima/ascii_runtime_measurements输出类似:
<PCR> <template-hash> <sha1-hash> <filename>其中文件名和 SHA1 哈希都能看到。这意味着用户态守护进程可以定期读取这个文件,把非法样本的哈希记录下来。
6.3 IMA + eBPF 联动思路
这里给出一个常见联动方案:
- 用户态守护进程监控
/sys/kernel/security/ima/ascii_runtime_measurements。 - 每出现一条新记录,就计算一次文件哈希和已知恶意库的匹配度。
- 如果命中恶意库,守护进程从该记录中提取文件名,调用前面写的
add_deny_path.py,把路径写入 deny map。 - 后续再有人访问这个文件,eBPF LSM 会直接拒绝。
更强的做法是使用 IMA appraise 模式,给可信文件打上签名,未签名或哈希不匹配的文件直接不被内核访问。但这需要给文件扩展属性写入签名,配置复杂度更高,适合作为后续扩展方向。
7. 功能测试与效果验证
7.1 测试 1:路径黑名单拦截
目的:验证 eBPF LSM 是否能在文件 open 阶段拦截