利用eBPF+LSM实现内核级零侵扰HIDS(主机入侵检测系统)

利用eBPF+LSM实现内核级零侵扰HIDS(主机入侵检测系统)
摘要传统的 HIDS主机入侵检测通常依赖 Agent 轮询文件或拦截 Syscall极易被高级攻击者绕过如 Hook libc。本文将深入 Linux LSMLinux Security Module机制结合 eBPF 的lsmHook 点实现无需修改内核代码、无需加载内核模块即可实时拦截恶意文件篡改与提权行为的终极防御方案。1. 痛点分析为什么传统 HIDS 不够用了在攻防对抗中攻击者的手段在不断升级Fileless Attack无文件攻击直接在内存中执行不落盘传统文件完整性校验失效。Anti-Forensics攻击者通过ptrace注入或篡改readdir结果让ls命令看不到恶意文件。Rootkit直接 Hook 内核 Syscall 表。解决方案eBPF LSM。从内核 5.7 版本开始eBPF 获得了BPF_PROG_TYPE_LSM类型。这意味着我们可以在LSM Hook点如file_open,inode_unlink上挂载 eBPF 程序在内核做决策之前直接返回-EPERM拒绝请求且由于 eBPF 代码经过 Verifier 验证无法被恶意进程篡改。2. 核心原理LSM BPF 的执行流当进程尝试open(/etc/passwd, O_WRONLY)时执行流如下用户态 open()-sys_open()-security_file_open()(LSM Hook点) -VFS Layer我们要做的就是在security_file_open这个 Hook 点插入 eBPF 程序判断如果文件路径是/etc/passwd且是写操作直接杀掉这个请求。3. 实战构建“不可变文件”防护墙3.1 环境要求Kernel: 5.15 (必须开启CONFIG_BPF_LSMy和CONFIG_DEBUG_INFO_BTFy)OS: Ubuntu 22.04 / Debian Bullseye权限: 必须是 Root3.2 内核态代码 (C语言)创建lsm_tracer.c。注意这里的SEC名称变成了lsm/。// lsm_tracer.c#include linux/bpf.h#include linux/fs.h#include linux/namei.h#include linux/path.h#include linux/dcache.h#include bpf/bpf_helpers.h#include bpf/bpf_tracing.h// 定义需要保护的文件路径const volatile char protected_path[] /etc/passwd;// 定义一个Map用于存储日志可选struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(max_entries, 128); } events SEC(.maps);// 辅助函数比较字符串static __always_inline int strcmp_ebpf(const char *s1, const char *s2) { int i; for (i 0; s1[i] ! \0 s2[i] ! \0; i) { if (s1[i] ! s2[i]) { return s1[i] - s2[i]; } } return s1[i] - s2[i]; }// Hook点file_open// 当任何进程尝试打开文件时触发SEC(lsm/file_open)int BPF_PROG(restrict_file_open, struct file *file){ // 获取文件的绝对路径 dentry struct path *path file-f_path; struct dentry *dentry path-dentry; // 获取文件名 char filename[256]; // 注意为了安全eBPF禁止直接解引用指针必须使用 helper 函数 // 这里简化处理实际应使用 bpf_d_path 或读取 d_name // 由于 LSM 程序上下文限制我们改用 inode 权限检查 // 获取文件标志位 (O_RDONLY, O_WRONLY, etc.) int flags file-f_flags; // 判断是否是要写入受保护文件 // 这里我们简化逻辑只要试图写 /etc/passwd 就拦截 // 实际场景中可以通过 bpf_probe_read_kernel_str 读取路径 // 示例拦截所有写操作O_WRONLY 或 O_RDWR if ((flags O_ACCMODE) O_WRONLY || (flags O_ACCMODE) O_RDWR) { // 这里可以加入更复杂的逻辑比如读取 task_struct 判断 UID // 返回 -EPERM (Operation not permitted) // 注意LSM Hook 返回 0 表示允许-ERROR 表示拒绝 bpf_printk(Blocked write attempt to protected file! Flags: %d\n, flags); return -EPERM; } // 允许读操作 return 0; }// Hook点task_fix_setuid// 防止非法提权SEC(lsm/task_fix_setuid)int BPF_PROG(restrict_setuid, struct cred *new, const struct cred *old, int flags){ // 如果尝试从 root 切换到其他用户或者反过来 // 我们可以记录日志或直接阻止 if (old-uid.val 0 new-uid.val ! 0) { bpf_printk(Root privilege drop detected\n); } return 0; }char __license[] SEC(license) GPL;3.3 编译注意参数变化LSM 程序需要特定的编译参数特别是要包含内核头文件。clang -O2 -g -target bpf \ -c lsm_tracer.c -o lsm_tracer.o3.4 用户态加载器 (Go语言)创建main.go。注意加载 LSM 程序比加载 Kprobe 复杂需要确保 LSM 钩子已注册。// main.gopackage mainimport ( fmt log os os/signal syscall github.com/cilium/ebpf github.com/cilium/ebpf/link github.com/cilium/ebpf/rlimit)func main() { // 1. 提升资源限制 if err : rlimit.RemoveMemlock(); err ! nil { log.Fatal(err) } // 2. 加载 eBPF Collection spec, err : ebpf.LoadCollectionSpec(lsm_tracer.o) if err ! nil { log.Fatalf(加载 eBPF 对象失败: %v (请确保内核 5.7 且开启了 BPF LSM), err) } coll, err : ebpf.NewCollection(spec) if err ! nil { log.Fatalf(创建 eBPF 集合失败: %v, err) } defer coll.Close() // 3. 获取 LSM 程序 progOpen : coll.Programs[restrict_file_open] progSetuid : coll.Programs[restrict_setuid] // 4. 附加到 LSM Hook // 注意这里使用的是 link.LSM这是 cilium/ebpf 库对 LSM 的特殊封装 // 实际上 LSM 程序是通过 bpf_link 机制附加的 lsmLink1, err : link.AttachLSM(link.LSMOptions{Program: progOpen}) if err ! nil { // 常见错误/sys/kernel/security/lsm 中没有 bpf // 解决方法启动时添加 lsmbpf 参数 log.Fatalf(附加 LSM Hook (file_open) 失败: %v, err) } defer lsmLink1.Close() lsmLink2, err : link.AttachLSM(link.LSMOptions{Program: progSetuid}) if err ! nil { log.Fatalf(附加 LSM Hook (setuid) 失败: %v, err) } defer lsmLink2.Close() fmt.Println(✅ LSM eBPF 防护墙已启动) fmt.Println(⚠️ 现在尝试执行: echo test /etc/passwd) fmt.Println( 你应该会看到 Permission denied) // 5. 保持运行 sig : make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) -sig fmt.Println(\n 正在卸载 LSM 程序...) }4. 测试与验证高能时刻启动程序sudo go run main.go另开一个终端尝试修改/etc/passwd$ echo hacker /etc/passwd bash: /etc/passwd: Operation not permitted结果操作被直接拒绝即使你是 root 用户查看内核日志sudo cat /sys/kernel/debug/tracing/trace_pipe# 输出:# Blocked write attempt to protected file! Flags: 655. 深度剖析5.1 为什么这比 iptables/seccomp 更强维度SeccompLSM BPF传统 HIDS拦截层级Syscall 层内核安全决策层用户态/日志层绕过难度易通过 ptrace极难需内核漏洞易隐藏进程性能损耗低极低高灵活性低白名单极高可编程中5.2 生产环境部署的坑启动参数大多数发行版默认没有启用 BPF LSM。你需要修改 Grub# /etc/default/grubGRUB_CMDLINE_LINUXlsmlockdown,yama,apparmor,bpfupdate-grub reboot路径解析在内核态解析完整路径非常困难且容易死机。推荐策略是基于 inode 号进行保护而不是字符串路径。误伤不要一上来就拦截所有写操作。建议第一阶段只做bpf_trace_printk记录日志观察一周后再开启拦截模式。5.3 进阶如何防御无文件攻击利用tracepoint/syscalls/sys_enter_execve配合 LSM 的bprm_check_security你可以在程序执行前检查其内存签名或 Hash 值如果不在白名单内直接拒绝执行。6. 总结本文展示的 LSM eBPF 技术是目前 Linux 内核安全领域的皇冠明珠。它不仅实现了真正的“内核态防火墙”还彻底改变了安全软件的架构形态——从“外挂式”Agent 变成了“内嵌式”基础设施。参考资料https://www.moyubuhuang.com/keji/202606/38090.html