作者:bybye@知道创宇404实验室
时间:2020年7月24日
漏洞背景
WalletService 服务是 windows 上用来持有钱包客户端所使用的对象的一个服务,只存在 windows 10 中。
CVE-2020-1362 是 WalletService 在处理 CustomProperty 对象的过程中出现了越界读写,此漏洞可以导致攻击者获得管理员权限,漏洞评级为高危。
微软在 2020 年 7 月更新对漏洞发布补丁。
环境搭建
- 复现环境:windows 10 专业版 1909 (内部版本号 18363.815)
-
设置 WalletService 服务启动类型为自动
-
调试环境:windbg -psn WalletService 即可。
漏洞原理与分析
漏洞点是设置 CustomProperty 对象的 Group 的 get 方法和 set 方法没有检查边界。
-
get 方法的 a2 参数没有检查边界导致可以泄露堆上的一些地址。
-
set 方法的 a2 参数没有检查边界,可以覆盖到对象的虚表指针,从而控制程序流。
漏洞利用过程
创建 CustomProperty 对象
WalletService 服务由 WalletService.dll 提供,WalletService.dll 实际上是一个动态链接库形式的 Com 组件,由 svchost.exe 加载。我们可以在自己写的程序(下面称为客户端)中使用 CoCreateInstance() 或者 CoGetClassObject() 等函数来创建对象,通过调用获得的对象的类方法来使用服务提供的功能。
如何创建出漏洞函数对应的对象呢?最简单的办法是下载 msdn 的符号表,然后看函数名。
我们想要创建出 CustomProperty 对象,ida 搜索一下,发现有两个创建该对象的函数:Wallet::WalletItem::CreateCustomProperty() 和 Wallet::WalletXItem::CreateCustomProperty()。
所以我们创建一个 CustomProperty 需要一个 WalletXItem 对象或者 WalletItem 对象,那么使用哪个呢?继续用 ida 搜索 CreateWalletItem 或者 CreateWalletXItem,会发现只有 CreateWalletItem。
那到这里我们需要一个 WalletX 对象,继续用 ida 搜索会发现找不到 CreateWalletX,但是如果搜索 WalletX,会发现有个 WalletXFactory::CreateInstance(),如果有过 Com 组件开发经验的同学就会知道,这个是个工厂类创建接口类的函数,上面提到的 CoCreateInstance() 函数会使 WalletService 调用这个函数来创建出接口类返回给客户端。
那么如何调用 WalletXFactory::CreateInstance() 并创建出 WalletX 对象呢?我们需要在客户端使用 CoCreateInstance() 。
HRESULT CoCreateInstance( REFCLSID rclsid, // CLSID,用于找到工厂类 LPUNKNOWN pUnkOuter, // 设置为 NULL 即可 DWORD dwClsContext, // 设置为 CLSCTX_LOCAL_SERVER,一个宏 REFIID riid, // IID, 提供给工程类,用于创建接口类实例 LPVOID *ppv // 接口类实例指针的地址 );
-
首先,我们需要 WalletXFactory 的 CLSID,可以使用 OLEViewDotNet 这个工具查看。
-
其次,我们需要一个 WalletX 的 IID,这个可以用 ida 直接看 WalletXFactory::CreateInstance() 这个函数。
有了 WalletXFactory 的 CLSID 和 WalletX 的 IID,然后在客户端调用 CoCreateInstance(),WalletService 就会调用 CLSID 对应的工厂类 WalletXFactory 的 CreateInstance(), 创建出 IID 对应的 WalletX 对象,并返回对象给客户端。
然后按照上面的分析,使用 WalletX::CreateWalletItem() 创建出 WalletItem 对象,然后使用 WalletItem::CreateCustomProperty() 创建出 CustomProperty 对象。
对于上面的步骤有疑问的同学可以去学一学 Com 组件开发,尤其是进程外组件开发。
伪造虚表,覆盖附表指针
由于同一个动态库,在不同的进程,它的加载基址也是一样的,我们可以知道所有dll里面的函数的地址,所以可以获得伪造的虚表里面的函数地址。
那么把虚表放哪里呢?直接想到的是放堆上。
但如果我们继续分析,会发现,CustomProperty 类里面有一个 string 对象,并且可以使用 CustomProperty::SetLabel() 对 string 类进行修改,所以,我们可以通过修改 string 类里面的 beg 指针 和 end 指针,然后调用 CustomProperty::SetLabel() 做到任意地址写。
有了任意地址写,我们选择把虚表放在 WalletService.dll 的 .data 节区,以避免放在堆上可能破坏堆上的数据导致程序崩溃。
控制程序流到 LoadLibrary 函数
使用伪造 vtable 并覆盖虚表指针的办法,我们可以通过调用虚函数控制 WalletService 的程序流到任意地址了。
那么怎么提权呢?在 windows 服务提权中,通常的办法是把程序流控制到可以执行 LoadLibrary() 等函数来加载一个由我们自己编写的动态链接库,因为在加载 dll 的时候会执行 dll 里面的 DllMain(),这个方法是最强大的也是最实用的。
这里使用漏洞提交者的方法,把虚表的某个地址覆盖成 dxgi.dll 里面的 ATL::CComObject\
我们可以通过上面的 SetLabel() 进行任意地址写,修改上图的全局变量 Src,使其指向我们自己实现的动态链接库的路径,然后调用对应的虚表函数,使程序流执行到 LoadLibrarExW() 即可。
实现一个动态链接库
在 DllMain() 里面写上我们希望以高权限执行代码,然后调用虚表里面对应的函数是 WalletService 的程序流运行到 LoadLibraryEx() 即可。
注意,因为 windows 服务运行在后台,所以需要在 DllMain() 里面使用命名管道或者 socket 等技术来进行回显或者交互,其次由于执行的是 LoadLibraryExW(),所以这里的 dll 路径要使用宽字符。
其它
在控制虚表函数程序流到 LoadLibraryExW() 时,需要绕过下面两个 check。
第一个是需要设置 this+0x80 这个地址的值,使得下面的 and 操作为 true。
第二个是要调整 qword_C5E88 和 qword_C5E80 是下面的变量 v4 指向具有写权限的内存。
漏洞利用结果
可以获得管理员权限
补丁前后对比
可以看到,打了补丁之后,get 方法和 set 方法都对 a2 参数添加了边界检测。
参考链接
[1] PoC链接
[2] 微软更新公告
[3] nvd漏洞评级
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1276/