如何反转 R_X86_64_JUMP_SLOT 重定位?

3

我正在构建一个ELF二进制文件,需要在运行时能够处理并反转自身的重定位。(显然,反转将发生在单独的缓冲区中,而不是在原始代码页中。)这样做的目的是为了让内存中的模块内容进行HMAC计算,并与从磁盘上的模块计算出的已知好值进行比较,以确保没有发生损坏。我知道这有些不寻常,但这是我们必须遵守的标准要求。

我已经能够反转二进制文件中的所有重定位,除了 全局偏移表中发生的 R_X86_64_JUMP_SLOT 重定位。查看测试模块的.rela.plt部分中的重定位条目,使用readelf -a mylib.so命令,我看到了以下重定位:

Relocation section '.rela.plt' at offset 0x968 contains 20 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000005018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 printf + 0
000000005020  002000000007 R_X86_64_JUMP_SLO 0000000000001ac5 processRelocations + 0
000000005028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 memcpy + 0
000000005030  000400000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0 
...

这些偏移量指向全局偏移表(特别是 .got.plt ),因此我可以使用 objdump -d -s -j .plt -j .got.plt mylib.so 获取更多信息。
Disassembly of section .got.plt:

0000000000005000 <_GLOBAL_OFFSET_TABLE_>:
    5000:       80 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00     .N..............
        ...
    5018:       10 10 00 00 00 00 00 00 20 10 00 00 00 00 00 00     ........ .......
    5028:       30 10 00 00 00 00 00 00 40 10 00 00 00 00 00 00     0.......@.......
...

Disassembly of section .plt:

0000000000001000 <.plt>:
    1000:       ff 35 02 40 00 00       pushq  0x4002(%rip)        # 5008 <_GLOBAL_OFFSET_TABLE_+0x8>
    1006:       f2 ff 25 03 40 00 00    bnd jmpq *0x4003(%rip)     # 5010 <_GLOBAL_OFFSET_TABLE_+0x10>
    100d:       0f 1f 00                nopl   (%rax)
    1010:       f3 0f 1e fa             endbr64
    1014:       68 00 00 00 00          pushq  $0x0
    1019:       f2 e9 e1 ff ff ff       bnd jmpq 1000 <.plt>
    101f:       90                      nop
    1020:       f3 0f 1e fa             endbr64

请注意,此二进制文件为小端。因此,.got.plt中的偏移量,即0x1010、0x1020、0x1030...似乎总是指向相应PLT条目中的endbr64指令。这类似于这个答案中的信息,该答案引用“在启动时,动态链接器使用指向适当PLT条目的第二条指令的地址填充GOT插槽。”但是,在我的情况下,它似乎指向PLT条目中的第四条指令。该答案进一步建议通过将模块的基地址添加到原始GOT值来执行此重定位。
然而,当我运行我的测试程序并查看动态链接后内存中的实际值时,它与我预期的不同。例如,现在运行,processRelocations的GOT条目位于0x5020处,最终值为0x7FF06A0F5AC5。该运行时模块的基地址在内存中为0x7ff06a0f4000。减去这个值得到的结果是0x1AC5,实际上是该函数的符号表条目的值。
Symbol table '.dynsym' contains 34 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
   ...
   32: 0000000000001ac5   491 FUNC    GLOBAL DEFAULT   10 processRelocations

然而,符号表项并不是我想要的;我需要回到原始值 0x1020,但我在符号表中也没有看到这个值。

我如何在运行时反转这些重定位以找到它们的原始值?


2
你考虑过使用-fno-plt进行编译吗?这样就可以完全不需要PLT条目,也希望不需要它们关联的惰性绑定机制。(GCC将使用call *printf@GOTPCREL(%rip),这会强制进行早期绑定:在进程启动时解析GOT条目) - Peter Cordes
@PeterCordes 很好,看起来这似乎解决了问题!但愿我早就知道那个选项!我仍然很好奇是否有一种方法可以实际上反转这些重定位,但是-fno-plt至少可以解决我的问题。谢谢!如果你把这个作为答案,如果没有更好的解决方案,我会接受它。 - Mitch Lindgren
当然,是的,至少出于好奇心找到问题的答案,而不仅仅是避免它。 - Peter Cordes
1个回答

1

您可以通过使用-fno-plt进行编译,以便根本没有PLT条目,因此相关的惰性绑定机制不会发挥作用,从而避免这个问题。

GCC和clang将使用call *printf@GOTPCREL(%rip)强制进行早期绑定:在进程启动时解析GOT条目。这使得每个调用更有效率, 一些distro(例如Arch GNU/Linux)已经以这种方式编译其软件包。

简而言之:这通常是一个好选择,只是在当前GCC和clang distro配置中默认情况下还没有开启(但很快就会有)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接