CVE-2020-1362 漏洞分析
2020-07-27 12:00:00 Author: paper.seebug.org(查看原文) 阅读量:518 收藏

作者:bybye@知道创宇404实验室
时间:2020年7月24日

漏洞背景

WalletService 服务是 windows 上用来持有钱包客户端所使用的对象的一个服务,只存在 windows 10 中。

CVE-2020-1362 是 WalletService 在处理 CustomProperty 对象的过程中出现了越界读写,此漏洞可以导致攻击者获得管理员权限,漏洞评级为高危。

微软在 2020 年 7 月更新对漏洞发布补丁。

环境搭建

  1. 复现环境:windows 10 专业版 1909 (内部版本号 18363.815)
  2. 设置 WalletService 服务启动类型为自动

    image-20200724115418829

  3. 调试环境:windbg -psn WalletService 即可。

漏洞原理与分析

漏洞点是设置 CustomProperty 对象的 Group 的 get 方法和 set 方法没有检查边界。

  1. get 方法的 a2 参数没有检查边界导致可以泄露堆上的一些地址。 enter description here

  2. set 方法的 a2 参数没有检查边界,可以覆盖到对象的虚表指针,从而控制程序流。 enter description here

漏洞利用过程

创建 CustomProperty 对象

WalletService 服务由 WalletService.dll 提供,WalletService.dll 实际上是一个动态链接库形式的 Com 组件,由 svchost.exe 加载。我们可以在自己写的程序(下面称为客户端)中使用 CoCreateInstance() 或者 CoGetClassObject() 等函数来创建对象,通过调用获得的对象的类方法来使用服务提供的功能。

如何创建出漏洞函数对应的对象呢?最简单的办法是下载 msdn 的符号表,然后看函数名。

我们想要创建出 CustomProperty 对象,ida 搜索一下,发现有两个创建该对象的函数:Wallet::WalletItem::CreateCustomProperty() 和 Wallet::WalletXItem::CreateCustomProperty()。

img

所以我们创建一个 CustomProperty 需要一个 WalletXItem 对象或者 WalletItem 对象,那么使用哪个呢?继续用 ida 搜索 CreateWalletItem 或者 CreateWalletXItem,会发现只有 CreateWalletItem。

img

那到这里我们需要一个 WalletX 对象,继续用 ida 搜索会发现找不到 CreateWalletX,但是如果搜索 WalletX,会发现有个 WalletXFactory::CreateInstance(),如果有过 Com 组件开发经验的同学就会知道,这个是个工厂类创建接口类的函数,上面提到的 CoCreateInstance() 函数会使 WalletService 调用这个函数来创建出接口类返回给客户端。

img

那么如何调用 WalletXFactory::CreateInstance() 并创建出 WalletX 对象呢?我们需要在客户端使用 CoCreateInstance() 。

HRESULT CoCreateInstance(
    REFCLSID rclsid, // CLSID,用于找到工厂类
    LPUNKNOWN pUnkOuter, // 设置为 NULL 即可
    DWORD dwClsContext, // 设置为 CLSCTX_LOCAL_SERVER,一个宏
    REFIID riid, // IID, 提供给工程类,用于创建接口类实例
    LPVOID *ppv // 接口类实例指针的地址
);
  1. 首先,我们需要 WalletXFactory 的 CLSID,可以使用 OLEViewDotNet 这个工具查看。

    img

  2. 其次,我们需要一个 WalletX 的 IID,这个可以用 ida 直接看 WalletXFactory::CreateInstance() 这个函数。

    img

有了 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() 做到任意地址写。

img

有了任意地址写,我们选择把虚表放在 WalletService.dll 的 .data 节区,以避免放在堆上可能破坏堆上的数据导致程序崩溃。

控制程序流到 LoadLibrary 函数

使用伪造 vtable 并覆盖虚表指针的办法,我们可以通过调用虚函数控制 WalletService 的程序流到任意地址了。

那么怎么提权呢?在 windows 服务提权中,通常的办法是把程序流控制到可以执行 LoadLibrary() 等函数来加载一个由我们自己编写的动态链接库,因为在加载 dll 的时候会执行 dll 里面的 DllMain(),这个方法是最强大的也是最实用的。

这里使用漏洞提交者的方法,把虚表的某个地址覆盖成 dxgi.dll 里面的 ATL::CComObject\::`vector deleting destructor(),因为这个函数调用的 LoadLibraryExW() 会使用一个全局变量作为想要加载的 dll 的路径。

img

我们可以通过上面的 SetLabel() 进行任意地址写,修改上图的全局变量 Src,使其指向我们自己实现的动态链接库的路径,然后调用对应的虚表函数,使程序流执行到 LoadLibrarExW() 即可。

实现一个动态链接库

在 DllMain() 里面写上我们希望以高权限执行代码,然后调用虚表里面对应的函数是 WalletService 的程序流运行到 LoadLibraryEx() 即可。

注意,因为 windows 服务运行在后台,所以需要在 DllMain() 里面使用命名管道或者 socket 等技术来进行回显或者交互,其次由于执行的是 LoadLibraryExW(),所以这里的 dll 路径要使用宽字符。

其它

在控制虚表函数程序流到 LoadLibraryExW() 时,需要绕过下面两个 check。

第一个是需要设置 this+0x80 这个地址的值,使得下面的 and 操作为 true。

image-20200724111317787

第二个是要调整 qword_C5E88 和 qword_C5E80 是下面的变量 v4 指向具有写权限的内存。

image-20200724111819875

漏洞利用结果

可以获得管理员权限

image-20200724121314068

补丁前后对比

可以看到,打了补丁之后,get 方法和 set 方法都对 a2 参数添加了边界检测。

img

img

参考链接

[1] PoC链接
[2] 微软更新公告
[3] nvd漏洞评级


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1276/



文章来源: https://paper.seebug.org/1276/
如有侵权请联系:admin#unsafe.sh