在Linux内核中包装功能时遇到麻烦

问题描述

| 我已经编写了一个LKM,可在您的内核中实现可信路径执行(TPE): 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_mmap,security_file_mprotect和security_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
我不知道是什么问题。我尝试了几种不同类型的锁定方法(有关详细信息,请参见start / stop_my_code的内部),无济于事。要触发内核OOPS,请编写一个简单的bash while循环,该循环无休止地启动后台的“ ls”命令。一分钟左右后,它会发生。 我正在RHEL6内核上对此进行测试,也可以在Ubuntu 10.04 LTS(2.6.32 x86_64)上运行。 尽管此方法到目前为止是最成功的方法,但是我尝试了另一种将内核函数复制到使用kmalloc创建的指针的方法,但是当我尝试执行该方法时,我得到了:内核尝试执行受NX保护的页面-漏洞利用尝试? (uid:0)。如果有人可以告诉我如何kmalloc空间并将其标记为可执行文件,那也将帮助我解决上述问题。 任何帮助表示赞赏!     

解决方法

1.似乎
security_bprm_check()
的开头在调用函数之前还没有完全恢复。 oops发生在ѭ4happens,即刚好在您放置跳动之后,所以看起来跳动的某些部分还在那一刻。我现在不能说为什么会发生这种情况。 看一下x86上的内核探针(KProbes)的实现,它可能会给您一些提示。有关详细信息,另请参见KProbes的描述。 KProbes需要以安全的方式修补和恢复几乎任意的内核代码,以完成其工作。 2.现在,您提到的关于方法复制的另一种方法。以下内容有些骇人听闻,内核开发人员会对此表示反对,但是如果没有其他方法,这可能会有所帮助。 您可以分配内存以将功能复制到分配内核模块代码的内存所在的同一区域。该区域默认情况下应该是可执行的。同样,KProbes使用此技巧来分配其绕行缓冲区。 存储器由
module_alloc()
功能分配,并由
module_free()
释放。这些函数当然不会导出,但是您可以按照与
security_file_mmap()
等相同的方式找到它们的地址。出于好奇,您正在使用
kallsyms_on_each_symbol()
,对吗? 如果以这种方式分配内存,这也可以帮助避免另一个不太明显的问题。在x86-64上,可用于kmalloc和模块代码的内存地址区域彼此之间相距很远(请参阅Documentation / x86 / x86_64 / mm.txt),这超出了任何相对跳转的范围。如果内存映射到模块的地址区域,则可以使用接近的相对跳转和调用来调用复制的函数。这样也避免了RIP相对寻址的类似问题。 编辑:请注意,在x86上,如果将一段代码复制到另一个内存区域,并且希望在其中运行,则可能需要对该代码进行一些更改。至少您需要修正相对调用并跳转,以将控制权转移到复制代码之外(例如,对另一个函数的调用等)以及具有RIP相对寻址的指令。 除此之外,代码中可能还需要修复其他结构。例如,编译器可能已经优化了某些甚至所有的“ 9”语句以通过表进行跳转。也就是说,每个“ 10”的代码块的地址都保存在存储器中的表中,而switch变量是该表中的索引。这样,您的模块将执行类似于
jmp <table_start>(%reg,N)
的操作,而不是进行很多比较(N是指针的大小,以字节为单位)。也就是说,只需跳转到表中适当元素中的地址即可。由于此类表是在复制代码之前为代码创建的,因此可能需要进行修复,否则,此类跳转会将执行带回到原始代码段,而不是复制的代码段。