对Microsoft COM for Windows远程代码执行漏洞CVE-2018-0824的技
2018年5月,微软修补了一个有趣的漏洞(CVE-2018-0824),漏洞由微软安全响应中心 (MSRC)的Nicolas Joly报道:
当它无法正确处理序列化对象时,“Microsoft COM for Windows”中存在一个远程代码执行漏洞。
成功利用此漏洞的攻击者可以使用一个特制文件或脚本执行操作。在电子邮件攻击场景中,攻击者可以通过向用户发送专门制作的文件并诱使用户打开该文件来利用漏洞。在基于Web的攻击场景中,攻击者可以托管一个包含旨在利用此漏洞的特制文件的网站(或利用受损网站接受或托管用户提供的内容)。但是,攻击者无法强制用户访问该网站。相反,攻击者必须诱使用户点击链接,通常通过电子邮件或Instant Messenger消息中的诱饵,然后诱使用户打开特制文件。
此安全更新通过更正“Microsoft COM for Windows”处理序列化对象的方式来解决漏洞。
咨询发布后,关键字“COM”和“序列化”几乎一下就跃到我的眼前。由于去年我已经在Microsoft COM上花了几个月的研究时间,所以我决定研究一下它。虽然这个漏洞可以导致远程代码执行,但我只对特权升级方面感兴趣。
在我详细介绍之前,我想简单介绍一下COM以及反序列化/编组是如何工作的。由于我远不是COM专家,所有这些信息要么是基于Don Box的伟大著作《Essential COM》,要么是令人印象深刻的“COM in 60 seconds”。我略过了一些细节(IDL/MIDL、Apartments、Standard Marshalling等),只是为了让介绍简短。
COM和编组的介绍
COM(组件对象模型)是一个Windows中间件,具有可重用代码(=component)作为主要目标。为了开发可重复使用的C++代码,微软的工程师以面向对象的方式设计了COM,并考虑了以下几个关键方面:
l 可移植性
l 封装
l 多态性
l 接口与实现的分离
l 对象可扩展性
l 资源管理
l 语言独立性
COM对象由接口和实现类定义。接口和实现类都由GUID标识。一个COM对象可以使用继承来实现多个接口。
所有的COM对象都实现了IUnknown接口,它类似于C ++中的以下类定义(由GitHub托管iunknown.cpp):
classIUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
};
所述的QueryInterface() 方法用于将COM对象投射到COM对象实现的不同接口,AddRef() 和Release() 方法用于引用计数。
为了保持简短,我选择继续使用现有的COM对象,而不是创建一个人为的示例COM对象。
“控制面板”COM对象由GUID {06622D85-6856-4460-8DE1-A81921B41C4B}标识。想要了解有关COM对象的更多信息,我们可以手动分析注册表,或者使用伟大的工具“OleView .NET”。
“控制面板(Control Panel)”COM对象实现了几个接口,我们可以在OleView .NET的屏幕截图中看到:COM对象(COpenControlPanel)的实现类可以在shell32.dll中找到。
想要以编程方式打开“控制面板”,我们使用COM API(由GitHub 托管的rawcontrolpanel.cpp):
HRESULT hr = 0;
GUID guidControlPanel = { 0x06622d85,0x6856,0x4460,{ 0x8d,0xe1,0xa8,0x19,0x21,0xb4,0x1c,0x4b } };
IUnknown* pUnknown = nullptr;
IOpenControlPanel* pControlPanel = nullptr;
hr = CoInitialize(0);
hr = CoCreateInstance(guidControlPanel, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID*)&pUnknown);
hr = pUnknown->QueryInterface(IID_IOpenControlPanel, (LPVOID*)&pControlPanel);
hr = pControlPanel->Open(nullptr, nullptr, nullptr);
l 在第6行我们初始化COM环境
l 在第7行中,我们创建了一个“控制面板”对象的实例
l 在第8行中,我们将实例转换到IOpenControlPanel接口
l 在第9行中,我们通过调用“打开(Open)”方法打开“控制面板”
运行后检查调试器中的COM对象,直到第9行向我们显示对象的虚拟函数表(vTable):
对象的vTable中的函数指针指向shell32.dll中的实际实现函数。原因是COM对象被创建为一个所谓的InProc服务器,这意味着shell32.dll 被加载到当前的进程地址空间中。当传递CLSCTX_ALL时,CoCreateInstance()会首先尝试创建一个InProc服务器。如果失败,则尝试其他激活方法(请参阅CLSCTX枚举)。
通过将CLSCTX_ALL参数更改为函数CoCreateInstance()到CLSCTX_LOCAL_SERVER,并再次运行程序,我们可以注意到一些差异:
对象的vTable现在包含来自OneCoreUAPCommonProxyStub.dll的函数指针。并且与Open()方法对应的第四个函数指针现在指向OneCoreUAPCommonProxyStub!ObjectStublessClient3()。
原因是我们将COM对象创建为进程外服务器。下图尝试给你一个架构概述(从Project Zero借用):