程序在动态载入内存的时候并不会将所有函数都加载进内存,而是采用延时绑定的机制,即当真正使用当该函数的时候才将GOT表中的地址转化成真实的地址,

写一个简单的demo进行测试:

1
2
3
4
5
6
7
#include <stdio.h>

int main(void) {
puts("Hello world!");
return 0;
}
//gcc -m32 -no-pie -g -o got_plt got_plt.c

编译完成后,我们进入gdb进行调试,先将程序反汇编,我们来看看在那下断点合适,我们既然要研究PLT和GOT表,那肯定要断在call put这条汇编指令这吧,下断点我们来看看

下完断点,我们运行起来,单步步入就来到图中的地方,它先跳到_GLOBAL_OFFSET_TABLE_里面,也就是我们说的GOT表,那它会JMP到哪里去呢?等下揭晓….

我们用pwndbg查看一下这个位置存了什么,是不是有点眼熟,这不就是刚刚JMP的下一条指令的地方吗?没错它又跳回来了…紧接着它push了一个参数又往下跳转了,又push了一个参数就跳到_dl_runtime_resolve_这个函数里面,在这就不详细的阐述_dl_runtime_resolve_函数的具体实现,咱只要记得是这个函数帮我们把GOT表里面的值换成了真实的函数地址

我们在此函数执行完成之后再去看0x804a00c里面存的是什么,对吧…现在就是真实的函数地址

如果还是保持怀疑,我们反汇编进去看看,是puts数的实现对吧…

最后提一嘴,GOT表只是存放函数地址的表而已,真正的调用是需要通过PLT表来进行跳转

下面是一位大佬画的图就拿来用了:

延迟绑定完成之后就可以直接去GOT里面拿真实的函数地址啦!