CVE-2017-0213 是一个比较冷门的COM 类型混淆 (Type Confusion)漏洞。巧妙的利用该漏洞,可以实现本地的提权。该漏洞由著名的Google Project zero 发现。漏洞信息原文可参见【1】
然而原文对漏洞的描述有些过于任性,尽管笔者熟悉好几国英文J,反复读了好几遍还是觉得云山雾罩。因此决定亲自分析下,和读者共同分享一下。
这个漏洞要从DCOM 谈起了。相信大家对Windows的组件对象模型(COM) 都已经非常熟悉了。而DCOM可能相对来说要陌生一些。DCOM是 分布式的COM, 类似于CORBA, 也就是说调用的COM 可以在远程主机上。
DCOM的详细信息可参见(阅读原文查看)
https://msdn.microsoft.com/en-us/library/cc226801.aspx
在COM模型中,我们知道所有的COM 接口都要继承 IUnkown 接口。通过QueryInterface函数,可以查询任意接口。
而在DCOM模型中,对应于IUnknown的接口为IRemunkown 和IRemUnkown2 两个远程接口。
相应的,QueryInterface对应的方法为:
IRemUnknown::RemQueryInterface([in] REFIPIDripid, [in] unsigned long cRefs, [in] unsigned short cIids, [in, size_is(cIids)] IID* iids, [out, size_is(,cIids)] PREMQIRESULT* ppQIResults)
和
IRemUnknown2::RemQueryInterface2([in]REFIPID ripid, [in] unsigned short cIids, [in, size_is(cIids)] IID* iids, [out, size_is(cIids)] HRESULT* phr, [out, size_is(cIids)] PMInterfacePointerInternal* ppMIF)
两者的主要区别在于返回的对象类型上。
IRemUnknown::RemQueryInterface 通过最后一个参数[out,size_is(,cIids)] PREMQIRESULT* ppQIResults 来返回对象。
看一下PREMQIRESULT的定义
typedef struct tagREMQIRESULT { HRESULT hResult; STDOBJREF std; } REMQIRESULT;typedef [disable_consistency_check]REMQIRESULT* PREMQIRESULT
STDOBJREF 通常包含OXID,IPID, OID 这些信息。
而 IRemUnknown2::RemQueryInterface2
通过最后又一个参数PMInterfacePointerInternal 来返回对象.
而 PMInterfacePointerInternal 的定义
typedef [disable_consistency_check]MInterfacePointer* PMInterfacePointerInternal;
根据MSDN的解释 (https://msdn.microsoft.com/en-us/library/cc226826.aspx)
MInterfacePointer is an NDR-marshaled structure that MUST contain a hand-marshaled OBJREF.
MInterfacePointer是一个NDR装组(marshal)的对象引用。
不难看出,IRemUnknown::RemQueryInterface 只是返回了对象的部分信息,而IRemUnknown2::RemQueryInterface2返回了整个对象的信息。
CVE-2017-0213的问题出现在 IRemUnknown2::RemQueryInterface2的 代码中。
代码调用CStdMarshal::Finish_RemQIAndUnmarshal2来完成对返回对象解组(Unmarshal)。
对于每一个MInterfacePointer指针,函数 CStdMarshal::UnmarshalInterface 对其进行解组,即从 IStream的数据中解组出相应的接口。问题出现在这里 ,解组的时候,解组代理是根据IStreamde数据中的OBJREF(IID) 来解组的,而并非 IRemUnknown2::RemQueryInterface2 中指定的 IID 。也就是说,这里没有对OBJREF 的IID 和IRemUnknown2::RemQueryInterface2中指定的IID 进行一致性检查。,如果在 IStream中的IID 和调用 IRemUnknown2::RemQueryInterface2 时指定的IID 不一致的时候,就会发生类型混淆。
类型混淆的漏洞通常可以通过内存损坏的方式来进行利用.然而漏洞发现者在利用时,并未采用内存损坏的方式来进行漏洞利用。按照漏洞发现者的说法,内存损坏的利用方式需要对内存进行精心布局,即便如此 ,在Windows 10上也可能会触发CFG(Control Flow Guard)。
漏洞发现者另辟其径,采用了一种基于LoadTypeLibrary来利用的方法。
背景知识:
如果将COM 接口注册PSOAInterface或者PSDispatch后,oleaut32.dll 会查找注册的Type Library信息(存放在注册表中),如果找到的话,将调用LoadTypeLibrary 来加载 Type Library. TypeLibrary在加载的时候,有个很有趣的行为: 首先会按GUID查找,如果查找失败的话,会按文件名来查找。如果按文件名查找也失败的话,这时会按照Moniker 来查找。这时,我们只需将包含scriptlet的Moniker注入到一个Type Library 文件中。就可以执行这段scriptlet。漏洞发现者采用的ScriptLet如下图所示。
现在我们知道,可以利用这个漏洞来成功加载JS, 从而达到执行任意文件的目的。
那么如何来利用这个漏洞来进行提权呢? 我们注意到,BITS 服务运行在 SYSTEM 完整性等级(IntegrityLevel)上。如果调用其
IBackgroundCopyJob::SetNotifyInterface(IUnknown *pNotifyInterface)
并传入一个精心构造的COM 接口,引发类型混淆,便可利用该漏洞来加载一个TypeLibrary。 这里,漏洞利用程序选择了COM 接口 IID_ITMediaControl(GUID {C445DDE8-5199-4BC7-9807-5FFB92E42E09}),其TypeLibGUID为 {21D6D480-A88B-11D0-83DD-00AA003CCABD}, 注册的DLL为 tapi3.dll。那么如何才能达到加载自己定义的tapi3.dll的目的呢?Tapi3.dll位于 %system32%目录下,覆盖此文件显然不可能。漏洞利用程序使用了这样一个技巧:利用NtCreateSymbolicLink重定向C:\ 到当前目录 。在当前目录下创建windows\system32\tapi3.dll即可。具体过程可参见漏洞利用源代码【2 】
最后上一张成功利用的截图。Windows 7 SP1 下成功弹出一个admin权限的cmd窗口。
“天下漏洞,唯冷不破”。CVE-2017-0213的无论从挖掘和利用,感觉都有些剑走偏锋,正属于这种比较冷门的一类。这种漏洞似乎难以通过fuzzing的方式来发现。通常这种漏洞的发现,需要对Windows的代码非常熟悉。而从漏洞的利用的角度来看,思路亦是非常巧妙。从这个漏洞的发现到利用,可见漏洞发现者在Windows 操作系统方面的造诣非同一般。
1.https://bugs.chromium.org/p/project-zero/issues/detail?id=1107
2.https://github.com/WindowsExploits/Exploits/blob/master/CVE-2017-0213/Source/CVE-2017-0213.cpp
*本文作者:兰云科技银河实验室,转子请注明来自 FreeBuf.COM
Microsoft Developer Network 微软开发者网络
http:///http://msdn.microsoft.com