无法在Linux内核中封装函数

我已经写了一个实现可信任path执行(TPE)到您的内核的LKM:

https://github.com/cormander/tpe-lkm

当我将WRAP_SYSCALLS定义为1时,遇到了一个偶然的内核OOPS(在这个问题的末尾描述),并且在我的智慧结束处试图追踪它。

一点背景:

由于LSM框架不会导出它的符号,所以我不得不将TPE检查插入正在运行的内核。 我写了一个find_symbol_address()函数,它提供了我需要的任何函数的地址,并且工作得非常好。 我可以调用这样的函数:

int (*my_printk)(const char *fmt, ...); my_printk = find_symbol_address("printk"); (*my_printk)("Hello, world!\n"); 

它工作正常。 我使用这种方法来查找security_file_mmapsecurity_file_mprotectsecurity_bprm_check函数。

然后用asm跳过我的函数来覆盖这些函数来做TPE检查。 问题是,当前加载的LSM将不再执行该函数的钩子代码,因为它已被完全劫持。

这里是我做的一个例子:

 int tpe_security_bprm_check(struct linux_binprm *bprm) { int ret = 0; if (bprm->file) { ret = tpe_allow_file(bprm->file); if (IS_ERR(ret)) goto out; } #if WRAP_SYSCALLS stop_my_code(&cs_security_bprm_check); ret = cs_security_bprm_check.ptr(bprm); start_my_code(&cs_security_bprm_check); #endif out: return ret; } 

注意#if WRAP_SYSCALLS部分(默认情况下定义为0)之间的部分。 如果设置为1,则调用LSM的钩子,因为我将原始代码写回到asm跳转并调用该函数,但偶尔会遇到带有“无效操作码”的内核OOPS:

 invalid opcode: 0000 [#1] SMP RIP: 0010:[<ffffffff8117b006>] [<ffffffff8117b006>] security_bprm_check+0x6/0x310 

我不知道是什么问题。 我已经尝试了几种不同types的locking方法(详见start / stop_my_code的内部)无效。 为了触发内核OOPS,写一个简单的bash while循环,无尽地启动后台“ls”命令。 一分钟左右,就会发生。

我在RHEL6内核上testing这个,也可以在Ubuntu 10.04 LTS(2.6.32 x86_64)上运行。

虽然这个方法是迄今为止最成功的,但是我尝试了另一种简单的将内核函数复制到我用kmalloc创build的指针的方法,但是当我尝试执行它时,我得到: 内核试图执行受NX保护的页面 – 利用尝试? (uid:0) 。 如果有人能告诉我如何kmalloc空间,并将其标记为可执行文件,这也将帮助我解决上述问题。

任何帮助表示赞赏!

Solutions Collecting From Web of "无法在Linux内核中封装函数"

看起来,在调用函数之前, security_bprm_check()的开头没有完全恢复。 oops发生在security_bprm_check+0x6 ,也就是在你放置的跳转之后,所以看起来跳转的一部分仍然在那里。 我现在不能说为什么会发生这种情况。

看看x86上的coreel Probes(KProbes)的实现,它可能会给你一些提示。 有关详细信息,另请参阅KProbe的说明。 KProbe需要以安全的方式修补和恢复几乎任意的内核代码片段来完成他们的工作。

2.现在提到的另一种方法是关于复制功能。 以下是一些黑客攻击,内核开发者会不满,但如果没有其他方法,这可能会有所帮助。

您可以分配内存以将功能复制到分配内核代码的内存的同一区域。 该区域应该是默认可执行的。 再次,KProbe使用这个技巧分配他们的绕道缓冲区。

内存由module_alloc()函数分配,并由module_alloc()释放。 这些函数当然不会被导出,但是您可以像使用security_file_mmap()等一样找到他们的地址。只是好奇心,您正在使用kallsyms_on_each_symbol() ,对不对?

如果你这样分配内存,这也可以帮助避免另一个不太明显的问题。 在x86-64上,可用于kmalloc和模块代码的内存地址空间相当远(参见Documentation / x86 / x86_64 / mm.txt ),超出了任何相对跳转的范围。 如果内存映射到模块的地址区域,则可以使用近似的相对跳转和调用来调用复制的函数。 RIP相对寻址的类似问题也可以通过这种方式避免。

编辑:请注意,在x86上,如果您将一段代码复制到不同的内存区域,并且您希望它在那里运行,那么代码中的某些更改可能是必需的。 至少你需要修正相关的调用和跳转,这些调用将跳转到复制代码之外(例如调用另一个函数等)以及RIP相对寻址的指令。

除此之外,代码中可能还有其他的结构需要修正。 例如,编译器可能已经优化了一些甚至所有的switch语句来通过一个表跳转。 也就是说,每种case的代码块的地址被保存在存储器中的一个表中,并且开关变量是该表中的索引。 这样,而不是许多比较,您的模块将执行像jmp <table_start>(%reg, N) (N是指针的大小,以字节为单位)。 也就是说,只是跳转到表中相应元素的地址。 因为这些表是在复制之前为代码创建的,所以修复可能是必要的,否则这种跳转将执行回原始代码段而不是复制的代码段。