在Linux内核中包装函数时遇到问题

Cor*_*son 11 linux kernel wrapper

我写了一个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");
Run Code Online (Sandbox Code Playgroud)

它工作正常.我使用此方法来定位security_file_mmap,security_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;
}
Run Code Online (Sandbox Code Playgroud)

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

invalid opcode: 0000 [#1] SMP 
RIP: 0010:[<ffffffff8117b006>]  [<ffffffff8117b006>] security_bprm_check+0x6/0x310
Run Code Online (Sandbox Code Playgroud)

我不知道问题是什么.我尝试了几种不同类型的锁定方法(详情请参见start/stop_my_code的内部)无济于事.要触发内核OOPS,请编写一个简单的bash while循环,无休止地启动后台"ls"命令.大约一分钟后,它就会发生.

我在RHEL6内核上测试它,也适用于Ubuntu 10.04 LTS(2.6.32 x86_64).

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

任何帮助表示赞赏!

Eug*_*ene 9

看来,在security_bprm_check()调用函数之前,它的开始并没有完全恢复.oops发生在security_bprm_check+0x6,即你放在那里的跳​​跃之后,所以看起来,跳跃的某些部分仍然存在于那一刻.我现在不能说为什么会发生这种情况.

看看x86上的Kernel Probes(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相对寻址的指令.

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