笔者最近重新研究了一下 office
下 ActiveX
控件堆喷射的相关细节,在此过程中对遇到的一个cve-2015-1642
poc进行了复现,在本文的最后,笔者探索了 ActiveX
控件堆喷射的部分原理,提出了一种比较简单的 ActiveX
控件堆喷射检测思路,实践证明该方式可以准确检出这类堆喷射。
由于笔者能力有限,不足之处还请读者斧正。
cve-2015-1642
被公布时为在野利用状态(0day)
漏洞细节首先是由 @yongchuank
在 2015.8.17
公布的,具体见:
https://labs.mwrinfosecurity.com/advisories/microsoft-office-ctasksymbol-use-after-free-vulnerability/
随后,NCC Group
公司的 @d0mzw
在 2015.10.30
公开了这个漏洞的进一步细节,并在文章内详细讨论了 office
内存破坏漏洞借助堆喷射进行利用的具体细节,作者还仔细讨论了office堆喷射的许多细节和通用利用编写方式。
https://www.nccgroup.trust/uk/our-research/understanding-microsoft-word-ole-exploit-primitives/
接着,玄武实验室的 Danny__Wei
在 2015.11.28
在自己的博客上公开了这个漏洞的poc,公开的poc是一段 C#
代码,poc构造思路基本遵循了 NCC Group
的文章。
http://www.cnblogs.com/Danny-Wei/p/5003302.html
笔者最近对 office
下 ActiveX
控件堆喷射的细节进行了进一步研究,研究过程中再次遇到上述材料,因此决定复现一下相关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
等工具搜到,将其拷贝到工程目录下,选中后点击确定即可,成功引入后,可以看到引用列表里面多了如下两个库:
最后添加所需的头文件即可:
此时我们就可以编译生成该poc了(确保你的机器上装有 office
,笔者的机器上装的是 office 2010
)。
编译完成后,双击 .exe
文件,出现一个窗口,点击上面的按钮就可以生成poc了(test.docx
):
此时这个 test.docx
还不是一个漏洞样本,它只带有堆喷射和 0x60
大小的内存占位功能。关于如何在此基础上构造一个 cve-2015-1642
的漏洞样本,@d0mzw
在他的文章里面写的很清楚:
笔者在此思路上用 7z
打开了生成的 docx
文件,并将 activeX1.xml
的 clsid
换成了漏洞的 clsid:44F9A03B-A3EC-4F3B-9364-08E0007F21DF
。此时我们就有了一个“理论上”可以劫持 eip
到 0xC0DEC0DE
的 cve-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
下开启页堆后的崩溃现场依然是下面这个,读者请以实际调试的情况为准)
现在笔者有一个问题:我不知道被释放前 eax
对象的内存大小为多少。我们来下个断点看一下:
到这里,我们已经知道被释放的对象大小为 0x60
.
现在,让我们来整理一下思绪,这个UAF如果要成功利用,所构造的文档在执行是需要满足哪些最简条件?
我们先来检视一下 @d0mzw
文章中提供的思路:
我们再来看一下 Danny__Wei
代码中对上述思路的实现,可以发现如果实际执行顺序为从下到上的话,和上述文章的思路完全一样:
顾名思义,DefragmenHeap
函数的作用是整理内存,实际内存申请时可能申请 0x60
大小的内存,占用的是稍微大一点的内存块,这个函数的作用就是把那些内存块先使用完,从而让系统分配精准的 0x60
大小的内存块,以便提高占位的有效性。
到这里,笔者脑海里的问题是:
我们借助调试器来探究一下上述问题。
要回答第1个问题,我们需要清楚以下几点:
笔者先回答a:显然,tabArrayB[j].Buttons.Add().ToolTipText = objAllocB/objAllocB/chunk;
这几处代码执行时存在一个统一的内存申请点。其次,每个 ActiveX[x].bin
文件在被映射到 office
进程空间时,还有一次统一的内存申请点,笔者将这部分的讨论放到本文最后。
关于 objAllocB/objAllocB/chunk
的申请,我们可以在堆喷射完的 winword
进程找一个对应堆块的指针,在只开启堆分配用户态栈回溯( +ust
)的情况下可以观察到如下输出:
再回答b:借助上面得到的信息,笔者下了如下断点进行观察:
从上面的日志可以观察到:实际执行时 chunk
块(fffe0
)的内存是最先申请的,随后是 DefragmenHeap
和 ReplaceHeap
中对 objAllocA/objAllocB
的申请,在不考虑字符串内容的情况下,这两个函数的执行顺序笔者并不关心。实际测试发现把 DefragmenHeap
函数中的 objAllocB
内存申请数量进一步增加,将 ReplaceHeap
函数删除,也可以实现正常占位。
接下来我们通过调试器来回答第2个问题:这个UAF在释放内存和重用内存之间的时间差够大吗?利用有足够的时间在这个时间区间内占用被释放的 0x60
大小的内存吗?
既然是UAF,调试时必须定位到 Free
在哪里?Reuse
在哪里?被释放的 0x60
字节内存是否成功被利用代码中申请的对象成功占位。
Danny__Wei
提供的poc在笔者的环境中并不能顺利完成占位,我们来借助调试器看一下究竟发生了什么:
根据上述观察,笔者推测利用代码中对 0x60
大小的字符串申请次数太少,于是笔者将 DefragmenHeap
函数的代码做了略微修改,如下:
同时在 button1_Click
函数进行了如下更改:
再次生成样本,笔者这次替换 activeX34.xml
中的 clsid
为漏洞 clsid
,再次打开样本,在调试器中观察如下:
最后回答第3个问题,根据上面的代码已经知道,笔者在 DefragmenHeap
函数中一共用了80个大小为0x60字节的字符串去占位被释放的对象。这个视环境不同而异,读者可以按需自行调整。
通过上述调试,笔者成功借助公开资料和调试器劫持了 cve-2015-1642
样本的控制流,在此基础上就可以写出这个漏洞的 RCE 利用。这个漏洞比较好的一点就是我们可以在UAF之间对内存进行成功占位,对 office
来说这类可以成功利用的 UAF 还是比较少的。
在本文的最后,笔者单独讨论一下 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
文件外加 40
个 activeX[x].xml
文件进行堆喷射。
关于这部分的更多细节可以参考这篇文章:
https://www.greyhathacker.net/?p=911
其中 activeX1.bin
的大小为 0x20500
字节:
以下调试环境仍为 windows7 sp1 x86 + office 2010 + windbg
笔者在调试器中( +ust
)将文档打开,查找满足上述大小的堆块:
笔者在上述栈回溯信息中注意到了两个函数:ole32!CMemBytes::WriteAt
和 ole32!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
函数。
分析到此处,笔者突然记起自己之前写过的一篇文章,里面谈到了在 office 2010
下借助 msxml6.dll
的符号打印解析的xml标签的断点,相关文章见:
https://www.anquanke.com/post/id/103080
我们来看一下 ole32!CMemBytes::WriteAt
函数是在解析到哪个标签时被调用的:
可以看到正是解析到 ax:ocx
标签后,开始调用 ole32!CMemBytes::WriteAt
映射 ActiveX
控件的内存。
接下来看一下第2个和第3个问题,我们来看一下 ole32!CMemBytes::WriteAt
的声明:
再次下断点,在函数调用前输出参数看一下:
可以观察到每次调用 ole32!CMemBytes::WriteAt
函数时待写入数据的大小(cb
)都为 0x1000
,偏移(ulOffset
)则按 0x1000
的顺序递增。由此笔者合理推断 ActiveX
控件在被映射到内存时,是按每次 0x1000
的大小被写入的,所以调用 ole32!CMemBytes::WriteAt
处应该位于一处循环内,我们看一下相关调用点的伪代码,果然如此:
现在让我们在第1次调用 ole32!CMemBytes::WriteAt
时断下,看一下写入的数据是什么?
可以看到写入的数据正是ole头部,并且在随后写入了 ActiveX[1].bin
控件的其余数据。
分析到这里,笔者想知道目的地址在哪里,既然有一个 ulOffset
,那么肯定有一个 base
,我们能通过上述数据得到 base
吗?答案是肯定的。
在 ole32!CMemBytes::WriteAt
函数中,调用完 ole32!CMemBytes::SetSize
后,有一处 memcpy
操作,这里就是在往新扩大的 0x1000
内存拷贝数据,其中第一个参数就是通过 base[offset]
来寻址的,笔者在寻找的就是这个 v5
, 可以看到 v5
是通过如下调用得到的:
看来 v5
是间接通过 CMemBytes
对象指针得到的,我们来看一下 GlobalLock
函数的实现:
可以看到在 GlobalLock
函数中,返回的值其实就是对传入的参数做了一次解引用,原来如此。
现在我们还需要搞清楚 CMemBytes
对象及其相关对象的结构体,幸运的是 IDA 已经给出了相关的结构体定义:
我们的目的就是通过 CMemBytes
对象指针去解析到 v5
对应的地址,所以我们可以将断点修正如下:
上面的日志是一次完整的从 offset=0
到 offset=0x20500
的过程(中间省略了大量重复日志),笔者注意到 base_addr
地址在中间发生过改变,笔者猜测一开始的地址在内存增长过程中可能大小不过,于是将之前已拷贝的数据拷贝到了一块更大的内存,并继续增长,这应该和 GlobalReAlloc
函数的实现有关,这里不再深究。
分析到这里,原则上笔者可以写出一个监控 office ActiveX
堆喷射总内存申请大小的实时 windbg 脚本,但是实际使用时发现带有伪寄存器的脚本执行的速度太慢,该脚本仅供参考。
由于笔者的日常工作涉及到沙箱检测技术的开发,所以我试着将上述调试结论融入沙箱的检测框架。通过累加堆喷射内存大小,并与预先准备的阈值(以下为75MB
)进行比较,从而判断一个 office
样本是否有 ActiveX
控件异常堆喷射行为。笔者对 cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826
常见攻击样本进行检测,获得了非常好的检出效果。
这里笔者给出若干漏洞的样本以及对应的检出日志,供读者参考:
由于 office
中一定还存在其他内存破坏漏洞,笔者预计未来仍然会有类似的利用手法出现,这些利用在针对旧版本 office(office 2007/office 2010)
的攻击中还是非常稳定的。
由于微软 EMET
的 HeapSpray
检测方式只是简单的占坑,在新样本的检测中并没有非常好的效果。因此笔者在本文最后提供了一种较为简单的检测 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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-3-26 13:07
被银雁冰编辑
,原因: