DHCP Security in Windows 10: Exploring the Critical Vulnerability CVE-2019-0726

    Image: Pexels

    With the release of January updates for Windows, the news of the critically dangerous vulnerability CVE-2019-0547 in DHCP clients stirred the public. The high CVSS rating and the fact that Microsoft did not immediately publish a performance assessment, which made it difficult for users to decide on an urgent system update, warmed up interest. Some publications even suggested that the lack of an index can be interpreted as evidence that a working exploit will appear in the near future.

    Solutions like MaxPatrol 8, are able to identify computers vulnerable to certain attacks on the network. Other solutions, such as PT NAD, detect such attacks themselves. To make this possible, it is necessary to describe both the rules for detecting vulnerabilities in products and the rules for detecting attacks on these products. In turn, for this to become possible, it is necessary for each individual vulnerability to find out the vector, the method and conditions of its operation, that is, literally all the details and nuances associated with the operation. A much more complete and deeper understanding is required than what can usually be compiled from descriptions on vendor sites or in CVE, such as:

    The vulnerability is manifested because the operating system incorrectly processes objects in memory.

    So, to add to the company's products the rules for detecting attacks on a newly-made vulnerability in DHCP, as well as the rules for identifying devices affected by it, you should understand the details. In the case of binary vulnerabilities, patch-diff is often used to gain insight into the underlying errors, that is, a comparison of changes made to the binary code of an application, library or kernel of the operating system with a specific patch, an update that fixes this error. But the first stage is always reconnaissance.

    Note : To go directly to the description of the vulnerability, bypassing the underlying DHCP concepts, you can skip the first few pages and go directly to the “DecodeDomainSearchListData Function” section.


    We turn to the search engine and view all currently known vulnerability details. This time there are a minimum of details, and all of them are free processions of the information gathered from the original publication on the MSRC website. This situation is quite typical for errors discovered by Microsoft during an internal audit.

    We find out from the publication that we are faced with a memory corruption vulnerability, contained in both client and server systems of Windows 10 version 1803 and appearing at the moment when an attacker sends specially crafted responses to a DHCP client. After a couple of days from that moment on the page, the performance indices will also appear:

    As you can see, MSRC rated "2 - Exploitation Less Likely". This means that a mistake with a high probability is either not operational at all, or the operation is fraught with such difficulties, overcoming of which will require too high labor costs. Admittedly, Microsoft does not tend to underestimate such estimates. This is partly influenced by the risk of reputation losses, and partly by some independence of the response center within the company. Therefore, suppose: since the threat of exploitation is indicated in the report as unlikely, it certainly is. Actually, this could have completed the analysis, but it would not be superfluous to double-check and at least find out what the vulnerability was. Ultimately, despite all the undeniable personality, errors tend to repeat themselves and manifest themselves in other places.

    From the same page we download the patch (security update) provided in the form of an .msu archive, unpack it and look for files most likely related to the processing of DHCP responses on the client side. Recently, it has become much more difficult to do this, since updates began to be delivered not in the form of separate packages that fix specific errors, but as a single cumulative package that includes all monthly corrections. This greatly increased the excess noise, that is, changes not related to our task.

    Among the entire set of files, the search finds several libraries suitable for the filter, which we compare with their versions on an unpatched system. The dhcpcore.dll library looks the most promising. In this case, BinDiff produces minimal changes:

    Actually, other than cosmetic changes made to a single function - DecodeDomainSearchListData. If you are well acquainted with the DHCP protocol and its options that are not used too often, then you can already assume that this function processes the list. If not, then move on to the second stage - the study of the protocol.

    DHCP and its options

    DHCP ( RFC 2131 | wiki ) is an extensible protocol whose replenishment capabilities are provided by the options field. Each option is described by a unique tag (number, identifier), the size occupied by the data contained in the option, and the data itself. This practice is typical of network protocols, and one of the options “implanted” into the protocol is the Domain Search Option described in RFC 3397 . It allows the DHCP server to set the standard domain name endings on clients, which will be used as DNS suffixes for the connection configured in this way.

    Let, for example, the following name endings were set on our client:


    Then, in any attempt to determine the address by the domain name, the DNS queries will substitute the suffixes from this list in turn until a successful display is found. For example, if the user entered ru in the address bar of the browser, then DNS queries will be generated first for ru.microsoft.com, then for ru.wikipedia.org:

    In fact, modern browsers are too smart, and therefore they do not look like names FQDNs respond by redirecting to the search engine. Therefore, below we enclose the conclusion of less spoiled utilities:

    It might seem to the reader that this is the vulnerability, because the mere possibility of replacing DNS suffixes with a DHCP server, which any device on the network can identify itself with, poses a threat to clients requesting any network parameters via DHCP . But no: as follows from the RFC, this is considered quite legitimate, documented behavior. Actually, the DHCP server is inherently one of those trusted components that can have a strong impact on the devices accessing them.

    Domain Search Option

    Domain Search Option is numbered 0x77 (119). Like all options, it is encoded with a single-byte tag with the option number. Like most other options, immediately after the tag is a single-byte size of the data following the size. Option instances may be present in the DHCP message more than once. In this case, data from all such sections are concatenated in the sequence in which they appear in the message.

    In the presented example, taken from RFC 3397 , the data is divided into three sections, each of 9 bytes. As you can see from the picture, the subdomain names in the fully qualified domain name are encoded with a single-byte length of the name, immediately followed by the name itself. Encoding of the fully qualified domain name ends with a null byte (that is, a null-sized subdomain name).

    In addition, the option uses the simplest method of data compression, or rather, just reparse points. Instead of the size of the domain name, the field may contain the value 0xc0. Then the next byte sets the offset relative to the beginning of the option data, which should be used to search for the end of the domain name.

    Thus, in this example, a list of two domain suffixes is encoded:


    DecodeDomainSearchListData Function

    So, the DHCP option number 0x77 (119) allows the server to configure DNS suffixes on clients. But not on machines with Windows operating systems. Microsoft systems have traditionally ignored this option, so historically the end of DNS names, if necessary, was rolled through group policies. This continued until recently, when the next release of Windows 10, version 1803, added processing for the Domain Search Option. Judging by the name of the function in dhcpcore.dll, into which the changes were made, it is in the added handler that the error in question lies.

    Getting to work. We comb the code a bit and find out the following. The DecodeDomainSearchListData procedure, in full accordance with the name, decodes the data from the Domain Search Option of the message received from the server. At the input, it receives a data array packed in the manner described in the previous paragraph, and at the output, it generates a null-terminated string containing a list of domain name endings, separated by commas. For example, this function converts the data from the example above into a string:


    DecodeDomainSearchListData is called from the UpdateDomainSearchOption procedure, which sets the returned list to the "DhcpDomainSearchList" registry key:
    which stores the main parameters of a specific network interface.

    Function DecodeDomainSearchListData fulfills in two passes. On the first pass, it performs all actions except writing to the output buffer. Thus, the first pass is devoted to calculating the size of memory required to accommodate the returned data. On the second pass, memory is already allocated for this data and the allocated memory is filled. The function is quite small, about 250 instructions, and its main job is to process each of the three possible options for the character represented in the input stream: 1) 0x00, 2) 0xc0, or 3) all other values. The hypothetical fix for the DHCP-related error basically comes down to adding a check on the size of the resulting buffer at the beginning of the second pass. If this size is zero, then the memory is not allocated for the buffer and the function immediately ends execution and returns an error:

    It turns out that the vulnerability manifests itself in cases where the size of the target buffer is zero. At the same time, at the very beginning of the execution, the function checks the input data, the size of which cannot be less than two bytes. Therefore, for operation it is required to select a non-empty option of domain suffixes in such a way that the size of the output buffer is zero.


    The first thing that comes to mind is that you can use the previously described reparse points so that non-empty input data generates an empty output line:

    A server configured to send an option with such content in the response will actually cause access violation on non-updated clients. This happens for the following reason. At each step, when the function parses part of the fully qualified domain name, it copies it to the target buffer and puts a dot after it. In the example taken from the RFC, the data will be copied to the buffer in the following order:

    1). eng.
2). eng.apple.
3). eng.apple.com.

    Then, when the domain contains zero domain size, the function replaces the previous character of the destination buffer with a comma:

    4). eng.apple.com,

    and continues parsing:

    5). eng.apple.com,marketing.
6). eng.apple.com,marketing.apple.
    7). eng.apple.com,marketing.apple.com.
8). eng.apple.com,marketing.apple.com,

    At the end of the input, it remains only to replace the last comma with a zero character and you get a line ready for writing to the registry:

    9). eng.apple.com,marketing.apple.com

    What happens when an attacker sends a buffer formed in the described way? If you look at the example, you can see that the list contained in it consists of one element - an empty string. On the first pass, the function calculates the size of the output data. Since the data does not contain a single non-zero domain name, the size is zero.

    On the second pass, a block of dynamic memory is allocated for placing data in it and copying the data itself. But the parsing function immediately encounters a null character, signifying the end of the domain name, and therefore, as was said, replaces the previous character from a dot to a comma. And here we are faced with a problem. The target buffer iterator is at position zero. There is no previous character. The previous character belongs to the header of the dynamic memory block. And this same character will be replaced with 0x2c, that is, with a comma.

    However, this only happens on 32-bit systems. Using unsigned int to store the current position of the target buffer iterator introduces adjustments to the processing on x64 systems. Let's pay closer attention to the piece of code responsible for writing a comma to the buffer:

    The unit is subtracted from the current position using the 32-bit eax register, while when addressing the buffer, the code accesses the full 64-bit rax register. In AMD64 architecture, any operations with 32-bit registers nullify the upper part of the register. This means that in the rax register, which previously contained zero, after subtraction, not the value –1, but 0xffffffff will be stored. Therefore, on 64-bit systems, the value 0x2c will be written to the address buf [0xffffffff], that is, far beyond the boundaries of the memory allocated for the buffer.

    The data obtained is in good agreement with Microsoft's performance assessment, because in order to exploit this vulnerability, an attacker needs to learn how to remotely perform heap spraying on a DHCP client and at the same time have sufficient control over the allocation of dynamic memory to record predefined values, namely a comma and zero byte, produced in the prepared address and led to controlled negative consequences. Otherwise, writing data to an unverified address will result in a drop in the svchost.exe process, along with all services currently hosted in it, and a further restart of these services by the operating system. A fact that attackers in certain conditions can also use for their own benefit.

    That would seem to be all that can be said about the error under investigation. Only the feeling remains that this is far from the end. As if we did not consider all the options. There must be something more that is hidden in these lines.


    Probably the way it is. If you look closely at the type of data causing the error and compare it with how exactly this error occurs, you will notice that the list of domain names can be changed in such a way that the resulting buffer will be non-zero size, but an attempt to write outside it is the same will be done. To do this, the first element in the list must be an empty string, and all others can contain normal domain endings. For instance:

    The presented option includes two elements. The first domain suffix is ​​empty, it immediately ends with a zero byte. The second suffix is ​​.ru. The calculated line size at the output will be equal to three bytes, which will allow to overcome the check imposed by the January update on the emptiness of the target buffer. At the same time, zero at the very beginning of the data will force the function to write a comma in the resulting string to the previous character, but since the current position of the iterator in the string is zero, as in the case considered above, the recording will again occur outside the allocated buffer.

    Now it is necessary to confirm the theoretical results obtained in practice. We simulate a situation in which the DHCP server sends a message with the option presented in response to a request from the client, and immediately catch an exception when trying to write a comma to the 0xffffffff position of the buffer allocated for the result line:

    Here register r8 contains a pointer to the incoming options, rdi is the address the selected destination buffer, and rax is the position in this buffer where the character is to be written. We got such results on a completely updated system (as of January 2019).

    We write about the discovered problem in Microsoft and ... they lose the letter. Yes, this sometimes happens even with reputable vendors. No system is perfect, and in this case you have to look for other ways of communication. Therefore, a week later, without even receiving an automatic response during this time, we contact the manager directly via Twitter and, according to the results of several days of analyzing the application, we find out that the details sent have nothing to do with CVE-2019-0547 and represent an independent vulnerability for which new CVE identifier. A month later, in March, the corresponding correction comes out, and the error receives the number CVE-2019-0726 .

    This is how you can sometimes try to figure out the details of the 1-day vulnerability to accidentally discover 0-day simply by trusting your intuition.

    Posted by Mikhail Tsvetkov, Positive Technologies Application Analysis Specialist.

    Also popular now: