0%

延迟绑定过程分析

延迟绑定(Lazy Binding)是动态链接器用来减少程序启动时间的一种技术。延迟绑定就是在函数第一次被调用的时候再和函数地址绑定。

延迟绑定涉及到plt表和got表,对这两个表我一直都不是很了解,所以这一篇博客主要是记录一些我关于这两个表的问题。

plt表和got表是什么?

因为在我的印象中plt表和got表好像都是找函数地址的,那这两个表有什么区别呢?只有知道是什么之后,才能知道它们的区别。

got表和plt表都是程序调用外部函数时,定位该函数需要使用到的表。

got表(Global Offset Table)是全局偏移量表,这是链接器在执行链接时需要填充的部分,保存了所有外部符号的地址信息。got表有四项内容:

  • GOT[0] 是.dynamic段的装载地址,.dynamic段包含了动态链接器用来绑定过程地址的信息,比如符号的位置和重定位信息;
  • GOT[1] 是动态链接器的标识link_map的地址;
  • GOT[2] 包含动态链接器的延迟绑定代码_dl_runtime_resolve的入口点,用于得到真正的函数地址,回写到对应的got表中;
  • GOT[3] 开始就是函数的地址。

plt表(Procedure Linkage Table)是程序链接表,有的说plt表里面存放的是代码(jmp,push,jmp ),还有的说存放的是got表项的地址,还有说 PLT[0] 存放指令, PLT[1] 之后存放got表项地址。

上图是ROP Emporium-Appendix A给的一段plt的示例,里面包含3个函数的代码段,但是开头有一个只有(push,jmp)的代码段。似乎plt表中就是存放的代码段。

我个人觉得,在函数未被调用的时候,plt表中存放的是一段代码,用于延迟绑定,绑定结束之后,plt表中的内容就是对应的got表项的地址。

为什么需要plt表?

为什么需要plt表?这个疑问指:为什么需要中间跳一层再找函数地址呢?它是因为需要实现延迟绑定所以出现的吗?

plt表是为了实现延迟绑定出现的一层间接跳转,延迟绑定提高程序启动的效率,延迟绑定减少进程启动开销的原理则要通过延迟绑定整个机制来解释。

延迟绑定是什么?

延迟绑定的调用过程如上图(这张图原出处不知道是哪里,这里是从GOT表和PLT表扒下来的)。

函数第一次被调用时,先进入plt表,plt中是三条指令:

1
2
3
jmp
push
jmp
  1. 第一个jmp指令跳往对应函数的.got.plt的入口,但是这个时候got表中还没有填充函数的真实地址。

  2. 所以从got表跳回到plt表中,继续执行push;jmp指令,跳回后需要进行push的操作,push是的值是对应函数在.got.plt入口的偏移;

  3. 第二个jmp指令跳回plt的开头,是一个push指令和一个jmp指令

  4. plt头部的push指令压入栈的其实是 GOT[1] 的地址即linkmap的地址,jmp到 **GOT[2] ** 即dl_runtime_resolve相关的函数,对动态函数进行地址解析和重定位,并且把函数真实地址回填到got表中

  5. 执行函数

整个延迟绑定的过程就是这样。

之后再调用该函数的时候,plt只有一个跳转指令,找到对应的函数地址之后执行函数。动态调试看了一个整个运行过程,有了一个更深入的的理解。下面是两张调试过程中的截图,第一张是首次调用puts函数时的汇编指令,第二张是第二次调用puts函数时的指令,和上面分析一致。

所以回到之前的问题,为什么需要plt表?这个答案就显而易见了。只有外部函数第一次被调用的时候,通过plt跳转调用got表的_dl_runtime_resolve,并填入正确函数地址。之后再次调用的时候,通过plt->got表直接跳转到对应的函数地址,不需要再次加载。延迟绑定可以减少进程的启动开销,降低程序的启动时间。

plt中push指令的作用是什么?

push指令是用于plt表和.got.plt表对齐的,因为两者的偏移不太一样,所以用push进行对齐。具体可以看GOT表和PLT表知识详解

小结

不知道是因为没有找到一手资料还是什么原因,感觉很多东西都模棱两可,各种博客的说法大体相似,但是有细微的区别。不过还是把整个脉络多弄清楚了,而且以写博客的形式来总结的话,会进一步了解一些细枝末节的东西。

补充

看到一篇讲劫持GOT表的博客手把手教你栈溢出从入门到放弃(下)

GOT 全称是全局偏移量表(Global Offset Table),用来存储外部函数在内存的确切地址。GOT 存储在数据段(Data Segment)内,可以在程序运行中被修改。PLT 全称是程序链接表(Procedure Linkage Table),用来存储外部函数的入口点(entry),换言之程序总会到 PLT 这里寻找外部函数的地址。PLT 存储在代码段(Code Segment)内,在运行之前就已经确定并且不会被修改,所以 PLT 并不会知道程序运行时动态链接库被加载的确切位置。PLT 表内存储的入口点就是GOT表对应的条目地址。

所以想利用延迟绑定进行攻击的时候,需要找gadget修改GOT表。

参考文献

ROP Emporium-Appendix A 短小精悍的对延迟绑定的解释,感觉相对准确,如果能看这个看懂,其他的不用看了

深入了解GOT,PLT和动态链接 非常完整详细的讲解博客

PLT表和GOT表 解释两个表是什么,每个表项是什么

深入理解GOT表和PLT表 完整讲述用plt和got表动态链接的流程

彻底搞清楚 GOT 和 PLT 光看博客分析不理解的话,可以跟着一起用pwndbg调试走一下流程

GOT表和PLT表知识详解 解释plt三条指令中push的作用

关于GOT表和PLT表的学习 解释了plt如何提高程序效率,对延迟绑定的过程详细进行了分析