在Linux内核中使用模块覆盖功能

21

不详细讲述原因,我正在寻找一种尽可能清晰的方法来替换内核函数和系统调用。我的最初想法是编写代码来覆盖一些函数,这些函数将采用原始函数(如果可能的话,调用函数),然后添加一些我自己的代码。关键是,我编写的函数必须具有原始函数的名称,这样其他代码在尝试访问它时,将访问我的函数。

我可以直接在内核中轻松地(相对地)执行此操作,只需将我的代码放入适当的函数中即可,但我想知道是否有人知道一些 C 的魔法,不是必须可怕的内核(或 C )编码实践,可以实现相同的结果。

思考 #define 和 typedefs,但我无法在脑海中完全理解它们。

简而言之:有没有人知道一种有效地覆盖 Linux 内核中函数的方法(从模块)?

编辑:由于已经有人问过,我基本上想要记录某些函数(创建/删除目录等)在内核内部,但为了健康起见,似乎使用可加载模块比每次更改都必须编写大型内核代码并重新编译要有意义。可以向内核添加最少量的代码,但我想将大部分工作卸载到模块中。


你能澄清你尝试做这三个中的哪一个吗? 1)覆盖现有内核代码中的内核函数调用 2)从你的内核模块中覆盖内核函数调用 3)覆盖用户空间中调用内核的系统调用除非是#1,否则我建议使用#defines。 - bmdhacks
1和2?我想在内核中保持调用不变,只是被调用的函数(在内核中)被我模块中定义的函数所替换。如果这有任何意义的话。 - Dan Fego
不是很清楚,如果您能解释一下“为什么”的部分可能会更有帮助。 - Robert Gamble
1
听起来你想在系统调用周围做一个非常薄的包装器。这不被认为是良好的实践,但对于某些用例,它可以工作。 - Evan Teran
有点类似,但我需要在系统调用完成之前执行操作,而不是之后。 - Dan Fego
1
是的,挂钩系统调用可以让你这样做,通常你会用以下方式替换系统调用:int new_open(/* 参数 /) { do_my_thing(/ 参数 */); return old_open(/参数/); } - Evan Teran
13个回答

7

我知道这个问题已经有三年了,但是为了其他人能够尝试做这样的事情,内核有一个叫做kprobes的接口可以满足你的需求。


我刚花了几分钟时间研究了一下,不知道为什么我(或其他人)三年前从未发现过这个。看起来差不多就是了。虽然我还没有使用和测试过它,但既然它似乎符合我所寻找的,我会将其标记为正确! - Dan Fego

4
你可能想要钩取系统调用(PDF链接),这将有效地让您记录用户进程调用内核函数。如果您真的想要记录内核函数的内核使用情况,您需要研究内核函数跟踪

被 Markdown 解析器搞砸的那个。谢谢你注意到了,已经修复了。 - geocar

3

我不太确定您想做什么,但我认为ksplice可能是一个不错的解决方案。它仍在开发中,所以我不知道它现在是否处于可用状态。


我所关注的并不是重新加载内核,而是将我的代码模块化到内核中,并尽可能避免对现有代码进行更改。 - Dan Fego

2
你考虑过使用LD_PRELOAD部署你的函数吗?
你的函数将通过一个共享库进行部署,该共享库位于由环境变量LD_PRELOAD指定的目录中。
惯例是拦截系统调用,然后在执行你的操作后将调用传递给实际的系统shlib。但你不必这样做。
也许可以看一下文章“为乐趣和利润构建库插入器”。虽然它是针对Solaris的,但也适用于Linux。
顺便说一下,大多数内存分析工具(如Purify)都是这样工作的。

1

这篇文章对你来说可能很有用。

基本上,由于系统调用表在较新的内核中未直接导出,您必须自己进行一些搜索以确定其位置。然后,您可以拦截所选的系统调用并对它们进行操作。替换其他内核函数要困难得多,除非其中一些组织方式与系统调用相同(它们出现在某些分派表等)- 这种情况非常罕见。


1

我认为你可以使用审计来完成这个任务


+1,审计系统将使操作员能够完全从用户空间执行其意图。 - MarkR

1

内核已经做了很多工作,以确保这种情况不会发生,特别是为了不向模块公开系统调用表的工作。记录文件访问的唯一支持机制是LSM,但它是面向安全性的,并且有着不确定的未来这里有一个文档API的PDF,但可能不是最新的。

与尝试破坏内核系统调用函数相比,inotify是监视文件创建、删除和修改的更好的方法,但它是从用户空间工作的。

引用自维基百科(http://en.wikipedia.org/wiki/Inotify): 可以监视的一些事件包括:

* IN_ACCESS - read of the file
* IN_MODIFY - last modification
* IN_ATTRIB - attributes of file change
* IN_OPEN and IN_CLOSE - open or close of file
* IN_MOVED_FROM and IN_MOVED_TO - when the file is moved or renamed
* IN_DELETE - a file/directory deleted
* IN_CREATE - a file/directory created
* IN_DELETE_SELF - file monitored is deleted

inotify自2.6.13内核版本开始存在,其前身是dnotify(http://en.wikipedia.org/wiki/Dnotify)。


0

0
根据KernelTrap.org的建议, 您可以进行简单的补丁和重新编译内核以导出sys_call_table变量。
// add the following in the file arch/i386/kernel/i386_ksyms.c
extern void* sys_call_table[];
EXPORT_SYMBOL(sys_call_table);

然后只需按照Linux内核模块编程指南中替换系统调用的步骤进行操作即可:

这里的源代码是一个内核模块的示例。我们想要“监视”某个用户,并在该用户打开文件时printk()一条消息。为此,我们替换了打开文件的系统调用为我们自己的函数,称为our_sys_open。此函数检查当前进程的uid(用户ID),如果它等于我们要监视的uid,则调用printk()以显示即将被打开的文件的名称。然后,无论如何,它都会使用相同的参数调用原始的open()函数来实际打开文件。 init_module函数替换了sys_call_table中的适当位置,并将原始指针保存在变量中。cleanup_module函数使用该变量将所有内容恢复回正常状态。这种方法很危险,因为存在两个内核模块更改同一系统调用的可能性。假设我们有两个内核模块A和B。 A的open系统调用将是A_open,B的将是B_open。现在,在将A插入内核时,系统调用被替换为A_open,它将在完成时调用原始的sys_open。接下来,将B插入内核,这将替换系统调用为B_open,完成时将调用它认为是原始系统调用A_open

0

谢谢,但我认为该方法在2.6版本已不再可行。sys_call_table[]不再被导出。 - Dan Fego

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接