混淆漏洞CVE-2017-0213技术分析
1. 引言
CVE-2017-0213 是一个比较冷门的COM 类型混淆 (Type Confusion)漏洞。巧妙的利用该漏洞,可以实现本地的提权。该漏洞由著名的Google Project zero 发现。漏洞信息原文可参见【1】
然而原文对漏洞的描述有些过于任性,尽管笔者熟悉好几国英文J,反复读了好几遍还是觉得云山雾罩。因此决定亲自分析下,和读者共同分享一下。
2. 技术分析
2.1 DCOM 简介
这个漏洞要从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返回了整个对象的信息。
2.2 漏洞分析
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 不一致的时候,就会发生类型混淆。
2.3 漏洞利用
类型混淆的漏洞通常可以通过内存损坏的方式来进行利用.然而漏洞发现者在利用时,并未采用内存损坏的方式来进行漏洞利用。按照漏洞发现者的说法,内存损坏的利用方式需要对内存进行精心布局,即便如此 ,在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: 到当前目录 。在当前目录下创建windowssystem32tapi3.dll即可。具体过程可参见漏洞利用源代码【2 】
最后上一张成功利用的截图。Windows 7 SP1 下成功弹出一个admin权限的cmd窗口。
3. 总结
“天下漏洞,唯冷不破”。CVE-2017-0213的无论从挖掘和利用,感觉都有些剑走偏锋,正属于这种比较冷门的一类。这种漏洞似乎难以通过fuzzing的方式来发现。通常这种漏洞的发现,需要对Windows的代码非常熟悉。而从漏洞的利用的角度来看,思路亦是非常巧妙。从这个漏洞的发现到利用,可见漏洞发现者在Windows 操作系统方面的造诣非同一般。
4. 参考文献
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
- 【深度学习】写诗机器人tensorflow实现
- PyTorch还是TensorFlow?这有一份新手指南
- Leetcode 300. Longest Increasing Subsequence
- Leetcode 299. Bulls and Cows
- Leetcode 297. Serialize and Deserialize Binary Tree
- Leetcode 295. Find Median from Data Stream
- 投入大见效慢,还要做AI?
- Leetcode 292. Nim Game
- Leetcode 290. Word Pattern
- 【深度学习】使用tensorflow实现VGG19网络
- Leetcode 289. Game of Life
- Leetcode 287. Find the Duplicate Number
- Leetcode 284. Peeking Iterator
- Leetcode 283. Move Zeroes
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LeetCode122|删除排序链表中的重复元素II
- LeetCode121|单值二叉树
- LeetCode120|二维数组中的查找
- LeetCode129|不用加减乘除做加法
- LeetCode128|二叉树的最大深度
- LeetCode127|检查平衡性
- 详解Android端与JavaWeb传输加密(DES+RSA)
- Android编程实现下载时主界面与详细界面一致更新的方法
- 详解Android Studio 3.0的新特性与适配
- Android开发实现Launcher3应用列表修改透明背景的方法
- Android开发中方向传感器定义与用法详解【附指南针实现方法】
- Android利用ViewDragHelper轻松实现拼图游戏的示例
- ListView实现聊天列表之处理不同数据项
- 运算符
- ViewPager实现带引导小圆点与自动跳转的引导界面