动态链接库
我们程序开发过程中都会用到系统函数,比如read,write,open等等
这些系统函数不需要我们实现,因为系统已经帮你完成这些工作,只需要调用即可,存放这些函数的库文件就是动态链接库。
通常情况下,我们对于pwn题接触到的动态链接库就是libc.so文件:
静态编译与动态编译
这里我们举一个例子来类比静态编译与动态编译的概念。
小明要开一个餐馆(program)餐馆的菜单上有几百种菜肴(函数),小明的餐馆每天都会来很多顾客,每个顾客点的菜都可能不一样。我们知道,每道菜所需要的食材(系统函数)都不一样,这些食材都存放于仓库(动态链接库)中。
那么现在问题来了,小明如何保证每个顾客点的菜都能被满足呢?
第一种方式:
小明把仓库中所有的食材都搬进厨房(静态编译)
这时,小明不需要挪地方(静态)只需要在厨房中就可以工作,但是这会带来冗余,可能厨房中的食材很多都用不上。
第二种方式:
小明每次遇到新的所需要的食材,才去仓库取(动态编译)
这时,小明可能挪动的比较频繁(动态),但是可以保证厨房里面没那么多可能用不到的东西。
静态编译
一个程序运行过程中可能会调用许许多多的库函数,这些库函数在一次运行过程中不能保证全部被调用。
静态编译的思路就是将所有可能运行到的库函数一同编译到可执行文件中
这一方式的优点就在于在程序运行中不需要依赖动态链接库。适用的场合就是比如你本地编译的程序需要的动态链接库版本比较特殊,如果在别的机器上运行可能对方动态链接库版本和你不一样会出bug,这时候用静态编译。
缺点就是编译过后程序体积很大,编译速度也很慢
动态编译
一个程序运行过程中会调用许许多多的库函数,这些库函数在一次运行过程中不能保证全部被调用。
动态编译的思路就是逢山开路,遇水架桥,直到遇到需要调用库函数的时候再去动态链接库中寻找
所以其优点之一是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源
缺点一是哪怕是很简单的程序只用到了链接库里的一两条命令,也需要附带一个相当庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。
延迟绑定
我们再回去看看小明:小明说我选择第二种方式(动态编译)
但是小明餐馆开业后发现搞不赢,每次都要去仓库找,太麻烦了
于是乎,小明想到:每次我遇到新的食材,我就去仓库找,但是每次找完,我就在小本子(got表)上记录这个食材的地址,这样下一次找就快很多了!
而存放这个地址的小本子就是got表。
这就是linux的延迟绑定机制,got表全称是Global Offset Table,也就是全局偏移量表。
在程序运行时,got表初始并不保存库函数的地址,只有在第一次调用过后,程序才将这一地址保存在got表中。
PLT与GOT
GOT(Global Offset Table,全局偏移表)
GOT 是数据段用于地址无关代码的 Linux ELF 文件中确定全局变量和外部函数地址的表。ELF 中有 .got 和 .plt.got 两个 GOT 表,.got 表用于全局变量的引用地址,.got.plt 用于保存函数引用的地址。
PLT(Procedure Linkage Table,程序链接表)
PLT 是 Linux ELF 文件中用于延迟绑定的表
不论是第几次调用外部函数,程序真正调用的其实是plt表,
plt表其实是一段段汇编指令构成
在第一次调用外部函数时,plt表首先会跳到对应的got表项中。
由于并没有被调用过,此时的got表存储的不是目标函数地址,此时的got表中存储的地址是plt表中的一段指令,其作用就是准备一些参数,进行动态解析。
跳转回plt表后,plt表又会跳转回plt的表头,表头内容就是调用动态解析函数,将目标函数地址存放入got表中。
在之后第二次以上的调用后程序已经完成了延迟绑定,got表中已经存储了目标函数地址,直接跳转即可