前言
之前看了个讲GOT和PLT的视频。看明白了,但没完全明白。所以,就把看明白的那部分,先整理成笔记写下来。以后全看明白了,再补充。
基础知识
.got(位于数据段)
GOT(Global Offset Table)全局偏移表。这是「链接器」为「外部符号」填充的实际偏移表。这里的外部符号当然也包含全局变量。同时也存放不需要延迟绑定的函数的地址。
GOT 表中前 3 个为特殊项,分别用于保存
.dynamic
段地址、本镜像的link_map
数据结构地址和_dl_runtime_resolve
函数地址.plt(位于代码段)
PLT(Procedure Linkage Table)程序链接表。它有两个功能,要么在
.got.plt
节中拿到地址,并跳转。要么当.got.plt
没有所需地址的时,触发「链接器」_dl_runtime_resolve
去找到所需地址。PLT表中的第一项为公共表项,剩下的是每个动态库函数为一项,每项 PLT 都从对应的 GOT 表项中读取目标函数地址
.got.plt(位于数据段)
这个是 GOT 专门为 PLT 准备的节。说白了,.got.plt 是 GOT 的一部分。它包含上述 PLT 表所需地址(已经找到的和需要去触发的)。功能类似 PE 的 IAT 表,存放的是需要延迟绑定的函数的地址
.plt.got(位于代码段)
用于存放 __cxa_finalize 函数对应的 PLT 条目
演示
小程序
1 | int main() |
gdb调试
具体步骤参考下图,第一次调用printf(对应库函数puts),调用时在.got.plt
中没找到函数地址,于是又回到了.plt
,然后就调用了函数_dl_runtime_resolve
,该函数位于ld.so,用于将共享库(例如libc.so)中的函数写入程序的.got.plt
表中。
第二次调用printf时,可以看到.got.plt
表里有函数地址,所以直接就跳转过去调用puts
了。
关于利用
在进行栈溢出的练习时,往往会通过 pwntools 拿到某个函数的 got 或 plt 地址,之后再将其写入 rop 中进行利用链的构筑。实际上,应该往 rop 写入 got 地址还是 plt 地址需要根据实际情况判断:
- 如果是通过 ret 的方式调用,那么使用
elf.plt['xxx']
- 如果是通过 call 的方式调用,那么使用
elf.got['xxx']
,前提是 xxx 已经被调用过