
那么,在linux上,如何从callq func @ plt指令到达函数func的真实地址?
解决方法 在动态链接器解析实际加载地址后,您只能在运行时找到相关信息.警告:接下来是稍微深一点的魔法……
为了说明正在发生的事情,请使用调试器:
#include <stdio.h>int main(int argc,char **argv) { printf("Hello,World!\n"); return 0; } 编译它(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 ./tccGNU gdb Red Hat linux (6.3.0.0-0.30.1rh)[ ... ](gdb) x/3i 0x4004280x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_table_+32>0x40042e: pushq(gdb) x/3i 0x4004080x400408: pushq 1049762(%rip) # 0x5008b0 <_GLOBAL_OFFSET_table_+8>0x40040e: jmpq *1049764(%rip) # 0x5008b8 <_GLOBAL_OFFSET_table_+16>0x400414: nop(gdb) x/gx 0x5008b00x5008b0 <_GLOBAL_OFFSET_table_+8>: 0x0000000000000000(gdb) x/gx 0x5008b80x5008b8 <_GLOBAL_OFFSET_table_+16>: 0x0000000000000000x10x400433: jmpq 0x400408(gdb) x/gx 0x5008c80x5008c8 <_GLOBAL_OFFSET_table_+32>: 0x000000000040042e
请注意,此值指向第一个jmpq之后的指令;这意味着在第一次调用时,puts @ plt slot将简单地“落到”:
(gdb) break mainBreakpoint 1 at 0x400500(gdb) runStarting program: tcc(no deBUGging symbols found)(no deBUGging symbols found)Breakpoint 1,0x0000000000400500 in main ()(gdb) x/i 0x4004280x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_table_+32>(gdb) x/gx 0x5008c80x5008c8 <_GLOBAL_OFFSET_table_+32>: 0x000000000040042e
函数地址和参数尚未初始化.
这是程序加载后的状态,但在执行之前.现在开始执行它:
(gdb) x/gx 0x5008b00x5008b0 <_GLOBAL_OFFSET_table_+8>: 0x0000002a9566b9a8(gdb) x/gx 0x5008b80x5008b8 <_GLOBAL_OFFSET_table_+16>: 0x0000002a955609f0(gdb) disas 0x0000002a955609f0Dump of assembler code for function _dl_runtime_resolve:0x0000002a955609f0 <_dl_runtime_resolve+0>: sub(gdb) break *0x0000000000400514Breakpoint 2 at 0x400514(gdb) continueContinuing.Hello,World!Breakpoint 2,0x0000000000400514 in main ()(gdb) x/i 0x4004280x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_table_+32>(gdb) x/gx 0x5008c80x5008c8 : 0x0000002a956c8870(gdb) disas 0x0000002a956c8870Dump of assembler code for function puts:0x0000002a956c8870 <puts+0>: mov %rbx,0xffffffffffffffe0(%rsp)[ ... ]x38,%rsp[ ... ]
所以这还没有改变 – 但是目标(libc初始化的GOT内容)现在是不同的:
$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 + Addend0000005008c0 000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 00000005008c8 000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
即在程序加载时,动态链接器将首先解析“init”部分.它将GOT引用替换为重定向到动态链接代码的指针.
因此,当首次通过.plt引用调用外部到二进制函数时,它将再次跳转到链接器.让它做到这一点,然后检查程序 – 状态再次改变:
所以现在你的重定向到了libc – 对puts()的plt引用最终得到了解决.
链接器的指令在哪里插入实际的函数加载地址(我们已经看到它为_dl_runtime_resolve做的那些来自ELF二进制文件中的特殊部分:
ELF不仅仅是上面的内容,但这三个部分告诉内核的二进制格式处理程序“这个ELF二进制文件有一个解释器”(它是动态链接器)需要先加载/初始化,它需要libc.so.在实际执行动态链接的步骤时,程序的可写数据部分中的偏移量0x5008c0和0x5008c8必须分别由__libc_start_main和put的加载地址替换.
从ELF的角度来看,究竟是怎么发生的,取决于解释器的细节(也就是动态链接器实现).
总结以上是内存溢出为你收集整理的linux – 如何从`callq func @ PLT`获取`func`的实际地址全部内容,希望文章能够帮你解决linux – 如何从`callq func @ PLT`获取`func`的实际地址所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)