我正在尝试重新实现老式的内核拦截(在this Phrack issue中描述)。
替换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(内核位于低内存中)。
那么(long)新函数指针是否只适合 movl 指令的4个 \ x00 字节?
顺便说一下,请将此链接到Can I replace a Linux kernel function with a module?和Overriding functionality with modules in Linux kernel,上面介绍的hacky方法更灵活(可以拦截非外部函数=>无需重新编译内核)。
答案 0 :(得分:4)
在任何x86(32或64位)上无法直接无条件地跳转到位移大于2GB的地址。
当我前段时间写一个绕道库时,我可以提出的重定向程序流程(x86-64)的最佳选项包括用M
字节备份目标函数的序言并覆盖目标函数序言有两条指示。
我使用%r11 寄存器而不是累加器。根据{{3}},%r11 是一个临时寄存器,不会在函数调用中保留。
第一条指令movq $addr, %r11
完全按照它的样子执行:它将指定的地址加载到寄存器中。第二条指令jmp *%r11
强制无条件地间接跳转到存储在%r11 中的地址。
附加到备份指令的末尾应该是另一个无条件间接跳回到原始目标的函数,到覆盖指令后的一个地址。然后,当您要调用原始文件时,可以调用备份函数序言的地址,并且程序流程会照常继续。
请记住,要备份的字节数M
必须是存储/跳转指令大小与覆盖指令的其余部分之和。做完这个伏都教后,你不想留下任何部分指示。
答案 1 :(得分:2)
注意:我假设这是针对x86_64。
函数指针是64位,movl
指令零扩展到64位寄存器,因此您必须重写机器代码。你想要的指令可能是48 B8 (imm64)
(即movq ..., %rax
),我认为跳转指令可以单独留下,但我对此并不了解。您应该在问题中添加'x86-64'和'assembly'标签。
答案 2 :(得分:1)
您可以使用JMP rel32
(0xE9)操作从当前地址执行32位相对跳转。这将允许您以5个字节跳转到源地址的2GB范围内的任何位置。它还有一个优点,它不会破坏%eax(在你的情况下这可能或不重要)。
那就是说,我建议改为查看kprobes API。这为您处理运行时修补的所有艰苦工作。它还处理应用于相同功能的多个标记和其他此类恶意,并且可移植到多个平台。特别是,如果您正在使用猴子修补方法,如果编译它可能会与标记API发生冲突,从而导致崩溃。如果动态可修补代码位于函数的前几个字节(LOCK前缀等)中,它也会导致崩溃。
您可能还想了解ftrace works的方式 - 根据内核配置,可能会更快地挂钩到ftrace。