动态链接

PLT与GOT表均为动态链接过程中的重要部分

1GOT: Global Offset Table, 全局偏移表,包含所有需要动态链接的外部函数的地址(在第一次执行后)

Linux虚拟内存分段映射中,一般会分出三个相关的段:

1 .plt: 即上文提到的过程链接表,包含全部的外部函数跳转指令信息

2 .got.plt: 即下文将要表达的GOT表,与PLT表搭配使用,包含全部外部函数地址(第一次调用前为伪地址,具体见下)

3 .got : 存放其他全局符号信息,注意与.got.plt不同,与下文函数动态链接过程关系不大(所以了解不深请见谅,有兴趣的读者也欢迎分享)

简单来说,PLT表存放跳转相关指令,GOT表存放外部函数(符号)地址

PLT

2.之后的每个表项,分为两部分(三句):

1
2
3
4
5
;part 1
jmp *fun@got
;part 2
push offset
jmp plt[0]

3.实际上,每个PLT都可理解为一个程序 (thunk)

avatar

GOT

1.每一项为单个地址

2.第一项指向dynamic段

4.第三项指向_dl_runtime_resolve函数

5.之后每项一一对应PLT表中每个表项(序号不同)

avatar

今天以 2015-XDCTF-pwn200 这个题目文件作为例子分析

avatar

看到 write@plt,为什么后面加了@plt,因为这个是PLT表中的数据的地址

@plt函数是编译系统自己加的

这里我们在 write@plt 处下断点

r运行

这里是第一次调用 write函数

avatar

我们 si 跟进

发现有三行代码,jmp、push、jmp,我们要将这3条指令理解清楚

第一行代码是通过PLT表跳转到GOT表

avatar

我们看一下要跳到的地址

发现要跳到下一步的push

avatar

这里也说一下,在调用一个函数的时候有两种方法,一个是通过PLT表调用,一个则是通过GOT表调用,因为PLT表最终也是跳转到GOT表,GOT表中则是一个函数真正的地址,需要注意的是,在一个函数运行第一次之前,GOT表中的数据为@plt函数中下一条指令的地址

那么,剩下两行代码(push、jmp)的作用就可以理解为:找到真实的write函数地址

avatar

这里的push 0x20,是压入一个参数入栈,压入的参数给哪个函数用呢,是给_dl_runtime_resolve这个函数,这个参数就相当于函数的id,告诉_dl_runtime_resolve要去找哪一个函数的地址

1
jmp 0x8048370  //跳转到函数

这里跳转到的地址其实是PLT[0],我们来查看一下这个地址,如下图

avatar

我们在n下一步,和上面的概述对上了,跳到_dl_runtime_resolve函数了,正是由这个函数来确定write函数的真实地址

avatar

avatar

avatar

我们一路n下一步

avatar

发现将要跳到真正的write函数中去了,这里留意一下write函数的真实地址,会和下面做比较

我们将write函数执行完

avatar

write函数第一次执行完了

我们此时,再来查看一下write函数

通过disass main再来看一眼write@plt的地址

通过x命令查看,发现又来到jmp、push、jmp这个地方了

这个时候再来查看第一个jmp跳到的地址

请和上上图的write函数地址做比较,发现是一致的

avatar

所以在第一次执行write@plt之后,write函数的真实地址就会放到第一个jmp里面

在第二次执行时,就会直接跳转过去,也就能初步理解延迟绑定机制

当第一次调用函数时,如下图(以下图片来自b站)

img

第2次,则直接从PLT表到GOT表,得到真实地址完成调用

img

动态链接就到这里了。(此博客参考prettyX 博客Zheng__Huang的博客