动态链接——PLT与GOT表
动态链接
PLT与GOT表均为动态链接过程中的重要部分
1GOT: Global Offset Table, 全局偏移表,包含所有需要动态链接的外部函数的地址(在第一次执行后)
2PLT: Procedure Link Table, 过程链接表,包含调用外部函数的跳转指令(跳转到GOT表中),以及初始化外部调用指令(用于链接器动态绑定dl_runtime_resolve)
Linux虚拟内存分段映射中,一般会分出三个相关的段:
1 .plt: 即上文提到的过程链接表,包含全部的外部函数跳转指令信息
2 .got.plt: 即下文将要表达的GOT表,与PLT表搭配使用,包含全部外部函数地址(第一次调用前为伪地址,具体见下)
3 .got : 存放其他全局符号信息,注意与.got.plt不同,与下文函数动态链接过程关系不大(所以了解不深请见谅,有兴趣的读者也欢迎分享)
简单来说,PLT表存放跳转相关指令,GOT表存放外部函数(符号)地址
PLT
1.第一个表项PLT[0]为(通用调用解析表项)没有存储任何外部函数的跳转信息,保存调用dynamic linker resolve函数_dl_runtime_resolve的参数(link_map)和地址
2.之后的每个表项,分为两部分(三句):
1 | ;part 1 |
3.实际上,每个PLT都可理解为一个程序 (thunk)
GOT
1.每一项为单个地址
2.第一项指向dynamic段
3.第二项指向link_map
4.第三项指向_dl_runtime_resolve函数
5.之后每项一一对应PLT表中每个表项(序号不同)
今天以 2015-XDCTF-pwn200 这个题目文件作为例子分析
看到 write@plt,为什么后面加了@plt,因为这个是PLT表中的数据的地址
@plt函数是编译系统自己加的
这里我们在 write@plt 处下断点
r运行
这里是第一次调用 write函数
我们 si 跟进
发现有三行代码,jmp、push、jmp,我们要将这3条指令理解清楚
第一行代码是通过PLT表跳转到GOT表
我们看一下要跳到的地址
发现要跳到下一步的push
这里也说一下,在调用一个函数的时候有两种方法,一个是通过PLT表调用,一个则是通过GOT表调用,因为PLT表最终也是跳转到GOT表,GOT表中则是一个函数真正的地址,需要注意的是,在一个函数运行第一次之前,GOT表中的数据为@plt函数中下一条指令的地址
那么,剩下两行代码(push、jmp)的作用就可以理解为:找到真实的write函数地址
这里的push 0x20,是压入一个参数入栈,压入的参数给哪个函数用呢,是给_dl_runtime_resolve这个函数,这个参数就相当于函数的id,告诉_dl_runtime_resolve要去找哪一个函数的地址
1 | jmp 0x8048370 //跳转到函数 |
这里跳转到的地址其实是PLT[0],我们来查看一下这个地址,如下图
我们在n下一步,和上面的概述对上了,跳到_dl_runtime_resolve函数了,正是由这个函数来确定write函数的真实地址
我们一路n下一步
发现将要跳到真正的write函数中去了,这里留意一下write函数的真实地址,会和下面做比较
我们将write函数执行完
write函数第一次执行完了
我们此时,再来查看一下write函数
通过disass main再来看一眼write@plt的地址
通过x命令查看,发现又来到jmp、push、jmp这个地方了
这个时候再来查看第一个jmp跳到的地址
请和上上图的write函数地址做比较,发现是一致的
所以在第一次执行write@plt之后,write函数的真实地址就会放到第一个jmp里面
在第二次执行时,就会直接跳转过去,也就能初步理解延迟绑定机制
当第一次调用函数时,如下图(以下图片来自b站)
第2次,则直接从PLT表到GOT表,得到真实地址完成调用
动态链接就到这里了。(此博客参考prettyX 博客和Zheng__Huang的博客)