有时,在拆卸x86二进制文件时,我会偶然发现
PLTGOT的引用,尤其是从动态库中调用过程的时候。 gdb中的程序:

(gdb) info file
Symbols from "/home/user/hello".
Local exec file: `/home/user/hello', file type elf64-x86-64.
Entry point: 0x400400
    0x0000000000400200 - 0x000000000040021c is .interp
    0x000000000040021c - 0x000000000040023c is .note.ABI-tag
    0x000000000040023c - 0x0000000000400260 is .note.gnu.build-id
    0x0000000000400260 - 0x0000000000400284 is .hash
    0x0000000000400288 - 0x00000000004002a4 is .gnu.hash
    0x00000000004002a8 - 0x0000000000400308 is .dynsym
    0x0000000000400308 - 0x0000000000400345 is .dynstr
    0x0000000000400346 - 0x000000000040034e is .gnu.version
    0x0000000000400350 - 0x0000000000400370 is .gnu.version_r
    0x0000000000400370 - 0x0000000000400388 is .rela.dyn
    0x0000000000400388 - 0x00000000004003b8 is .rela.plt
    0x00000000004003b8 - 0x00000000004003c6 is .init
 => 0x00000000004003d0 - 0x0000000000400400 is .plt
    0x0000000000400400 - 0x00000000004005dc is .text
    0x00000000004005dc - 0x00000000004005e5 is .fini
    0x00000000004005e8 - 0x00000000004005fa is .rodata
    0x00000000004005fc - 0x0000000000400630 is .eh_frame_hdr
    0x0000000000400630 - 0x00000000004006f4 is .eh_frame
    0x00000000006006f8 - 0x0000000000600700 is .init_array
    0x0000000000600700 - 0x0000000000600708 is .fini_array
    0x0000000000600708 - 0x0000000000600710 is .jcr
    0x0000000000600710 - 0x00000000006008f0 is .dynamic
 => 0x00000000006008f0 - 0x00000000006008f8 is .got
 => 0x00000000006008f8 - 0x0000000000600920 is .got.plt
    0x0000000000600920 - 0x0000000000600930 is .data
    0x0000000000600930 - 0x0000000000600938 is .bss


然后拆卸时(puts@plt):

(gdb) disas foo
Dump of assembler code for function foo:
   0x000000000040050c <+0>: push   %rbp
   0x000000000040050d <+1>: mov    %rsp,%rbp
   0x0000000000400510 <+4>: sub    q4312078qx10,%rsp
   0x0000000000400514 <+8>: mov    %edi,-0x4(%rbp)
   0x0000000000400517 <+11>:    mov    q4312078qx4005ec,%edi
=> 0x000000000040051c <+16>:    callq  0x4003e0 <puts@plt>
   0x0000000000400521 <+21>:    leaveq
   0x0000000000400522 <+22>:    retq
End of assembler dump.


那么,什么是GOT / PLT吗?

评论

我建议阅读《链接器和装载器》这本书,这是一本有关该主题的优秀书籍。这些手稿可在此处免费获得:iecc.com/linker

#1 楼

PLT代表过程链接表,简单来说,它用于调用在链接时地址未知的外部过程/函数,并由动态链接器在运行时解析。

GOT代表“全局偏移表”,类似地用于解析地址。 PLT和GOT以及其他重定位信息在本文中都有更详细的说明。二十部分!):此处为“链接器第1部分”的入口点。

评论


有没有更快的阅读20部分内容的方法?为什么在第一部分到下一部分没有链接?

– 0x90
2013年6月8日20:31



@ 0x90实际上,直到这个精彩系列的第4部分,才提到PLT和GOT,如果您只需要此信息,请访问以下链接:airs.com/blog/archives/41。我还是建议您阅读整本书,这是一个很棒的系列!

– Ishay Peled
2015年5月10日11:00

@ 0x90,因为他当然希望您为此写一个链接器

– Ciro Santilli郝海东冠状病六四事件法轮功
2015年5月29日14:58

20篇博客文章的索引:a3f.at/lists/linkers

–a3f
16-2-23在7:47

您也可以只更改URL末尾的数字! airs.com/blog/archives/39 airs.com/blog/archives/40 airs.com/blog/archives/41等。

– K. Dackow
19/12/7在20:22

#2 楼

让我总结一下https://reverseengineering.stackexchange.com/a/1993/12321上给出的​​链接,但现在不进行认真的分析。

当Linux内核+动态链接器要使用exec运行二进制文件时,传统上,它只是在链接期间将ELF节转储到链接器指定的已知内存位置。 />因此,只要您的编码是:


在代码内部引用了全局变量
从代码内部称为函数

编译器+链接器可以将地址硬编码到程序集中,然后一切正常。

但是,在处理共享库时我们该怎么做呢?共享库每次都必须加载到潜在的不同地址,以避免两个之间发生冲突共享库?

天真的解决方案是将重定位元数据保留在最终可执行文件上,就像实际的链接程序一样,并且每当加载程序时,让动态链接程序遍历每个访问并对其进行修补。加上正确的地址。

但是,这可能会非常耗时,因为可能会有很多引用某个程序上的补丁程序,然后该程序将需要很长时间才能开始运行。

解决方案与往常一样是添加另一个间接级别:GOT和PLT,它们分别是由编译系统+动态链接程序设置的两个额外的内存块。

程序启动后,动态链接程序检查共享库的地址,并对GOT和PLT进行黑客攻击,以便指向正确地访问所需的共享库符号:




程序访问共享库的全局变量时,编译器+链接器会发出两个内存访问:

mov    0x200271(%rip),%rax        # 200828 <_DYNAMIC+0x1a0>
mov    (%rax),%eax


第一个将动态链接器先前设置的GOT变量的真实地址加载到rax中。

第二个间接访问实际上是通过rax中的地址间接访问变量的。对于代码,事情要复杂一些。



第一次调用该函数时,PLT代码使用存储在GOT中的偏移量来确定共享库的实际最终位置。函数,然后:


存储此预先计算的值
跳转到其中

下次调用该函数时,该值已被

由于这种惰性解析机制:即使共享库中有很多共享库,程序也可以快速开始运行。符号
我们可以通过使用LD_PRELOAD变量即时替换函数




现在,位置独立可执行文件(PIE)是发行版(例如Ubuntu)上的默认设置18.04。

Muc与可执行文件库一样,这些可执行文件经过编译,因此每次执行时都可以将它们放置在内存中的随机位置,以使某些漏洞难以利用。

因此,这是不可能的在这种情况下,就不再对绝对函数和变量地址进行硬编码。可执行文件必须是:


用户指令指针相对地址,如果汇编语言上可用的话,例如:



B执行26位跳转,B.cond 19位进行跳转

“ LDR(文字)”执行19位加载

ADR计算其他指令可以使用的21位相对地址
否则使用GOT / PLT