Exploiting SIGRed (CVE-2020–1350) on Windows Server 2012/2016/2019

Datafarm
7 min readSep 25, 2020

--

by Worawit Wangwarunyoo , DATAFARM Research Team, Datafarm Company Limited

This post describes the exploitation (RCE) of SIGRed (CVE-2020–1350) on Windows Server 2012 R2 to Windows Server 2019. For vulnerability detail please see the checkpoint research post

Prepare name servers

To reduce steps on setting up a domain name, I configure “Conditional Forwarders” on a target Windows DNS Server as figure below. While I use dnslib to create my malicious DNS client and server for “evildns.com” domain.

Trigger the bug

If we simply follow on checkpoint post to trigger the bug, we are likely to end up with a crash inside memcpy called from dns!SigWireRead.

The reason is SIG Resource Record is allocated around the end of a process heap. Triggering the bug need overwrite past SIG record buffer around 64KB, so memcpy will attempt writing past a current heap space which is invalid memory region. If we dump memory around address that caused a crash, we see an address is invalid.

To exploit a heap overflow bug, normally we have to know a heap internal and some object structure that allocated on heap.

WinDNS Heap Manager

WinDNS manages its own memory pools. There are 4 memory pool buckets (dns!StandardAllocLists) for different allocation sizes (0x50, 0x68, 0x88, 0xa0). If a required allocation size is greater than 0xa0, WinDNS will use native Windows heap (with HeapAlloc and HeapFree functions).

Below is pseudocode for dns!Mem_Alloc function used for dynamically allocated memory in dns

Next is pseudocode for dns!Mem_Free function used for freeing memory allocated by Mem_Alloc

After reverse engineering the Mem_Alloc and Mem_Free functions, we can see some issues that help us to exploit the vulnerability

WinDNS heap never return memory to native Windows Heap

If a memory size is less than or equal to 0xa0, WinDNS only simply put it to a head of free list. Native Windows heap treats it as used memory. So we can freely corrupt the native Windows heap chunk metadata because a heap metadata is checked when doing allocation and free.

All WinDNS heap header value is known

A WinDNS heap header contains metadata such as buffer type, size, bucket index, cookie (fixed value). All metadata value is known without a need of information leak. Because we have to start exploiting by overwriting many objects in heap, this condition is very helpful to exploit this bug without a help from another information leak bug.

Free chunks are kept as singly linked list

This kind of data structure can imply that chunks are allocated and freed in reverse order (LIFO). There are known techniques to abuse free chunks in singly linked list after memory corruption. E.g.

  • Fake free list to control next allocation location. This might result to overlapped chunks, arbitrary write (I think arbitrary write is difficult for this case because a cookie 8 bytes is checked before Mem_Alloc returning an address)
  • Controlling chunk allocation order by freeing them in reverse order

Monitoring objects in heap

To know what objects in heap are allocated when processing a user query, I add a breakpoint in Mem_Alloc to print stack trace for monitoring objects and code path. Then I send various DNS request to a server. Below is sample of it.

I found a few interesting objects. First, DNS Resource Record (RR) object is created every time when Windows DNS server cache a response from authoritative name server (SIG record that allocated when triggering the bug is RR object too). A RR header has a data size field which can be overwritten and used for information leak later. Another one is timeout object. It contains a pointer to function with 1 argument. We can overwrite them to control rip (Program Counter) register later.

Heap buffer overflow without any crash

This step should be easy now after studying a lot of WinDNS heap. All I did is make server do allocation of many RR objects that never be freed while exploiting. Then freeing an object that followed by many RR objects (total size must be >64KB), the freed chunk will be allocated by SIG RR object when triggering the vulnerability.

Information Leak

When triggering the bug, we can overwrite a valid RR object by modified only data size field. Then, we can query an overwritten RR to leak adjacent chunk. Leaking overflown data is useless, so we should free adjacent chunk first. WinDNS heap will write a pointer to next free chunk, then we can leak pointer to a heap address.

With carefully craft overflown data and free order, we can leak a heap address in overflown area. Because we can fully control overflown data, so we can create a fake free list in there. Then new objects will be allocated in our control area. We can read/write them.

Then I tried to leak dns.exe module by finding some heap object that contains address in an executable module. I found 2 objects (one points to BSS section, another one points to string section). Do a query to make a server allocates object that has a pointer to dns.exe then read its content followed by calculating base dns.exe address.

Note: While leaking DNS address, we can determine a target OS and version from least significant 12 bits because module must be load at start of memory page.

Controlling Program Counter (rip)

As mentioned in previous section, timeout object contains a pointer to function with 1 argument. We can make it allocated in overflown area then overwrite it to control PC. A pointer to function in timeout object is used in dns!Timeout_CleanupDelayedFreeList function. But dns.exe since Windows Server 2012 is compiled with Control Flow Guard (CFG). With CFG enabled, we can only jump to a function that is in allowed list. If we try to jump at function epilogue (for starting ROP), we end up crashing inside ntdll!LdprValidateUserCallTarget same as below figure (from Windows Server 2012 R2).

Common procedure to do code execution, when CFG is enabled, is modifying a return address in stack, which need arbitrary write ability. But we cannot do arbitrary write now. We also don’t know any thread stack address. Now we can only control heap in overflown area. It is very rare to see a program store stack address in a heap object. I don’t even try finding a stack address in heap.

Next, I tried to do arbitrary read by searching an object that contains a pointer to data. Then make it allocated in our control area and overwrite a pointer. I expected a server will dereference pointer and copy a data to me. But what I found needs so many conditions, cannot be used (there might be an object that can be used for arbitrary read but I cannot find it).

Then I checked functions in dns.exe that is allowed by CFG with a hope to find something useful. Most functions can be skipped because we can control only 1 argument. We can completely ignore functions with argument more than 1. After spending countless hours to bypass CFG, I found dns!NsecDNSRecordConvert function.

From a decompiled code, param_1 is obviously a pointer to struct. At line 15, a server finds a buffer length for copying from string pointer at param_1+0x20. At line 18, a server allocates a memory for storing data. At line 22, a server copies string to a new allocated memory. With fully controlled function argument, we can make the function copies data (as string) from any address into a new allocated memory. Also, we can make a new allocated memory in our controlled area. Then read a data. So we can do arbitrary read with dns!NsecDNSRecordConvert function.

Code Execution

With ability to call a function in CFG allowed list with 1 argument and arbitrary read, I was thinking to do code execution with kernel32!WinExec or msvcrt!system. I choose msvcrt!system because kernel32.dll is more likely to be modified by Microsoft monthly patch. Offset in msvcrt.dll should be usable on any patch level.

To do code execution with msvcrt!system, I find msvcrt!memcpy by reading from dns.exe import table, then calculating msvcrt!system address. Finally, repeating the controlling PC steps but jumping to msvcrt!system. Hooray, get code execution.

After I succeed developing an exploit for Windows Server 2012R2. I just modified offsets of dns.exe and msvcrt.dll for Windows Server 2016 and Windows Server 2019. And it works perfectly.

Note: Dlls in Windows Server 2019 are compiled with CFG export suppression (below figure is msvcrt.dll). If dns.exe enables it, the exploit path should be much more difficult.

Demonstration Video

Here is a demo video for Windows Server 2012 R2, Windows Server 2016 and Windows Server 2019

RCE on Windows Server 2012 R2 with CVE-2020–1350 (SIGRed)

RCE on Windows Server 2016 with CVE-2020–1350 (SIGRed)

RCE on Windows Server 2019 with CVE-2020–1350 (SIGRed)

--

--