|Image credit: Unsplash|
The article touched upon two functions of the library dhcpcore.dll: UpdateDomainSearchOption, mentioned in passing, and DecodeDomainSearchListData which is called by the first function and was described in more detail. As always happens when looking for vulnerabilities, even if the important findings boil down to just one or two functions, there’s a lot more code to review first. And occasionally you notice small things which are not relevant to the task at hand, but may have their own significance or may become useful later. Even if you have no time to dwell on them in the moment, your brain still takes note, and they surface again if after a while you get the chance to go back and check your guess.
This is exactly what happened this time. When researching the DhcpExtractFullOptions function responsible for processing all options in the DHCP response from the server, in particular the option calling UpdateDomainSearchOption, one’s attention is immediately drawn to two arrays on the stack, each containing 256 elements:
And there’s no sign of any checks limiting the values of iterators of these arrays. At the time we were dissecting a different vulnerability, so this information was not relevant. Therefore all we could do was remember this part of the code for later.
A few weeks later, we thought back to the DhcpExtractFullOptions function that had caught our attention earlier. We took it in a disassembler, worked out the pieces of code that were not fully parsed, and tried to figure out what those two curious static arrays were for.
When function execution begins, the arrays and their iterators are zeroed out:
The function parses all options in the packet received from the DHCP server, collects information from them, and processes it. Also, based on the results of parsing, it logs the respective event in the ETW (Event Tracing for Windows) service. Logging of events is exactly where the buffers we are looking at are involved. Along with a lot of other data, these are passed to the EtwEventWriteTransfer procedure. Preparing all data for logging takes a lot of work, and is not very relevant to the vulnerability we are discussing, so we will skip the examples.
It’s more important to see how those buffers are filled. Filling is part of the option parsing cycle. First, a function with the self-explanatory name ParseDhcpv4Option is called for the current option received for processing. It either fills the fields in the dhcp_pointers object using the received data, or makes a note about an unknown option if it encounters an option identifier for which there is no handler.
Once returned from ParseDhcpv4Option, the value of the identifier for the current option option_tag is written to the next element of the all_tags array, the first of the arrays we are looking at. If the function encounters an unknown option and therefore did not set the is_known_option flag, the value of the identifier is also written to the next element of the second array—unknown_tags. Of course, the variables mentioned in this article got meaningful names only after code analysis.
So, the all_tags array stores tags of all options from the received message, while the unknown_tags array has only the tags for options unknown to the parser. And there is no check at all for the indices of the arrays. Therefore, the values of those indices can exceed 256 and cause writes outside of the memory allocated for the arrays on the stack. To cause overflow of the first array, it’s enough for the DHCP server to send a packet with more than 256 options. The same stays true for the second array, with the only difference being that we need to send options that the client cannot handle.
Now let’s try to test our theoretical conclusions in practice. First let’s note that an option tag is one byte in size, while the array elements have the type int, which means an element size of four bytes. We therefore have an overflow where we control every fourth byte, and the rest are zeroed out on overwrite.
The easiest way to test our assumption is to overwrite the security cookie of the function stored in the stack, which will cause an exception related to a security check. Let’s simulate a situation in which the DHCP server sends a large enough number of options to cause an overwrite. Let’s say there are 0x1a0 (416) options with identifier 0xaa and zero size. So the size of each option is two bytes, and the total size of the packet with all headers will be 1100—1200 bytes. This value is within the MTU limit for Ethernet, therefore we have reason to believe the message will not be fragmented, which will help us to avoid any complications.
We send the packet formed this way in response to a request from the DHCP client and on the client’s computer we capture an exception in the respective svchost.exe process:
As we can see from the stack trace, option identifiers from our packet overwrote both the stack cookie and the return address for the function.
Of course, creating a usable exploit for this fault would require significant effort from the attacker. On modern systems, a buffer stack overflow is a complex and hard-to-exploit vulnerability due to all the modern protection mechanisms. On the other hand, let’s not forget that all those mechanisms protect return addresses and exception handlers from being overwritten, prevent execution of code in memory locations not assigned for that purpose, or prevent prediction of addresses. But, for instance, they can do nothing against overwriting of local variables stored on the stack between the overflowing buffer and return address. And the DhcpExtractFullOptions function contains several potentially dangerous variables in that range.
We wrote to Microsoft again to inform about the bug we found. After some correspondence, and after analysis of the request lasting about a week, we got a response saying that a CVE identifier for this vulnerability was being prepared, a patch was scheduled for release in March, and the vulnerability had been already reported to Microsoft by someone else. This is not very surprising: the fault is in the open and buffers without checks on the index limits are always the first to draw attention. Quite often they can be found by automated analysis.
In March, as promised, there came a patch fixing the fault, now identified as CVE-2019-0697. The researcher who previously reported the vulnerability was Mitch Adair, the same Microsoft employee who also found DHCP vulnerability CVE-2019-0547, fixed in January.
Author: Mikhail Tsvetkov, Positive Technologies