Lui*_*BOL 4 linux assembly function shared-libraries
在我的Linux程序中,我需要一个接收地址的函数,addr
并检查callq
放置的指令是否addr
正在调用func
从共享库加载的特定函数.我的意思是,我需要检查自己是否有这样的事情callq func@PLT
在addr
.
那么,在Linux上,如何func
从callq func@PLT
指令中获取函数的真实地址?
在动态链接器解析实际加载地址后,您只能在运行时找到相关信息.
警告:接下来是稍微深一点的魔法......
为了说明发生了什么,请使用调试器:
#include <stdio.h>
int main(int argc, char **argv) { printf("Hello, World!\n"); return 0; }
Run Code Online (Sandbox Code Playgroud)
编译它(gcc -O8 ...
).objdump -d
在二进制显示(优化printf()
被替换puts()
为不能承受的普通字符串...):
Disassembly of section .init: [ ... ] Disassembly of section .plt: 0000000000400408 <__libc_start_main@plt-0x10>: 400408: ff 35 a2 04 10 00 pushq 1049762(%rip) # 5008b0 <_GLOBAL_OFFSET_TABLE_+0x8>> 40040e: ff 25 a4 04 10 00 jmpq *1049764(%rip) # 5008b8 <_GLOBAL_OFFSET_TABLE_+0x10> [ ... ] 0000000000400428 <puts@plt>: 400428: ff 25 9a 04 10 00 jmpq *1049754(%rip) # 5008c8 <_GLOBAL_OFFSET_TABLE_+0x20> 40042e: 68 01 00 00 00 pushq $0x1 400433: e9 d0 ff ff ff jmpq 400408 <_init+0x18> [ ... ] 0000000000400500 <main>: 400500: 48 83 ec 08 sub $0x8,%rsp 400504: bf 0c 06 40 00 mov $0x40060c,%edi 400509: e8 1a ff ff ff callq 400428 <puts@plt> 40050e: 31 c0 xor %eax,%eax 400510: 48 83 c4 08 add $0x8,%rsp 400514: c3 retq
现在加载它gdb
.然后:
$ gdb ./tcc GNU gdb Red Hat Linux (6.3.0.0-0.30.1rh) [ ... ] (gdb) x/3i 0x400428 0x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32> 0x40042e: pushq $0x1 0x400433: jmpq 0x400408 (gdb) x/gx 0x5008c8 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>: 0x000000000040042e
注意这个值指向第一个之后的指令jmpq
; 这意味着puts@plt
在第一次调用时,插槽将简单地"通过"到:
(gdb) x/3i 0x400408 0x400408: pushq 1049762(%rip) # 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8> 0x40040e: jmpq *1049764(%rip) # 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16> 0x400414: nop (gdb) x/gx 0x5008b0 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8>: 0x0000000000000000 (gdb) x/gx 0x5008b8 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16>: 0x0000000000000000
函数地址和参数尚未初始化.
这是程序加载后的状态,但在执行之前.现在开始执行它:
(gdb) break main Breakpoint 1 at 0x400500 (gdb) run Starting program: tcc (no debugging symbols found) (no debugging symbols found) Breakpoint 1, 0x0000000000400500 in main () (gdb) x/i 0x400428 0x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32> (gdb) x/gx 0x5008c8 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>: 0x000000000040042e
因此,这并没有改变尚未 -但目标(在GOT
为内容libc
初始化)现在是不同的:
(gdb) x/gx 0x5008b0 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8>: 0x0000002a9566b9a8 (gdb) x/gx 0x5008b8 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16>: 0x0000002a955609f0 (gdb) disas 0x0000002a955609f0 Dump of assembler code for function _dl_runtime_resolve: 0x0000002a955609f0 <_dl_runtime_resolve+0>: sub $0x38,%rsp [ ... ]
即在程序加载时,动态链接器将首先解析" init
"部分.它将GOT
引用替换为重定向到动态链接代码的指针.
因此,当首次通过.plt
引用调用外部到二进制函数时,它将再次跳转到链接器.让它做到这一点,然后检查程序 - 状态再次改变:
(gdb) break *0x0000000000400514 Breakpoint 2 at 0x400514 (gdb) continue Continuing. Hello, World! Breakpoint 2, 0x0000000000400514 in main () (gdb) x/i 0x400428 0x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32> (gdb) x/gx 0x5008c8 0x5008c8 : 0x0000002a956c8870 (gdb) disas 0x0000002a956c8870 Dump of assembler code for function puts: 0x0000002a956c8870 <puts+0>: mov %rbx,0xffffffffffffffe0(%rsp) [ ... ]
所以你的重定向到libc
现在 - 最终得到解决的PLT
参考puts()
.
链接器的指令在哪里插入实际的函数加载地址(我们已经看过它的地址_dl_runtime_resolve
来自ELF二进制文件中的特殊部分:
$ readelf -a tcc [ ... ] Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align [ ... ] INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] [ ... ] Dynamic section at offset 0x700 contains 21 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] [ ... ] Relocation section '.rela.plt' at offset 0x3c0 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 0000005008c0 000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0 0000005008c8 000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
ELF不仅仅是上面的内容,但是这三个部分告诉内核的二进制格式处理程序"这个ELF二进制文件有一个解释器 "(它是动态链接器)需要首先加载/初始化,它需要 libc.so.6
,并且这些偏移0x5008c0
和0x5008c8
在该程序的可写数据部分必须取代的通过用于加载地址__libc_start_main
并puts
分别地,当实际执行动态链接的步骤.
从ELF的角度来看,究竟是怎么发生的,取决于解释器的细节(也就是动态链接器实现).