Monday, August 10, 2020

Heap exploit mitigation in Glibc 2.32: Safe Linking

 There were various heap challenges in many CTFs, exploiting the vulnerabilities in ptmalloc, the memory allocator of glibc. Each year the more advanced exploit techniques the players discovered, the more exploit mitigations were applied to the codebases.

In this blog post, I'll cover the new heap exploit mitigation introduced in glibc 2.32 called Safe Linking. Before digging into the main topic, I'll briefly explain the previous exploit techniques and its related mitigations applied, finally summarizing how effective I think the new mitigation is to defend attackers from exploitation.


Tcache in ptmalloc


Since glibc 2.26, the concept of tcache is added to the malloc implementation. Basically freed memories(also called chunks) are managed by singly linked list or double linked list, linked to the chunks that have the same size. Specifically for chunks that are smaller than the size of 0x80, each singly linked list is called fastbin. The tcache is almost the same concept as fastbin, but it covers bigger chunks than fastbin with no security checks such as the size of chunk or whether a chunk is double freed or not. So tcache improved the performance of ptmalloc, but it gave chances for attackers to more easily exploit the vulnerabilities in heap memory.

The code below is a part of tcache implementations from glibc 2.27. tcache_put is called when a chunk is freed to be put into tcache bin, and tcache_get is called to get freed chunk to be returned to malloc from tcache. There is no double free check logic, or any verification of the pointer returned to user. So attackers easilly get arbitrary memory by overwriting the pointer value in the cached chunk with arbitrary address that attackers wants.

Later in glibc 2.29, the logic that checks double free is added to source code by inserting a new pointer that points to tcache structure in library when a chunk is freed to tcache. This mitigation can be bypassed if an attacker can clear the pointer in freed chunk with memory write primitive. The logic is not that complicated, so it is recommended to read the source code for more details.
/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

Safe Linking


In glibc 2.32 Safe Linking is added as security feature to the allocator. The basic idea is to do xor operation on the stored pointer with the address the pointer is to be stored in. The figure below describes the idea cleanly, where P denotes the pointer to the next entry in tcache(or fastbin) and L is the address that P will be written to. Please note that L is shift-righted by 12, which is to make the value page-aligned.


The result pointer is restored when the cached chunk is popped from tcache to be allocated, with checking if the restored pointer is aligned with minimal alignment value which is 0x10.
/* Safe-Linking:
   Use randomness from ASLR (mmap_base) to protect single-linked lists
   of Fast-Bins and TCache.  That is, mask the "next" pointers of the
   lists' chunks, and also perform allocation alignment checks on them.
   This mechanism reduces the risk of pointer hijacking, as was done with
   Safe-Unlinking in the double-linked lists of Small-Bins.
   It assumes a minimum page size of 4096 bytes (12 bits).  Systems with
   larger pages provide less entropy, although the pointer mangling
   still works.  */
#define PROTECT_PTR(pos, ptr)                                                  \
  ((__typeof(ptr))((((size_t)pos) >> 12) ^ ((size_t)ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)

/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) {
  tcache_entry *e = (tcache_entry *)chunk2mem(chunk);

  /* Mark this chunk as "in the tcache" so the test in _int_free will
     detect a double free.  */
  e->key = tcache;

  e->next = PROTECT_PTR(&e->next, tcache->entries[tc_idx]);
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *tcache_get(size_t tc_idx) {
  tcache_entry *e = tcache->entries[tc_idx];
  if (__glibc_unlikely(!aligned_OK(e)))
    malloc_printerr("malloc(): unaligned tcache chunk detected");
  tcache->entries[tc_idx] = REVEAL_PTR(e->next);
  --(tcache->counts[tc_idx]);
  e->key = NULL;
  return (void *)e;
}


Bypass the mitigation


Safe Linking prevents attackers from tampering the linked list pointer that the freed chunks have. The point of Safe Linking is that it asks attackers for heap memory read primitive. The developer says that the effect of Safe Linking to the performance is negligible. So it is certain that Safe Link is effective for mitigating exploits with almost zero cost. However, the mitigation can still be bypassed if attacker can leak the address that is used to calculate protected pointer. Considering memory read primitive is common in CTFs, I don't think the Safe Linking will be the important topic in CTF scenes. Mitigation is just a mitigation, so it is necessary that developers always write code in secure ways so that they do not make any vulnerabilities in their project.

References


Wednesday, February 12, 2020

CVE-2019-1108 Root Cause Analysis

posted by Lanph3re

Introduction

In December 2019, MS RDP vulnerability is introduced in BlackHat Europe 2019 session. The link is here.

RDP stands for Remote Desktop Protocol, which allows users to connect to remote desktop endpoint via network. Protocol detail is below from wikipedia.

Remote Desktop Protocol (RDP) is a proprietary protocol developed by Microsoft, which provides a user with a graphical interface to connect to another computer over a network connection. The user employs RDP client software for this purpose, while the other computer must run RDP server software.
Clients exist for most versions of Microsoft Windows (including Windows Mobile), Linux, Unix, macOS, iOS, Android, and other operating systems. RDP servers are built into Windows operating systems; an RDP server for Unix and OS X also exists. By default, the server listens on TCP port 3389 and UDP port 3389.

CVE-2019-1108, which is an information disclosure vulnerability, is a bug in RDP client which is triggered in processing RDP sound virtual channel(RDPSND). RDP client can receive sounds from RDP server, for sound option is enabled by default.

Sound information is delivered by RDPSND virtual channel. Virtual channel is dedicated channel for its specific purpose, in above case, sound. There are many virtual channels between RDP server and client which allows them to communicate various types of information.

Fuzzer Implementation

As in BlackHat presenstation slides, fuzzer should be modified to fuzz the rdp client. So I needed to audit the original winafl fuzzer source code and had to a little dev. Winafl is forked version of AFL fuzzer to use in Windows.



Implementing the fuzzer took a little long time, as I wasn't familiar with Windows APIs and also had little knowledge of how the original fuzzer is implemented.

In winafl, there is a function called write_to_testcase which writes mutated data in file. I added the code that send mutated data to RDP server using socket. As vulnerability is caused by virtual channel, the data itself served to RDP client by fuzzer is meaningless. RDP server get its data from client(using socket), and relay them back to RDP client via RDPSND virtual channel.

When modifying fuzzer was done and ran for a couple of hours, I got the crash.
To get seed for fuzzing, I hooked mstscax!CChan::ChannelOnPacketReceived() using frida.



Root Cause Analysis

The patch for vulnerability is released in July in 2019.

CVE-2019-1108 | Remote Desktop Protocol Client Information Disclosure Vulnerability
Security Vulnerability
Published: 07/09/2019 | Last Updated : 11/19/2019
MITRE CVE-2019-1108
An information disclosure vulnerability exists when the Windows RDP client improperly discloses the contents of its memory. An attacker who successfully exploited this vulnerability could obtain information to further compromise the user’s system.
To exploit this vulnerability, an attacker would have to connect remotely to an affected system and run a specially crafted application.
The security update addresses the vulnerability by correcting how the Windows RDP client initializes memory. 
FAQ
What type of information could be disclosed by this vulnerability?
The type of information that could be disclosed if an attacker successfully exploited this vulnerability is uninitialized memory.

As noted in FAQ, the vulnerability is caused by uninitialized memory. The patch would be done in a way that initializes newly allocated memory(ex. memset()).

I installed the patch to figure out differences in patched version, got the patched dll. I began with comparing the vulnerable version and patched version of mstscax.dll in bindiff.


In the lists of patched function, there is CRDPSound::vcwaveChooseSoundFormat(), which sounds interesting. With control flow graph by bindiff, I saw the difference.


The patch added memset() right after the malloc() function.
Before digging into the root cause of the vulnerability, here is the format of RDPSND PDU(Protocol Data Unit) between RDP server and client.



sndFormats in RDPSND PDU is array of AUDIO_FORMAT which is information about audio formats supported by server(or client). Its definition is as follows.



Then I took a look at CRDPSound::DataArrived() which is the caller function of the patched CRDPSound::vcwaveChooseSoundFormat(). The bug is caused by PDU whose msgType value is 7.

Simple PDU verification is done, and then calls CRDPSound::vcwaveChooseSoundFormat().

client_snd_formats = (struct SNDFORMATITEM *)malloc(total_size); allocates memory. Then parsing PDU sent by server is done, checking if (snd_formats->nAvgBytesPerSec). Here I could find what vulnerability was. The bug is that uninitialized memory is returned when nAvgBytesPerSec field of all AUDIO_FORMAT in PDU is zero.

So the patch added memset(client_snd_formats, 0, total_size); to prevent uninitialize memory from being returned.

Writing exploit

I could read values in memory with triggering the vulnerability.
But it is no use reading garbage values. So I used heap spray so that I could
read meaningful values in memory, which is image base of mstscax.dll.

To spraying heap, Dynamic channel is used.
It is channel that RDP server and client can use to reliable communication.
Its internal implementation uses lots of memory allocation and free. So it can be used to manipulate heap.

A dynamic channel manager is allocated in heap when a new dynamic channel is created.
The point is that this manager has vftable which has lots of function pointers that can be
used to leak the base address of mstscax.dll.

I allocated about 1000 channels(heap spray), and free them all.
RDP server can send CLOSE_REQUEST_PDU to the client to free a dynamic channel.
Hers is structure of CLOSE_REQUEST_PDU.


cbId indicates the length of the ChannelId field.
- 0x00: The ChannelId is 1 byte wide.
- 0x01: The ChannelId is 2 bytes wide.
- 0x02: The ChannelId is 4 bytes wide.

So exploit scenario is as follows.
1. Spray dynamic channels
2. Free all the channels allocated
3. Trigger the vulnerability
4. Get the image base of mstscax.dll

Finally I leak the base address. Exploit can be found here.

I had little experience exploiting windows heap, so this exploit is unreliable :(