ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

eBPF+IMA LSM:构建一个内核态玩具杀毒软件原型

eBPF+IMA LSM:构建一个内核态玩具杀毒软件原型 有个朋友在某次安全技术交流结束后跑来问我你说我用 eBPF 写一个杀毒软件是不是很酷我第一反应是eBPF 配合 IMA LSM 做内核态检测原型确实可行但你写出来的东西大概率活不过第一轮性能压测。他很快反问那为什么现在这么多项目都在讨论 eBPF 做 AV、做 EDR我意识到很多人把“能拿到内核事件”和“能做成杀毒软件”画了等号。我想聊的就是怎么用 eBPF 配合 IMA LSM把一个“crappy ring-0 toy antivirus”造出来——顺带说清楚它为什么只能是个 toy。1. 很多人想用 eBPF 写杀毒先要搞清楚它到底在杀什么1.1 “ring-0 玩具杀软”这个说法其实已经把重点说透了标题里的 ring-0在常见的安全语境里指的是 CPU 的最高特权级也就是内核态。Windows 上很多杀软有内核驱动Linux 上过去要做一个同样的事通常也得写内核模块。内核模块出错就是宕机开发调试门槛很高。eBPF 的出现把一个相对可控的“内核内执行环境”带给了普通开发者于是很多项目开始尝试在 eBPF 里做安全检测。但“ring-0”不等于“安全”。你可以把一个程序放进内核不代表它能挡住所有恶意行为。“crappy”这个词才是项目的真实底色它能跑功能有限边界粗糙只适合当玩具或者教学原型。明白这一点再去看 eBPF IMA 的杀毒方案才不会产生不切实际的期待。这个玩具的核心价值不是真的去对抗一轮 APT 攻击而是让你理解一次文件执行在内核里经过哪些路径一个安全检测系统需要哪些模块才能成立。1.2 IMA、eBPF、LSM 三个概念放到一件小事里说清假设用户执行了/tmp/demo.sh。IMAIntegrity Measurement Architecture会先对这个文件做哈希度量并把度量结果记录到内核的运行时度量列表中。它回答的是“这个文件的内容是什么”。LSMLinux Security Module提供了一组内核安全钩子例如 file_open、bprm_check。内核在执行脚本前会经过这些钩子安全模块可以在钩子函数里决定是否允许继续。eBPF 是一种内核内虚拟机可以让开发者安全地挂载到 tracepoint、kprobe以及 BPF LSM 提供的挂钩点从而看到事件发生、修改返回值、记录上下文。三个东西合在一起就组成了一条完整的检测链路eBPF 负责“看到文件被执行”IMA 负责“拿到文件的完整性度量”LSM 钩子负责“在被执行前插入我们的检查逻辑”。用户态程序再根据哈希和策略决定放行或告警。1.3 为什么不是写一个用户态扫描器传统做法是扫描磁盘文件把文件和病毒库比对。这种模式的问题在于你无法实时知道一个文件什么时候被创建、被修改、被执行。要实时就得轮询文件系统要么扫描全盘要么使用 inotify/Fanotify。但这些都是用户态机制和内核态之间始终隔着一层上下文切换而且你拿不到执行链路中的精确上下文。eBPF 的优势是实时、低开销、内核视角。一个文件被 execve 进来eBPF 程序可以在事件发生的路径上立刻感知。IMA 又提供了哈希度量作为数据源两者结合就可以实现一个简单的“文件行为监控器”。但它并不是真正的杀毒软件。真正的杀毒软件还需要病毒特征库、静态启发式分析、模拟执行、宏分析、固件检测、云端信誉系统等。eBPF IMA 只能给你一个“检测管线”的骨架离商业安全产品还差很多层。2. 先搭一条最小可运行链路文件被打开哈希被校验事件被记录2.1 内核侧要准备什么我建议先准备一个可随时重启的虚拟机不要在主机的物理内核上做这种实验。IMA 策略一旦在内核中存在部分版本里无法在运行时完全清空eBPF 程序如果写得不合适也会影响系统行为。先确认内核配置是否支持要用的能力。常见检查方式grep -E CONFIG_(IMA|BPF|BPF_SYSCALL|BPF_LSM|SECURITYFS) /boot/config-$(uname -r)如果输出里CONFIG_BPFy和CONFIG_BPF_SYSCALLy说明 eBPF 基础能力没问题CONFIG_IMAy说明 IMA 功能被编译进内核CONFIG_BPF_LSMy说明 BPF LSM 支持可用CONFIG_SECURITYFSy说明需要挂载 securityfs 来管理 LSM 策略。在实际发行版里这几个配置不一定全部开启。特别是CONFIG_BPF_LSM不少发行版默认没有启用。如果没启用BPF LSM 路径就无法工作但 tracepoint 路径仍然可以用。2.2 用 IMA 测量模式先拿到基线IMA 有多种模式常见的有 measure、appraise、audit。measure 模式会计算并记录文件哈希但不阻止执行appraise 模式会在哈希校验不通过时拒绝访问。玩具方案里我建议先用 measure先看数据不要一上来就强制拦截。需要挂载 securityfsmount -t securityfs securityfs /sys/kernel/security写入一条测量策略让内核在每次执行二进制或脚本时计算哈希echo measure funcBPRM_CHECK /sys/kernel/security/ima/policy注意这条命令只是一种常见写法具体策略语法取决于内核版本和发行版配置。如果写入失败第一步先看dmesg第二步确认当前用户是否有权限第三步确认是否已有策略规则存在。在某些内核配置下一旦写入过策略后续清理需要重启虚拟机。写入成功后可以查看 IMA 的运行时度量记录tail /sys/kernel/security/ima/ascii_runtime_measurements如果能看到类似boot_aggregate或执行文件的哈希记录说明 IMA 测量链路已经走通。这一步的价值是拿到“文件哈希”的权威来源后续和 eBPF 采集的事件做关联。2.3 再用 eBPF 监听执行与打开事件IMA 负责度量eBPF 负责事件。最快速的验证方式是先用 bpftrace 看能不能捕获 execve 系统调用bpftrace -e tracepoint:syscalls:sys_enter_execve { printf(%d %s %s\n, pid, comm, str(args-filename)); }这个命令会在每次执行新进程时打印 PID、进程名和被执行的路径。如果这个能跑通说明 eBPF 事件通道没问题。如果要更贴近“杀毒”场景可以挂到 LSM 钩子上比如security_file_open。这样文件被打开时就能感知。但 LSM hook 的可用性依赖CONFIG_BPF_LSM并且需要把bpf注册到 LSM 列表中否则程序无法 attach。很多发行版里CONFIG_BPF_LSM没有开启你会遇到类似unknown func或invalid argument的错误。从工程经验看如果只是想先跑通一个玩具原型不需要执着于 LSM 钩子。tracepoint 已经能覆盖“看到文件被执行”这个需求。LSM 的价值在于“执行前拦截”这个后面再补。2.4 用户态决策哈希、白名单和告警内核侧的 eBPF 程序把事件抛给用户态用户态进程负责决策。为什么不能全部放在 eBPF 里做因为 eBPF 程序有复杂度限制不适合在 BPF 指令流里维护一个大哈希表、加载特征库、做正则匹配。更合理的分工是eBPF 只负责采集事件把 PID、路径、调用链等基本信息发出去。IMA 负责提供文件哈希。用户态程序维护白名单或规则集计算哈希后与白名单比对。如果匹配失败用户态程序可以记录告警也可以调用接口终止进程。这个链路里真正的“决策”在用户态。虽然牺牲了一点实时性但对玩具原型来说完全够用而且更容易调试和更新规则。3. 把“拿到事件”变成“内核态检测”中间有多道坎3.1 决策要不要放在内核态很多人会把“放在内核态”当成优势本身。实际上决策在用户态还是内核态要看你做什么判断。如果只是对少量文件做哈希比对用户态完全能胜任而且写起来简单得多。用户态可以做 SQLite 查询、加载 YARA 规则、访问外部 API这些都是 eBPF 里很难实现的。但如果你要追求极低的延迟或者要在进程 exec 之前就完成拒绝那就必须依赖内核态动作。IMA 的 appraise 模式可以在内核态直接拒绝哈希不匹配的文件但它依赖签名和密钥管理体系这比“用 bpftrace 看一眼事件”复杂得多。更务实的做法是先让 eBPF 事件通知用户态由用户态调用kill或通过系统调用干预。这个过程会有几百微秒到几毫秒的延迟对玩具场景足够了。真正进入生产级 EDR 时再考虑把关键决策下放到内核使用 LSM 钩子返回错误码来阻断。3.2 LSM 钩子、eBPF 和 IMA 策略可能会打架Linux 的 LSM 框架里可以同时启用多个安全模块模块之间存在固定顺序。BPF LSM 在这个顺序里的位置直接影响你的 eBPF 程序能否看到其他 LSM 已经处理过的结果。IMA 本身也实现了一些安全钩子如果你的策略和 BPF LSM 策略同时存在会让问题变得难排查。最常见的现象是你写了一个 BPF LSM 程序想拦截某个文件执行但同一个 hook 上还有 AppArmor 或 SELinux 的策略二者返回值和处理逻辑叠加最后文件还是被执行了或者反而被拒绝了。你在 eBPF 里看到的输入是原始参数但在它之前可能已经有模块修改了路径、改写了挂载命名空间或者对文件做了 overlay 映射。所以原型环境里建议先关闭不必要的 LSM 模块或者在启动参数里明确指定 LSM 顺序。这不是为了“绕过”安全机制而是为了让实验变量足够干净方便你确认到底是 BPF 程序没触发还是 LSM 顺序干扰了结果。3.3 一个比较稳妥的原型判断流程把整个体系看作一条流水线事件发生execve或open触发。eBPF 程序在事件路径上采集路径、PID、进程名、返回结果等上下文。eBPF 把事件写入 ring buffer 或 perf buffer。用户态程序接收事件拿到真实路径计算文件哈希。用户态程序查询白名单/黑名单。如果文件不在白名单中记录告警并决定是否终止进程。每一步都不复杂但串联起来就是一个可用的安全检测原型。这个流程最容易被忽略的是“真实路径”这一层。用户看到的/tmp/demo.sh可能是一个符号链接真实文件在/usr/local/packages/demo.sh也可能是一个 overlayfs 路径在宿主机和容器里看到的路径不一样。如果只拿用户态路径去做白名单哈希永远匹配不上。4. 参数、边界和排错这个玩具离生产到底差多远4.1 内核配置、启动参数和挂载点先花十分钟确认很多问题不是写错代码而是环境没准备好。我列了一个检查维度可以直接当作上手清单检查项判断标准常见问题内核版本至少 5.7 以上BPF LSM 才可用内核太老部分能力缺失CONFIG_BPF / CONFIG_BPF_SYSCALL需要 yeBPF 程序无法加载CONFIG_BPF_LSM需要 yBPF LSM attach 失败CONFIG_IMA需要 yIMA 目录不存在SECURITYFS 挂载/sys/kernel/security有内容策略无法写入IMA policy 状态能读到规则或能写入规则策略为只读当前用户权限需要 root 或 CAP_SYS_ADMIN写入与加载被拒绝先花十分钟跑一遍能避免绝大多数“为什么没反应”的疑问。4.2 最常见的问题是“事件没出来”和“策略没生效”我先说一个通用排查链路现象 → 输入 → 环境 → 参数 → 日志。不要一上来就怀疑代码写得不对。现象bpftrace 执行后没有任何输出。输入确认是不是真的执行了一个二进制文件还是执行了一个内建 shell 命令。for、cd、echo这些命令不一定触发 execve。环境确认内核配置确认是否有 root 权限确认是否在容器里缺少 securityfs 可见性。参数确认 attach 点名称是否正确比如 tracepoint 路径里有没有打错字LSM hook 是否被内核支持。日志运行dmesg -w观察内核日志很多时候 eBPF verifier 会明确告诉你失败原因。如果 IMA 策略没有生效可以这样排查查看/sys/kernel/security/ima/ascii_runtime_measurements是否有内容。如果没有检查 policy 写入是否成功以及是否选择了measure而不是其他模式。再确认是不是所有文件都被过滤了比如 IMA 策略里可能写了uid0或fsnameext4等条件。4.3 五个边界为什么它只能叫 toy antivirus第一特征能力缺失。它没有恶意特征库不能识别未知恶意软件。白名单模式只能发现“不在列表里的文件”无法回答“这个文件为什么是恶意的”。第二检测面有限。用 execve 和 open 事件做检测只能看到“文件被打开/执行”这个维度。一个恶意脚本可以完全不做文件落地直接内存执行或者借用合法工具进程做间接操作。toy 方案看不到这些。第三性能问题。如果对每个执行文件都做完整哈希在大规模环境中会带来明显延迟。尤其是在打包脚本、编译任务、批量工具调用场景中哈希计算会成为瓶颈。第四策略维护成本高。白名单需要持续更新。开发环境里每天都会生成新二进制临时脚本、构建产物都会触发误报。玩具系统可以人工加白名单生产系统这就变成了一个需要流程化管理的策略平台。第五自身防护不足。eBPF 程序可以被预期约束限制但并不能完全阻止有足够权限的人卸载你的程序。真正的安全产品还需要自我完整性校验、防篡改机制、内核侧审计日志这些已经远远超出“toy”的范畴。5. 从玩具到可用原型我建议你按这个框架拆5.1 五层拆开事件采集、数据源、决策引擎、动作、日志我把这类项目拆成五层这样既能避免一开始就陷入某段代码也方便后面替换模块。分层职责玩具实现生产级方向事件采集感知内核事件tracepoint / BPF LSM多 hook 组合、事件去重、上下文关联数据源提供判断依据IMA 哈希、文件路径特征库、信誉查询、威胁情报决策引擎判断是否放行本地白名单动态策略、ML/规则引擎、多源评分动作执行处置记日志/杀进程LSM 拦截、隔离、云侧联动日志保留审计记录输出到文件审计平台、Kafka、SIEM 集成这个框架的好处是每一层都可以单独迭代。事件采集不完善时可以用日志层补信息决策引擎不成熟时可以先只告警不阻断。每一层之间用明确的接口连接后面换掉任何一层都不会影响其他部分。5.2 先跑通、再批量、最后自动化我的建议是分三个阶段推进。第一阶段先跑通最小链路。找几个测试文件手动执行确认 eBPF 事件能出来IMA 哈希能取到用户态白名单能正确判断。第二阶段扩大样本集。放几十个系统命令进去观察误报率和性能开销。重点看两个东西一是文件哈希计算的耗时二是批量执行时事件是否丢。eBPF ring buffer 如果满了事件会被丢这在日志里可能表现为“偶尔缺记录”。第三阶段再考虑自动化。把白名单放进配置文件或数据库把告警接到已经存在的监控体系里比如文件日志、消息队列、工单系统。这时候你已经不是在写一个玩具而是把玩具身上练出来的经验落成一个轻量级检测原型的骨架。5.3 这类项目真正适合谁不适合谁它适合三类人学 eBPF 和内核安全事件机制需要一个有画面感的练手项目的人。安全平台开发工程师想快速验证“实时检测文件执行”这类需求的可行性。反病毒或者 EDR 产品的新人想理解内核态检测中事件、策略、哈希、阻断之间的关系。它不适合想用它保护真实生产环境的人。想直接替换商业杀软或 EDR 产品的人。没有时间维护白名单和策略指望一套规则解决所有问题的人。把预期放低反而能做出一个不错的学习项目。如果你一开始就期待它成为一个“全网最强”的杀毒软件那大概率会在性能、误报、日志风暴里很快放弃。回到朋友那个问题。用 eBPF 写一个玩具杀毒值不值得我觉得很值得。它最大的收获不是得到一套能用的杀毒软件而是让你把“用户态扫描”这件事放到内核视角重新看一遍。真正可复用的是你对事件、哈希、策略、拒绝、日志这条链路的理解。等你要做真正的安全产品或者想评估 eBPF 是否能承担某类安全检测时这个玩具会是一个非常清晰的参考系。先跑通一条事件链再谈对抗强度这比一开始就追求“什么都检测”要靠谱得多。
返回列表