如何通过linux内核中的c指针覆盖x86`movq`指令的操作数值

Posted

技术标签:

【中文标题】如何通过linux内核中的c指针覆盖x86`movq`指令的操作数值【英文标题】:How to overwrite operand value of x86 `movq` instruction via c pointer in linux kernel 【发布时间】:2021-10-21 17:40:15 【问题描述】:

目前我已经在linux内核的一页pageA上使用汇编编写了一个代码段。代码是

SYM_CODE_START(sr_function)
    movq $0, %rdx
    movq $0x7ffff7fc6000, %rsi 
    movq $0x19016bc83000, %rcx
    movq $9, %rdx
    movq $0, %rdx
    jmp goto_fce_func
SYM_CODE_END(sr_function)

在运行时,我想通过c指针覆盖mov指令的第一个操作数,例如,我使用kmap(pageA)将页面pageA映射到内核空间。

也就是说,现在第一条指令movq $0, %rdx 的地址是0x1000

我想用C指针指向:

    将第一个操作数从值 0 更改为 0x19016bc83000 以用于第一个 mov instruction movq $0, %rdx

    将第一个操作数从值 0x7ffff7fc6000 更改为 0x19016bc74000 以获取第二个mov instruction movq $0x7ffff7fc6000, %rsi

    将第一个操作数从值0x19016bc83000 更改为0 以用于第三个mov instruction movq $0x19016bc83000, %rcx

你知道我该怎么做吗?

使用objdump -D对该代码段的反汇编显示

0000000000001000 <sr_function>:
    1000:   48 c7 c2 00 00 00 00    mov    $0x0,%rdx
    1007:   48 be 00 60 fc f7 ff    movabs $0x7ffff7fc6000,%rsi
    100e:   7f 00 00 
    1011:   48 b9 00 30 c8 6b 01    movabs $0x19016bc83000,%rcx
    1018:   19 00 00 
    101b:   48 c7 c2 09 00 00 00    mov    $0x9,%rdx
    1022:   48 c7 c2 00 00 00 00    mov    $0x0,%rdx
    1029:   e9 de ef ff ff          jmpq   c <goto_fce_func>

【问题讨论】:

在反汇编中,您可能会发现包含要修改的指令参数的字节偏移量。在运行时使用这些偏移量来查找字节并执行修改。 请注意,在某些架构上,代码页可能设置为不可写,而数据页可能为 noexec。 【参考方案1】:
1000:   48 c7 c2 00 00 00 00    mov    $0x0,%rdx
1007:   48 be 00 60 fc f7 ff    movabs $0x7ffff7fc6000,%rsi
100e:   7f 00 00 
1011:   48 b9 00 30 c8 6b 01    movabs $0x19016bc83000,%rcx

您的第一条指令将很难修补,因为您想用 64 位值修补它,但它目前已编码为处理 32 位立即数。

让我们看一下指令 - mov $0x0, %rdx 并逐字节了解它的编码:

0x48 - 此前缀字节指定要遵循的指令是 64 位大小的。 0xc7 - 这指定指令是mov reg, imm32 指令 0xc2 - 这指定目标寄存器(编码在底部 3 位中) - 在这种情况下,rdx(这是寄存器号 2) 0x00, 0x00, 0x00, 0x00 - 在 little endian 中编码您的 32 位立即数。

发生这种情况时,rdx 的前 32 位无论如何都会清零 - 但这会阻止您直接修补立即数,因为您需要访问所有 64 位。

对此最简单的解决方案是将您的汇编指令修补为movabs $0, %rdx - 汇编器将为此输出可修补的指令。


对于以下两个movabs 指令 - 它们具有相似的编码,除了它们的第二个字节(0xbe 用于第一个,0xb9 用于第二个)在字节内编码目标寄存器 :它们属于0xb8 + r操作码,其中+r编码目标寄存器:0xbe == 0xb8 + 6rsi是6号寄存器,0xb9 == 0xb8 + 1和rcx是1号寄存器。


一旦你有了三个指令,每个指令都有一个 64 位立即数要修补,修补本身就很容易了。您需要一个 char* 变量,该变量指向您要修补的指令 - 然后您希望将其增加适当的字节数,使其指向偏移量 - 然后您希望将其转换为 uint64_t* -并适当地修补它。

char* ptr; // points to e.g. the last movabs instructions
ptr += 2; // skip over 0x48 and 0xb9
*((uint64_t*)ptr) = 0; // zero out the entire immediate

【讨论】:

好的,谢谢,这对我很有帮助。除了这个话题,我想知道,你把编码的东西放在哪里了?能否请您提供一些链接,以便我可以了解有关汇编编码的更多信息? 当然 - 可能最好的彻底学习方法是阅读英特尔 x86 SDM 第 2 卷的第 2 章:software.intel.com/content/www/us/en/develop/download/… - 一旦您了解了基本编码的工作原理,这个网站就很棒了x86 参考 - ref.x86asm.net/coder64.html - 通常,在不使用反编译器的情况下对 x86 程序集进行逆向工程是通过实践经验了解更多信息的好方法。 orq $0, %rdx 也只需要 32 位立即数,所以我看不出这有什么帮助。 AFAIK 在一条指令中加载 64 位立即数的唯一方法是movabs。如果您要修改原始程序集,您是否可以不只做movabs $0, %rdx,假设您可以阻止汇编程序将其“优化”成mov @NateEldredge 你在这两个帐户上都是对的 :) 我会解决这个问题

以上是关于如何通过linux内核中的c指针覆盖x86`movq`指令的操作数值的主要内容,如果未能解决你的问题,请参考以下文章

x86汇编语言的MOV指令

在为 x86_64 构建 Linux 内核时如何禁用 CONFIG_PM

汇编指令和寄存器

AT&T x86_32 汇编_004_数据传递

如何查看linux内核打印信息

Linux x86-64 上物理内存中的用户空间和内核之间是不是存在显式拆分?