首页
社区
课程
招聘
[原创]CVE-2018-8453提权漏洞学习笔记
发表于: 2022-7-28 11:10 18791

[原创]CVE-2018-8453提权漏洞学习笔记

2022-7-28 11:10
18791

一.前言

1.漏洞描述

由于win32kfull中的NtUserSetWindowFNID在对窗口对象的fnid进行设置的时候,没有判断该窗口是否已经释放,这样就可以对一个已经释放的窗口进行fnid的设置。而在xxxSBTrackInit和xxxFreeWindow中都存在用户层的回调,通过对函数的劫持可以在回调中释放掉xxxSBTrackInit函数中使用的tagSBTRACK结构体,这样当xxxSBTrackInit释放该结构体的时候就会因为双重释放导致BSOD的产生。通过xxxSBTrackInit函数释放结构体之前会对结构体中的部分成员进行解引用的操作,在相应的内存地址中放置PALETTE的cEntries成员的地址来利用解引用扩大该值,实现越界地址写入相邻PALETTE的pFirstColor来实现任意地址读写,最终实现提权。

2.实验环境

  • 操作系统:Win10 x64 1709 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro, WinDbg

二.漏洞分析

1.关键结构体

由于在Win10系统中,很多符号并没有导出,所以一些结构体成员只能通过分析得到。对于窗口对象tagWND,和本次漏洞有关的成员定义如下:

3: kd> dt tagWND
   +0x000 head             : _THRDESKHEAD
   +0x052 fnid             : Uint2B
   +0x0A8 pcls             : Ptr64 tagCLS
   +0x180 cbwndExtra          : Uint8B

3: kd> dt _THRDESKHEAD
   +0x000 h               : Ptr64 Void
   +0x008 cLockObj             : Uint4B
   +0x010 pti              : Ptr64 tagTHREADINFO
   +0x018 rpdesk             : Ptr64 tagDESKTOP
   +0x020 pSelf             : Ptr64 UChar

tagWND偏移0x52的fnid表明了窗口的状态,当值包含0x8000的时候,表示窗口被释放:

#define FNID_DELETED_BIT            0x00008000

tagWND偏移0x10的pti指向tagTHREADINFO结构体,改结构体保存了线程信息,定义如下:

3: kd> dt tagTHREADINFO
   +0x198 pq             : Ptr64 tagQ
   +0x2B0 pSBTrack         : Ptr64 tagSBTRACK

tagTHREADINFO偏移0x198的pq指向tagQ结构体,结构体定义如下:

1: kd> dt tagQ
   +0x068 spwndCapture     : Ptr64 tagWND

tagTHREADINFO偏移0x2B0指向tagSBTRACK结构体,当鼠标在一个滚动条按下左键的时候,系统会通过该结构体用来标记鼠标的当前状态,结构体定义如下:

3: kd> dt tagSBTRACK -v
struct tagSBTRACK, 17 elements, 0x68 bytes
   +0x000 fHitOld          : Bitfield Pos 0, 1 Bit
   +0x000 fTrackVert       : Bitfield Pos 1, 1 Bit
   +0x000 fCtlSB           : Bitfield Pos 2, 1 Bit
   +0x000 fTrackRecalc     : Bitfield Pos 3, 1 Bit
   +0x008 spwndTrack       : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
   +0x010 spwndSB          : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
   +0x018 spwndSBNotify    : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
   +0x020 rcTrack          : struct tagRECT, 4 elements, 0x10 bytes
   +0x030 xxxpfnSB         : Ptr64 to     void 
   +0x038 cmdSB            : Uint4B
   +0x040 hTimerSB         : Uint8B
   +0x048 dpxThumb         : Int4B
   +0x04c pxOld            : Int4B
   +0x050 posOld           : Int4B
   +0x054 posNew           : Int4B
   +0x058 nBar             : Int4B
   +0x060 pSBCalc          : Ptr64 to struct tagSBCALC, 16 elements, 0x40

2.xxxFreeWindow函数分析

内核通过xxxFreeWindow来释放窗口,函数会将要释放的窗口对象的fnid与0x8000进行或运算,表示窗口被释放。接着判断窗口对象是否有扩展内存,即cbwndExtra是否为0,如果不为0,则执行xxxClientFreeWindowClassExtraBytes来释放扩展内存:

.text:00000001C0050A10                 mov     r8, [rdi+180h]  			 ; r8 = tagWND->cbwndExtra
.text:00000001C0050A17                 mov     eax, 8000h
.text:00000001C0050A1C                 or      [rdi+52h], ax  			 ; tagWND->fnid |= 0x8000
.text:00000001C0050A20                 lea     rax, [r8-1]
.text:00000001C0050A24                 cmp     rax, 0FFFFFFFFFFFFFFFDh
.text:00000001C0050A28                 ja      short loc_1C0050A6D 	          ; 判断r8是否为0
.text:00000001C0050A2A                 test    dword ptr [rdi+130h], 800h
.text:00000001C0050A34                 jnz     loc_1C00513AD
.text:00000001C0050A3A                 call    cs:__imp_PsGetCurrentProcess
.text:00000001C0050A40                 mov     ecx, [rax+304h]
.text:00000001C0050A46                 test    ecx, 40000008h
.text:00000001C0050A4C                 jnz     short loc_1C0050A66
.text:00000001C0050A4E                 mov     eax, [r15+1D0h]
.text:00000001C0050A55                 test    r14b, al
.text:00000001C0050A58                 jnz     short loc_1C0050A66
.text:00000001C0050A5A                 mov     rcx, [rdi+180h] ; rcx = tagWND->cbwndExtra
.text:00000001C0050A61                 call    xxxClientFreeWindowClassExtraBytes

xxxClientFreeWindowClassExtraBytes函数会执行KeUserModeCallback来返回用户层:

.text:00000001C00B6D25                 mov     r8d, 8       
.text:00000001C00B6D2B                 lea     rax, [rsp+48h+arg_10]
.text:00000001C00B6D30                 lea     r9, [rsp+48h+var_18] 
.text:00000001C00B6D35                 mov     [rsp+48h+var_28], rax 
.text:00000001C00B6D3A                 lea     rdx, [rsp+48h+arg_18] 
.text:00000001C00B6D3F                 lea     ecx, [r8+76h]   ; ecx = 0x76 + 0x8 = 0x7E
.text:00000001C00B6D43                 call    cs:__imp_KeUserModeCallback

xxxClientFreeWindowClassExtraBytes函数执行完成之后,函数会判断窗口对象的fnid值的低12位是否在0x2A0到0x2AA之间:

.text:00000001C00509EF                 movzx   eax, word ptr [rdi+52h] ; eax = tagWND->fnid
.text:00000001C00509F3                 mov     edx, 3FFFh
.text:00000001C00509F8                 movzx   ecx, ax
.text:00000001C00509FB                 and     cx, dx      
.text:00000001C00509FE                 mov     edx, 29Ah
.text:00000001C0050A03                 lea     r8d, [rdx+6]    ; r8d = 0x29A + 0x6 = 0x2A0
.text:00000001C0050A07                 cmp     cx, dx
.text:00000001C0050A0A                 jnb     loc_1C005117A    ; 此处会跳转
                    ; 省略部分代码
.text:00000001C005117A loc_1C005117A:                         
.text:00000001C005117A                 mov     ebx, 4000h
.text:00000001C005117F                 test    bx, ax
.text:00000001C0051182                 jnz     loc_1C0050A10   ; r8 = tagWND->cbwndExtra
.text:00000001C0051188                 cmp     cx, r8w              ; r8w = 0x2A0
.text:00000001C005118C                 jbe     loc_1C00514B7   ; fnid <= 0x2A0跳转
.text:00000001C0051192                 mov     eax, 2AAh
.text:00000001C0051197                 cmp     cx, ax
.text:00000001C005119A                 ja      short loc_1C00511AC ; fnid >= 0x2AA则跳转
.text:00000001C005119C                 mov     eax, [r15+1D0h]
.text:00000001C00511A3                 test    r14b, al
.text:00000001C00511A6                 jz      loc_1C00515D8     ; 这里需要跳转

如果fnid在0x2A0到0x2AA之间,则会调用SfnDWORD函数:

.text:00000001C00515D8 loc_1C00515D8:                         
.text:00000001C00515D8                 mov     rax, cs:__imp_gpsi
.text:00000001C00515DF                 xor     r9d, r9d		; r9d = 0
.text:00000001C00515E2                 movzx   ecx, cx
.text:00000001C00515E5                 xor     r8d, r8d
.text:00000001C00515E8                 mov     [rsp+100h+var_C8], r13
.text:00000001C00515ED                 mov     dword ptr [rsp+100h+var_D0], r14d
.text:00000001C00515F2                 mov     rax, [rax]
.text:00000001C00515F5                 lea     edx, [r9+70h]		; edx = 0 + 0x70 = 0x70
.text:00000001C00515F9                 mov     rax, [rax+rcx*8-1210h]
.text:00000001C0051601                 mov     rcx, rdi
.text:00000001C0051604                 mov     qword ptr [rsp+100h+var_D8], rax
.text:00000001C0051609                 mov     qword ptr [rsp+100h+var_E0], r13
.text:00000001C005160E                 call    SfnDWORD
.text:00000001C0051613                 jmp     loc_1C00511AC

SfnDWORD函数也会调用KeUserModeCallback函数返回用户层:

.text:00000001C006F85A                 lea     r9, [rsp+108h+arg_18] ; 
.text:00000001C006F862                 mov     r8d, 30h ; '0'  ;
.text:00000001C006F868                 lea     rdx, [rsp+108h+var_C8] 
.text:00000001C006F86D                 lea     ecx, [r8-2Eh]   ; ecx = 0x30 - 0x2E = 0x2
.text:00000001C006F871                 call    cs:__imp_KeUserModeCallback

3.xxxSBTrackInit函数分析

xxxSBTrackInit是用来执行鼠标左键按下滚动条进行拖动的函数,该函数的部分代码如下,函数首先申请一块内存用来保存pSBTrack结构体,对这块内存进行初始化,并对部分成员进行引用;接着函数调用xxxSBTrackLoop用来处理拖动滚动条要处理的消息;最后函数对相关成员进行解引用后,释放掉pSBTrack结构体:

__int64 __fastcall xxxSBTrackInit(struct tagWND *tagWND, __int64 a2, int a3, int a4)
{
  //  申请内存并进行初始化
  pSBTrack = Win32AllocPoolWithQuota(0x68, 'tssU');
  pSBTrack_1 = pSBTrack;
  if ( !pSBTrack )
    return pSBTrack;
  
  // 对成员进行初始化
  *(_DWORD *)pSBTrack &= 0xFFFFFFFE;
  *(_QWORD *)(pSBTrack + 0x40) = 0i64;
  *(_QWORD *)(pSBTrack + 8) = 0i64;
  *(_QWORD *)(pSBTrack + 0x10) = 0i64;
  *(_QWORD *)(pSBTrack + 0x18) = 0i64;
  *(_QWORD *)(pSBTrack + 0x30) = xxxTrackBox;
  
  // 将pSBTrack存储与tagTHRAEDINFO->pSBTrack中
  *(_QWORD *)(*((_QWORD *)tagWND + 2) + 0x2B0i64) = pSBTrack;

  // 对spwndTrack,spwndSB,spwndSBNotify进行引用
  arr[0] = pSBTrack_1 + 8;
  arr[1] = tagWND;
  HMAssignmentLock(arr);
  arr[0] = pSBTrack_1 + 0x10;
  arr[1] = tagWND;
  HMAssignmentLock(arr);
  arr[0] = pSBTrack_1 + 0x18;
  arr[1] = *((_QWORD *)tagWND + 0xD);
  HMAssignmentLock(arr);

  xxxCapture(*(_QWORD *)gptiCurrent, tagWND, 3i64);
  pti = *((_QWORD *)tagWND + 2);
  if ( pSBTrack == *(_QWORD *)(pti + 0x2B0) )
  {
    // 消息分发
    xxxSBTrackLoop(tagWND, a2, (struct tagSBCALC *)v17);
    
    // 释放pSBTrack对象
    pti = *((_QWORD *)tagWND + 2);
    pSBTrack = *(_QWORD *)(pti + 0x2B0);
    if ( pSBTrack )
    {
      // 解引用
      HMAssignmentUnlock(pSBTrack + 0x18);
      HMAssignmentUnlock(pSBTrack + 0x10);
      HMAssignmentUnlock(pSBTrack + 8);
      // 释放pSBTrack
      Win32FreePool(pSBTrack);
      pti = *((_QWORD *)tagWND + 2);
      *(_QWORD *)(pti + 0x2B0) = 0i64;
      return pti;
    }
  }
}

xxxSBTrackLoop会调用xxxTranslateMessage和xxxDispatchMessage来分发处理消息,xxxDispatchMessage函数会调用上面说的SfnWORD函数来返回用户层:

4.NtUserSetWindowFNID函数分析

该函数用来设置窗口对象的fnid增加指定的值,但是,这里增加的时候,函数没有判断窗口是否已经被释放,即是否具备0x8000。这就会导致,进行设置的时候很有可能会对一个已经释放的窗口的fnid值进行设置:

__int64 __fastcall NtUserSetWindowFNID(__int64 a1, __int16 fnid)
{
  hwnd = ValidateHwnd(a1);
  if ( hwnd )
  {
    if ( *(_QWORD *)(*(_QWORD *)(hwnd + 0x10) + 400i64) == PsGetCurrentProcessWin32Process(v5) )
    {
      // 判断要设置的fnid是否满足要求
      if ( fnid == 0x4000 ||  fnid - 0x2A1 <= 9 && (*(_WORD *)(hwnd + 0x52) & 0x3FFF) == 0 )
      {
        // 设置tagWND->fnid
        *(_WORD *)(hwnd + 0x52) |= fnid;
      }
    }
  }
}

5.漏洞成因

这个漏洞的成因比较复杂,要将上面的几个函数都联系起来看,成因如下:

  • 当向滚动条控件发送WM_LBUTTONDOWN(左键按下)的消息时候,xxxSBTrackInit函数就会被调用,xxxSBTrackInit函数会调用xxxDispatchMessage,该函数又会调用SfdDWORD来返回用户层

  • 如果用户HOOK了用户层对应的处理函数,就可以在该函数中调用DestroyWindow来释放拥有该滚动条控件的窗口。这样就会执行xxxFreeWindow,该函数会首先将窗口的fnid标记为删除的窗口,接着在该窗口存在扩展对象的时候,调用xxxClientFreeWindowClassExtraBytes函数返回用户层

  • 如果用户HOOK了用户层对应的处理函数,就可以在处理函数中调用NtUserSetWindowFNID,将窗口的fnid加入0x2A1的标记。这样xxxClientFreeWindowClassExtraBytes函数返回以后,会因为被修改的窗口的fnid值的低12位为0x2A1导致再次调用SfdDWORD返回用户层,此时在对应的处理函数中释放掉xxxSBTrackInit函数申请的pSBTrack结构体,这块内存就会处于释放状态

  • 当xxxFreeWindow函数返回后,就会返回到xxxSBTrackInit继续执行,而xxxSBTrackInit会在最后释放pSBTrack结构体,而这个结构体已经被释放,此时如果在释放就会产生BSOD错误

三.漏洞触发

要成功触发这个漏洞,就需要在SfdDWORD在用户层的处理函数中释放pSBTrack结构体,此时只需要通过向滚动条发送WM_CANCELMODE消息,该函数会导致xxxEndScroll函数来释放内存,该函数的主要代码如下:

__int64 __fastcall xxxEndScroll(struct tagWND *pwnd, int a2)
{
         // 要释放pSBTrack结构体的三个条件
         pti = *((_QWORD *)pwnd + 2);
         pSBTrack = *(_QWORD *)(pti + 0x2B0);
         if ( !pSBTrack )                                                                              // pSBTrack != NULL
              return pti;
         pq = *(_QWORD *)(*(_QWORD *)gptiCurrent + 0x198i64);             
         if ( *(struct tagWND **)(pq + 0x68) != pwnd )                               //  pq->spwndCapture == pwnd
              return pti;
         if ( !*(_QWORD *)(pSBTrack + 0x30) )                                            //  pSBTrack->xxxpfnSB != NULL
              return pti;
       
        // 释放掉pSBTrack结构体
        pti = *((_QWORD *)pwnd + 2);
        if ( pSBTrack == *(_QWORD *)(pti + 0x2B0) )
        {
          spwndSB = *(struct tagWND **)(pSBTrack + 0x10);
          if ( !spwndSB || (zzzShowCaret(spwndSB), pti = *((_QWORD *)pwnd + 2), pSBTrack == *(_QWORD *)(pti + 0x2B0)) )
          {
            *(_QWORD *)(pSBTrack + 0x30) = 0i64;
            HMAssignmentUnlock(pSBTrack + 0x10);
            HMAssignmentUnlock(pSBTrack + 0x18);
            HMAssignmentUnlock(pSBTrack + 8);
            Win32FreePool(pSBTrack);
            pti = *((_QWORD *)pwnd + 2);
            *(_QWORD *)(pti + 0x2B0) = 0i64;
          }
        }
  
  return pti;
}

其中第二处的限制需要窗口一个新得滚动条对象,并对其调用SetCapture。整个触发漏洞的流程如下:

  1. 创建一个带有八字节额外内存的窗口对象,并将该对象的句柄赋值到额外内存中供之后使用。同时,在该窗口中在创建一个滚动条对象用来触发漏洞

  2. HOOK SfdDWORD和xxxCreateFreeWindowClassExtraBytes返回到用户层会执行的函数

  3. 向创建的窗口发送WM_LBUTTIONDWORD函数来调用xxxSBTrackInit函数

  4. 在用户层定义的xxxClientFreeWindowClassExtraBytes会根据要释放的内存中保存的是否是第一步中窗口的窗口句柄,来判断是否要修改fnid和调用SetCapture

  5. 在用户层定义的SfdDWORD的处理函数中,会判断如果是第一次调用就会通过DestroyWindow来调用xxxFreeWindow。如果是第二处调用,则发送WM_CANCELMODE来是否pSBTrackInit函数

  6. 当xxxSBTrackInit函数最后释放pSBTrack结构体的时候就会因为双重释放导致BSOD


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

最后于 2022-8-11 20:53 被1900编辑 ,原因:
收藏
免费 5
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/08/12 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (1)
雪    币: 864
活跃值: (5144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
每日一阅,提神醒脑
2022-8-8 17:46
0
游客
登录 | 注册 方可回帖
返回