程序对于外部函数的调用需要在生成可执行文件时将外部函数链接到程序中,(C语言应该讲过)链接的方式分为静态链接和动态链接。
静态链接得到的可执行文件包含外部函数的全部代码,动态链接得到的可执行文件中并不包含外部函数的代码,而是运行时将动态链接库(若干外部函数的集合)加载到内存的某个位置,再在发生调用时去链接库定位所需的函数。
怎么理解呢,这里打个比喻,静态链接是给你一件成品武器,调用时直接整个武器都已经组装好了到你手里,动态链接是给你一个武器库,调用时去武器库里找相应的武器。
动态链接是如何找到对应的武器:从PLT获取到地址后跳转到对应的GOT中,并且PLT一般在低地址。
Globle offset table全局偏移量表,位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址,GOT表存储在数据段,(在IDA中是也就是.data段)可以在程序运行中被修改。
hijack GOT:
修改某一个被调用函数的地址,让其指向另一个函数,例如修改printf()函数的地址让其指向system(),这样做的结果就是原本对于printf()的调用就变成了调用system()函数。
procedure linkage table过程连接表,位于代码段,是一个每个条目是16字节内容的数组。其中PLT[0]储存的信息能用来跳转到动态链接器中,PLT【1】是系统启动函数(__libc_start_main),其余每个条目都负责调用一个具体的函数。
用来存储外部函数的入口点,换而言之程序总会到PLT这里寻找外部函数的地址。
第一步:确定函数A在GOT表中的地址,和函数B在内存中的地址,将函数B的地址写入函数A在GOT表中。
(例如我们知道了printf函数在GOT表中的位置,以及system函数在内存中的地址,就可以将system写入GOT表替代printf)
那么,我们如何确定printf函数在GOT表中的地址呢
程序调用函数时通过PLT表跳转到了GOT表的对应条目,所以我们当然可以在函数调用的汇编指令中找到PLT表中该函数的入口地点,从而定位到该函数在GOT表中的对应条目。
然后是确定函数B(system函数)在内存中的地址
如果系统开启了内存布局随机化,程序每次运行动态链接库的加载位置都是随机的,就很难通过调试工具直接确定函数的地址。加入函数B在栈溢出之前被调用郭,我们就可以通过前一个问题的答案(从GOT表中获取已有的地址)。
但是一般情况下往往没有那么理想。
然而,函数在动态链接库的相对位置是固定的,并且在动态库生成时就已经确定。加入我们知道了函数A的地址,同时也知道函数A和函数B在动态链接库的相对位置,就可以推算出函数B的地址。
可以将PLT表理解为一辆列车,GOT表示终点站,我们通过PLT表这辆列车,就可以到达GOT表,如果我们通过某种手段修改了这辆列车的目的地,那我们就可以到我们想要去的GOT表中的位置
大致步骤如下,
确定函数A在GOT表中的地址,以及函数B在内存中的地址,将函数B的地址写入函数A在GOT中的地址。