gpt4 book ai didi

linux - 如何从 `func`获取 `callq func@PLT`的实际地址

转载 作者:行者123 更新时间:2023-12-02 14:07:25 26 4
gpt4 key购买 nike

在我的 Linux 程序中,我需要一个函数,它接受地址 addr 并检查位于 addrcallq 指令是否正在调用从共享库加载的特定函数func。我的意思是,我需要检查 addr 处是否有类似 callq func@PLT 的内容。

那么,在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  $0x10x400433:       jmpq   0x400408(gdb) x/gx 0x5008c80x5008c8 <_GLOBAL_OFFSET_TABLE_+32>:    0x000000000040042e

请注意,该值指向紧接第一个 jmpq 之后的指令。 ;这意味着 puts@plt第一次调用时,槽将简单地“落入”:

(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>:    0x0000000000000000

函数地址和参数尚未初始化。
这是程序加载后、执行前的状态。现在开始执行它:

(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

所以这还没有改变 - 但目标(GOT初始化的libc内容)现在不同了:

(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    $0x38,%rsp[ ... ]

即在程序加载时,动态链接器将首先解析“init”部分。它取代了 GOT带有重定向到动态链接代码的指针的引用。

因此,当第一次通过 .plt 调用外部二进制函数时引用,它会再次跳转到链接器。让它这样做,然后检查程序 - 状态再次改变:

(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)[ ... ]

因此,您的重定向直接进入 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 + Addend0000005008c0  000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 00000005008c8  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0

ELF 不仅仅是上面这些,但这三个部分告诉内核的二进制格式处理程序“这个 ELF 二进制文件有一个需要加载的解释器”(这是动态链接器)/首先初始化,它需要 libc.so.6 ,并且抵消 0x5008c00x5008c8程序可写数据部分中的 必须替换__libc_start_main 的加载地址和puts ,分别是实际执行动态链接步骤时的情况。

从 ELF 的角度来看,这到底是如何发生的,取决于解释器(又名动态链接器实现)的细节。

关于linux - 如何从 `func`获取 `callq func@PLT`的实际地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15066725/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com