A Bug Has No Name: Multiple Heap Buffer Overflows In the Windows DNS Client

Illustration of a 4 paned window A Bug Has No Name: Multiple Heap Buffer Overflows In the Windows DNS Client

Share

Introduction

CVE-2017-11779 fixed by Microsoft in October of 2017, covers multiple memory corruption vulnerabilities in the Windows DNS client. The issues affect computers running Windows 8/ Server 2012 or later, and can be triggered by a malicious DNS response. An attacker can exploit this issue to gain arbitrary code execution in the context of the application that made the DNS request.

This means that if an attacker controls your DNS server (e.g., through a Man-in-the-Middle attack or a malicious coffee-shop hotspot) – they can gain access to your system. This doesn’t only affect web browsers – your computer makes DNS queries in the background all the time, and any query can be responded to in order to trigger this issue.

The below video explains more about this CVE, but if you're interested in the technical details, keep reading.

Technical TL;DR

In Windows 8/Windows Server 2012, Microsoft extended DNSSEC support to the Windows DNS client, the code for which exists in DNSAPI.dll. One of the DNS Resource Records (RRs) introduced to support DNSSEC was the NSEC3 record, handled by the Nsec3_RecordRead function.

The vulnerabilities in CVE-2017-11779 all relate to how the Nsec3_RecordRead function unsafely parses NSEC3 RRs, resulting in multiple out-of-bounds writes. The responsible DNSAPI DLL is commonly used by the DnsCache service which runs under svchost.exe as the NT AUTHORITY\NETWORK SERVICE user, and provides the DNS caching service for the DNS client on Windows systems. It’s also imported by other applications for making DNS queries.

It is important to note that as the record is malformed, it should not traverse any sane DNS resolvers. Because of this, the issue can only be triggered if the victim(s) are accepting DNS responses directly from the attacker-controlled server. Typically, this would require an active Man-in-the-Middle attack.

The title of this blog post is a reference to the vulnerable DNS RR, NSEC3 (Next Secure Record version 3). NSEC3 records can be used by resolvers to verify the non-existence of a record name as part of DNSSEC validation.

Also, I quite like “Game of Thrones.”

This Bug in CVE-2017-11779 – Explained

Your computer performs DNS requests when you’re browsing, or streaming music, and even when you’re doing nothing. Background actions such as your computer checking for Windows updates also make these requests. Most of the time whatever program is making the request doesn’t see the response directly, but it rather goes through the DNS caching service, which then saves the response for reuse. This cuts down on the number of DNS requests the system needs to make.

DNS is a plaintext protocol and vulnerable to Man-in-the-Middle attacks. Partially because of this, DNSSEC (Domain Name Security) extensions were created. This introduced several new DNS records to deliver this information to DNS clients and servers. DNSSEC tries to solve some problems, but not the most common ones — and it introduces new problems, too. 

Windows added client functionality for DNSSEC in Windows 8 and Server 2012, with the introduction of several new DNS records. This functionality came along with a vulnerability in one of the records used for DNSSEC: NSEC3.  The Windows DNS client doesn’t do enough sanity checking when it processes a DNS response that contains an NSEC3 record. Malformed NSEC3 records can trigger this vulnerability and corrupt the memory of the DNS client. If this is done carefully, it can result in arbitrary code execution on the target system. 

Because the record is malformed, it doesn’t make it through the normal DNS system. Servers along the way will drop it because it doesn’t fit the standard for NSEC3 records. This is a good thing, because otherwise this issue would be easier to exploit and have far more serious implications. So, for an attacker to exploit this issue, they need to be between you and the DNS server you’re using. For example, if you’re using coffee shop Wi-Fi and someone is tampering with it, or if they’ve hacked your cable router - they can modify DNS responses that your computer receives.

This vulnerability has some additional upsides for attackers: 

  • First, it can result in code execution at different privilege levels. including as the administrative system user. If the DNS cache service crashes, the next DNS response will go directly to the application that made the request. This means that an attacker could crash the DNS caching service, and wait until a DNS query that is known to be related to a sensitive system task, like Windows Update. The attacker could potentially respond to this request with the malicious code execution payload and successfully gain complete control over the victim’s system.
  • Second, an attacker has unlimited attempts to exploit it. The DNS caching service that handles the storage of DNS responses automatically restarts when it crashes, and it won’t notify the user of the crash. So, an attacker can respond to requests coming directly from applications with innocuous responses, to ensure the caching service restarts, and then attack that service repeatedly. This can help an attacker bypass some of the protections Microsoft has built into Windows to protect against memory corruption vulnerabilities.

What is Affected and How Do We Fix This?

These vulnerabilities affect all versions of Windows from Windows 8 / Windows Server 2012 through Windows 10 / Windows Server 2016. Versions of Windows prior to this are not vulnerable.

To remediate this vulnerability, immediately apply the Microsoft security patches released in October 2017.

Technical Details

Three heap buffer overflows in DNSAPI.dll can be triggered by a malicious DNS server, or by a man-in-the-middle attack, by sending a malformed NSEC3 Response Record (“RR”) in reply to a DNS request. All three conditions exist in one basic block.

All addresses in this report refer to DNSAPI.dll version 6.3.9600.18512 (x86, Windows 8.1). These issues have also been confirmed for version 10.0.14393.206 (x64, Windows 10).

Allocation of Destination Buffer

The Nsec3_RecordRead function allocates a destination buffer ("destbuf") for the NSEC3 response data by calling DNSAPI!DNS_AllocateRecordEx. The allocation size for destbuf is determined by the 16-bit user-controlled data length field, the last generic field in a DNS Resource Record prior to record-specific data. By manipulating the data length field, the size of destbuf can be controlled by an attacker and used to perform out-of-bounds read and out-of-bounds write attacks.

The data length field described above is highlighted in the following Wireshark capture of an NSEC3 Resource Record:

Captured NSEC3 Resource Record with user-supplied data length field highlighted
FIGURE 1 - Captured NSEC3 Resource Record with user-supplied data length field highlighted
DNSAPI retrieves this value in the Dns_ReadRecordStructureFromPacket function, and it is consumed in Nsec3_RecordRead when determining buffer allocation size.

Heap Overflow #1 – NSEC3 Salt_Length

The first heap buffer overflow is triggered at DNSAPI!Nsec3_RecordRead+0xB9, where memcpy is provided the user-supplied 8-bit NSEC3 Salt Length as its copy size. The location of the NSEC3 Salt Length  field in an example NSEC3 Resource Record is shown below: 

FIGURE 2 - User-controlled NSEC3 Salt Length field

This heap overflow can be used to write out of bounds if the attacker-controlled NSEC3 Salt Length size is greater than the attacker-controlled size of destbuf.

The Nsec3_RecordRead function uses the attacker-controlled value of the NSEC3 Salt Length field as the size argument for memcpy, shown below:

.text:742574D4         mov     bh, [esi+4]    ; User-controlled NSEC3 Salt Length size

.text:742574D7         add     esi, 5        ; Start of NSEC3 Salt data in RR

.text:742574DA         mov     eax, [ebp+var_4]

.text:742574DD         mov     [edi+1Ch], bh

.text:742574E0         add     eax, 20h

.text:742574E3         movzx   edi, bh

.text:742574E6         push    edi        ; Size (user-controlled)

.text:742574E7         push    esi        ; Src (NSEC3 RR data)

.text:742574E8         push    eax        ; Dst (size of buf is user-controlled)

.text:742574E9         call    memcpy        ; Nsec3_RecordRead+0xB9

FIGURE 3 - User-supplied value used as size parameter

This memcpy copied 0xff bytes from the attacker’s DNS Resource Record to destbuf.

Heap Overflow #2 – NSEC3 Hash Length

The second heap buffer overflow occurs shortly after the first instance, in the same basic block, at Nsec3_RecordRead+0xD9:

.text:742574EE         mov     eax, [ebp+var_4]
.text:742574F1         add     esi, edi

.text:742574F3         mov     bl, [esi]              ; User-controlled NSEC3 Hash Length size

.text:742574F5         inc     esi

.text:742574F6         mov     [ebp+Src], esi  ; Start of NSEC3 Hash data in RR

.text:742574F9         mov     [eax+1Dh], bl

.text:742574FC         add     eax, 20h

.text:742574FF         movzx   esi, bl

.text:74257502         add     eax, edi

.text:74257504         push    esi             ; Size (user-controlled)

.text:74257505         push    [ebp+Src]       a      ; Src  (data in NSEC3 RR)

.text:74257508         push    eax             ; Dst  (size of buf is user-controlled)

.text:74257509         call    memcpy          ; Nsec3RecordRead+0xD9

FIGURE 4 - Disassembly showing population of copy size argument

As with the first instance, the call to memcpy takes a user-supplied value for the size argument. The size of the destination buffer is the same user-controlled buffer used in instance 1, destbuf. The location of the NSEC3 Hash Length field in an example NSEC3 Resource Record is shown below:

Example NSEC3 Resource Record

Heap Overflow #3 – Integer Underflow

<code style="background-color:transparent;"><span style="font-size:16px;background-color:#ede7f6;"><code><code></code></code></span></code>

The final, and most useful heap buffer overflow does not directly consume a user-supplied length field, but instead performs some arithmetic on other user-supplied fields prior to use in a memcpy operation in the same basic block at Nsec3_RecordRead+0x106. This is shown as two subtractions:

.text:7425750E         mov     ecx, [ebp+var_C]    ; User-supplied NSEC3 RR size

.text:74257511         movzx   eax, bl            ; NSEC3 Hash length (from ex #2)

.text:74257514         sub     cx, ax            ; Potential underflow #1

.text:74257517         movzx   eax, bh            ; NSEC3 Salt length (from ex #1)

.text:7425751A         mov     ebx, [ebp+var_4]

.text:7425751D         sub     cx, ax            ; Potential underflow #2

.text:74257520         movzx   eax, cx

.text:74257523         push    eax            ; Size (user-controlled, wrapped)

.text:74257524         mov     eax, [ebp+Src]

.text:74257527         add     eax, esi

.text:74257529         mov     [ebx+1Eh], cx

.text:7425752D         push    eax            ; Src (NSEC3 RR data)

.text:7425752E         lea     eax, [edi+20h]

.text:74257531         add     eax, esi

.text:74257533         add     eax, ebx

.text:74257535         push    eax            ; Dst (size of buf is user-controlled)

.text:74257536         call    memcpy            ; Nsec3_RecordRead+0x106


FIGURE 5 - Unsafe subtract operations performed against user-supplied data length

Above, two sub operations are carried out against the saved record length value retrieved from the malicious packet (DNS_RR_Size). The saved record length (saved_record_len) is the user-supplied DNS_RR_Size less 6; this value is intended to indicate the length of the NSEC3 record without the leading header data about hash algorithm and number of iterations.

The following pseudo-code demonstrates the calculation performed:

saved_record_len = DNS_RR_Size – 6 // performed outside this basic block

nsec3_nho_len = saved_record_len - nsec3_hash_len - nsec3_salt_len

The issue here lies in the fact that the nsec3_nho_len value is used as an unsigned 16-bit integer by memcpy. By setting the values of the nsec3_hash_len and nsec3_salt_len fields to values cumulatively larger than the value of the saved_record_len field, an integer underflow occurs, resulting in a very large value being passed as the size to memcpy. This can be leveraged as an out-of-bounds read and out-of-bounds write.

A proof of concept can be created to trigger an out-of-bounds read and out-of-bounds write, using the following values:

  • saved_record_len of 0x00f9 (a.k.a DNS_RR_Size of 0xff – 0x6)
  • nsec3_hash_len of 0xf8
  • nsec3_salt_len of 0x6

This combination resulted in the size parameter for the final value being 0xfffb (0x00f9 – 0xf8 – 0x06 = 0xfffb). At the final memcpy at Nsec_RecordRead+0x106, the various arguments had the following states:

dest (esp) size: 0x128

src (esp+0x4) size: 11e0

copy_size (esp+0x8): fffb

Example Exception

In the following case, attacker-controlled data is used for both the source and destination registers, eax and edx:

eax=30303030 ebx=0000251e ecx=00000000 edx=02f839e8 esi=00000001 edi=000001de

eip=7433d37f esp=0394fb8c ebp=0394fb94 iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206

dnsapi!coalesceRemoveFromGroup+0x58:

7433d37f 895004          mov     dword ptr [eax+4],edx ds:0023:30303034=????????

0:008> dd edx

02f839e8  30303030 30303030 02f839f0 02f839f0

02f839f8  30303030 30303030 30303030 30303030

02f83a08  30303030 00000001 00000001 30303030

02f83a18  30303030 30303030 30303030 30303030

02f83a28  30303030 30303030 30303030 30303030

02f83a38  30303030 30303030 30303030 30303030

02f83a48  30303030 30303030 30303030 30303030

02f83a58  30303030 30303030 30303030 30303030

0:008> kv

ChildEBP RetAddr  Args to Child             

0394fb94 7433d300 02f83490 7432ef50 0000251e dnsapi!coalesceRemoveFromGroup+0x58 (FPO: [Non-Fpo])

0394fbbc 7432e635 743894ac 02fa2578 0000251e dnsapi!Coalesce_Complete+0x17e (FPO: [Non-Fpo])

0394fc24 7434021b 02f495f0 00000000 02f495f0 dnsapi!Send_AndRecvComplete+0x26b (FPO: [Non-Fpo])

0394fc48 743404d4 02f495f0 00000000 02f7a1f8 dnsapi!Send_AndRecvTcpComplete+0xef (FPO: [Non-Fpo])

0394fc68 74340105 7432dbf0 02f49cf0 02f78dd0 dnsapi!Recv_TcpCallbackCompletion+0xe9 (FPO: [Non-Fpo])

0394fc7c 74e688da 0394fd64 02f495f0 02f7a2cc dnsapi!Recv_IoCompletionCallback+0x109 (FPO: [Non-Fpo])

…omitted for brevity…

FIGURE 6 - Example exception resulting from heap corruption

To trigger this exception, the first response set the ‘truncated’ DNS bit, forcing the client to perform a second DNS query over TCP to enable larger payload delivery. With careful heap manipulation, it is possible to overwrite objects in memory that contain function pointers, ultimately resulting in the execution of arbitrary code.

Considerations for Exploitation

These vulnerabilities have several desirable attributes for exploitation: the vulnerability can be triggered without user interaction, it can affect processes running at different privilege levels (including SYSTEM) and the DnsCache service under svchost.exe restarts on failure. This means an attacker can first kill the DnsCache service to have a more deterministic starting state of the heap, exploit the issue multiple times to leak addresses for defeating ASLR, and then use the disclosed addresses when delivering the final exploit.

As a constraint, at this point it is unknown if a malicious payload could successfully traverse a recursive DNS server. To date, tested DNS resolvers do not accept the malformed record.

Wrapping Up

This issue can be exploited on a local network without user interaction, and deserves attention and timely patching.

New functionality always brings new vulnerabilities, and the introduction of DNSSEC to Windows was no exception. The last mile (between your computer and its DNS resolver) will remain a weak point in DNS, so consider the use of a Virtual Private network (VPN) or using your phone as a personal hotspot to reduce the likelihood of an attacker interfering with your DNS traffic.

Acknowledgements

This vulnerability was brought to you by Nick Freeman, a member of the Bishop Fox consulting team in New York (we're hiring!), caffeine, and lots of time messing with vtrace. Additional thanks go to Denis Andzakovic, who wrote the handy fuzzotron tool – a version of which was used to initially trigger this vulnerability.

Finally, our thanks go to Microsoft for clear communication throughout the remediation process.

For further information, please see our advisory 

Subscribe to Bishop Fox's Security Blog

Be first to learn about latest tools, advisories, and findings.


Default fox headshot purple

About the author, Nick Freeman

Security Researcher

Nick Freeman is a security researcher. He was formerly a consultant at Bishop Fox.
More by Nick

This site uses cookies to provide you with a great user experience. By continuing to use our website, you consent to the use of cookies. To find out more about the cookies we use, please see our Privacy Policy.