高级栈溢出01 动态链接简介
为什么要有动态链接以及动态链接使用的两个表的由来:
因为静态链接中库文件会被多次加载,造成严重空间浪费。
GOT表:全局偏移量表
我们在源文件中extern了一个函数,编译源文件的时候我们并不知道这个函数是在我们的源代码里还是在动态库里,所以需要加一个表来存放这个extern函数的真实地址。
PLT:过程链接表
动态库用时加载,用完可卸载,延迟绑定的属性,为了实现该属性我们还需要在GOT表的基础上加一个PLT表用于延迟绑定。
我们设计程序的时候讲究低耦合性,系统设计的时候也是如此。所谓低耦合性是指每一步尽量和其他步骤没有太大关系,这样我们修改其中的一个部分对其他部分的影响较小,也比较容易维护。
假设我们在源文件中extern int add()和extern int hello()两个函数,add函数在动态库里,hello函数是我们自己写的另一个源文件里,我们该如何编译源文件使其生成的call指令具有统一性。因为add和hello两个函数在哪里应该是我们链接的
时候才应该考虑的事情,否则会加大编译器的实现难度,而且编译器和链接器强耦合。
解决方法是加一个PLT表,让我们在第一次真正用到某个外部函数的时候再进行查找调用,而我们的源代码可以直接生成类似call hello@plt和call add@plt等相同格式的指令。第一次用到某个函数时查找是怎么实现的哪?由于PLT在代码段运行时不可写,我们只好让PLT表从编译的时就指向GOT表,GOT表先空着,之后查到函数真实地址后就填写到GOT表对应位置。怎么查找哪?在PLT的 表头上设置一个查找函数就好了,我们给函数编上号(比如.rel.plt的下标或者是PLT表中的偏移),给查找函数的参数就是这个编号我们的查找函数就知道该查找谁了,然后将真实地址填到GOT表里,这就实现了延迟绑定。
两个表的结构
GOT表:
GOT表的前三项比较特殊:
第一项:.dynamic段的地址,我们定义它是GOT[0]
第二项:模块的ID,或者说是link_map更准确。GOT[1]
第三项:_dl_runtime_resolve(link_map_obj, reloc_index),这个函数是查找动态链接器里查找函数地址的函数,GOT[2]
第四、五......项:都是引用函数表项,第一次运行时指向PLT相关项,后续将指向的函数真正地址。
PLT表项:
PLT[0]:会先push GOT[1],即先将本模块ID压入栈中,然后jmp GOT[2],这是在构造调用_dl_runtime_resolve()的汇编指令,只是一部分,它只是传入了一个参数,还有一个参数需要在具体表项中传递进来。
之后的每个PLT表项都是类似的:
func@plt:
jmp func@got
push n
jmp PLT[0]
这就是PLT和GOT结合使用时的精巧设计。
调用一个库函数的过程:
我们第一次调用的时候会进入func@plt,然后我们跳到了GOT表的对应位置,假设为GOT[3]。由于是第一次调用GOT表,此时GOT[3]中保存的地址是func@plt+0x4的位置,即push n的地方然后继续执行jmp PLT[0]。
进入PLT[0]后又push link_map,之后就跳转到了_dl_runtime_resolve()。我们此时发现先前两个push 指令正好是_dl_runtime_resolve()的参数。然后_dl_runtime_resolve()会查找func的真实地址,并将其填入GOT[4]。之后调用func后返回。
我们接下来再次调用func@plt时,我们还是会进入PLT表对用条目,然后继续jmp func@got,但是由于GOT[4]已经是func的真实地址了,我们直接就会执行func然后返回,不会再进行复杂的查找操作。

本人版权意识淡薄,图片自:https://www.yuque.com/hxfqg9/bin/erh0l7
动态链接相关的段:
.interp
动态链接需要一个加载器,这个加载器怎么确定用哪个?这个加载器是由ELF文件确定的,就是这个.interp段中存储的字符串指定。这个段内容非常简单,就保存了一个字符串,而就是这个字符串指定了动态链接器的路径。
这个路径一般是个软链接,软连接就像windows下的快捷方式一样。
.dynamic
这个节里包含指向.dynsym、.dynstr、.
.ret.dyn和.rel.plt
这两个表是重定位表,分别相当于静态链接中的rel.data和rel.text。rel.dyn修正的是数据引用,修正位置是.got以及数据段;而rel.plt是对函数引用的修正,修正位置在.got.plt。你问我为什么需要重定位?因为PIC的关系即地址无关代码和普通寻址方式之间差异比较大:相对寻址和绝对寻址之间的差距需要使用重定位修正。
ELF文件中令人头大的四个节分别是.plt .plt.got .got .got.plt
这四个节放到一起就不知道各自是干什么的了,现在总结以下:
| section | 所在 segment | section 属性 | 用途 |
|---|---|---|---|
| .plt | 代码段 | RE(可读,可执行) | .plt section 实际就是通常所说的过程链接表(Procedure Linkage Table, PLT) |
| .plt.got | 代码段 | RE | .plt.got section 用于存放 __cxa_finalize 函数对应的 PLT 条目 |
| .got | 数据段 | RW(可读,可写) |
|
| .got.plt | 数据段 | RW | .got.plt section 用于存放需要延迟绑定的函数的地址 |

浙公网安备 33010602011771号