ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

eBPF+IMA LSM实战:构建内核级文件访问控制反病毒原型

2026/8/30 9:05:01 拓冰建站 浏览量
eBPF+IMA LSM实战:构建内核级文件访问控制反病毒原型 这次我们来看一个很有意思的 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_BPFy CONFIG_BPF_SYSCALLy CONFIG_BPF_LSMy CONFIG_IMAy CONFIG_DEBUG_INFO_BTFy接着检查当前 LSM 列表里是否包含 bpfcat /sys/kernel/security/lsm如果输出里没有bpf可以通过内核启动参数追加lsmbpf或者在编译内核时调整配置。这里要注意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.hCO-RE 是 eBPF 现代开发方式的基础它依赖内核的 BTF 信息。我们可以用 bpftool 从内核导出 vmlinux.hbpftool 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也可以先加载再手动 attachsudo 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, checkTrue) print(fdeny 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 并配置策略首先挂载 securityfssudo mount -t securityfs securityfs /sys/kernel/security查看 IMA 是否可用ls /sys/kernel/security/ima/部分发行版默认没有启用 IMA启动时必须加内核参数ima_policy1修改 GRUB 或直接在启动命令行里追加该参数重启后 IMA 策略目录才会支持写入。写入两条最基本的策略分别度量程序执行和只读打开echo measure funcBPRM_CHECK /sys/kernel/security/ima/policy echo measure funcFILE_OPEN maskMAY_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 阶段拦截