掘金 人工智能 08月15日
如何轻松编写Rootkit:Linux内核系统调用拦截技术解析
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了在Linux内核中拦截系统调用的技术,重点介绍了Trail of Bits开源的KRF故障注入工具。文章首先阐述了系统调用的基本概念及其在内核与用户空间交互中的作用,并解释了为何内核态拦截比LD_PRELOAD或动态插桩等方法更具优势,尤其是在处理静态链接程序和减少运行时开销方面。随后,文章详细讲解了定位系统调用表(sys_call_table)的方法,以及如何在内核模块中实现对特定系统调用(如read(2))的拦截,包括修改CR0寄存器的写保护位以允许修改内核代码。最后,文章展示了多种高级应用场景,如强制故障注入、用户定向故障注入和数据篡改,并列举了KRF工具相对于其他替代方案(如syscall_intercept、ptrace、eBPF/kprobes)的独特优势,强调了其精确拦截和配置文件操作能力。

🛡️ **内核态系统调用拦截的优势**: 与LD_PRELOAD(仅限动态链接,易与实际系统调用不符)和动态插桩(运行时开销大)等技术相比,内核态故障注入直接修改系统调用,规避了这些限制,对静态链接程序同样适用,且运行时开销极小。

🔍 **系统调用与拦截实现**: 系统调用是内核向用户空间暴露资源(如I/O、进程管理)的接口,触发时伴随昂贵的上下文切换。文章通过`kallsyms_lookup_name`定位`sys_call_table`,并利用修改CR0寄存器的写保护位(CR0_WRITE_UNLOCK)来实现在内核模块中重写系统调用函数的入口,例如拦截`read(2)`。

🔧 **高级故障注入应用**: 内核态拦截技术可实现多种高级功能,包括强制返回错误码(如`-ENOSYS`)进行故障注入、基于用户ID或其他条件进行定向故障注入,以及篡改系统调用接收或返回的数据,为软件测试和安全分析提供了强大工具。

🚀 **KRF工具的独特能力**: Trail of Bits开发的KRF工具利用此内核态拦截技术,具备按可执行文件精确拦截、支持系统调用“配置文件”操作、实时故障注入以及完全兼容静态链接程序等特性,是解决传统故障注入方法局限性的有效方案。

⚖️ **替代方案对比**: 文章对比了其他故障注入和监控技术,如基于LD_PRELOAD的`syscall_intercept`(依赖反汇编)、`ptrace`(性能问题和调试器冲突)以及eBPF/kprobes(仅支持参数记录)。KRF的内核态拦截方式在功能和性能上展现出显著优势。

如何轻松编写Rootkit - Trail of Bits技术博客

我们开源了名为KRF的故障注入工具,它利用内核态系统调用拦截技术。您现在就可以使用它来发现程序中的错误假设(及由此产生的漏洞)。快来试试吧!

本文介绍如何通过普通内核模块在Linux内核中拦截系统调用。我们将快速回顾系统调用及其拦截意义,然后演示拦截read(2)系统调用的基础模块实现。

与其他故障注入策略有何不同?

其他故障注入工具主要依赖以下技术:

    LD_PRELOAD技巧:拦截libc暴露的系统调用包装函数。存在明显缺陷:

      仅适用于动态链接场景(Go等新语言和静态编译趋势使其适用性降低)包装函数与实际系统调用可能存在显著差异(如open()可能调用openat(2))

    动态插桩框架(如DynamoRIO/Intel PIN):

      允许在函数或机器码级别检测系统调用带来显著的运行时开销

内核态故障注入规避了上述问题:直接重写实际系统调用,且几乎不增加运行时开销。

系统调用基础

系统调用是内核向用户空间暴露资源的接口,涉及:

与传统函数调用不同,系统调用需要昂贵的上下文切换(x86通过int 80h/sysenter指令触发)。

系统调用表定位技术

Linux内核通过sys_call_table数组管理系统调用,但自2.6版本后不再直接导出该符号。我们使用kallsyms_lookup_name接口可靠获取其地址:

static unsigned long *sys_call_table;int init_module(void) {  sys_call_table = (void *)kallsyms_lookup_name("sys_call_table");  if (!sys_call_table) {    printk(KERN_ERR "查找sys_call_table失败\n");    return -1;  }  return 0;}

系统调用注入实现

基础拦截示例(以read为例):

asmlinkage long phony_read(int fd, char __user *buf, size_t count) {  printk(KERN_INFO "拦截到read调用:fd=%d, %lu字节\n", fd, count);  return orig_read(fd, buf, count);}

x86架构需处理CR0寄存器的写保护位:

#define CR0_WRITE_UNLOCK(x) \  do { \    unsigned long __cr0; \    preempt_disable(); \    __cr0 = read_cr0() & (~X86_CR0_WP); \    BUG_ON(unlikely(__cr0 & X86_CR0_WP)); \    write_cr0(__cr0); \    x; \    __cr0 = read_cr0() | X86_CR0_WP; \    BUG_ON(unlikely(!(__cr0 & X86_CR0_WP))); \    write_cr0(__cr0); \    preempt_enable(); \  } while (0)

高级应用场景

    强制故障注入
asmlinkage long phony_read(int fd, char __user *buf, size_t count) {  return -ENOSYS;}
    用户定向故障
asmlinkage long phony_read(int fd, char __user *buf, size_t count) {  if (current_uid().val == 1005) return -ENOSYS;  return orig_read(fd, buf, count);}
    数据篡改
asmlinkage long phony_read(int fd, char __user *buf, size_t count) {  unsigned char kbuf[1024];  memset(kbuf, 'A', sizeof(kbuf));  copy_to_user(buf, kbuf, sizeof(kbuf));  return sizeof(kbuf);}

KRF工具特性

我们开发的KRF工具具备:

替代方案对比

    syscall_intercept:基于LD_PRELOAD但使用capstone反汇编libcptrace(2):用户空间子进程监控方案,存在调试器冲突和性能问题eBPF/kprobes:仅支持参数记录,无法实现实际拦截

技术说明:本文介绍的CR0写解锁机制源自PaX/grsecurity的"rare write"实现

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Rootkit 故障注入 系统调用 内核模块 KRF Linux安全
相关文章