# Anatomy Of An Exploit: RCE With CVE-2020-1350 SIGRed

**URL:** <https://onehack.st/t/anatomy-of-an-exploit-rce-with-cve-2020-1350-sigred/176363>\
**Category:** Tutorials & Methods\
**Tags:** freebies, tips-tricks\
**Created:** [March 23, 2021, 12:57pm UTC](https://onehack.st/t/anatomy-of-an-exploit-rce-with-cve-2020-1350-sigred/176363 "2021-03-23T12:57:28Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![TheStrength](https://onehack.st/user_avatar/onehack.st/thestrength/32/46753_2.png) [@TheStrength](https://onehack.st/u/TheStrength)\
**Post date:** [March 23, 2021, 12:57pm UTC](https://onehack.st/t/anatomy-of-an-exploit-rce-with-cve-2020-1350-sigred/176363/1 "2021-03-23T12:57:28Z")

</div>

At Grapl, we believe that in order to build the best defensive system we need to deeply understand attacker behaviors. As part of that goal we’re investing in offensive security research. Keep up with our blog for new research on high risk vulnerabilities, exploitation, and advanced threat tactics.

RCE PoC for CVE-2020-1350 SIGRed can be found here: [https://github.com/chompie1337/SIGRed\_RCE\_PoC](https://github.com/chompie1337/SIGRed_RCE_PoC)

## Overview

SIGRed, CVE-2020-1350, is a vulnerability in the Microsoft Windows DNS service that was disclosed on July 14, 2020. It was discovered by Sagi Tzadik, of Check Point Research[[1]](https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/), who released an in-depth write up of the bug the day the patch was released. The vulnerability received a CVSS score of 10.0, the highest level of severity . On Windows, a DNS server is a Domain Controller, and its Administrators are part of the Domain Admins group. By default, the Domain Admins group is a member of the Administrators group on all computers that have joined a domain, including the domain controllers[[8]](https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/active-directory-security-groups). If exploited carefully, attackers can execute code remotely on the vulnerable system and gain Domain Admin rights, effectively compromising the entire corporate infrastructure. This write up contains a detailed breakdown of the exploit methods used in the [released proof of concept](https://github.com/chompie1337/SIGRed_RCE_PoC)[[2]](https://github.com/chompie1337/SIGRed_RCE_PoC).

## The Vulnerability

CVE-2020-1350 is an integer overflow vulnerability that leads to a heap-based buffer overflow when processing malformed DNS **SIG** resource records. A **SIG** record is a type of DNS resource record that contains a digital signature for a record set(one or more DNS records with the same name and type)[[10].](https://en.wikipedia.org/wiki/List_of_DNS_record_types) To exploit SIGRed, an attacker can configure an“evil” domain whose **NS** record points to a malicious DNS server. When a client makes a DNS query for the“evil” domain to the victim server, the victim server will query the DNS server above it. The DNS server will respond back with an NS record indicating that the malicious DNS server is the authority for that domain, and the record will be cached by the victim. Afterwards, when a client sends the victim a DNS **SIG** query for the domain, the victim server will query the malicious DNS server. The malicious DNS server will send a malformed DNS **SIG** record as a response.

![](https://static.wixstatic.com/media/aa91b3_adb7b85135e44f38bcca549851f38d68~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_adb7b85135e44f38bcca549851f38d68~mv2.png/v1/fill/w_740,h_522,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_adb7b85135e44f38bcca549851f38d68~mv2.webp)

_How an attacker can exploit SIGRed_

The vulnerability is present in the function **dns!SigWireRead** (in the DNS service binary, **dns.exe** ), which is used to cache a DNS **SIG** record response from another DNS server.

![](https://static.wixstatic.com/media/aa91b3_ffb865d47c0b4f54925b8c2acb34b036~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_ffb865d47c0b4f54925b8c2acb34b036~mv2.png/v1/fill/w_740,h_391,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_ffb865d47c0b4f54925b8c2acb34b036~mv2.webp)

_Decompilation of the vulnerable function, dns!SigWireRead_

Refer to line 11 in the decompilation of the vulnerable function **dns!SigWireRead.** The function **RR\_AllocateEx** is passed a 16 bit unsigned integer for the size parameter. It is possible to send a DNS **SIG** record response such that size calculated is greater than 0xFFFF, which triggers the integer overflow.

## **Triggering the Vulnerability**

Due to packet size constraints, it is not possible to trigger the vulnerability by solely sending a **SIG** record with a very large signature. In fact, triggering the vulnerability is not possible over UDP, because the maximum allowed size of a DNS message over UDP is either 512 or 4096 bytes, depending on whether the server supports **EDNS0**. In either case, it is not enough to trigger the vulnerability. Using DNS truncation [[9]](https://serverfault.com/questions/991520/how-is-truncation-performed-in-dns-according-to-rfc-1035), the malicious DNS server can tell the victim server to retry the request over TCP. The total size limit of a DNS message over TCP is 64KB (0xFFFF). This is still not enough to trigger the vulnerability, as the message limit includes space for headers and the original query.

**DNS Name Compression**

DNS names can be, and often are, compressed in a DNS message. By manipulating the compression of the name in the DNS message over TCP, it is possible to inflate **sigName.Length** without increasing the total size of the DNS message.

![](https://static.wixstatic.com/media/aa91b3_0f0a903422704ca2b869b12030130780~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_0f0a903422704ca2b869b12030130780~mv2.png/v1/fill/w_740,h_522,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_0f0a903422704ca2b869b12030130780~mv2.webp)

_DNS name compression in a DNS message packet_

In the above example, the 0xC0 byte (highlighted in the green box above) has the most significant two bits set. This indicates that the following 14 bits represent the offset of the DNS name, relative to the start of the DNS message. In the example pictured above, it is 0xC bytes (highlighted in the blue box) from the start of the message.

DNS names are encoded as follows: a single byte denotes the number of characters before a ‘.’ character, terminating in a null byte. For example, **[www.google.com](http://www.google.com)** may be encoded as:

![](https://static.wixstatic.com/media/aa91b3_7ff18513e4ca46b295a19f24c7f0bb4c~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_7ff18513e4ca46b295a19f24c7f0bb4c~mv2.png/v1/fill/w_740,h_35,al_c,lg_1,q_90/aa91b3_7ff18513e4ca46b295a19f24c7f0bb4c~mv2.webp)

which indicates that the name consists of a string of 3 bytes, a period, 6 bytes, a period, and then three bytes terminated by a null.

In the case pictured in the packet capture above, the offset points to 0x1, because the requested domain begins in ’9.’. Therefore, if we change the following 14 bits from 0x0C to 0x0D, the DNS name offset will point to 0x39, indicating that the next part of the DNS name is 0x39 bytes forward, which extends into the signature portion of the packet. This way, we can trick the Windows DNS service into calculating the DNS name size to be much larger than the number of actual bytes used to represent the DNS name.

As seen in the decompilation of **dns!SigWireRead** , the total size passed to **RR\_AllocateEx** is calculated as: **signatureLength + sigName.Length + 0x14**. The maximum length for a DNS name is 0xFF bytes. The additional bytes from the fake name length are enough to trigger the vulnerability and also stay under the maximum message size of 65KB!

For a more detailed explanation of the vulnerability and how to trigger it, including how to trigger it from a browser, please see the original [Check Point Research](https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/)[article](https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/) [[1]](https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/).

## **Exploitation**

This was my first time writing a RCE exploit in user mode, and I learned a lot about heap based exploitation. It is my hope that this write up will help others who are interested in learning heap exploitation.

The exploitation strategy for this bug is interesting because it requires both a malicious client and server. It also depends on careful heap manipulation to not only achieve RCE but also make the exploitation reliable. This section will describe all the necessary pieces and show how they are used together to obtain RCE on the victim server.

### **Triggering the Vulnerability without Crashing**

The most time consuming portion of this exploit was understanding the correct way to manipulate the heap. This section will focus on the details of heap grooming to avoid crashes and control heap buffer freeing and allocation.

**WinDNS Heap Manager**

It is first necessary to understand how the WinDNS service manages heap memory. The WinDNS service manages its own memory pools [[3]](https://datafarm-cybersecurity.medium.com/exploiting-sigred-cve-2020-1350-on-windows-server-2012-2016-2019-80dd88594228). If the requested size of a buffer is more than 0xA0 bytes, it will request the memory from the Windows native heap manager ( **HeapAlloc** ). Otherwise, it will use a memory pool bucket (sizes 0x50, 0x68, 0x88, and 0xa0). The buffers in each of the buckets are stored in a singly linked list. If there are no more available buffers in the selected bucket, a memory chunk will be requested from the native heap, divided into separate buffers, then added to the list of the corresponding bucket. For buffers of sizes 0x50, 0x68, 0x88, and 0xA0 memory chunks of sizes 0xFF0, 0xFD8, 0xFF0, and 0xFA0 are requested, respectively.

![](https://static.wixstatic.com/media/aa91b3_3601d110d2484f8198fa362f9253d1cc~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_3601d110d2484f8198fa362f9253d1cc~mv2.png/v1/fill/w_740,h_584,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_3601d110d2484f8198fa362f9253d1cc~mv2.webp)

_Approximate decompilation of dns!Mem\_Alloc_

When buffers in one of the memory buckets are freed, they are not returned to the Windows native heap. Instead, they are added back to the list of available buffers for that bucket. Buffers are allocated on Last-In-First-Out(LIFO) basis, meaning the last buffer to be freed will be the next to be allocated.

![](https://static.wixstatic.com/media/aa91b3_9c3247819fbb4c8aa6e5c3c291a747af~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_9c3247819fbb4c8aa6e5c3c291a747af~mv2.png/v1/fill/w_740,h_355,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_9c3247819fbb4c8aa6e5c3c291a747af~mv2.webp)

_Approximate decompilation of dns!Mem\_Free_

**WinDNS Buffer Structure**

The structure of a WinDNS buffer is as follows:

![](https://static.wixstatic.com/media/aa91b3_6a9906be77d3468aa059e7ce333c7027~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_6a9906be77d3468aa059e7ce333c7027~mv2.png/v1/fill/w_740,h_487,al_c,lg_1,q_90/aa91b3_6a9906be77d3468aa059e7ce333c7027~mv2.webp)

This will become useful during exploitation.

**Avoiding a Segmentation Fault During memcpy**

The first issue I ran into when writing my exploit, was a segmentation fault occurring during the **memcpy** of the large signature into the allocated heap based buffer. I had to make sure the entirety of the overflow bytes were copied into a valid memory address.

While investigating the heap layout, I found that the Windows native heap manager allocated the bucket memory chunks within “internal” heap segment of sizes 0x41FD0-0x41FF0. From my observations, these heap segments **only** contain memory chunks used for the WinDNS memory buckets. So, if we ensure the size of the overflown buffer is less than 0xA0, we can be sure it will be within one of these chunks.

![](https://static.wixstatic.com/media/aa91b3_0e307cff0e4e467e8f4b790713211a5f~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_0e307cff0e4e467e8f4b790713211a5f~mv2.png/v1/fill/w_740,h_167,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_0e307cff0e4e467e8f4b790713211a5f~mv2.webp)

_Heap segments containing WinDNS memory chunks after heap spray_

This buffer will be somewhere inside a heap segment of size ~0x41ff0. The total number of bytes needed is slightly more than 0xFFFF. Therefore the probability that the entirety of the overflow ends up at valid memory address is relatively high.

**Making a hole**

We can guarantee our chances of landing at a valid memory address if we can trigger the freeing of a buffer in the middle of a many contiguous heap segments, reallocate it and overflow the buffer. This is a common technique in heap exploitation.

In this case, we can cause a buffer to be freed by making a query to the victim client and having our malicious DNS server return a response with a short TTL (Time-To-Live). Similarly, we can ensure cached record buffers won’t be freed by assigning a long TTL. Expired records are freed every ~2 minutes.

![](https://static.wixstatic.com/media/aa91b3_bbbaa7f0334643eb838f44792c9f897f~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_bbbaa7f0334643eb838f44792c9f897f~mv2.png/v1/fill/w_740,h_287,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_bbbaa7f0334643eb838f44792c9f897f~mv2.webp)

_Malicious DNS server controls TTL, which can be used for heap grooming_

The process to make a hole in the heap and reallocate it has a few basic steps:

- Make many queries for subdomains of the evil domain to the victim server.
- The malicious DNS server will give the victim a response, which the victim will cache in heap memory (heap spray).
- The malicious DNS server will assign a long TTL (Time-To-Live) for all subdomains except one, which will be given a short TTL.
- WinDNS frees buffers for expired records every ~2 minutes, so we wait for the buffer to be freed.
- Make another query for the subdomain whose **SIG** record just expired; this time the malicious DNS server will give a malformed response to trigger the overflow.
- Because buffers are LIFO allocated, the new record buffer will have the same address of the expired **SIG** record in memory.

![](https://static.wixstatic.com/media/aa91b3_3524254ae14c4838b8d6e689e3927922~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_3524254ae14c4838b8d6e689e3927922~mv2.png/v1/fill/w_740,h_500,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_3524254ae14c4838b8d6e689e3927922~mv2.webp)

_Making a hole in the heap to avoid SEGFAULT_

**Avoiding Crashes due to Overwriting Other Objects on the Heap**

While I was able to reliably avoid segmentation faults during **memcpy** , I still encountered many other crashes from overwriting objects on the heap.

![](https://static.wixstatic.com/media/aa91b3_6cc4d9128781460cb9bac4950bf35403~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_6cc4d9128781460cb9bac4950bf35403~mv2.png/v1/fill/w_740,h_392,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_6cc4d9128781460cb9bac4950bf35403~mv2.webp)

_Crash due to overwriting cache tree nodes_

I decided to look in **WinDbg** to see what types of buffers were being allocated near the overflown buffer.

![](https://static.wixstatic.com/media/aa91b3_b840978044f845569aebf01f53839875~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_b840978044f845569aebf01f53839875~mv2.png/v1/fill/w_740,h_378,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_b840978044f845569aebf01f53839875~mv2.webp)

_Memory allocation tracking in Windbg_

I could see that new WinDNS memory chunks of size 0xFF0 and 0xFA0 were being allocated near the heap buffer I was overflowing. The latter came as a result of the heap spray (record buffers of size 0xA0). But what about the chunks of size 0xFF0? These contained buffers of size 0x88, used to store objects related to the DNS record cache, which is saved as a binary tree. I was overwriting cache tree objects and causing a crash when the tree was traversed.

The solution became clear at this point. Remember that the overflown buffer is within a heap segment that is shared only by other WinDNS managed memory chunks. This means the objects being overwritten are of a size \<= 0xA0 and fit into one of the memory buckets managed by WinDNS. We know that buffers in these memory buckets are never released back to the native heap, and are instead returned to the free buffer list of the corresponding bucket size. So, we can groom the heap by forcing the allocation of many buffers of size 0x88 and cause them to be freed. Once they are freed, they will be returned to the free buffer list, avoiding the need to allocate new heap memory. We can then spray the heap with many buffers that will not be freed to ensure the buffer we overflow will be in a new heap segment away from the objects we don’t want to overwrite.

![](https://static.wixstatic.com/media/aa91b3_a49f5d88fa044d338298fa33a32375e8~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_a49f5d88fa044d338298fa33a32375e8~mv2.png/v1/fill/w_740,h_551,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_a49f5d88fa044d338298fa33a32375e8~mv2.webp)

_Heap grooming to avoid overwriting important heap objects_

### **Overwriting Objects in the Heap**

Now that we have sufficiently groomed the heap to avoid a crash, the next step is to overwrite heap objects that will create the exploit primitives. In the last section, we made a hole for the overflown buffer. With this hole, we are set up to overwrite the “throwaway” cached records we sprayed the heap with.

**Know Your Surroundings**

Because we spray the heap, many new memory chunks will be allocated. These buffers are added contiguously to the freelist, meaning they are allocated in contiguous order. Therefore, the order the DNS **SIG** record queries are made will be the order in which they appear on the heap. So, we know exactly what records will be overwritten during the overflow.

![](https://static.wixstatic.com/media/aa91b3_513f09773370416da1c0c33f0fb2968a~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_513f09773370416da1c0c33f0fb2968a~mv2.png/v1/fill/w_740,h_501,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_513f09773370416da1c0c33f0fb2968a~mv2.webp)

_Overwriting RR\_Record objects with fake ones_

**RR\_Record Structure**

First, let’s look at the structure of a cached WinDNS record:

![](https://static.wixstatic.com/media/aa91b3_2e95baa7527f41e3b4fc7dec0c87a822~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_2e95baa7527f41e3b4fc7dec0c87a822~mv2.png/v1/fill/w_740,h_356,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_2e95baa7527f41e3b4fc7dec0c87a822~mv2.webp)

Knowing this and the structure of **WINDNS\_BUFF** will make it easy to craft fake **RR\_Record** objects.

**Controlling Buffer Freeing**

Previously, we freed record buffers of our choosing by simply giving them a short TTL and waiting for them to expire and be freed. This is good, but waiting at least two minutes is a problem. Not because we’re impatient, but because it gives us less control over reallocation. It will be useful to be able to trigger the immediate freeing of buffers.

When a **RR\_Record** object is retrieved from the cache to respond to a query, the fields **dwTTL** and **dwTimeStamp** are first checked before returning the response. This is because it is possible that the record’s TTL has already expired. Remember, the record cache is only cleaned up every 2 minutes. It is possible a record has expired in between cleanups. We can abuse this by simply zeroing out the **dwTTL** and **dwTimeStamp** fields in a fake **RR\_Record** object and sending a query for the corresponding subdomain. This will cause the buffer to be freed.

**Controlling Buffer Allocation**

Now controlling buffer allocation is straight forward. Since WinDNS buffers are allocated LIFO, once we free a buffer, it will be next of that bucket size to be allocated. Even better, because we also control the values in the **WINDNS\_BUFF** structure we can fake the size of the original buffer! This means that we can allocate objects of different sizes in the area of the heap that we control.

![](https://static.wixstatic.com/media/aa91b3_dc2048cfccc741e897dbdc45ae762324~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_dc2048cfccc741e897dbdc45ae762324~mv2.png/v1/fill/w_740,h_447,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_dc2048cfccc741e897dbdc45ae762324~mv2.webp)

_Controlling allocation of buffers of different sizes_

## **Leaking Memory**

**Leaking Heap Addresses**

We can now leak an address in the heap by doing the following:

- Trigger the freeing of a fake **RR\_Record**.
- Give the fake **RR\_Record** above the freed one a large **wRecordSize**.
- Send the victim a **SIG** query for the subdomain with the fake large **wRecordSize**.
- The response will go past the real size of the buffer, and include the **WINDNS\_FREE\_BUFF** structure data of the freed record below it. This leaks a valid heap address in the **pNextFreeBuff** field.

![](https://static.wixstatic.com/media/aa91b3_67311c33ac5a4faea4a0a7839f284245~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_67311c33ac5a4faea4a0a7839f284245~mv2.png/v1/fill/w_740,h_392,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_67311c33ac5a4faea4a0a7839f284245~mv2.webp)

_Leaking a heap pointer using fake RR\_Record objects_

Great, we obtained our first memory leak! However, we don’t actually know where the leaked pointer is relative to the area of the heap we control. It would be even better if we could get the address of our overflown buffer. To do this, we can simply free two fake **RR\_Record** objects, and leak the **WINDNS\_FREE\_BUFF** of the buffer we freed last. When a buffer is freed, the pointer to the buffer that was freed before it is written to the **pNextFreeBuff** field.

![](https://static.wixstatic.com/media/aa91b3_3d7a3b96f79c4588b9c015366dc55366~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_3d7a3b96f79c4588b9c015366dc55366~mv2.png/v1/fill/w_740,h_506,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_3d7a3b96f79c4588b9c015366dc55366~mv2.webp)

_Leaking a pointer to the controllable part of the heap_

We now know the exact address of portion of the heap we control! This will be useful later.

**Leaking dns.exe Address**

Next, we need to leak an address inside **dns.exe** to defeat ASLR [[5]](https://en.wikipedia.org/wiki/Address_space_layout_randomization). To leak addresses inside of **dns.exe** , we can trigger the allocation of a special kind of object that I will refer to as a **DNS\_Timeout** object.

A **DNS\_TimeOut** object has the following structure:

![](https://static.wixstatic.com/media/aa91b3_27b27d797c8a42fb8bdc4d2a2537b3af~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_27b27d797c8a42fb8bdc4d2a2537b3af~mv2.png/v1/fill/w_740,h_232,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_27b27d797c8a42fb8bdc4d2a2537b3af~mv2.webp)

When a DNS record expires, **dns!RR\_Free** is called. If a DNS record is type **DNS\_TYPE\_NS** , **DNS\_TYPE\_SOA** , **DNS\_TYPE\_WINS** , or **DNS\_TYPE\_WINSR** [[6]](https://docs.microsoft.com/en-us/windows/win32/dns/dns-constants) they are not freed immediately. Instead, **dns!Timeout\_FreeWithFunctionEx** is called.

![](https://static.wixstatic.com/media/aa91b3_b9b5250b36a9499ba2d4376f60e28ca0~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_b9b5250b36a9499ba2d4376f60e28ca0~mv2.png/v1/fill/w_740,h_441,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_b9b5250b36a9499ba2d4376f60e28ca0~mv2.webp)

_Approximate decompilation of dns!RR\_Free_

In **Timeout\_FreeWithFunctionEx** , a WinDNS buffer is allocated for a **DNS\_Timeout** object. Then, the address of **RR\_Free** and a string are written to the **pFreeFunction** and **pszFile** fields, respectively. These will be our **dns.exe** address leaks. If we trigger the allocation of a timeout object in the area of the heap we control, we can use the same method as before to leak the addresses.

![](https://static.wixstatic.com/media/aa91b3_a3e6de45bb44402ba503f7b68a6cbc03~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_a3e6de45bb44402ba503f7b68a6cbc03~mv2.png/v1/fill/w_740,h_405,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_a3e6de45bb44402ba503f7b68a6cbc03~mv2.webp)

_Decompilation of dns!Timeout\_FreeWithFunctionEx_

We trigger the allocation of the object by first freeing a fake **RR\_Record** object with a fake buffer size of 0x50, which is the bucket memory size allocated for a DNS\_Timeout object. Then, we make some **NS** queries to the victim for the evil domain. A timeout object will be allocated for each query once the records expire. It is necessary to make several of these queries in case new buffers of size 0x50 have been freed while waiting for the **NS** records to be expire. We can again leak the memory by making a request for the cached record above it, with a fake large **wRecordSize**.

![](https://static.wixstatic.com/media/aa91b3_0faf3a4354db4d17a30d01f93830e3d6~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_0faf3a4354db4d17a30d01f93830e3d6~mv2.png/v1/fill/w_740,h_513,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_0faf3a4354db4d17a30d01f93830e3d6~mv2.webp)

_Leaking dns.exe address by allocating DNS\_Timeout object_

Now that we have leaked addresses inside **dns.exe** , we can use them to calculate the addresses of functions inside the binary. By taking the last 12 bits of the leaked addresses, we can create a mapping to offsets for various versions of **dns.exe**.

_Originally, I thought I could trigger the allocation of a timeout object by simply freeing a fake **RR\_Record** object with **wRecordType = DNS\_TYPE\_NS**. Thus, avoiding having to wait the 2 minutes for the NS records to expire. However, when I tried to do this, some check prevents the call to **RR\_Free** on **fakeRR\_Record** s with a modified **wRecordType**. I ran out of time while investigating the issue, so this is a potential area for improvement._

### **Arbitrary Read**

Finally, we have all the pieces for an arbitrary read primitive.

Note that we already have the ability to get code execution by overwriting the **pFreeFunction** pointer in the **DNS\_timeout** object that was allocated. In the function **dns!Timeout\_CleanupDelayedFreelist** , the function address in **pFreeFunction** is called for each timeout object in the **CoolingDelayedFreeList**. This list contains the **DNS\_Timeout** objects representing records that are ready to be freed. Luckily, a **DNS\_Timeout** object contains a field for one parameter that is passed to this function.

![](https://static.wixstatic.com/media/aa91b3_5268b6c5246d486b8796c1026e1c28f8~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_5268b6c5246d486b8796c1026e1c28f8~mv2.png/v1/fill/w_740,h_597,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_5268b6c5246d486b8796c1026e1c28f8~mv2.webp)

_Approximate decompilation for dns!Timeout\_CleanupDelayedFreeList_

We can trigger the vulnerability again after the timeout object is allocated to overwrite these fields.

Modern versions of **dns.exe** are compiled with Control Flow Guard (CFG) [[4]](https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard). One known way to bypass CFG is to corrupt return addresses on the stack [[11]](https://improsec.com/tech-blog/bypassing-control-flow-guard-in-windows-10) and execute using a ROP [[7]](https://en.wikipedia.org/wiki/Return-oriented_programming) method. However, we currently don’t have a stable way to write to the stack. Instead, we can find a valid call target (i.e. a function within **dns.exe** ) to use for a primitive. A suitable candidate is **dns!NsecDnsRecordConvert** , which takes one parameter [[3]](https://datafarm-cybersecurity.medium.com/exploiting-sigred-cve-2020-1350-on-windows-server-2012-2016-2019-80dd88594228).

A parameter to **NsecDnsRecordConvert** should have the following structure:

![](https://static.wixstatic.com/media/aa91b3_9d45541820f94f49a93ee8e423e4165e~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_9d45541820f94f49a93ee8e423e4165e~mv2.png/v1/fill/w_740,h_155,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_9d45541820f94f49a93ee8e423e4165e~mv2.webp)

Inside this function, a buffer is allocated and a call to **Dns\_StringCopy** is made. This is where the read primitive lies. Since we control the function parameter passed in and its content, we can make the **pDnsString** field an address we want to read. Inside **DNS\_StringCopy** , a buffer is allocated and the data pointed to by **pDnsString** (up until a null byte) is copied in.

![](https://static.wixstatic.com/media/aa91b3_31668603d0c740689bd13149ef90472a~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_31668603d0c740689bd13149ef90472a~mv2.png/v1/fill/w_740,h_353,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_31668603d0c740689bd13149ef90472a~mv2.webp)

_Decompilation of dns!NsecDnsRecordConve\*\*rt_

Since we also control **wSize** , we control the size of the buffer that gets allocated. So, we force the the allocation of the new buffer into the area of the heap we control. After the data has been copied, we leak the memory using the same method as before.

![](https://static.wixstatic.com/media/aa91b3_7152eedcbcb64374bc3bbaf325d91518~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_7152eedcbcb64374bc3bbaf325d91518~mv2.png/v1/fill/w_740,h_354,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_7152eedcbcb64374bc3bbaf325d91518~mv2.webp)

_Arbitrary read primitive_

The address we read should be somewhere within the dns.exe import table that contains an address from **msvcrt.dll**. I chose **dns!\_imp\_exit** which contains the address to **msvcrt!exit**. This will break the ASLR of **msvcrt.dll**. With this, we can calculate the address of **msvcrt!system**.

_Note: **Dns\_StringCopy** expects to copy a null-terminated string. If the least significant byte of the address is 0x00, the size of the calculated string will be 1, and the address won’t be copied. In the samples I obtained this wasn’t a problem, but I did not test all possible versions of **msvcrt.dll**._

### **Remote Code Execution**

All of the of the pieces are now in place to get remote code execution. We can again trigger the allocation of a **DNS\_Timeout** object. Then, we overwrite the **pFreeFunction** with **msvcrt!system** and **pFreeFuncParam** with a heap address to memory that contains the payload command. To obtain a reverse shell, I chose to use **mshta.exe** to execute a HTA shell from an attacker hosted HTTP server. I found this to be the easiest solution, but there are many other possibilities. The exploit could also be reworked to use any other function(s) instead of **system**.

_SIGRed RCE Exploit Demo Video |_ [https://youtu.be/yiqLmfQCqeY](https://youtu.be/yiqLmfQCqeY)

### **Detecting Exploitation and Workaround Fix**

The released PoC includes a [Grapl rule](https://github.com/chompie1337/SIGRed_RCE_PoC/blob/main/sigred_grapl_rule/main.py) to detect exploitation of SIGRed. To implement a rule for your preferred SIEM, look for invalid child processes of dns.exe. Note that this rule will only detect exploitation with the released PoC and DoS exploits. It is possible an attacker can rework the exploit to implement a payload that stays contained within the dns.exe process context. [Learn how Grapl can help keep your infrastructure secure.](https://www.graplsecurity.com/)

If patching is not possible, a workaround fix is available:

![](https://static.wixstatic.com/media/aa91b3_f7eed6ce2c444095ba31e17a0cbf2e02~mv2.png/v1/fit/w_300,h_300,al_c,q_5/file.png) ![](https://static.wixstatic.com/media/aa91b3_f7eed6ce2c444095ba31e17a0cbf2e02~mv2.png/v1/fill/w_740,h_72,al_c,q_90,usm_0.66_1.00_0.01/aa91b3_f7eed6ce2c444095ba31e17a0cbf2e02~mv2.webp)

The workaround blocks exploitation by limiting the DNS TCP receive message size to 0xFF00 bytes. This prevents the integer overflow from occurring when calculating the buffer size needed to cache a **SIG** record.

### **Conclusion**

This blog post is part of Grapl’s initiative to strengthen defensive techniques by investing in offensive security research. If you enjoyed this post, please[subscribe](http://graplsecurity.com/subscribe)to Grapl’s updates for more!

### **Acknowledgements**

[Sagi Tzadik](https://twitter.com/sagitz_) of CheckPoint research, for the original vulnerability discovery and [write-up](https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/).

[maxpl0it](https://twitter.com/maxpl0it), who wrote the first public DoS [PoC](https://github.com/maxpl0it/CVE-2020-1350-DoS)for this bug, and answered a ton of my questions. He also kindly provided me with some of his research notes. My exploit uses code from his PoC as a basis.

[Worawit Wang](https://twitter.com/sleepya_), who released a [write up](https://datafarm-cybersecurity.medium.com/exploiting-sigred-cve-2020-1350-on-windows-server-2012-2016-2019-80dd88594228) about exploiting SIGRed. I used several of the discussed techniques.

[Andréa](https://twitter.com/azza_314), for her excellent work creating the graphics in this write-up.

[Connor McGarr](https://twitter.com/33y0re), who also wrote a DoS PoC for SIGRed and answered my questions.

[Michael Maltsev](https://twitter.com/m417z), the creator of [Winbindex](https://winbindex.m417z.com/), from which I was able to obtain many samples of dns.exe and msvcrt.dll to preprogram offsets.

[InsanityBit](https://twitter.com/InsanityBit) and [Grapl](https://www.graplsecurity.com/), for supporting this research.

`Source: graplsecurity`

**Happy learning!**
