eBPF+IMA LSM实战:构建内核级文件访问控制反病毒原型
2026/8/30 9:04:45 网站建设 项目流程

这次我们来看一个很有意思的 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 组合起来,一个简化版的检测流程是这样:

  1. 用户态守护进程启动,连接 BPF map。
  2. 用户态通过 IMA 度量列表或外部威胁情报,获得一批恶意文件哈希。
  3. 用户态根据哈希反查文件路径,把路径写入 BPF map 的 deny 名单。
  4. BPF LSM 程序挂在file_openbprm_checkhook 上,每次文件被打开或程序被执行时,读取文件路径。
  5. BPF 程序在 deny map 中查找路径,匹配成功则返回-EPERM,内核直接拒绝这次操作。
  6. 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_open

5.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 联动思路

这里给出一个常见联动方案:

  1. 用户态守护进程监控/sys/kernel/security/ima/ascii_runtime_measurements
  2. 每出现一条新记录,就计算一次文件哈希和已知恶意库的匹配度。
  3. 如果命中恶意库,守护进程从该记录中提取文件名,调用前面写的add_deny_path.py,把路径写入 deny map。
  4. 后续再有人访问这个文件,eBPF LSM 会直接拒绝。

更强的做法是使用 IMA appraise 模式,给可信文件打上签名,未签名或哈希不匹配的文件直接不被内核访问。但这需要给文件扩展属性写入签名,配置复杂度更高,适合作为后续扩展方向。

7. 功能测试与效果验证

7.1 测试 1:路径黑名单拦截

目的:验证 eBPF LSM 是否能在文件 open 阶段拦截

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询