GiTS 2014: gitsmsg

tl;drgitsmsg is a messaging server. A heap overflow led to arbitrary read / write and eventual code exec
after circumventing RELRO.

Binary and exploit available here.

The program

First, we reverse engineered much of the binary.
You “login” as a user, then can compose messages to other users. The messages
were saved to a linked list and could be edited before being serialized to disk.
Each message is a tagged union of {byte,dword,double}{_,array}
or string. A string indexed into an array of static strings.
A “typical” usage might be:

The vulnerability

In short, when initializing one of the message type, the programmer forgot to
factor the data type width when calculating the message size:

This eventually gives us a heap overwrite:

At this point, it seems relatively straightforward. We will allocate 2 messages,
leaving the heap in this state:

We then free the first message:

And allocate a new message of type DOUBLE_ARRAY, allowing us to overwrite and
modify the second message:

Our goal will be to overwrite a GOT entry and give us a shell. Since the program
is PIE, we have to leak an address first. We do this by editing the second
message which does 2 things for us: it allows us to put it back into a valid
state, and it will put a address from the .data segment into the heap (if a
string message is edited, it will update the message data pointer to point
to the correct string in the .data segment).

Actually, at this point we have an arbitrary read and an arbitrary write
primitive. Since the data for the first message overlaps with the type and
pointer of the second message, we can edit the first message to change the type
of the second. If we change the type of the second message to dword and its
pointer to <address>, we can get the contents of the second message to read
from that address and edit the contents of the second message to write to that
address.

Once we have the program base, we use our arbitrary read primitive to leak a
libc address. We know it is an Ubuntu machine, so we download a couple versions
of libc and compare the address to the symbol in each of the versions to match
the correct libc version. We can now overwrite free with system and delete
our message to get a shell!

Except this didn’t work – the program had full RELRO support, so the GOT was
read only.

To get around this, we had to do some painful stuff. We noticed a directory
traversal attack in the login function, and though we could use that to put the
key into the heap (and read it later). Unfortunately, the malloc
implementation seemed to clobber the key after it freed the blob. Instead,
our strategy
was to overwrite an atexit handler function pointer located in libc with the
address of system and to overwrite the argument for this handler with a buffer
we controlled. Unfortunately, this function pointer was encrypted. To decrypt,
we computed what the function pointer was supposed to be by leaking an address
from ld.so and using the address and the encrypted value to calculate the key.

We then encrypted our target address with this key, update the function pointer
to use our new address and updated the argument to point to a buffer we
controlled

To trigger our exploit, we just sent the disconnect message (which fortunately
didn’t disconnect the socket). For our final exploit, we used the payload
cat key >&4 to dump the key to the already open socket.

Writeup by Alex Reece, see me on Google+ or my blog.

    • Wesley Jin
    • January 21st, 2014

    Great Job!

    I have a question. How were you able to predict the heap state? After freeing the BYTE_ARRAY msg, how could you be sure that the allocated DOUBLE_ARRAY would occupy the same spot on the heap?

    I noticed that the you used 0x3c0 as the size of the 1st msg.
    However, you use 127 as the size of second message. How did you know that the second heap chunk would be broken from the 1st reliably?

    Perhaps, I am misunderstanding something?

    Thanks,
    Wes

  1. No trackbacks yet.