一 样本概况
一个基于栈内存越界拷贝的office漏洞。
样本信息
测试环境
Win7 x86
office 2003
windbg
火绒剑
漏洞原理
MSCOMCTL.OCX模块是office解析ActiveX控件用到的一个动态库,当一个office相关文档中包含ActiveX这类的控件元素的话,比如按钮、列表、树形控件等,当文档通过office打开时,MSCOMCTL.OCX就会被载入到office程序的进程空间,调用其来解析显示空间,当office解析到一个被构造好的控件,比如列表控件,就会发生栈内存越界拷贝。
分析目标
漏洞利用原理
行为分析
拿到POC,运行一下发现弹出错误,如下图:
然后运行一下样本,会弹出计算器,如下图:
所以这个exp的shellcode在运行时候会释放文件,用火绒剑观察一下,如下图:
感染PE文件,启动自释放文件。去路径下去看a.exe就是计算器。
打开样本弹出计算器,所以Shellcode应该会调用WinExec函数。用Windbg附加WINWORD.EXE,bp kernel32!WinExec下断
0:009> bp kernel32!WInExec
0:009> bl
0 e 76d9e695 0001 (0001) 0:**** kernel32!WinExec
0:009> g
然后g,把样本拖进word。
ModLoad: 27580000 27685000 C:\Windows\system32\MSCOMCTL.OCX
Breakpoint 0 hit
eax=0012163b ebx=0001c000 ecx=00121568 edx=77cc64f4 esi=0001c000 edi=0c00afe6
eip=76d9e695 esp=00121588 ebp=001215a0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!WinExec:
76d9e695 8bff mov edi,edi
程序断在WinExec入口点。这时候计算器还没弹出来,看一下WinExec的参数,发现是a.exe。我去到这个目录,发现它就是一个计算器。
看来shellcode应该是把系统的calc复制到别的目录再运行它。
0:000> dd esp
00121588 00121852 0beaca80 00000000 0beaca80
00121598 0001c000 00000744 001215f0 00121a78
001215a8 0bfec2a8 0c00aff0 0001c000 00000001
001215b8 275c8a0a 0c00aff0 0bfec2a8 0001c000
001215c8 00000000 0bf27078 0bfec290 00002d26
001215d8 00002d26 0bfe9a48 00002841 0002159e
001215e8 00000000 00000744 00000000 00121669
001215f8 1005c48b c7000001 4d032400 005ae908
0:000> da 0beaca80
0beaca80 "C:\Users\15pb-win7\a.exe"
WinExec的返回地址是0x00121852。往上翻翻,没发现shellcode从哪开始。Kb打印堆栈
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00121584 00121852 0beaca80 00000000 0beaca80 kernel32!WinExec
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\MSCOMCTL.OCX -
001215b4 275c8a0a 0c00aff0 0bfec2a8 0001c000 0x121852
001215f0 00121669 1005c48b c7000001 4d032400 MSCOMCTL!DllGetClassObject+0x41cc6
00000000 00000000 00000000 00000000 00000000 0x121669
重新载入,在MSCOMCTL!DllGetClassObject+0x41cc0下断
275c89fd ff75f4 push dword ptr [ebp-0Ch]
275c8a00 8d45f8 lea eax,[ebp-8]
275c8a03 53 push ebx
275c8a04 50 push eax
275c8a05 e863fdffff call MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
275c8a0a 8bf0 mov esi,eax
275c8a0c 83c40c add esp,0Ch
275c8a0f 85f6 test esi,esi
275c8a11 7c3d jl MSCOMCTL!DllGetClassObject+0x41d0c (275c8a50)
275c8a13 837df800 cmp dword ptr [ebp-8],0
275c8a17 8b7d08 mov edi,dword ptr [ebp+8]
断在这个call的上一句,Kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
001215e8 275e701a 06642584 0d9a08e8 00000000 MSCOMCTL!DllGetClassObject+0x41cc0
00121610 275e7361 06642584 0d9a08e8 0d9a08e8 MSCOMCTL!DLLGetDocumentation+0xd08
00121630 275ca8b6 0bfabe80 0d9a08e8 0bfabcd8 MSCOMCTL!DLLGetDocumentation+0x104f
001216b0 2758aee8 0bfabc88 00000000 0d9a08e8 MSCOMCTL!DllGetClassObject+0x43b72
001216e0 27600908 0bfabcd8 0d9a08e8 00000000 MSCOMCTL!DllGetClassObject+0x41a4
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Microsoft Office\OFFICE11\WINWORD.EXE -
001216f4 301ecdcd 0bfabcdc 0d9a08e8 00000000 MSCOMCTL!DllUnregisterServer+0xc31
发现这一层函数的返回地址是0x 275e701a,它前面一个call是
0:000> u 275e701a -5
MSCOMCTL!DLLGetDocumentation+0xd03:
275e7015 e8ad19feff call MSCOMCTL!DllGetClassObject+0x41c83 (275c89c7)
275e701a 85c0 test eax,eax
275e701c 7c27 jl MSCOMCTL!DLLGetDocumentation+0xd33 (275e7045)
判断溢出点就是这个call里面。
发现了熟悉的发现万能跳转0x7ffa4512和shellcode。并且在
275c8a05 e863fdffff call MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
之前这里还没有被shellcode覆盖,显然,溢出发生在此
几个单步
Shellcode通过jmp esp覆盖返回地址利用。
接下来用IDA打开有漏洞的MSCOMCTL.OCX。定位前面那两个关键call,先看上层call,直接跳到文章后面打开IDA处,下面这段是另一种定位关键call的方法。
因为释放文件时需要调用kernel32.dll中的GetFileSize函数来判断需要释放的文件。所以Windbg开始调试POC时候,在GetFileSize函数下断,
bp kernel32!GetFileSize
但是Word运行时会调用很多次GetFileSize,直接下断点的话,会执行很多次。
我们知道漏洞出现MSCOMCTL.OCX,因此我们可以在加载MSCOMCTL.OCX后在对GetFileSize下断点。用到命令
sxe ld:MSCOMCTL.OCX
加载MSCOMCTL.OCX时候会断下来。bp
0:013> sxe ld:MSCOMCTL.OCX
0:013> g
ModLoad: 27580000 27685000 C:\Windows\system32\MSCOMCTL.OCX
eax=00000000 ebx=00000000 ecx=30036f04 edx=77cc64f4 esi=7ffdf000 edi=0012049c
eip=77cc64f4 esp=001203b4 ebp=00120408 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
77cc64f4 c3 ret
0:000> bp kernel32!GetFileSize
0:000> bl
0 e 768f0273 0001 (0001) 0:**** kernel32!GetFileSize
然后g运行程序,程序停在GetFileSize处
0:000> g
Breakpoint 0 hit
eax=00121617 ebx=0dff08e8 ecx=00000049 edx=000001af esi=0012164f edi=0012165b
eip=76d55d47 esp=001215ac ebp=001215f0 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
kernel32!GetFileSize:
76d55d47 8bff mov edi,edi
反汇编窗口如下
76d55d47 8bff mov edi,edi
76d55d49 55 push ebp
76d55d4a 8bec mov ebp,esp
76d55d4c 5d pop ebp
76d55d4d eb05 jmp kernel32!GetFileSize+0xd (76d55d54)
76d55d4f 90 nop
76d55d50 90 nop
76d55d51 90 nop
76d55d52 90 nop
76d55d53 90 nop
76d55d54 ff25d419d176 jmp dword ptr [kernel32+0x19d4 (76d119d4)]
76d55d5a 83c00a add eax,0Ah
76d55d5d 83c10a add ecx,0Ah
76d55d60 e907ab0000 jmp kernel32!DuplicateHandle+0x236 (76d6086c)
76d55d65 83c00c add eax,0Ch
76d55d68 83c10c add ecx,0Ch
76d55d6b e9fcaa0000 jmp kernel32!DuplicateHandle+0x236 (76d6086c)
76d55d70 90 nop
76d55d71 90 nop
76d55d72 90 nop
76d55d73 90 nop
76d55d74 90 nop
然后gu跳出GetFileSize函数,程序已经进入shellcode区域
观察堆栈调用上一层是MSCOMCTL!DllGetClassObject+0x41cc6 处
观察下
0:000> u MSCOMCTL!DllGetClassObject+0x41cc6
MSCOMCTL!DllGetClassObject+0x41cc6:
275c8a0a 8bf0 mov esi,eax
275c8a0c 83c40c add esp,0Ch
275c8a0f 85f6 test esi,esi
275c8a11 7c3d jl MSCOMCTL!DllGetClassObject+0x41d0c (275c8a50)
275c8a13 837df800 cmp dword ptr [ebp-8],0
275c8a17 8b7d08 mov edi,dword ptr [ebp+8]
275c8a1a 742a je MSCOMCTL!DllGetClassObject+0x41d02 (275c8a46)
275c8a1c 83650c00 and dword ptr [ebp+0Ch],0
IDA查看
猜测是判断出问题溢出,然后观察地址275c8a50处,猜测问题出在此处附近,打开IDA打开MSCOMCTL.OCX模块,定位到此处。此处反汇编如下
.text:275C89C7 ; int __stdcall sub_275C89C7(int, BSTR bstrString)
.text:275C89C7 sub_275C89C7 proc near ; CODE XREF: sub_275C8807+10p
.text:275C89C7 ; sub_275E7004+11p ...
.text:275C89C7
.text:275C89C7 var_14 = dword ptr -14h
.text:275C89C7 dwBytes = dword ptr -0Ch
.text:275C89C7 var_8 = dword ptr -8
.text:275C89C7 var_4 = dword ptr -4
.text:275C89C7 arg_0 = dword ptr 8
.text:275C89C7 bstrString = dword ptr 0Ch
.text:275C89C7
.text:275C89C7 ; FUNCTION CHUNK AT .text:275D3085 SIZE 0000001D BYTES
.text:275C89C7
.text:275C89C7 push ebp
.text:275C89C8 mov ebp, esp
.text:275C89CA sub esp, 14h
.text:275C89CD push ebx
.text:275C89CE mov ebx, [ebp+bstrString]
.text:275C89D1 push esi
.text:275C89D2 push edi
.text:275C89D3 push 0Ch ; dwBytes
.text:275C89D5 lea eax, [ebp+var_14]
.text:275C89D8 push ebx ; lpMem
.text:275C89D9 push eax ; int
.text:275C89DA call sub_275C876D
.text:275C89DF add esp, 0Ch
.text:275C89E2 test eax, eax
.text:275C89E4 jl short loc_275C8A52
.text:275C89E6 cmp [ebp+var_14], 6A626F43h
.text:275C89ED jnz loc_275D3085
.text:275C89F3 cmp [ebp+dwBytes], 8
.text:275C89F7 jb loc_275D3085
.text:275C89FD push [ebp+dwBytes] ; dwBytes
.text:275C8A00 lea eax, [ebp+var_8]
.text:275C8A03 push ebx ; lpMem
.text:275C8A04 push eax ; int
.text:275C8A05 call sub_275C876D
.text:275C8A0A mov esi, eax
.text:275C8A0C add esp, 0Ch
.text:275C8A0F test esi, esi
.text:275C8A11 jl short loc_275C8A50
.text:275C8A13 cmp [ebp+var_8], 0
.text:275C8A17 mov edi, [ebp+arg_0]
.text:275C8A1A jz short loc_275C8A46
.text:275C8A1C and [ebp+bstrString], 0
.text:275C8A20 lea eax, [ebp+bstrString]
.text:275C8A23 push ebx ; int
.text:275C8A24 push eax ; len
.text:275C8A25 call sub_275C8A59
.text:275C8A2A mov esi, eax
.text:275C8A2C pop ecx
.text:275C8A2D test esi, esi
.text:275C8A2F pop ecx
.text:275C8A30 jl short loc_275C8A50
.text:275C8A32 push [ebp+bstrString] ; strIn
.text:275C8A35 lea ecx, [edi-24h]
.text:275C8A38 call sub_27585BE7
.text:275C8A3D push [ebp+bstrString] ; bstrString
.text:275C8A40 call ds:SysFreeString
.text:275C8A46
.text:275C8A46 loc_275C8A46: ; CODE XREF: sub_275C89C7+53j
.text:275C8A46 cmp [ebp+var_4], 0
.text:275C8A4A jnz loc_275D308F
.text:275C8A50
.text:275C8A50 loc_275C8A50: ; CODE XREF: sub_275C89C7+4Aj
.text:275C8A50 ; sub_275C89C7+69j ...
.text:275C8A50 mov eax, esi
.text:275C8A52
.text:275C8A52 loc_275C8A52: ; CODE XREF: sub_275C89C7+1Dj
.text:275C8A52 ; sub_275C89C7+A6C3j
.text:275C8A52 pop edi
.text:275C8A53 pop esi
.text:275C8A54 pop ebx
.text:275C8A55 leave
.text:275C8A56 retn 8
.text:275C8A56 sub_275C89C7 endp
F5反编译一下如下:
int __stdcall sub_275C89C7(int a1, BSTR bstrString)
{
BSTR v2; // ebx@1
int result; // eax@1
int v4; // esi@4
int v5; // [sp+Ch] [bp-14h]@1
SIZE_T dwBytes; // [sp+14h] [bp-Ch]@3
int v7; // [sp+18h] [bp-8h]@4
int v8; // [sp+1Ch] [bp-4h]@8
v2 = bstrString;
result = sub_275C876D((int)&v5, bstrString, 0xCu);
if ( result >= 0 )
{
if ( v5 == 1784835907 && dwBytes >= 8 )
{
v4 = sub_275C876D((int)&v7, v2, dwBytes);
if ( v4 >= 0 )
{
if ( !v7 )
goto LABEL_8;
bstrString = 0;
v4 = sub_275C8A59((UINT)&bstrString, (int)v2);
if ( v4 >= 0 )
{
sub_27585BE7(bstrString);
SysFreeString(bstrString);
LABEL_8:
if ( v8 )
v4 = sub_275C8B2B(a1 + 20, v2);
return v4;
}
}
return v4;
}
result = -2147418113;
}
return result;
}
int __cdecl sub_275C876D(int a1, LPVOID lpMem, SIZE_T dwBytes)
{
LPVOID v3; // ebx@1
int result; // eax@1
LPVOID v5; // eax@3
int v6; // esi@4
int v7; // [sp+Ch] [bp-4h]@1
const void *lpMema; // [sp+1Ch] [bp+Ch]@3
v3 = lpMem;
result = (*(int (__stdcall **)(LPVOID, int *, signed int, _DWORD))(*(_DWORD *)lpMem + 12))(lpMem, &v7, 4, 0);
if ( result >= 0 )
{
if ( v7 == dwBytes )
{
v5 = HeapAlloc(hHeap, 0, dwBytes);
lpMema = v5;
if ( v5 )
{
v6 = (*(int (__stdcall **)(LPVOID, LPVOID, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(v3, v5, dwBytes, 0);
if ( v6 >= 0 )
{
qmemcpy((void *)a1, lpMema, dwBytes);
v6 = (*(int (__stdcall **)(LPVOID, void *, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(
v3,
&unk_27632368,
((dwBytes + 3) & 0xFFFFFFFC) - dwBytes,
0);
}
HeapFree(hHeap, 0, (LPVOID)lpMema);
result = v6;
}
else
{
result = -2147024882;
}
}
else
{
result = -2147418113;
}
}
return result;
}
分部来看:
275e7015 e8ad19feff call MSCOMCTL!DllGetClassObject+0x41c83 (275c89c7)
首先分配20个字节空间
275C89CA sub esp, 14h ; 分配20字节局部空间
然后调用
275C89DA call sub_275C876D
这个call的功能是从样本读取dwBytes,这里是0CH字节返回到eax指向的内存地址。
关键call就是sub_275C876,进去看
.text:275C877C lea ecx, [ebp+var_4]
.text:275C877F push 4
.text:275C8781 push ecx
.text:275C8782 push ebx
.text:275C8783 call dword ptr [eax+0Ch]
.text:275C8786 cmp eax, esi
.text:275C8788 jl short loc_275
读取4个字节到[ebp+var_4],返回值和0比较,如果小于0,读取失败,转到失败处理,否则
分配0x8282字节空间,接着
我们看到:275C87CB rep movsd此时edi就是前面读取的0x8082字节,然后复制过去,
返回值就是我们的shellcode;
ep movsd 所在的这层函数在溢出后能正常返回,上上层函数在返回时候才跳去执行shellcode。分析完成,我们看到其实漏洞发生的根本原因在于
.text:275C89F3 cmp [ebp+dwBytes], 8
.text:275C89F7 jb loc_275D
比较从样本读取过来的数据时候处理错误,这里应该是大于8个字节就转到错误处理,它写成了小于8个字节转到错误处理。而这个字节大小是我们可以控制的,所以当我们传递比如0x8282,那么程序就会读取样本相应位置可控的0x8282字节到堆空间,然后把这0x8282字节复制到0x127b7c地址,进而覆盖掉函数的返回地址,执行我们的shellcode。
参考资料:
帖子:
https://www.52pojie.cn/thread-453723-1-1.html