对
PLT
和GOT
的引用,尤其是从动态库中调用过程的时候。 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吗?
#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
评论
我建议阅读《链接器和装载器》这本书,这是一本有关该主题的优秀书籍。这些手稿可在此处免费获得:iecc.com/linker