Introduction
In our previous blog entry, we described the KASLR improvements in Windows 10 Anniversary Update. In this article we’ll discuss the possible bypasses of GDI mitigation.
In the beginning we’ll provide the basic information which is necessary to better understand of our technics and after that we'll describe bypasses. We highly recommend you read the previous post.
This article is based on our ZeroNights 2016 presentation.
Allocation of GDI and USER objects
Let’s look at the allocation USER and GDI objects.
USER objects (window, dde conversation, hook, menu… see MSDN list):
1. Allocated by HMAllocObject.
2. Can be allocated in Desktop heap, Shared heap, in pool - PagedSessionPool (pool type is 33) or PagedSessionPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE (41) depending on object type.
3. We can read Desktop heap from usermode and USER object’s content as result.
GDI objects (bitmap, brush, pen… see MSDN list):
1. Allocated by HmgAlloc.
2. Can be allocated in PagedSessionPool. Also, there are lookaside lists with previously allocated blocks.
3. Further we’ll reference PagedSessionPool as GDI pool.
We can see that USER and GDI objects can be allocated in the same pool. Also, reading content of some USER objects can give us additional information about kernel addresses.
Desktop heap
Desktop heap is very important in our research.
Because of this, we decided to describe features of Desktop heap briefly.
Desktop heap is created with Desktop object (CreateDesktopHeap).
Desktop heap is mapped into a process address space if a process is switched to a specified desktop.
Desktop heap contains some USER objects and structures.
We can get user mode mapping of kernel mode address (located in desktop heap) by a simple calculation
UserModeAddress = KernelModeAddress – TEB.Win32ClientInfo.ulClientDelta
Some Windows API are using this feature to avoid entering into kernel when reading structures.
It is very important too. We will return to this API behavior later.
Below is short list of different objects inside Desktop heap (incomplete).
Bypass methods
Our methods are based on the following points:
Anniversary Update suppressed addresses of GDI objects, but USER objects were left intact.
We still can get addresses of USER objects from gSharedInfo from usermode.
Also, we can read content of some USER objects that were allocated in Desktop heap.
Some USER objects and structures are allocated in the GDI pool.
Discussed methods:
Arbitrary read/write via USER objects (as SURFOBJ alternative)
Leak of addresses from GDI pool.
Spray approach.
Arbitrary read/write via USER objects
We need exact address for corruption when we have vulnerability where it is possible write specific kernel address.
We could use SURFOBJ address and overwrite some SURFOBJ fields before Anniversary update. Now we can use USER objects to gain arbitrary read/write because we can get their addresses.
The main idea is very similar to arbitrary read/write with 2 SURFACEs, because we use two objects – one corrupts data pointer of another’s.
Speaking about required conditions for read/write primitive using USER objects, we have list of them:
1) We must have ability to write QWORD values because normally we write different pointers. If we will be able to write separate DWORDs only, it can lead to BSODs during replacing parts of pointer.
2) We must have ability to work under wow64 and pure x64 process.
3) We must have access to whole address space on x64.
4) Method has to be easy and stable in implementation.
5) Method should not corrupt (‘lock’, set flags etc) any data during reading or writing.
Let’s describe method itself.
Method based on using two USER objects – Window (tagWND) and Menu (tagMENU).Both objects are placed in desktop heap.
tagWND is structure which can have some “extra data”, controlled by user (via SetWindowLongPtr API).
Size of tagWND extra data is located in field tagWND.cbwndExtra.
Let's describe second object - tagMENU. Popup menu (CreatePopupMenu) can contain some items (InsertMenuItem) described by tagITEM structure.
Pointer to items array is located in tagMENU.rgItems.
It is possible to read/write different fields of menu items via WinAPI functions (GetMenuItemRect,GetMenuItemInfo/SetMenuItemInfo).
We can easily place tagWND before tagMENU in memory (because we can get addresses of both objects). If tagWND.cbwndExtra was corrupted because of some vulnerability, we can read/write tagMENU.rgItems pointer via SetWindowLongPtr. When pointer to rgItems was replaced to some arbitrary address, we can read/write it via menu items API.
Arbitrary read
1. The most ‘obvious’ way to read content from tagITEM is GetMenuItemInfo, but there is a problem. tagITEM structure is located in desktop heap. Windows API GetMenuItemInfo is using this fact to read item’s content from usermode. So, if we replaced tagITEM pointer in tagMENU to arbitrary kernel address, we have to use another API, which can get info from kernel mode.
2. GetMenuItemRect is able to do that, but it returns result in specific format (RECT structure).
3. GetMenuItemRect reads 2 fields – tagITEM.cxItem, tagITEM.cyItem, so we can calculate 64-bit value from 2 DWORDs. Moreover, cxItem and cyItem are located sequentially in tagITEM structure which is also required for reading of x64 value.
4. GetMenuItemRect requires a Window as argument, so we need to provide one. Also, it is better to specify popup menu, because with other menu we will have some ‘excess’ internal calls. Read example is below
GetMenuItemRect(hWnd, hMenu, 0, &rect);
ldw =(rect.right - rect.left); // -> low dword
hdw=(rect.bottom - rect.top); // -> high dword
result = MAKEULONGLONG(ldw, hdw);
Arbitrary write
Before discussing write primitive, it is important to understand behavior of necessary menu item API.
We will use SetMenuItemInfo for writing.
BOOL WINAPI SetMenuItemInfo( HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFO lpmii );
typedef struct tagMENUITEMINFO {
UINT cbSize;
UINT fMask; // MIIM_DATA , MIIM_ID …
UINT fType;
UINT fState; // -> MIIM_STATE
UINT wID; // -> MIIM_ID
HMENU hSubMenu; // -> MIIM_SUBMENU
HBITMAP hbmpChecked;
HBITMAP hbmpUnchecked;
ULONG_PTR dwItemData; // -> MIIM_DATA
LPTSTR dwTypeData; // -> MIIM_STRING
UINT cch;
HBITMAP hbmpItem;
} MENUITEMINFO, *LPMENUITEMINFO;
SetMenuItemInfo takes Menu handle and pointer to structure MENUITEMINFO.
This structure has an interesting field – fMask. Using this field we can give SetMenuItemInfo information what fields from structure have to be written. For instance, if we set MIIM_ID to fMask only wID field will be written.
Now we have required information about SetMenuItemInfo behavior and we can finally describe write primitive.
We can use API SetMenuItemInfo(MIIM_DATA) for writing content of arbitrary address.
MIIM_DATA allows to write DWORD64 on x64 system (dwItemData is ULONG_PTR). SetMenuItemInfo writes tagITEM.dwItemData field.
We will have little obstacle working from wow64. If we call “write” from wow64 process, we need to call service NtUserThunkedMenuItemInfo directly, because wow64 stub doesn’t allow to use x64 MENUITEMINFOW structure and as the result we can’t write 64 bit field (only 32-bit one).
But this obstacle is not a serious obstacle at all because we can easily call x64 code from x86 process (see Link section).
In both cases (read and write) we need to calculate arbitrary address according to tagITEM field offset.
Set arbitrary address for read (ArbAddr)
SetWindowLongPtr(hWndCorrupted, ExtraDataIndex, (LONG_PTR) ArbAddr - 0x48);
Where 0x48 is cxItem offset.
Set arbitrary address for write
SetWindowLongPtr(hWndCorrupted, ExtraDataIndex, (LONG_PTR) ArbAddr - 0x38);
Where 0x38 is dwItemData offset. You can see described above on picture below.
Also, you can see write and read tagITEM's fields on picture below.
Let’s list method’s pros and cons.
Pros:
1. Easy to implement. In most cases we can use documented API (except wow64 direct NtUserThunkedMenuItemInfo call).
2. Can be used under wow64 process and pure x64 process.
3. Access to full address space on x64.
4. Works under low integrity process.
5. tagITEM and tagMENU are not changing between Windows versions. tagWND is changing, but it is not affecting this method's implementation (we have full access to object’s addresses and their content).
Cons (Limitation):
1. We need vulnerability, which can corrupt memory in desktop heap or vulnerability where we can write specific address (tagWND.cbwndExtra offset).
2. Desktop heap must be mapped in exploitation context.
As conclusion, we can say, that it is possible to use another combination of objects like tagCLS + tagMENU (because tagCLS has cbclsExtra field and we can use SetClassLongPtr function) or find another USER read/write primitive. We offered one of the easiest implementations. The main idea here to take a look at USER objects from point of bypass of latest Windows 10 mitigations.
GDI Pool addresses leaks
If we have vulnerability where we can’t write specific address (like overflow), we can try to continue use old SURFACE technique. If we want to do that, we need to analyze different structures and objects, and find as much GDI pool pointers as possible. Then we need to select most interesting and use them during exploitation.
We found that:
We can leak addresses of some GDI structures or USER structures/data allocated in GDI pool.
We can find USER objects which are allocated in the GDI pool.
Based on both methods we can prepare memory for exploitation.
Main technique is allocation of GDI object (SURFOBJ) on place of freed object/structure which address we can get. Then we can use old SURFACE corruption technique.
Use of USER objects was described at Ekoparty 2016, we’ll show using of data and structures.
GDI structure address leak
We can get address of specific GDI structure by reading Desktop heap.
Base information:
Every window class (RegisterClass API) described by tagCLS structure.
tagCLS contains pointer to tagDCE structure (pdce field) if class was created with CS_CLASSDC style.
tagCLS is located in Desktop Heap, so we can read it content.
tagDCE is allocated in PagedSessionPool as GDI objects (CreateCacheDC).
tagDCE has constant length – 0x60 (Win 10 x64).
Getting tagDCE pointer:
1. RegisterClass with CS_CLASSDC style.
2. Create window for this class in order to cache DC and allocate tagDCE.
3. Get pointer to tagWND, then to tagCLS (tagWND.pcls) and finally tagCLS.pdce.
We need to call DestroyWindow + UnregisterClass in order to free tagDCE.
See picture below.
We can get pdce kernel pointer by using following code
DWORD64 pWndUMAddr = GetUserObjectKAddr(hWnd) - TEB.Win32ClientInfo.ulClientDelta;
DWORD64 pClassUMAddress = *(DWORD64 *)(pWndUMAddr + 0x98) - TEB.Win32ClientInfo.ulClientDelta;
DWORD64 pDceAddress = *(DWORD64 *)(pClassUMAddress + 0x18);
USER structures/data allocated in GDI pool
Some user objects are allocated in GDI pool, but they are not alone! There are many user structures allocated in GDI pool (tagPOPUPMENU, tagWND.pTransform, tagSBTRACK …).
We can get their addresses by reading object’s content from desktop heap (as USER objects contain pointers to this structures).
We also found, that tagCLS.lpszMenuName (see image above) allocated in GDI pool. This tagCLS field represents WNDCLASSEX.lpszMenuName (UNICODE).
We can easily allocate it by RegisterClass and free by UnregisterClass.
Also we can control size of this allocation.
We can easily get lpzsMenuName address by using following code (similar to pdce example above)
DWORD64 pWndUMAddr = GetUserObjectKAddr(hWnd) - TEB.Win32ClientInfo.ulClientDelta;
DWORD64 pClassUMAddress = *(DWORD64 *)(pWndUMAddr + 0x98) - TEB.Win32ClientInfo.ulClientDelta;
DWORD64 lpszMenuName = *(DWORD64 *)(pClassUMAddress + 0x88);
Summary: Objects in GDI pool
We can get addresses of different object/structures allocated in GDI pool. We can divide them by type, see table below
Obviously, some USER objects/structures aren’t possible to use during exploitation because we can’t easily allocate/control them.
We made list of potentially “exploitable” objects and structures with their pros and cons.
Usage of accelerator tables and clip data was shown at Ekoparty 2016. We’ll describe alternative way. Following list is incomplete, there are other candidates.
Spray approach (tagCLS.lpszMenuName)
Let's describe how to use tagCLS.lpszMenuName during exploitation.
1. Allocate tagCLS structure by calling RegisterClass with controlled lpszMenuName.
2. Allocate Window for class (as we don’t want to scan desktop heap) and get addresses of allocated tagCLS.lpszMenuName.
3. DestroyWindow and free tagCLS by UnregisterClass.
4. Allocate SURFOBJ (Bitmap)*.
5. Now, we can expect that SURFOBJ will be allocated on place of lpszMenuName.
*As there is lookaside list for previous GDI objects allocations, we need to fill lookaside before trying to allocate bitmap on freed space.
tagCLS.lpszMenuName spray advantages:
1. Easy allocation and destruction.
2. We can control size of tagCLS.lpszMenuName, because it is UNICODE_STRING buffer (not structure!).
3. tagCLS.lpszMenuName field allocated on GDI pool.
4. We can easily get address of field.
5. Method is working under low integrity process.
6. Big strings are allowed (> 4kb).
Conclusion
We can use a method of arbitrary read/write via USER objects when we can write a specific address.
We can use tagCLS.lpszMenuName spray for other vulnerabilities.
We also can use prediction of GDI objects addresses to make exploitation more stable.
We can use GDI structures(tagDCE), USER objects, USER structures allocated on GDI pool for exploitation. Choice of object will depend on vulnerability.
Links
https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms648001(v=vs.85).aspx
http://blog.rewolf.pl/blog/?p=102
https://www.coresecurity.com/system/files/publications/2016/10/Abusing-GDI-Reloaded-ekoparty-2016_0.pdf
https://msdn.microsoft.com/en-us/library/windows/desktop/ms725486(v=vs.85).aspx
https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms724291(v=vs.85).aspx
Link to ZeroNights presentation will be available later.
Yurii Drozdov & Liudmila Drozdova