• 内存保护机制及绕过方案——通过覆盖虚函数表绕过/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.攻击结果:

          

     成功。

  • 相关阅读:
    Java [Leetcode 190]Reverse Bits
    Java [Leetcode 88]CMerge Sorted Array
    Java [Leetcode 160]Intersection of Two Linked Lists
    Java [Leetcode 111]Minimum Depth of Binary Tree
    Java [Leetcode 225]Implement Stack using Queues
    D365 FO 视图Computed字段
    D365 FO最佳实践BP(五)-Display 方法缓存
    D365 FO最佳实践BP(四)-EDT未迁移
    How to change comment
    How to force to Fullscreen Form
  • 原文地址:https://www.cnblogs.com/zhang293/p/8909635.html
Copyright © 2020-2023  润新知