PlaidCTF 2011 – 19 – Another small bug – 250 pts

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

“This time, let’s attack /opt/pctf/z2/exploitme.

Using IDA Pro I can see that the program takes one command line argument, which is interpreted as an unsigned integer representing the number of bytes to read from stdin into a 512 byte buffer. There is a length check that is meant to ensure that we don’t pass more than 512 as the length argument. However, it also checks the return value of a function that is supposed to log the error.

Examining the log_error() function (named by me) we see that the logfile is /home/z2/logs/assert.log, and that the function returns zero if the file could not be opened. If we can somehow get fopen() to fail, that would mean the length check in the main() function becomes ineffective.

Normally, I would accomplish this by using

to set the maximum number of open file descriptors before executing the vulnerable program, in this case we don’t even need that though. :D Turns out the /home/z2/logs directory does not exist, and thus the fopen() will always fail. Since the buffer that is read into is located on the stack, we end up with a vanilla stackbased buffer overflow.

No stack canaries are used, and the executable does not even have NX enabled. The only hurdles we need to overcome are that the stack is randomized and that the binary is statically linked (so we can’t jump to system() in libc). To deal with this, I chose to use a return-to-text based attack. For completeness I chose to make two variants of my exploit, one that relies on .bss being executable and one that would work with full NX too.

My first exploit simply returns into read(), with the stack set up to read from file descriptor 0 (stdin) to the .bss segment, and then return into that buffer. It relies on the following addresses, easily retrieved from the binary using IDA Pro:

  • 0x80489d8 – read()
  • 0x804b510 – .bss

My second exploit returns into mmap() to map a writable and executable buffer at a fixed location, then into an address with six pops and a ret instruction to go past the mmap() arguments on the stack and return into read() just as in the previous exploit. This relies on the address of:

  • 0x8049abc – mmap()
  • 0x8048529 – pop * 6 + ret
  • 0x80489d8 – read()

This is the source of my second exploit:

Note the call to sleep between the exploit buffer and the shellcode. This is to make sure that only the exploit buffer is read by the fgets(), and the shellcode by the call to read() that I set up.