栈溢出之改写GOT --修改某个被调用函数的地址,让其指向另一个函数

栈溢出之改写GOT

--修改某个被调用函数的地址,让其指向另一个函数

首先要搞清楚一个关系  程序对外部函数的调用需要在生成可执行文件时将外部函数链接到程序中,链接的方式分为静态链接和动态链接。静态链接得到的可执行文件包含外部函数的全部代码,动态链接得到的可执行文件并不包含外部函数的代码,而是在运行时将动态链接库(若干外部函数的集合)加载到内存的某个位置,再在发生调用时去链接库定位所需的函数。内存包含库和程序, 库包含函数  要明白这样一个逻辑关系, 库在内存中的地址就是基址, 函数在内存中的地址就是真实地址

可程序是如何在链接库内定位到所需的函数呢?这个过程用到了两张表--GOT 和 PLT。GOT 全称是全局偏移量表(GlobalOffsetTable),用来存储外部函数在内存的确切地址。GOT 存储在数据段(Data Segment)内,可以在程序运行中被修改。PLT 全称是程序链接表(ProcedureLinkageTable),用来存储外部函数的入口点(entry),换言之程序总会到 PLT 这里寻找外部函数的地址。PLT 存储在代码段(Code Segment)内,在运行之前就已经确定并且不会被修改,所以 PLT 并不会知道程序运行时动态链接库被加载的确切位置。那么 PLT 表内存储的入口点是什么呢?就是 GOT 表中对应条目的地址。

GOT 表的初始值都指向 PLT 表对应条目中的某个片段,这个片段的作用是调用一个函数地址解析函数。

当程序需要调用某个外部函数时,首先到 PLT表内寻找对应的入口点,跳转到 GOT 表中。

如果这是第一次调用这个函数,程序会通过 GOT 表再次跳转回 PLT表,运行地址解析程序来确定函数的确切地址,并用其覆盖掉 GOT 表的初始值,之后再执行函数调用。

当再次调用这个函数时,程序仍然首先通过 PLT表跳转到 GOT 表,此时 GOT 表已经存有获取函数的内存地址,所以会直接跳转到函数所在地址执行函数。整个过程如下面两张图所示。

这个过程就是延迟绑定, 这个过程也启示了我们如何实现函数的伪装,那就是到 GOT 表中将函数 A 的地址修改为函数 B 的地址。这样在后面所有对函数 A 的调用都会执行函数 B。

那么我们的目标可以分解为如下几部分:确定函数 A 在 GOT 表中的条目位置,确定函数 B 在内存中的地址,将函数 B 的地址写入函数 A 在 GOT 表中的条目。

首先,如何确定函数 A 在 GOT 表中的条目位置?

程序调用函数时是通过 PLT 表跳转到 GOT 表的对应条目,所以可以在函数调用的汇编指令中找到 PLT 表中该函数的入口点位置,从而定位到该函数在 GOT 中的条目。

其次,如何确定函数 B 在内存中的地址?

如果系统开启了内存布局随机化,程序每次运行动态链接库的加载位置都是随机的,就很难通过调试工具直接确定函数的地址。假如函数 B在栈溢出之前已经被调用过,我们当然可以通过前一个问题的答案来获得地址。通过PLT表找到GOT表中B在内存中的准确位置

但我们心仪的攻击函数往往并不满足被调用过的要求,也就是 GOT表中并没有其真实的内存地址。幸运的是,函数在动态链接库内的相对位置是固定的,在动态库打包生成时就已经确定。所以假如我们知道了函数 A的运行时地址(读取 GOT 表内容),也知道函数 A 和函数 B 在动态链接库内的相对位置,就可以推算出函数 B 的运行时地址。

最后,如何实现 GOT 表中数据的修改?

很难找到合适的函数来完成这一任务,不过我们还有强大的 ROP(DIY大法好)。假设我们可以找到以下若干条 gadget,就不难改写 GOT 表中数据,从而实现函数的伪装。ROP 的具体实现请回看上一章,这里就不再赘述了。

从修改 GOT 表的过程可以看出,这种方法也可以在一定程度上绕过内存随机化。

posted @ 2020-10-18 21:39  bonelee  阅读(613)  评论(0编辑  收藏  举报