漏洞描述

Microsoft Windows ‘Win32k.sys’ 中某些函数在处理 位于user-mode下的内存对象时存在竞争条件错误,本地攻击者可以利用此漏洞以RING0权限执行任意代码。

漏洞分析

这种类型漏洞由 二次访问(double fetch)用户空间地址引起的:
对user-land的二次访问: time-of-check , time-of-use 。
如下:

.text:BF8C3120 mov eax, _W32UserProbeAddress
[...]
.text:BF8C3154 cmp [ecx+8], eax           ; time-of-check
.text:BF8C3157 jnb short loc_BF8C315C
.text:BF8C3159 mov eax, [ecx+8]           ; time-of-use

上面代码为用户模式SetWindowLong(hwnd, GWL_STYLE, 0)在win32k中的实现 。
其中 ecx为KeUsermodeCallback的输出参数pOutputBuffer,指向下面结构体:

typedef struct _CALLBACK_OUTPUT {
    NTSTATUS st;
    DWORD cbOutput;
    PVOID pOutput;
} CALLBACK_OUTPUT;

接下来的 指令会读取eax指向的内存:

.text:BF8BC4A8 push 7
.text:BF8BC4AA pop ecx
.text:BF8BC4AB mov esi, eax
.text:BF8BC4AD rep movsd

如果在 cmp [ecx+8], eax指令执行之后 ,读取ecx+8指向的内存内容之前,将ecx+8指向的内存中的内容修改成kernel-mode地址,将造成一个任意内核地址读漏洞。

由于ecx+8指向一个user-mode地址,所以我们可以使用两个线程,一个线程不停的触发上面代码的调用,另一个调用不停的修改ecx+8指向的内存的内容。

我们可以通过以下方法来增加攻击的时间窗口:

  1. 可以使ecx+8指向一个 non-cache的并且相邻的页面。
    这样当访问ecx+8时,将发生两次虚拟地址转译和两次缓存请求。

    ecx+8 —> F0 12 40 00

    前三个字节位于前一页面,当CPU读取前三个字节时,会线性地址到物理地址的转换, 并从cache中读取内容。最后一个字节(00)位于相邻的页面,CPU也会进行地址转译, 并从cache中读取内容,由于之间消耗时间较多,此字节内容可能被其它线程修改。
    禁用缓存有以下两种方法:

    • 内存API中使用PAGE_NOCACHE标志。
    • PAGE_WRITECOMBINE标志也可禁用缓存。
  2. 刷新TLB表,可以减慢地址转译速度。
    TLB可以通过INVLPG 指令刷新
    • 线程上下文切换(preemption or SwitchToThread)
    • 工作集API (VirtualUnlock or EmptyWorkingSet)
  3. 攻击者的线程优先级要高于其它线程。
  4. 攻击者线程的指令尽可能的简单。

参考