Usage of Windows kernel exploits have been on the rise, and are often used to break out of a browser sandbox. Many of the vulnerabilities found over the years have been in the driver win32k.sys, which handles system calls from gdi32.dll and user32.dll. To try and mitigate many of these vulnerabilities proactively Microsoft has implemented what is called Win32 Syscall Filter in Windows 10. The overall idea is to be able to block many System calls to win32k.sys for an entire process, such that unknown vulnerabilities cannot be taken advantage of. I have not been able to find many details about the implementation and how effective it really is, the only discussion I’ve found is the presentation Rainbow Over the Windows by Peter Hlavaty.
System Calls 101
My approach to understanding the filtering technique was to first look into how a System Call is actually executed. The analysis was performed on 64 bit version of Windows 10 Anniversary Update. Normally when a Win32k System Call is initiated it comes from a function in gdi32.dll or user32.dll which ends up calling the actual System Call in win32u.dll. However, it is possible to perform the System Call directly using assembly, in the following proof of concept I looked up the System Call number for the Win32k function NtGdiDdDDICreateAllocation which happens to be 0x119E. I then created the test application show below:
Where the function NtGdiDdDDICreateAllocation comes from the following assembly:
When this is run the syscall instruction makes the transition to kernelmode and the function to actually handle this System Call is located at nt!KiSystemStartService. However since many System Calls are invoked we need to set a condition in the debugger to catch the correct one:
Running the proof of concept and enabling the breakpoint yields:
Which first of all shows the System Call number to be 0x119E as we supplied, but also the arguments 1, 2, 3 and 4 in RCX, RDX, R8 and R9. Looking in Ida Pro we arrive at the following code:
Going through this code an interesting question is, what is the content of RBX and where does it come from. Trying to reference KiSystemServiceStart we find that RBX is set in the following function:
MOV RBX GS:188 loads the address of the kernel thread for the kernel process which represents Win32SyscallFilter.exe into RBX, this can also be verified below:
Looking Into The Algorithm
The question is then what is at RBX+0x78, it turns out that it is a series of flags and the two flags which are referenced in this case are GuiThread and RestrictedGuiThread since they are the 6th and 19th bits of that flag.
In our case we have the following flag values:
Since the thread is not a GUI thread, the execution is redirected to convert it to a GUI thread and then return to the same point. Continuing execution will see that happen:
This has nothing to do with the Win32kSyscallFilter and is normal practice. The next check is interesting, however. The RestrictedGuiThread flag indicates if the System Call filter is enabled, this may also be checked at the process level:
So for the current process and thread, the System Call filter is not enabled. Looking at the further execution shows the consequences of that flag:
If System Call filtering is enabled KeServiceDescriptorTableFilter takes the place of the KeServiceDescriptorTableShadow, which is used when no filtering is enabled. The next part is looking up the table of System Calls to use as shown below:
RDI contains the System Call number aftermath that has been performed on it. In the case of Win32k System Calls it will be 0x20. So depending on whether System Call filtering is enabled a different table is loaded into R10, the two options are:
The table is then used to deference the actual function call:
By following the algorithm above in the debugger we find:
So it is clear that the System Call number is a negative offset into the W32pServiceTable structure, and then points to the actual NtGdiDdDDICreateAllocation function. This is all very fine, but what is the difference if System Call filtering had been enabled. This may be examined by using the W32pServiceTableFilter instead:
We see that there is no difference from before, that is because NtGdiDdDDiCreateAllocation is not one of the APIs which are filtered. If we had chosen some other System Call like NtGdiDDCCIGetCapabilitiesStringLength, which has a System Call number of 0x117E we get the following two outputs depending on whether System Call filtering is enabled, first without:
And with System Call filtering:
It is clear that if System Call filtering is enabled and the System Call in question is not allowed another function is called. The filtered function verifies that System Call filtering is enabled and then simply exits the System Call.
Exploitation Ramifications
Now that we understand how System Call filtering works, we need to look at how well it protects against kernel exploitations. The first thing to look at is which processes are protected, so far it is only Microsoft Edge and at the moment there is no supported way to enable it for a third party application. This means that System Call filtering is only interesting in regards to Microsoft Edge exploits, and only for the kernel exploit. Below we can see that for MicrosoftEdgeCP.exe, which is the rendering process, System Call filtering is in fact enabled:
Recalling my earlier blog post on reenabling the usage of the tagWND object as a read/write primitive, I wondered if any of the System Calls used in that method would be filtered. The kernelmode functions used in that method are:
- NtUserCreateWindowEx
- NtUserDestroyWindow
- NtUserSetWindowLongPtr
- NtUserDefSetText
- NtUserInternalGetWindowText
- NtUserMessageCall
None of these have a corresponding stub_* method in win32k.sys, meaning that they are not filtered. The conclusion is then that Win32k System Call filtering does not block the System Calls required to leverage a vulnerability. The filtering must then block the System Calls used in triggering the vulnerability, be that a write-what-where condition or a pool overflow. The protection offered by Win32k System Call filtering is neat, but hinges on whether the System Calls used to trigger the vulnerability are filtered, which is a case by case situation.