понедельник, 28 ноября 2016 г.

LPE vulnerabilities exploitation on Windows 10 Anniversary Update

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

четверг, 24 ноября 2016 г.

Windows 10 Anniversary Update: GDI handle management and vulnerabilities exploitation

Introduction 


Windows 10 Anniversary Update came with a lot of new mitigations. In the links section you can find reference to Microsoft presentation about improvements.
We now interested in KASLR improvements, that are introduced for Windows 10 x64 only.
We will focus on one related to GDI objects addresses.
Now PEB.GDISharedHandleTable doesn’t disclose GDI objects addresses and this can lead to some difficulties during exploitation.
This article describes what changes were made in win32k code in order to implement this mechanism. On the next blog post we will describe bypasses of this mitigation.
This article is based on our Defcon Russia presentation (you can find link in Links section).

GDI handle management before Windows 10 Anniversary Update


GDI objects - Bitmap, Brush, Pen, DC, Font... You can find GDI objects list on MSDN (links).
GDI handle management (in this article) is process includes creation of GDI handle table, allocation of GDI handles, insertion/deletion of objects into handle table etc.
Win32k.sys contains GDI handle manager (win32kbase.sys on Windows 10).
win32k!Hmg* functions are responsible for handle management.
HmgCreate function creates GDI handle table and initializes other handle manager structures. HmgInsertObject function inserts every created GDI object into handle table.
Handle table pointer saved in win32k variable gpentHmgr in kernel mode.
Every object in handle table described by following structure

 typedef struct {  
 PVOID64 pKernelAddress;  
 USHORT wProcessId;  
 USHORT wCount;  
 USHORT wUpper;  
 USHORT wType;  
 PVOID64 pUserAddress;  
 } GDICELL64;  

The most interesting field here is pKernelAddress which represents kernel address of specific GDI object.
The handle table is being mapped to address space of every GUI process by win32k!GdiProcessCallout function.
Pointer to GDICELL array is located in PEB.GdiSharedHandleTable in usermode.
So, we could get GDI objects kernel addresses from usermode from PEB.GdiSharedHandleTable.
HmgInsertObject is good starting point for research of changes because it contains handle table access code.
Old HmgInsertObject function is relatively simple. Incomplete code

 HmgInsertObject(_BASEOBJECT *ObjectKernelAddress,   
               unsigned __int16 flags,   
               unsigned __int8 objtype) {   
        ...   
           Handle = hGetFreeHandle(objtype);   
           ENTRYOBJ::vSetup( (gpentHmgr + 0x18 * LOWORD(Handle)),   
                            ObjectKernelAddress,   
                            objtype,  
                            flags,   
                            LOWORD(Handle));   
        ...  
 }  

ENTRYOBJ::vSetup function is filling GDICELL64 structure with given parameters.
New HmgInsertObject is much more complicated (we will describe changes later).

Usage of GDI object addresses 


How attackers used GDI kernel objects addresses during exploitation ?

More stable exploitation:
1) It is possible to check if object was allocated on the right place after spray.
2) It is possible to change memory layout as necessary.

Arbitrary read/write:
1) It is possible to change different fields of Bitmap (SURFACE in kernel) and gain arbitrary read and write (and gain system privileges).
This method was described many times, one detailed description you can read from Core Security Blog (you can find link at the end of article).
Briefly, SURFACE (corruption of its substructure SURFOBJ) is one of the popular ways to achieve privilege escalation, which is working from Vista to 10.
In this technique we can use 2 SURFACES (SURFOBJ1 and SURFOBJ2 on picture) located close to each other in memory. If we can corrupt specific field in SURFOBJ1 we will be able to write SURFOBJ2 pvScan0. When pvScan0 is changed to arbitrary address we can read/write it via GetBitmapBits/SetBitmapBits API on SURFOBJ2. Picture below is shown what fields should be corrupted in SURFOBJ1 in order to gain arbitrary read/write via SURFOBJ2.

SURFACE arbitrary read write

We can use GDI objects for exploitation even if we have vulnerability in different (not win32k) system component. In fact GDI objects provide good exploitation primitive.

Windows 10 Anniversary Update 


GDI handles management was changed a lot after update of Windows 10. PEB.GdiSharedHandleTable doesn’t contain kernel addresses anymore.
Below are two pictures – first with old content of GdiSharedHandleTable and second one with new content.


New GdiSharedHandleTable


New GdiSharedHandleTable

We can see that updated handle table doesn’t contain any addresses.
Speaking about kernel-side changes, new handle management classes and functions were added in win32kbase.sys. Few Hmg* functions were added. List is below


 HmgAllocateDcAttr  
 HmgMarkLazyDelete  
 HmgPentryFromPobj  
 HmgReplaceObject  

The most interesting from them is HmgPentryFromPobj, because it references new class GdiHandleManager. New handle management classes were added – GdiHandleManager, GdiHandleEntryDirectory, GdiHandleEntryTable, EntryDataLookupTable.
Also, new syscall was added – win32kbase! NtGdiGetEntry. This syscall is being used from gdi32.dll during querying GDI handle table from different functions (like DeleteObject or ReleaseDC).
This syscall returns GDICELL64 structure corresponding to specified handle (but without kernel address – see detailed research below).
We can easily track all changes via HmgInserObject function. We also can analyze new HmgCreate function in order to better understand structure of new tables.

New handle manager structures 


Let’s describe changes in details.
First, new kernel global variable was introduced – win32kbase!gpHandleManager.
gpHandleManager initializes by function GdiHandleManager::Create. This function allocates structure of 0x20 size (pointer to this memory later is being put to gpHandleManager variable ), we will later reference this structure as GdiHandleManager. Also, another structure is being initialized inside GdiHandleManager::Create by call GdiHandleEntryDirectory::Create.
GdiHandleEntryDirectory::Create allocates and initializes another structure of size 0x810 (we will reference it as GdiHandleEntryDirectory).
Allocated pointer is being put to GdiHandleManager structure at 0x10 offset. GdiHandleEntryDirectory structure can contain 256 pointers to structures created by function GdiHandleEntryTable::_Create (we will reference it like GdiHandleEntryTable). GdiHandleEntryDirectory has 8-byte header ( it contains count of created tables and ‘busy’ flag which is being set when all tables are filled up), 256 GdiHandleEntryTable pointers and max handle count.
New GdiHandleEntryTable can be created by function GdiHandleManager::AcquireEntryIndex .
Speaking about 256 entries, in fact only first one is being used.
Size of GdiHandleEntryTable can be different depending on number of table. Size is 0x20 bytes for first table and 0x20 + 0x18 * max_handle_count (0x18 is GDICELL64 size). Also, the address of gpentHmgr is being put on 0x0 offset for first table, address of GdiHandleEntryTable+0x20 is being put on 0x0 offset for next tables. In fact, first qword of GdiHandleEntryTable is old gpentHmgr, but without GDI objects addresses. As we can see, there is no usermode mapping at all for next tables. Next interesting structure pointer is located on 0x18 offset inside GdiHandleEntryTable.
This structure is created by call GdiHandleEntryTable::EntryDataLookupTable::Create (we will reference this memory as EntryDataLookupTable). EntryDataLookupTable has size

 8 * (max_handle_count + 255)/256 + 0x10  


The default value for max_handle_count is 0x10000. In fact, EntryDataLookupTable contains 0x10 bytes header and 256 pointers to memory blocks and each block contains 256 LOOKUP_ENTRY structures. Pointer to EntryDataLookupTable + 0x10 is located on offset 0x0 inside EntryDataLookupTable. Inside LOOKUP_ENTRY structure at offset 0x8 we can finally get an object kernel address.
Now we must go a long way to reach GDI object address.
Speaking about new handle structure, now it contain object type and two 1-byte indexes (see picture), instead of one word index in old handle table.

You can see associated structures on picture below.

GDI Handle tables after Windows 10 anniversary update

Also, we defined some structures (probably they can be interesting for the reader)

 struct GdiHandleManager {  
 DWORD64 unknown;  
 DWORD max_handle_count;  
 DWORD unknown1;  
 GdiHandleEntryDirectory * Dir;  
 };  
 struct GdiHandleEntryDirectory {  
 BYTE busy_flag ;  
 BYTE unknown;  
 WORD TableCount ;  
 DWORD unknown1 ;  
 GdiHandleEntryTable * Tables[0x100] ;  
 DWORD MaxHandleCount ;  
 } ;  
 struct GdiHandleEntryTable {  
 GDICELL64 * SharedMem_or_CellData ;  
 DWORD MaxHandleCount ;  
 DWORD unknown1 ;  
 DWORD unknown2 ;  
 DWORD unknown3 ;  
 EntryDataLookupTable * GdiLookupTable ;  
 } ;  
 struct EntryDataLookupTable {  
 LookupEntryAddress *LookupTableData ;  
 DWORD MaxHandleCount ;  
 DWORD unknown1 ;  
 } ;  
 struct LookupEntryAddress {  
 LOOKUP_ENTRY *leaddress ;  
 } ;  
 struct LOOKUP_ENTRY {  
 DWORD64 unknown;  
 PVOID64 GdiObjectAddress;  
 };  

It is really big changes in comparison with old gpentHmgr structure (in fact, it contained array of GDICELLs with kernel addresses)

Old gpentHmgr structure


How to get GDI object address by handle via Windbg (x64) ? 


In order to understand all described structures better we compared two Windbg commands – for old handle table and for new one.
Windbg command looked like this (handle in this case - 0x3c05096a) before Anniversary update

 dq poi(poi(win32kbase!gpentHmgr) + 0x18*(0x3c05096a & 0xffff))  

Lower word in handle was used as index in gpentHmgr table (0x096a in example above).
Updated Windbg command (handle in this case - 0x1f0509e) is below

 dq poi(poi(poi(poi(poi((poi(poi(win32kbase!gpHandleManager)+0x10) + 8 + 0*8)) +  
 0x18)) + ((0x1f0509ea & 0xffff) / 0x100) * 8) + (0x1f0509ea & 0xff)*0x10 + 8)  

On the picture below we can see result of execution of second command (successful dump of object content) .

Handle content Windbg


What new PEB.GdiSharedHandleTable contains? 


Let’s discuss the content of updated PEB.GdiSharedHandleTable. Handle entry size wasn’t changed, it is like old GDICELL64 size – 0x18

 typedef struct {   
 PVOID64 pKernelAddress;  
 USHORT wProcessId;   
 USHORT wCount;  
 USHORT wUpper;  
 USHORT wType;  
 PVOID64 pUserAddress;  
 } GDICELL64;  

The main change – pKernelAddress contains value 0xffffffffff000000 | dword_index, where

 dword_index = [zero_byte][unused_table_index][lookup_entry_address_index]|[lookup_entry_index]  

So, no kernel addresses anymore.

Changes summary 


Object metadata (pid, object type …) and object address were saved together in GDICELL structure before Anniversary update and both were mapped to userspace.
Now, only object metadata is mapping, kernel addresses are located in kernel pool inaccessible from usermode.

LPE vulnerabilities exploitation after updates 


All these changes made exploitation more difficult. But there are few possible solutions.
GDI is good, but not only exploitation approach.
We still have USER objects (window, cursor, menu etc, see MSDN incomplete list) and we can still get their addresses! i.e. we can use USER objects in some exploits instead of GDI objects.
We can still use GDI objects (SURFOBJ): we can try to predict location of object via spray.
Next blog entry will be about bypasses of described mitigation (from our ZeroNights 2016 presentation).

We also happy to answer your question about this research, so feel free to ask us.

Links 


Our presentation from Defcon Russia DCG#7812 (30th of September 2016)

http://www.slideshare.net/DefconRussia/windows-10-gdi-68999382 

Additional links, related to this research

https://www.coresecurity.com/blog/abusing-gdi-for-ring0-exploit-primitives
https://www.blackhat.com/docs/us-16/materials/us-16-Weston-Windows-10-Mitigation-Improvements.pdf
https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms724291(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms725486(v=vs.85).aspx

Yurii Drozdov & Liudmila Drozdova