I am building an ELF binary which needs to be able to process and reverse its own relocations at runtime. (The reversing will happen in a separate buffer, not in the original code page, obviously.) The purpose of this is so that the module contents in memory can be HMAC'd and compared against a known good value calculated from the module on disk, to ensure no corruption has occurred. I'm aware that this is somewhat unusual, but it's a requirement of a standard that we have to adhere to.
I've been able to reverse all of the relocations in the binary except for the R_X86_64_JUMP_SLOT
relocations which happen in the Global Offset Table. . Looking at the relocation entries in my test module's .rela.plt
section with readelf -a mylib.so
, I see these relocations:
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
...
These offsets point to the global offset table (specifically .got.plt
) so I can get more info with 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
Note that this binary is little endian. So the offsets in .got.plt
, i.e. 0x1010, 0x1020, 0x1030...
seem to always point to the endbr64
instruction in the corresponding PLT entry. That's similar to the info in this answer which quotes "At startup time the dynamic linker fills the GOT slot with the address pointing to the second instruction of the appropriate PLT entry." However, in my case it seems to point to the fourth instruction in the PLT entry. That answer further suggests that this relocation is done by adding the module's base address to the original GOT value.
However, when I run my test program and look at the actual value in memory after dynamic linking, it's not what I expect. For instance, running it now, the GOT entry for at 0x5020
for processRelocations
ends up with the value 0x7FF06A0F5AC5
. The base address of my module in memory for that run was 0x7ff06a0f4000
. Subtracting this yields 0x1AC5
, which actually appears to be the value of the symbol table entry for that function:
Symbol table '.dynsym' contains 34 entries:
Num: Value Size Type Bind Vis Ndx Name
...
32: 0000000000001ac5 491 FUNC GLOBAL DEFAULT 10 processRelocations
However, the symbol table entry is not what I want; I need to get back to the original value of 0x1020
, and I don't see that value anywhere in the symbol table either.
How can I reverse these relocations at runtime to find their original values?