A Bug Has No Name: Multiple Heap Buffer Overflows In the Windows DNS Client
by Nick Freeman, on Oct 10, 2017 10:25:17 AM
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.
For members of the media - feel free to check out our FAQs for more information.
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.
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:
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:
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:
Heap Overflow #3 – Integer Underflow
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
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
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
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.
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.
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.