Linux系统调用监控
最近我深入研究了Linux,特别是探索Linux内核。我发现Michael Kerrisk的《Linux编程接口》(TLPI)等资源是极好的参考手册,涵盖了系统调用(syscalls)的应用。要快速了解Linux系统调用,可以在终端中使用man 2 intro命令(或在线查看手册页)查看介绍性内容。在阅读TLPI时,我渴望更深入地探索系统调用本身的实现。为此,我最近阅读了大量内核源代码,并将代码片段串联起来,以更好地理解某些功能。我的首要目标是找到一种检测给定系统调用何时被使用的方法,以及一种查看在执行前传递给它的信息的方法。作为整体探索的延伸,我最终想要一个可加载内核模块(LKM),该模块可以监听给定的系统调用,并至少记录观察到的系统调用。理想情况下,该模块还会打印有关调用参数的详细信息。最终我得到了一段代码,可以扩展以监控任意系统调用,以及一种研究Linux内核安全的机制。
Kprobes
一些初步研究让我发现了一个名为Kprobes的内核跟踪设施,可用于钩取大多数内核符号。据我理解,在内核中注册kprobe会使其在内存中目标符号之前立即插入,并在此过程中保存符号的信息。注册的kprobe然后执行在kp->pre_handler中编写的任何代码,运行指令,然后执行在可选的kp->post_handler中可能编写的任何内容。我认为Kprobes设施非常酷,我肯定会在某个时候再次使用它,但它并没有立即有用(或者我认为如此)来让我查看特定系统调用的使用情况,而且我的初始探测在日志中 mostly 产生了垃圾。
Kallsyms
更多研究最终让我找到了kallsyms,这是一个用于提取内核符号的设施。我找到了一个似乎完全符合我需要的代码片段。使用kallsyms_lookup_name()函数,内核模块查找系统调用表的地址。通过使用表的地址,您可以用自己的定义临时替换系统调用的定义(在启用对表的读写访问之后,感谢Stack Overflow上的一个答案),在我的情况下,这只是在调用使用时包含一个printk来写入日志文件。我知道我只触及了这个设施的皮毛,但更深入的调查可以(并且希望会)在以后进行。
所以,我编译了模块并尝试加载它。它编译成功(好迹象),但尝试加载模块时出现了一个奇怪的错误(坏迹象):
|
|
酷。不知道为什么kallsyms_lookup_name未定义,但我必须调查一下。
经过一些额外研究,我找到了一个答案。事实证明,自内核版本5.7.0以来,内核不再全局导出该符号。有了这个知识,我现在必须找到一个替代解决方案。
Kprobes(重访)
我在一个内核黑客GitHub仓库上发现了一个问题,讨论了我遇到的相同事情,线程中的人们想出了一些真正辉煌的东西。还记得kprobes以及它们对这个项目没有立即有用吗?开玩笑的——事实证明该设施非常有用,只是不是以我最初预期的方式。在kprobe结构中返回的一件事是地址,即探针在内存中的位置。也许你已经可以看到这走向哪里了。我们可以使用kprobe来检索kallsyms_lookup_name()函数的地址,因为kprobes基本上可以看到任何内核结构。然后我们可以将该kprobe的地址视为函数本身,从而绕过内核将其暴露给我们的需要。
全部整合
这应该是制作一个工作概念验证所需的一切。将我找到的代码片段烘焙到模块中,它现在可以用于读/写系统调用表,并将处理程序插入到我想要查看的任何系统调用中。目前,我一直在以getuid()为目标,因为它相对简单。在一个终端窗口中,我用insmod插入模块,运行id命令(它依赖于getuid()系统调用),然后用rmmod删除模块。
加载模块、运行ID命令、移除模块
到目前为止相当直接。在另一个运行dmesg -wH的终端中,我看到模块设置信息,包括kallsyms_lookup_name()、sys_call_table和getuid()系统调用的地址。然后模块在安静下来之前看到三个getuid()调用。几秒钟后,我运行id命令,系统调用被识别并记录。又几秒钟后,我运行rmmod,这导致拦截的系统调用的其余部分。
成功的系统调用观察
我一开始不确定其他getuid()调用来自哪里,但最终意识到这很可能是因为我使用sudo命令插入或移除模块。
这似乎工作得非常好,我有想法将其扩展为对其他潜在项目更有用的东西。此外,dmesg输出本身现在不是很有用,所以下一步将是输出寄存器值和任何其他我能想到的相关信息。
结论(和代码)
这里有一个重要的注意事项。Kprobes设施是一个可以可选禁用的功能。如果它没有启用,并且你感觉冒险,你可以使用Kprobes文档中指定的选项编译内核,以确保你可以加载模块来使用该设施。Red Hat风味似乎默认启用了必需的功能。根据Debian(或任何其他)风味,你的情况可能会有所不同。
总的来说,这是一个有趣的练习和优秀的学习经验。它是世界上最有用的东西吗?不是。是否有其他更健壮的解决方案可用?几乎肯定。我认为SystemTap会很好地满足要求。也就是说,理解如何钩入内核,以及在整个项目中学到的一切,将在我未来从事其他项目时非常宝贵。
说到未来的其他项目……接下来是什么?除了我最初加深对Linux系统调用理解的目标之外,我现在找到了一种用似乎任何我想要的东西覆盖系统调用(以及其他内核结构)的方法。除了简单监控,我如何能够扩展(并不可避免地破坏)系统调用功能?进一步超越系统调用表,我还能覆盖哪些其他类型的内核结构和内存?而且,关键的是,这对我可怜的电脑会做什么?我没有期望一个晚上的修补会导致任何这些,但我很兴奋看到我能想出什么。
好了,足够人类的话了。是时候来一些计算机的话了。如果你感兴趣,可以在这里找到模块代码。请注意评论中列出的链接,因为如果没有找到它们,我不会走这么远。