plt/got表在pwn中经常被提及与使用,这篇文章就简单讲讲plt/got表的作用及流程。
首先为什么需要使用这门技术?
大家都知道操作系统通常使用动态链接的方法来提高程序运行的效率。在动态链接的情况下,程序加载的时候并不会把链接库中所有函数都一起加载进来,而是程序执行的时候按需加载,如果有函数并没有被调用,那么它就不会在程序生命中被加载进来。这样的设计就能提高程序运行的流畅度,也减少了内存空间。
plt/got在其中就起到了重要作用,起到了映射作用。
GOT
GOT(Global Offset Table)全局偏移表用于记录在 ELF 文件中所用到的共享库中符号的绝对(真实)地址
。在程序刚开始运行时,GOT 表项是空的,当符号第一次被调用时
会动态解析符号的绝对地址然后转去执行,并将被解析符号的绝对地址记录在 GOT 中,第二次调用同一符号时,由于 GOT 中已经记录了其绝对地址,直接
转去执行即可(不用重新解析)。
PLT
PLT(Procedure Linkage Table)过程链接表的作用是将位置无关的符号转移到绝对地址。当一个外部符号被调用时,PLT 去引用 GOT 中的其符号对应的绝对地址,然后转入并执行。
用一张图来描述就是:
这里可以注意一下这里的表并不意味着表里的数据就是一个(地址、指令),还可能是一串代码(function),在后面会体现出来。
### DEMO
这里我们通过用gdb跟踪调试一个带
puts
的程序来讲解。这段程序在ida
中的体现如下(只针对puts):调用点:
plt表指向:
got表:
为了先有个整体的思路,这里先放两张图,图中的地址并不是这个demo的地址,但流程一样。
第一次调用时got表还没有该函数的真实地址,此时的流程是:
第一次调用以后got表里已经有该函数的真实地址,以后的流程是:
特别说明的是:1
_dl_runtime_resolve:在函数被第一次执行时进行地址解析和重定位工作
打开gdb,我们在call _puts
的前面下个断点,然后进行调试:
这里有点疑惑的地方就是我的
_dl_runtime_resolve
没有被识别出来。因为疑惑,所以在后面我在可能它的地址
0xf7fee000
处下了断点,然后c
执行。在它的内部果然返回了
puts
的真实地址:第二次跟踪
这次我们直接跟plt --> got
可以看到我们在got表中
直接
取到了puts
的真实地址0xf7e6bca0
,这个值也跟第一次得到的一样。
这时,我们在回过头看上面那两张流程图就比较能理解了。