Windows Kernel Shellcode on Windows 10 – Part 2

This blog post is the second in the series on Windows kernel shellcode and picks up the nulling out ACLs method described by Cesar Cerrudo at Black Hat in 2012. You can find part 1 here.

The same assumptions as in the previous blog post apply here, that being the exploit has gained arbitrary kernel mode code execution and we can handcraft the assembly code to run. I see the ACL NULL technique used almost as much as the token replacement one. The idea is to locate the SecurityDescriptor, or in effect the ACL, of a privileges process and replace it with NULL. This value tells Windows that no permissions have been assigned for the process and hence everyone has full access to it. In Windows 10 Anniversary a mitigation for this has been implemented as noted by Nettitude Labs:

To sum up the blog post, nulling the SecurityDescriptor of a privileged process like winlogon.exe causes a BugCheck. We can verify this by finding the EPROCESS of winlogon.exe and its SecurityDescriptor:


Manually setting this to zero, simulating the shellcode, yields:


Along with a BSOD:


Just has foretold by Nettitude Labs. This means that the null ACL method described by Cesar Cerrudo no longer works, but the question is whether it can be modified to work. The mitigation only checks if the SecurityDescriptor is zero, not what the contents of the ACL is, this means we can modify that instead and achieve the same thing.

Looking into ACL’s

Since we cannot just remove the ACL entirely we have to figure out how to modify it, so it can be abused. An ACL consists of quite a few parts, the main structure comes from the _SECURITY_DESCRIPTOR as defined on MSDN:


What is interesting from this part is the DACL or Discretionary ACL, since it specifies the access particular users have to the object, in this case winlogon.exe. An ACL has the following structure according to MSDN:


What this does not say, is that the ACL object really is only a header, and the actual content is in the subsequent access-control entries or ACE’s. For a DACL there are two types of ACE’s, ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE, we are clearly more interested in the ACCESS_ALLOWED_ACE. An ACCESS_ALLOWED_ACE has the following structure:


The ACCESS_ALLOWED_ACE says which rights a certain SID has through the ACCESS_MASK.

So to sum it up, the SecurityDescriptor pointer points to the a SECURITY_DESCRIPTOR object which contains a DACL with one or more ACCESS_ALLOWED_ACE structures. This is quite a lot of structures to sort through, but luckily there is a command to dump it all in WinDBG called !sd. !sd takes the SecurityDescriptor pointer as argument. We can find the SecurityDescriptor pointer as shown below:


However when calling !sd on that pointer we run into problems:


The problem is that the lower bits are actually a fast reference, 4 bits in the case of x64, so we have to zero those out, then we get the correct results:


This dump tells us a lot, first of all the AceCount of the DACL is 2, which means it contains two ACE’s, both of which turn out to be ACCESS_ALLOWED_ACE. One is for NT AUTHORITYSYSTEM and one for BUILTINAdministrators. It also shows that SYSTEM has full rights over the process. There are many routes to make access to winlogon.exe possible, for the one I chose I wanted to give the rights SYSTEM has to someone else. My idea was to change the SYSTEM SID to some low privileged group, hence giving any members of that group full rights to the process. This requires us to find the SID for that ACE in memory. Going back to the structures the DACL ACL structure should be at offset 0x20 as can be seen from WinDBG:


Dumping the ACL structure there gives:


Which is clearly wrong since we just saw there were only two ACE’s for the DACL. Toying around with it I found that dumping it at offset 0x30 gave the correct results, leaving me to think the symbols present are not up to date:


Sadly the symbols for the ACE structures are not present, so the rest has to be done by pure hex dump, at offset 0x30 we find:


The ACL header takes up 8 bytes, followed by each of the ACE’s. Each of the ACE’s start with the ACE_HEADER which according to MSDN looks like this:


Which means it has a size of 4 bytes, then comes the Mask which is just a DWORD, in this case 0x1FFFFF, and lastly the SID which then starts at offset 0x40 of the SecurityDescriptor. From the !sd command above we notice that the first SID is S-1-5-18, this matches the hex data we have, since 1 is at offset 0x41, 5 is at offset 0x47 and 18, or 0x12, is at offset 0x48. We now know where the SID which has all rights for the process is located and its structure in memory. The question is what to change it to, looking up global SID’s we find that S-1-5-11 is the Authenticated Users SID, which certainly means us. We can then just change one byte from 0x12 to 0xb to change the user from SYSTEM to Authenticated Users, this can be seen below:


Which is also what Process Explorer shows us:


The next step is normally to create a thread in winlogon.exe and run the usermode shellcode with SYSTEM rights. However when we try that we get an error:


We do not have permissions to get a handle to the process, this is because winlogon.exe runs at a higher integrity level than the Shellcode.exe process. So even though we are a member of the Authenticated Users group and have full control of winlogon.exe we cannot get a handle to it since we are currently at a lower integrity level. This problem does not come from winlogon.exe, but rather from our own process, and the token we have. The token for the current process is located at offset 0x358 in the EPROCESS as seen below:


We notice that the pointer to the token again is a fast reference, so we need to ignore the lower 4 bits, giving us:


The MandatoryPolicy is interesting for us in this case:


A value of three tells us that the TOKEN_MANDATORY_POLICY_NO_WRITE_UP flag is set, which means that we cannot access objects with a higher integrity level then the present one. We also notice that if this value is changed to 0, we are allowed to do so. Manually modifying it in the debugger yields:


From this it is clear that changing those two bytes will allow us to inject code into winlogon.exe just like with a NULL ACL.

The Shellcode

The way we do this is by first finding the KTHREAD from the GS register and then the EPROCESS at offset 0x220 from that:


At offset 0x450 in the EPROCSS we find the name of the process executable:


We can use this to iterate through all the EPROCESSES till we find the right one, this may be done by searching for the first 4 bytes of the name:


This is implemented below:


Once we have located the EPROCESS of winlogon.exe we find its SecurityDescriptor, remove the fast reference and change the byte at offset 0x48 to 0xb:


Now we need to modify the MandatoryPolicy of the exploited process, we already have the EPROCESS address, so we find the Token pointer, remove the fast reference and change the byte at offset 0xd4 to 0:


And that’s it. It may not be quite as pretty or easy as the old ACL NULL trick, but it achieves the same effect only changing two bytes in memory.

The assembly code can be found at the following GitHub: