【漏洞分析】CVE-2017-0199:深入分析 Microsoft Office RTF 漏洞

http://p2.qhimg.com/t01f0fde8db4829c30e.png

翻译:lfty89

预估稿费:190RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


0x00 前言

近日,研究人员又发现了数款CVE-2017-0199漏洞的样本。虽然微软在今年4月份已经发布了针对该漏洞的补丁,但由于其利用方式相对简单,全世界的使用率仍然很高,这里分享了一些钓鱼邮件样本的分析报告。目前大部分关于CVE-2017-0199漏洞的文章都将讨论的焦点放在如何构建POC上,本文另辟蹊径,从分析漏洞补丁出发,以一个较高层次的视角来解析漏洞原理,最后分享了一些分析样本的经验。


0x01 漏洞补丁分析

分析人员通常会构建一个黑盒测试环境来观察一个恶意代码样本的行为。这一思路同样可以用在漏洞补丁功能测试上,比如针对CVE-2017-0199漏洞,研究人员使用最新的Microsoft Office套件,运行一些样本,观察补丁程序的工作模式。在测试时,研究人员在发现样本仍然能够成功地从远程服务器上下载payload并保存在Internet Explorer的临时文件夹中,最后由于补丁的原因,payload并没有运行。

经过分析,发现漏洞补丁主要包括两个组件:

OLE32.dll:6.1.7601.23714 on Windows 7 x86

MSO.dll:14.0.7180.5002 on Microsoft Office 2010 on x86

首先对打过补丁和未打补丁的OLE32.dll文件做个对比分析:

http://p7.qhimg.com/t010f535f0500326f68.png

图 1:OLE32.dll 6.1.7601.23714 (左) 和 6.1.7601. 23392 (右)

从图1中高亮部分可以看到函数的名称已经改变,推测可能是补丁新增的函数,接着使用IDA-Pro做进一步的分析:

http://p2.qhimg.com/t0180a386d7b3ac7e50.png

图2:使用IDA交叉引用找到FilterActivation函数

通过交叉引用找到其调用函数FilerActivation,并发现其能被两个内部组件函数ICoGetClassObject和ICoCreateInstance调用,而这两个函数又被上层用于COM对象实例化的API如CoCreateInstance和CoGetClassObject封装。

对这些COM函数做对比分析,可以初步假设这个FilterActivation函数是一个用于应对CVE-2017-0199漏洞的一个新增函数。

http://p4.qhimg.com/t01639820054975085c.png

图 3:ICoCreateInstance函数的比较结果

下面开始论证这一假设。

首先查看FilterActivation函数的伪代码:

http://p4.qhimg.com/t01873e43bbf34514ac.png

图 4:FilterActivation伪代码

FilterActivation函数首先获取cisid参数的值,并将其传递给一个定义在全局变量g_ActvationFilter中的处理函数,而g_ActvationFilter是在COM初始化过程中被MSO.dll通过CoRegisterActivationFilter函数初始化的,同时MSO.dll也包含在漏洞补丁中。

下面再看一下MSO.dll的初始化过程,首先从OLE32.dll得到CoRegisterActivationFilter的API地址,然后使用g_pActivationFilter作为参数调用它:

http://p8.qhimg.com/t018ac10330cbec9d6a.png

图 5:MSO.dll初始化过程

我们知道g_pActivationFilter是一个指向IActivationFilter类型的指针,同时也保存在全局变量g_ActvationFilter中,最终通过静态分析和交叉引用找到其调用函数:mso_IActivationFilterHandleActivation():

http://p9.qhimg.com/t0119944ffa39044f81.png

图 6:通过静态分析和交叉引用找到调用函数

通过查看该函数还可以发现一些与访问拒绝相关的代码:

CLSID_SCRIPTLET = {06290bd3-48aa-11d2-8432-006008c3fbfc}

CLSID_HTA = {3050f4d8-98b5-11cf-bb82-00aa00bdce0b}

CLSID_HTA是一个表示Microsoft HTML Application(文件后缀.hta)的全局唯一标识符,由32个16进制字符组成,攻击者可以利用其结构达到远程代码执行的目的。例如,一个Microsoft Office文档可以指定URL Moniker从远程服务器上下载该文档的嵌入式资源。

通过解读MSDN对MIME的定义描述,如果远程服务器提供了“application/hta”的MIME类型,那么下载的文件就会被mshta.exe装载执行,在后台,HTA COM对象的实例会在绑定操作过程中被创建,之后URLMON.dll会调用CoCreateInstance函数,后者初始化URL Moniker。但是在最新版本的OLE32.dll中,FIlterActivation函数会在在COM对象被初始化之前对其CLSID值进行过滤检测,若值为CLSID_HTA则无法通过(见图7)。

http://p6.qhimg.com/t01ceffc50323b343b3.png

图 7: 过滤检测

但从代码可看到,CLSID_SCRIPTLET值同样也无法通过。这里提供了一个能够初始化scriptlet COM对象的POC,但是需要用户交互,如点击文档中的带“script:”或者“scriptlet:”的超链接。不过,就算是需要用户交互,“一个包含超链接内容的文档将导致代码执行”这一事实听起来仍然有点匪夷所思。

下面的栈结构反映了文档中的超链接被点击后的执行过程:

//
// Parse the moniker name "script:xxxxxx OR scriptlet:xxxxxx"
//
0:000> kb
ChildEBP RetAddr  Args to Child             
001e92fc 766cce60 159ceff8 1627cfc8 001eaa4c scrobj!ComMonikerFactory::ParseDisplayName
001e9354 766ccf2c 1627cfc8 001eaa4c 001e9384 ole32!FindClassMoniker+0xf8 [d:w7rtmcomole32commoniker2cmonimp.cxx @ 1850]
001e938c 7543655a 1627cfc8 001eaa4c 001ea484 ole32!MkParseDisplayName+0xbb [d:w7rtmcomole32commoniker2cmonimp.cxx @ 1467]
001ea414 71b803c9 1627cfc8 001eaa4c 001ea484 urlmon!AppDataFolderList::GetPackageDependencyStateForIUri+0x17f3f
001ea444 71b76421 00000001 106a6e74 00000000 hlink!HrParseDisplayNameEx+0x197
001ea498 71b7681a 00000001 001eaa4c 00000000 hlink!HLNK::HrSetStringReference+0x91
001ea4b0 61c08bd2 162daf28 00000001 001eaa4c hlink!HLNK::SetStringReference+0x25
WARNING: Stack unwind information not available. Following frames may be wrong.
001ea4cc 6181c47f 16298fd0 00000001 001eaa4c mso!Ordinal10017+0x2aa1
001eba9c 61c1f384 16298fd0 00000000 00000000 mso!Ordinal8417+0x28a
001ebac0 697b4415 16298fd0 00000000 00000000 mso!Ordinal2959+0x1c
001ebb10 697b5377 00000000 00000000 0f5d0948 wwlib!DllGetLCID+0x7edf8f
001ebbc4 697b7190 0f5d0948 00000000 0000008e wwlib!DllGetLCID+0x7eeef1
001ebbec 69394c1d 69ca4400 0000008e 0000007e wwlib!DllGetLCID+0x7f0d0a
001eda34 6938facc 0000008e 0000007e 00000000 wwlib!DllGetLCID+0x3ce797
001eda64 692ebe3c 048c72b0 00000201 00000009 wwlib!DllGetLCID+0x3c9646
001edaf0 68dc4acd 00350666 00000201 00000009 wwlib!DllGetLCID+0x3259b6
001edb30 757ec4b7 00350666 00000201 00000009 wwlib!DllGetClassObject+0xf471
001edb5c 757ec5b7 68dc4a8c 00350666 00000201 USER32!InternalCallWinProc+0x23
001edbd4 757ecbe9 00000000 68dc4a8c 00350666 USER32!UserCallWinProcCheckWow+0x14b
001edc34 757ecc40 68dc4a8c 00000000 001edc54 USER32!DispatchMessageWorker+0x357
001edc44 68e1426d 69c9e630 69c9e630 001edc6c USER32!DispatchMessageW+0xf
001edc54 68e13e05 69c9e630 757e2b1d 69c9e630 wwlib!GetAllocCounters+0x4da95
001edc6c 68e13d4b 00000001 1311cfe4 12f10f8c wwlib!GetAllocCounters+0x4d62d
001edc94 68e12cf0 68db517d 7728cebc 68db0000 wwlib!GetAllocCounters+0x4d573
001efdf4 2fb91c68 2fb90000 00000000 0121ffd1 wwlib!GetAllocCounters+0x4c518
001efe18 2fb91ec2 2fb90000 00000000 0121ffd1 winword!wdGetApplicationObject+0x63a
001efea8 7728ef8c 7ffd8000 001efef4 7713367a winword!wdGetApplicationObject+0x894
001efeb4 7713367a 7ffd8000 366f1751 00000000 kernel32!BaseThreadInitThunk+0xe
001efef4 7713364d 2fb92045 7ffd8000 ffffffff ntdll!__RtlUserThreadStart+0x70
001eff0c 00000000 2fb92045 7ffd8000 00000000 ntdll!_RtlUserThreadStart+0x1b

http://p0.qhimg.com/t0126421c485ce73862.png

图 8: 文档中的超链接在点击后被ParseDisplayName函数解析

概括地来说,scriptlet又称为Windows Script Component(WSC),其设计目的是执行Javascript、VBScript以及PerlScript等脚本语言,同样也可以执行XML文件中的脚本代码。通过相关逆向分析,我们发现当一个超链接被触发后,scriptlet的解释器会通过文件后缀将关联URL的文件识别为脚本文件,导致HLINK.dll加载并实例化WSC、CROBJ.dll继而执行该文件。

下面的栈结构反映了关联超链接的scriptlet文件的执行过程:

0:000> kb
ChildEBP RetAddr  Args to Child             
0027a25c 6a720899 00000000 00000000 14bf8ff0 jscript!COleScript::ExecutePendingScripts
0027a278 6cb6831f 14bf0de8 00000001 14baae28 jscript!COleScript::SetScriptState+0x51
0027a288 6cb68464 14bdefd8 14bdefe4 00000000 scrobj!ScriptEngine::Activate+0x1a
0027a2a0 6cb699d3 00000000 1420ffd0 00000000 scrobj!ComScriptlet::Inner::StartEngines+0x6e
0027a2f0 6cb6986e 00000000 015dffb0 0027a320 scrobj!ComScriptlet::Inner::Init+0x156
0027a300 6cb6980b 015dffb0 14b9cf10 00000000 scrobj!ComScriptlet::New+0x3f
0027a320 6cb697d0 14b9cf10 00000000 00000000 scrobj!ComScriptletConstructor::CreateScriptletFromNode+0x26
0027a340 6cb737e2 015dffb0 00000000 00000000 scrobj!ComScriptletConstructor::Create+0x4c
0027a360 6cb74545 00000000 71a6245c 0027a460 scrobj!ComScriptletFactory::CreateScriptlet+0x1b
0027a380 7671b53d 015d1ff0 1417efc8 1420ffd0 scrobj!ComScriptletMoniker::BindToObject+0x4d
0027a3b4 71a6a858 138a4fd0 1417efc8 00000000 ole32!CCompositeMoniker::BindToObject+0x105 [d:w7rtmcomole32commoniker2ccompmon.cxx @ 1104]
0027a3f0 71a65ab7 1390ff78 00000001 14158fe0 hlink!HLBC::GetObjectA+0x143
0027a468 65638cb8 00000000 00000000 00000000 hlink!HLNK::Navigate+0x2ae
WARNING: Stack unwind information not available. Following frames may be wrong.
0027a488 71a68352 13b1efd0 00000000 00000000 mso!Ordinal10017+0x2b87
0027a4b0 6524c724 13b1efd0 00000000 00000000 hlink!HlinkNavigate+0xc0
0027ba88 6564f384 13b1efd0 00000000 00000000 mso!Ordinal8417+0x52f
0027baac 66684415 13b1efd0 00000000 00000000 mso!Ordinal2959+0x1c
0027bafc 66685377 00000000 00000000 0e9bc948 wwlib!DllGetLCID+0x7edf8f
0027bbb0 66687190 0e9bc948 00000000 00000087 wwlib!DllGetLCID+0x7eeef1
0027bbd8 66264c1d 66b74400 00000087 0000007d wwlib!DllGetLCID+0x7f0d0a
0027da20 6625facc 00000087 0000007d 00000000 wwlib!DllGetLCID+0x3ce797
0027da50 661bbe3c 03d152b0 00000201 00000009 wwlib!DllGetLCID+0x3c9646
0027dadc 65c94acd 0008094e 00000201 00000009 wwlib!DllGetLCID+0x3259b6
0027db1c 757ec4b7 0008094e 00000201 00000009 wwlib!DllGetClassObject+0xf471
0027db48 757ec5b7 65c94a8c 0008094e 00000201 USER32!InternalCallWinProc+0x23
0027dbc0 757ecbe9 00000000 65c94a8c 0008094e USER32!UserCallWinProcCheckWow+0x14b
0027dc20 757ecc40 65c94a8c 00000000 0027dc40 USER32!DispatchMessageWorker+0x357
0027dc30 65ce426d 66b6e630 66b6e630 0027dc58 USER32!DispatchMessageW+0xf
0027dc40 65ce3e05 66b6e630 757e2b1d 66b6e630 wwlib!GetAllocCounters+0x4da95
0027dc58 65ce3d4b 00000001 12accfe4 12ad0f8c wwlib!GetAllocCounters+0x4d62d
0027dc80 65ce2cf0 65c8517d 7728cebc 65c80000 wwlib!GetAllocCounters+0x4d573
0027fde0 2f1d1c68 2f1d0000 00000000 0008ffd1 wwlib!GetAllocCounters+0x4c518
0027fe04 2f1d1ec2 2f1d0000 00000000 0008ffd1 winword!wdGetApplicationObject+0x63a
0027fe94 7728ef8c 7ffdd000 0027fee0 7713367a winword!wdGetApplicationObject+0x894
0027fea0 7713367a 7ffdd000 45b88b21 00000000 kernel32!BaseThreadInitThunk+0xe
0027fee0 7713364d 2f1d2045 7ffdd000 ffffffff ntdll!__RtlUserThreadStart+0x70
0027fef8 00000000 2f1d2045 7ffdd000 00000000 ntdll!_RtlUserThreadStart+0x1b

微软目前已经将上述两个能够导致远程代码执行的COM对象加入黑名单,但该做法近乎简单粗暴,同时也不排除未来发现新的具备相同的效果的COM对象。


0x02 样本分析


下面我们分析两个样本的部分结构来探讨一下样本设计者规避检测的思路。

样本1

sha256:

94d324cbd50ab65e2170514c49ce0e91c73335d0ec8af978d0ac99c62d8a431e
b48055a517fe1077b95860c8816e3ae75174affac487554d7f812f3c96c36eae

样本1的RTF部分结构如下:

http://p3.qhimg.com/t0165628729359027c3.png

图 9: 样本1的RTF结构

在上图中我们发现“objdata”包含的控制字段“deftabN”将“objdata”包含的16进制字符串分隔成了两段。根据RTF的定义,“deftabN”必须跟在一个10进制值后面,这里如果我们忽略“deftab”跟随的10进制值,图9中高亮部分的16进制字符串会被解析为:

http://p0.qhimg.com/t0100fe7be3ecf00cba.png

图 10: 样本的解析结果被改变

这种16进制字符串并没有影响Word RTF的解析器,但是却巧妙地回避了一些利用静态特征检测恶意URL的规则。目前已发现的大部分样本主要通过将“objlink”替换为“objemb”来触发漏洞。

样本2

sha256:

e0320c0066c69aa35a654d01e951fdc9e489bf66b4c79608156ad446f10d88dd

经过测试发现,样本2仍然能够通过大部分杀软的静态检测。目前很多杀软的做法是尝试发现RTF文件中嵌入的OLE数据流,而OLE数据流一般通过定位“objdata”关键字来确定。根据RTF官方定义,“objdata”必须跟随“*”控制字符,在样本2中,“*”被一个任意字符取代。而对于Word RTF解析器,如果遇到无法识别的控制符号,则直接跳过,继续解析关键字“objdata”后面的数据,而这一点可能就是绕过检测的主要原因。


0x03 总结


本文以CVE-2017-0199漏洞补丁的分析为切入点,发现其解决针对Microsoft Office的scriptlet COM远程代码执行的工作机制,这一方面在之前分析CVE-2017-0199漏洞的文章中鲜有提及;此外,本文提到的一些RTF样本躲避静态检测的技巧对安全防护的设计也有一定的启发作用。要防御该漏洞,最好的办法是使用最新的Microsoft Office,同时及时更新操作系统补丁。


0x04 参考文献


[1]  http://blog.fortinet.com/2017/05/30/spear-phishing-fileless-attack-with-cve-2017-0199

[2]  https://msdn.microsoft.com/en-us/library/ms775147(v=vs.85).aspx

[3]  https://twitter.com/enigma0x3/status/859124491720458247

(完)