On August 11, 2015 Microsoft released 14 security fixes, including an SMB Server fix. In this post I'll explain how I triggered the SMB Server bug.
Microsoft Security Bulletin MS15-083:
Of all the available patches, I focused in this one: Server Message Block Memory Corruption Vulnerability - CVE-2015-2474
"An authenticated remote code execution vulnerability exists in Windows that is caused when Server Message Block (SMB) improperly handles certain logging activities, resulting in memory corruption."
The affected software was Windows Vista and Windows Server 2008, both 32 and 64 bits. It's worth noting that this is the first time Microsoft has published a security fix for SMB Server since 2011.
Installing the Patch:
Once the "Windows6.0-KB3073921-x86.msu" patch was downloaded, I tried to install it and got this message:
That's weird, because when a kernel patch is installed, the operating system has to be restarted. In this case, the patch installer didn't prompt me to restart.
Looking at "c:\windows\system32\drivers" I could see that "srv.sys" and "srvnet.sys" had changed.
I noticed that the new "srvnet.sys" file date was April 2011.
On the other hand, the new "srv.sys" file date was correct.
Diffing Stage - Part 1:
Comparing the new "srv.sys" version (v6.0.6002.19438) to the previous one (v6.0.6002.18407) released by Microsoft in MS11-020, I realized that there weren't code changes, only changes in some compilation strings.
I looked for information about the patch and found a tweet sent by a collegue, Greg Linares
(@Laughing_Mantis), where he showed some code changes in "srvnet.sys".
I contacted Greg and confirmed that the patch installer was wrong, and thanks to him I was able to finish manually decompressing the patch by using the "expand.exe" command.
Diffing Stage - Part 2:
Once the patch was decompressed, I found two "srv.sys" versions and two "srvnet.sys" versions.
Diffing the older "srvnet.sys" version (v6.0.6002.18462) against the new one (v6.0.6002.23746) -- both versions included by the same patch installer -- I was able to find the code changes I was looking for!
There were seven functions with important changes, and more with fewer changes.
The important changes were found in:
According to Microsoft, the bug was in "certain logging activities", so I focused on the "SrvLibLogError" function.
Here's the diff between the original code and the new one:
It's clear that an Integer Overflow had been fixed.
Looking at the "IoAllocateErrorLogEntry" call code, I could see that this fix prevents the "message size" from being reinitialized to zero when the logging message size is bigger than 255 bytes.
If this happens in the unpatched version, not enough memory will be allocated to write the logging message and a HEAP OVERFLOW will be produced.
For some reason, the variable used as "message size" was incorrectly cast to UNSIGNED CHAR before being passed as parameter.
In the new code version, this variable was used correctly as UNSIGNED INT.
Diffing Stage - Bonus Track:
Checking the "SrvLibLogError" function in different Windows versions, I noted that this vulnerability is still present in Windows 7, Windows 8, Windows 8.1 and Windows 10.
This is an image of the vulnerable basic block in "Windows 10" 64 bits, part of "srvnet.sys"
On the other hand, this bug was silently fixed in Windows 2008 R2.
Although the way to reproduce this bug seems to be suppressed in these operating systems, it's important to clarify that the "SrvLibLogError" function is EXPORTED by "srvnet.sys" and that it is still called by "srv.sys" when the SMB connection is established when using SMBv1.
That means if any Windows driver (first or third party) calls this exported function, the vulnerability will be introduced again.
Possible Ways to Reach the Vulnerable Function:
Once the vulnerability is detected, the challenge for the exploit writer is then finding the correct INPUT to trigger the bug, in this case via the SMB protocol.
This illustrates all of the possible ways to reach the "SrvLibLogError" function
In the 6th argument this function receives a list of strings to be logged, and in the 7th argument it receives the number of strings to be logged.
Following the callgraph and looking call by call, I noted that the most interesting call came from the "SrvLibLogSpnError" exported function, located in the same library (srvnet.sys).
Looking up, this function is imported and called only by the "SrvValidateSecurityBuffer" function, present in "srv.sys" and "srv2.sys".
That means the attack can be done using the SMB protocol version 1 and version 2.
Interestingly, the call to the "SrvLibLogSpnError" function appeared in the MS11-020 Microsoft patch.
Triggering Stage - Part 1
Using a Windows 7, by executing commands like "\\192.168.60.60\shared" directed to a Windows Server 2008, I could see that the "srv2!SrvValidateSecurityBuffer" function was called when the SMB Server received the "Session Setup Request" packet with the "NTLMSSP_AUTH" option via SMBv2.
This was the first requirement to reach the vulnerable function:
The value of the "_SmbServerNameHardeningLevel" variable has to be different from zero.
This variable is set by reading the value of the "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\SmbServerNameHardeningLevel" registry key.
This variable is related to the policy setting "Server SPN target name validation level", part of the "SMB hardening".
Triggering Stage - Part 2
Once the key was set, the second problem appeared.
The error code returned by the "MapSecurityError" function was 0xc00000bb.
That means that if the error code was different from zero, the logging function wouldn't be called.
Seeing "https://msdn.microsoft.com/en-us/library/cc704588.aspx" I realized that the returned value meant "STATUS_NOT_SUPPORTED".
The "MapSecurityError" function receives the output of the "QueryContextAttributesW" function as argument, located in the "ksecdd.sys" driver.
Looking for information about the second one at "https://msdn.microsoft.com/en-%20us/library/windows/desktop/aa379337 (v=vs.85).aspx" I realized that the "ulAttribute" parameter was set with the 0x1b value, which meant "SECPKG_ATTR_CLIENT_SPECIFIED_TARGET".
Here is the description of this constant:
Analyzing the code of the "ksecdd!QueryContextAttributesW" function I confirmed that the support for this constant DIDN'T EXIST.
So, I found a contradiction, because the patch fixed a vulnerability in "Windows Vista" and "Windows 2008", but the way to reach the vulnerable function wasn't available in those operating systems.
Triggering Stage - Part 3
Reading the link to the patch page again, I saw that there was a reference to "Extended Protection for Authentication" (EPA).
Looking for more information, I found a blog post written in 2009 by Microsoft in the "Security Research and Defense Blog".
The section read: "Microsoft is releasing several non-security updates that implement Extended Protection for Authentication as a mechanism to help safeguard authentication credentials on the Windows platform ..."
I downloaded and installed the EPA support for Windows 2008 from the first blog post link:
Triggering Stage - Final Part
With EPA installed, the "SmbServerNameHardeningLevel" registry key set as 1 or 2, the "File Sharing" option enabled and the "Password protected sharing" option disabled, the "QueryContextAttributesW" function started to return 0 ("STATUS_SUCCESS") by using any user, registered or not by the SMB Server.
It became interesting when the "pBuffer" parameter of the "QueryContextAttributesW" function returned this value:
Sniffing the connection in Wireshark, I realized that the "pBuffer" parameter was returning the "Target Name" attribute of the "NTLMv2 Response" structure included in the "Session Setup AndX Request" packet.
Depending on the SMB version used by the connection, it's the third or fourth SMB packet sent by the SMB client.
When I realized this, I started to build and send crafted "Session Setup AndX Request" packets like this:
and the result was this :-)
Microsoft scored this bug with this exploitability index:
Now, let's see what happens when this bug is triggered:
A kernel exception was generated by the "nt!ExFreePoolWithTag" function when this tried to free the CURRENT CHUNK allocated by the "IoAllocateErrorLogEntry" function.
It's important to note that the pool type where the heap overflow is produced is in the number 0 (NonPagedPool).
You can see a complete pool type list here: https://msdn.microsoft.com/en-us/library/windows/hardware/ff559707 (v=vs.85).aspx
Let's see a little bit more:
The "nt!ExFreePoolWithTag" function detected that the HEADER of the NEXT CHUNK is CORRUPTED.
In this case, we can see that the current chunk is 0x8c7913b8 and the next one is 0x8c791478, so that means that the next header is "41 41 41 41 41 41 41 41".
What would happen if the chunk header had valid values ?
Analyzing the CHUNK HEADER, we can find a format like this:
9 bits (previous chunk size / 8) + 7 bits (misc) + 9 bits (current chunk size / 8) + 7 bits (allocated|free|misc) + 4 bytes (TAG)
I control ALL the data that I write, so I could set a VALID VALUE in the first field of the next chunk (previous chunk size field).
For example, if the allocated chunk by "IoAllocateErrorLogEntry" function measures 256 bytes (0x100 hex value), it means that the "previous chunk size" of the NEXT CHUNK HEADER should be 0x100 / 8 = 0x20.
In fact, if I set the correct value to the overwritten "previous chunk size", when the CURRENT CHUNK is freed, all checks will be okay and nothing will happen, so the target won't crash.
Once we know how to bypass the first BSoD, we can use the art of the remote exploitation by using all kinds of heap spray techniques.
To exploit this vulnerability, I could use a very old technique called "HEAP COALESCING", since this technique was mitigated from Windows 7.
On the other hand, if I were able to control remote allocations with accuracy, I could overwrite a memory object, so I could get one or more arbitrary writes.
Another option could be to overwrite the low part of a function pointer.
The max memory size that I can overwrite beyond the allocated chunk is near to 2300 bytes.
In the case of this vulnerability, the most important trick of the exploitation process is that, if the exploit fails and the Windows kernel crashes, the target will be restarted automatically.
In the worst case scenario, this means the attack could be done infinite times until RCE (Remote Code Execution) is reached.
It's clear that the exploitation is very tricky, but I'm not sure whether the exploitability index is correct.
Microsoft re-released this patch on September 8, 2015.
Now, the patch installer works fine and this fix is correctly installed.
Only, the "SrvLibLogError" function was fixed.
On the other hand, Windows XP and Windows 2003 remain being vulnerable because the support has ended.