3

我正在尝试重新实现旧的庞然大物内核拦截(在这个 Phrack 问题中描述)。

替换 32 位函数调用的代码如下:

#define SYSMAPADDR 0x12345678
#define CODESIZE 7
static char acct_code[7] = "\xb8\x00\x00\x00\x00"/*movl $0, %eax*/
"\xff\xe0";/*jmp *%eax*/
*(long*)&acct_code[1] = (long)my_hijacking_function;
// here, use either set_pages_rw or trick CR0 to do this:
memcpy(SYSMAPADDR, acct_code, CODESIZE);

但是原函数的64位地址是0xffffffff12345678(内核位于低内存)。

那么(长)新函数指针是否只适合movl指令的 4 个\x00字节?

顺便说一句,请将此链接到我可以用模块替换 Linux 内核功能吗?用Linux内核中的模块覆盖功能,上面描述的hacky方法更灵活(可以拦截非外部函数=>无需重新编译内核)。

4

3 回答 3

4

在任何 x86(32 位或 64 位)上,都无法直接且无条件地跳转到位移大于 2GB 的地址。

当我前段时间编写一个迂回库时,我能想出的重定向程序流(对于 x86-64)的最佳选择涉及按M字节备份目标函数的序言并用两条指令覆盖目标函数的序言。

我使用%r11寄存器而不是累加器。根据AMD64 ABI Draft 0.99.5%r11是一个临时寄存器,不会在函数调用之间保留。

第一条指令 ,movq $addr, %r11就像它看起来的那样:它将指定的地址加载到寄存器中。第二条指令,jmp *%r11强制无条件间接跳转到存储在%r11中的地址。

附加到备份指令的末尾应该是另一个无条件间接跳转回原始目标的函数,跳转到被覆盖指令之后的地址。然后,当你想调用原始函数时,你可以调用备份函数序言的地址,程序流程照常进行。

请记住,要备份的字节数M必须是存储/跳转指令的大小与被覆盖指令的剩余部分的总和。在执行此伏都教之后,您不想留下任何部分说明。

于 2011-04-19T03:21:17.450 回答
2

注意:我假设这是针对 x86_64 的。

函数指针是 64 位的,movl指令零扩展为 64 位寄存器,因此您必须重写机器代码。你想要的指令大概是48 B8 (imm64)(即movq ..., %rax),我觉得跳转指令可以不用管,但是我对这个了解不多。您可能应该在您的问题中添加“x86-64”和“程序集”标签。

于 2011-04-16T19:01:07.683 回答
1

您可以使用JMP rel32(0xE9) 操作从当前地址执行 32 位相对跳转。这将允许您以 5 个字节跳转到源地址 2GB 范围内的任何位置。它还具有不会破坏 %eax 的优点(这在您的情况下可能很重要,也可能不重要)。

也就是说,我建议改为查看kprobes API。这将为您处理运行时修补的所有艰苦工作。它还处理应用于同一功能的多个标记和其他类似的问题,并且可以移植到多个平台。特别是,如果您的猴子修补方法正在使用中,如果编译进去,它可能会与标记 API 冲突,从而导致崩溃。如果动态可修补代码位于函数的前几个字节(LOCK 前缀等),也会导致崩溃。

您可能还想了解ftrace 的工作原理- 根据内核配置,挂接到 ftrace 可能会更快一些。

于 2011-03-07T18:19:14.353 回答