Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2015-0057 — 针对 CVE-2015-0057(一个 win32k.sys 释放后使用漏洞)的详细技术分析与漏洞利用实现,涵盖从 XP 到 8.1 的 32 位和 64 位 Windows 系统。 | Kitploit
工具/GitHubGitHub/highandhigh/cve-2015-0057
内存取证漏洞分析漏洞利用逆向工程论文与研究学习与教育二进制利用
GitHubhighandhigh/cve-2015-0057

CVE-2015-0057

针对 CVE-2015-0057(一个 win32k.sys 释放后使用漏洞)的详细技术分析与漏洞利用实现,涵盖从 XP 到 8.1 的 32 位和 64 位 Windows 系统。

查看仓库
81610年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2015-0057漏洞在32位和64位系统上的利用

作者:Aaron Adams

翻译:55-AA

译注:本文部分地方采用了意译,如有疑问请参阅原文。

术语:

  • 操作原语(primitive):类似于一个功能函数,是一系列腐蚀操作的集合,以完成一个完整的可重复利用的功能,如读取内存、写入任意数据等。

代序

今年早些时候,我接触到一个有趣的关于win32k.sys(CVE-2015-0057)的漏洞,并且实现了在 32 位和 64 位系统上的稳定利用,其适用范围从 XP 到 Windows 8.1 (也有一些例外)。本文详细描述了我是如何在这两个平台上完成利用的,结尾部分也附带了一些别的东西。另外也描述了如何在开启 SMEP 的 Windows 8.1 上实现低完整性权限下的利用。

本文很长,我努力提供尽可能多的细节来展示利用这个漏洞的复杂性,而不是隐藏这些细节,当然我也回避了一些细节。希望这些细节对大家有所帮助。

前言

2015年2月10日,微软公布了 MS15-010 的相关细节。这个 BUG 由 enSilo 的 Udi Yavo首先发现。Udi 在 breaking malware blog 上给出了很好的分析"one bit rule-bypassing windows 10 protections using single bit"。我推荐认真阅读这篇文章以便深入了解这个 BUG,虽然我将在本文中将给出尽可能多细节,这些细节涉及了在触发漏洞时必须克服的一些障碍。这个漏洞的利用非常有意思,许多细节来源于 Udi 的博客,下面是他声明:

合理披露:虽然这个博客是技术性的,但我们不会透露任何代码和完整的细节,以防止任何技术专家重现这个漏洞利用。

作为利用这个漏洞的额外奖励,我们得到了一个口袋妖怪的进化:技术精灵。我想我得给 Udi 一些荣誉,因为他发现这个 BUG,并在博客上提供相关情况以及利用这个 BUG 的细节,这些东西太有用了。

之前,我从来没有利用过 win32k.sys 的漏洞,也不熟悉用户模式回调和许多相关的 API,所以我也致谢一些著名的安全研究人员在网上提供的新奇的资源,如 Skywing、Tarjei Mandt、Alex Ionescu 和 j00ru 等。这些人提供如此多的技术信息公开,他们都应该得到褒奖。我大量参考的是 Tarjei Mandt 的一篇文章Win32k.sys exploitation paper。

在我写这个漏洞利用的时候,一个优秀的逆向工程师实现了CVE-2015-1701的稳定利用,其中关于用户模式回调的实例代码是很有用的,感谢该作者。

值得注意的是,我下面的分析是在 Windows 7 上完成的,因为它似乎是唯一的版本,这个版本的 win32k.sys 中的所有结构都有对应的符号。这些符号大多数可用于别的版本的 Win32k.sys 中的结构。不知什么原因,微软将这些符号从 windows 8 中取消了。

最后,我想说的是,我的利用方法相当复杂。完全可能有一个更容易的方法实现它,只是我没有发现。我很想听到有人使用了不同的方法。无论哪种方式,我希望所有这些对研究 win32k.sys 的漏洞是有帮助的。

BUG

下面我们在 win32k!xxxEnableWndSBArrows 的反汇编代码中一睹这个 BUG 的芳容,这是一个相当精妙的 BUG:

未打补丁的情况:

.text:FFFFF97FFF1B157D mov r8d, r13d 
.text:FFFFF97FFF1B1580 mov rdx, r14 
.text:FFFFF97FFF1B1583 call xxxDrawScrollBar    ; 触发usermode callback 
.text:FFFFF97FFF1B1588 jmp short loc_FFFFF97FFF1B1519
 [...] 
.text:FFFFF97FFF1B1519 mov eax, [rbx]           ; 引用 tagSBINFO 指针而没有经过检查 
.text:FFFFF97FFF1B151B mov ebp, 0FFFFFFFBh 
.text:FFFFF97FFF1B1520 xor eax, esi

在上面的代码中,Win32K!xxxdrawscrollbar 可以在适当的情况下回调到用户空间,在用户空间代码中 tagSBINFO 的指针可能被攻击者释放掉,再次返回到上面的代码时,0xFFFFF97FFF1B1519 处的代码将引用一个无效的指针。

打补丁的情况:

    .text:FFFFF97FFF1D69C3 xor r8d, r8d 
    .text:FFFFF97FFF1D69C6 mov rdx, rbp 
    .text:FFFFF97FFF1D69C9 call xxxDrawScrollBar        ; 触发usermode callback
    .text:FFFFF97FFF1D69CE cmp rbx, [rdi+0B0h]          ; 检查 tagSBINFO 指针是否正确 
 ---.text:FFFFF97FFF1D69D5 jz short loc_FFFFF97FFF1D69E4; 如果正确则继续原来的流程 
 |  .text:FFFFF97FFF1D69D7 mov rcx, rbp 
 |  .text:FFFFF97FFF1D69DA call _ReleaseDC 
 |  .text:FFFFF97FFF1D69DF jmp loc_FFFFF97FFF1D6958     ; 跳转到函数退出 
 |->.text:FFFFF97FFF1D69E4 mov eax, [rbx]               ; 放心地使用正确的 tagSBINFO 指针 
    .text:FFFFF97FFF1D69E6 xor eax, r14d

在上面的补丁版本中,我们看到在使用 tagSBINFO 指针前进行判空。相关的结构信息将在后面给出。

基础 —— 内存腐蚀阶段1

在实现这个利用时,我们进行了多次腐蚀(corruption)。并在其中的一次腐蚀中触发这个漏洞。

造成这个 BUG 的技术根源是一个桌面堆的 UAF(use-after-free)。起先,这个让我很困惑,由于我不熟悉 win32k.sys 的用户模式回调机制,也不清楚它是如何运作的。因此,我认为这是一个锁的条件竞争导致 UAF。事实上,那个结构的锁是被正确使用的,而且其流程是符合预期的。简言之,问题真正的原因是:

  1. win32k!xxxEnableWndSBArrows 函数拥有桌面堆上的一个 tagSBINFO 的指针,该指针从窗口相关的 tagWND 结构中读取,用于描述一个滚动条。
  2. win32k!xxxEnableWndSBArrows 调用了某个函数,导致用户模式回调(该函数可在用户模式被 HOOK)。
  3. 一旦代码是用户空间执行,桌面堆上的结构的可能通过其他 win32k 的系统调用而被修改,包括从桌面堆中释放 tagSBINFO 结构。
  4. 再次返回到内核模式,win32k!xxxEnableWndSBArrows 没有从 tagWND 中引用 tagSBINFO,也没有检查原先引用的指针是否有效,而是直接使用了原先引用的指针(实际上该指针已经被释放了)。

就是这些了,不考虑用户模式回调,这一阶段是相当明了的。

理解我们如何控制流程

但是,我们如何进行腐蚀、为什么要腐蚀呢?正如 Udi 的博客中提到的那样,你能够在某个位置设置或清除 2 比特,而这个位置被系统代码认为是 tagSBINFO 结构中的 WSBflags 字段。这不是正规的 UAF 利用思路,但是文章给出了一个如何操作的提示,我将在下面章节中说明。首先让我们了解如何操纵控制这些比特位。

tagSBINFO 结构(32位和64位是一致的):

kd> dt -b !tagSBINFO 
win32k!tagSBINFO 
    +0x000 WSBflags     : Int4B 
    +0x004 Horz         : tagSBDATA 
        +0x000 posMin   : Int4B 
        +0x004 posMax   : Int4B 
        +0x008 page     : Int4B 
        +0x00c pos      : Int4B 
    +0x014 Vert         : tagSBDATA 
        +0x000 posMin   : Int4B 
        +0x004 posMax   : Int4B 
        +0x008 page     : Int4B 
        +0x00c pos      : Int4B

这个 UAF 漏洞位于 win32k!xxxEnableWndSBArrows() 函数中,该函数用于开启或关闭一个或两个(水平或垂直)滚动条控件的箭头。滚动条控件是一个用于操纵滚动条的特殊窗口。可以通过 CreateWindow() 函数以内置的 "SCROLLBAR" 窗口类为参数来创建它。

win32k!xxxEnableWndSBArrows() 的函数原型:

BOOL xxxEnableWndSBArrows(PWND wnd, UINT WSBflags, UINT wArrows);

参数 WSBflags 的含义和在 WinUser.h 中定义的一致,用于表明是哪些滚动条将被操作:

#define SB_HORZ 0 
#define SB_VERT 1 
#define SB_CTL  2 
#define SB_BOTH 3

参数 wArrows 表示箭头的状态是可用还是不可用。被置位表示箭头不可用,否则表示可用。 参数 wArrows 的最低两位表示水平滚动条,接着的两位表示垂直滚动条,其余的位与本次利用的目的无关。

下面的代码取自 win32k!xxxEnableWndSBArrows() 函数,如果 SB_HORZ 或 SB_BOTH 被置位,则设置或取消水平箭头的相关比特位:

这个 BUG 存在于设置水平滚动条和垂直滚动条标志的时候。在刷新水平滚动条之后,一旦该滚动条对应的窗口在桌面上可见时,win32k!xxxEnableWndSBArrows() 函数将调用 win32k!xxxDrawScrollBar(),早先的文章中曾提到这可能触发一个潜在的用户模式回调。

在我们讨论用户模式回调前,继续讨论在调用 win32k!xxxDrawScrollBar() 之后将发生什么。这实际上和水平滚动条有相同的逻辑,仅仅有几个比特位的区别。如果我们选择关闭垂直滚动条,并假定我们触发了 UAF,那么这将会把 2 比特位写入到 tagSBINFO 堆块的某个地方。因此,如果原来的值是 0x2,现在就变成了 0xe。如下图所示。

这一比特的改变足够导致最终的代码执行。我没有深究如何通过清除比特位是完成利用,但它是有可能的。

上面所述的重点是,为了同时能操作水平和垂直滚动条,必须以某种方式创建具有这两个元素的滚动条控件。这个通过调用 CreateWindow() 并设置 WS_HSCROLL 和 WS_VSCROLL 标志来实现。代码如下:

g_hSBCtl = CreateWindowEx( 
    0,                  // No extended style 
    "SCROLLBAR",        // class 
    NULL,               // name 
    SBS_HORZ | WS_HSCROLL | WS_VSCROLL, // 垂直+水平 
    10,                 // x 
    10,                 // y 
    100,                // width 
    100,                // height 
    g_hSpray[UAFWND],   // 无模式父窗口 
    (HMENU)NULL,
    NULL,               // window owner 
    NULL                // extra params 
    );

可以通过下面的代码确保其可见(通常这是缺省的,这里显式调用一下):

result = ShowWindow(g_hSBCtl, SW_SHOW);

滚动条默认是开启的,当我们准备尝试触发漏洞代码时,我们可以设置为滚动条不可用,以便腐蚀我们需要的比特位:

result = EnableScrollBar(g_hSBCtl, SB_CTL | SB_BOTH, ESB_DISABLE_BOTH);

触发漏洞

尽管我们上面描述了 BUG 的细节以及如何触发相关代码,但是我们依然忽略了最重要的步骤,就是拦截由 win32k!xxxDrawScrollBar() 发起的用户模式回调,以便我们能在 win32k!xxxEnableWndSBArrows() 继续执行前改变堆的内容。我们需要真正触发这个 BUG,但如果不了解任何 Win32k.sys 相关的的情况以及相关的API,就像我开始的样子,这对自己来说就是一次冒险。

先前的文章中有一个很好的调用栈的示意图,深入展示了这个过程,通过 win32k!xxxDrawScrollBar() 进行触发,然后 ClientLoadLibrary() 被调用,并且通过KeUserModeCallback() 进行派发处理。我们需要确实搞清楚 KeUserModeCallback() 的调用情况,以便我们在自己的进程中进行 HOOK。

我发现一些好的论文中或多或少提及了用户模式回调的相关资料。其中涉及到 win32k 的部分是非常有用的:

  • https://media.blackhat.com/bh-us-11/Mandt/BH_US_11_Mandt_win32k_WP.pdf(Tarjei的论文)
  • http://azimuthsecurity.com/resources/recon2012_mandt.pptx (Tarjei的PPT包含许多扩展信息)
  • http://www.nynaeve.net/?p=204
  • http://www.cprogramdevelop.com/3825874/
  • http://www.zer0mem.sk/?p=410
  • https://www.reactos.org/wiki/Techwiki:RegisterUserApiHook
  • http://pasotech.altervista.org/windows_internals/Win32KSYS.pdf
  • http://j00ru.vexillium.org/?p=614
  • http://uninformed.org/index.cgi?v=10&a=2#SECTION00042000000000000000
下载工具