hack.lu CTF - Challenge 16 WriteUp

Tue 02 November 2010 by damien

We attended Hack.lu this year in Luxembourg. This security conference is really nice and provides a Capture The Flag (CTF) contest organized by FluxFingers, the CTF Team of Ruhr-Universit├Ąt Bochum (Germany).Here is the write-up of the challenge 16.

We were given a file named secret.pyc containing python bytecode for cPython.

We first tried to import this module inside an ipython session, but with no luckbecause we encountered a SystemExit exception.

So we decided to inspect this bytecode. We quickly found a wrapper around the python "dis"module (see http://nedbatchelder.com/blog/20080...) that will do the trick.

Since we didn't have a python decompiler at hand, and because the code is short, wedecided to do it by hand. We use the following script to debug the "secret" module:

#!/usr/bin/env python

import sys
sys.argv = ["secret.pyc", "1000", "1337", "1000", "1000", "1001"]
import pdb
pdb.set_trace()
# don't forget to adjust __file__ in line 72
import secret

This gave a quite large output, so the analysis of the bytecode is here.Finally we are left with around 100 code lines to analyze.

#!/usr/bin/env python

foobar = __import__("sys")
import hashlib
if len(foobar.argv) < 4:
    foobar.exit(0)

secret1 = foobar.argv[1]
secret2 = foobar.argv[2]
secret3 = foobar.argv[3]
secret4 = foobar.argv[2]
secret5 = foobar.argv[3]
secret1 = secret1

secret4 = secret4
secret3 = secret1
secret1 = secret1
secret3 = secret5
secret1 = foobar.argv[1]
secret4 = secret2
secret3 = secret1

secret3 = secret5
secret5 = secret3
secret2 = secret2

if float(secret2) != 1337.0
    foobar.exit(0)

token = 0

f1 = open(foobar.argv[0], "r")
f2 = open(foobar.argv[0], "r")

f = f1.read()
f1.close()

g = f2.read()
f2.close()

if f != g:
    token += 0
    foobar.exit(0)

token += 11111111111

secret5 = abs(hash(g))

m = map(lambda x: hash(x) % 31337, foobar.modules.keys())
# all this for detecting decompiler modules I think ...

if 8732 in m:
    foobar.exit(0)
    token += 54362345
    foobar.exit(0)

if 8474 in m:
    token += 12312344

if 8474 in m:
    token += 312395
    foobar.exit(0)

if 27445 in m:
    foobar.exit(0)

token += 53123
i = foobar._getframe().f_lasti

# i = 574
token += i

if __file__ != "secret.pyc":
    foobar.exit(0)

token += 1000
key = hashlib.sha1('adsf'+str(token+int(secret3))).digest()

p = '\x83\xc8\x8f\xb6!\xec\xb2\xbc\x8d\xf4\xb5q\x08\x9d\xaa\x99R\xf3J\x02\xdb\xd9\x95\xa0!\xcf\xa6\xf2\x94\xdd\xe0=p\x85\xf0\xb49\xd0Yz\xca\xf7\xd8\xee^\xc7\xe1\x97\xd2\x84\xb7c\x08\x8c\xcb\xb4B\xde\x1f\n\x8c\xae\x8f\xb1!\xde\xc6\xdf\xbd\xc1\xe0\\r\xda\x92\xf2`\x87:;\xed\x83\xf5\xf7\x13\xb5\xf4\xab\xd2\xd8\x8c\x1cP\xb1\xe0\xa3Q\x87g\x00\x92\xae\x9e\xd4\x0c\xdb\xda\xaa\xb1\xc2\xe0\\r\xdc\xa8\x99R\xf3J\x17\xed\xff\xa4\xc7q\x98\x9f\x88\xc4\xbf\xcd7Q\xca\xd0\xd9v\x87g\x00\x9a\x96\xf5\xe6x\x98\xc8\xbc\xab\xd0\xe0\\r\xdc\xab\x99R\xf3J7\xed\xf8\xa3\xa0!\xcf\xa4\xcc\xff\xef\x94q]\xbd\xc4\xcfg\x87g\x00\x9a\x91\xf5\xe6x\x98\xcf\xa5\xb2\xd2\x9bq\x08\x9d\xa9\xa59\xc1>W\xfd\xfe\xa4\xd1l\xef\xd4\xa8\xa9\xf6\xf64%\xe7\xe8\xa2\x04\xaa\x0c^'

os = ""
for i, c in enumerate(p):
        os += chr(ord(c) ^ ord(key[(i % len(key))]))

import cPickle

try:
    o = cPickle.loads(os)
except:
    foobar.exit(0)

d = []
for i, w in enumerate(o):
    r = ''
    for c in w:
        r += chr(ord(c) ^ (int(secret1)+2*i))

    d.append(r)
s = " ".join(d)

print repr(s)

Once we eliminated all the anti-analysis code, we obtained the following infos :

  • we need to find the value of 2 integers: secret1 and secret3
  • secret3 is use to create the pickled data of a list
  • secret1 is use as argument for the chr function hence its value must be contained between 0 and 255
  • the value of token is 11111112685

We quickly wrote a function to do the bruteforce for us:

#!/usr/bin/env python

import sys
import hashlib
import cPickle
import string

token = 11111112685

p = '\x83\xc8\x8f\xb6!\xec\xb2\xbc\x8d\xf4\xb5q\x08\x9d\xaa\x99R\xf3J\x02\xdb\xd9\x95\xa0!\xcf\xa6\xf2\x94\xdd\xe0=p\x85\xf0\xb49\xd0Yz\xca\xf7\xd8\xee^\xc7\xe1\x97\xd2\x84\xb7c\x08\x8c\xcb\xb4B\xde\x1f\n\x8c\xae\x8f\xb1!\xde\xc6\xdf\xbd\xc1\xe0\\r\xda\x92\xf2`\x87:;\xed\x83\xf5\xf7\x13\xb5\xf4\xab\xd2\xd8\x8c\x1cP\xb1\xe0\xa3Q\x87g\x00\x92\xae\x9e\xd4\x0c\xdb\xda\xaa\xb1\xc2\xe0\\r\xdc\xa8\x99R\xf3J\x17\xed\xff\xa4\xc7q\x98\x9f\x88\xc4\xbf\xcd7Q\xca\xd0\xd9v\x87g\x00\x9a\x96\xf5\xe6x\x98\xc8\xbc\xab\xd0\xe0\\r\xdc\xab\x99R\xf3J7\xed\xf8\xa3\xa0!\xcf\xa4\xcc\xff\xef\x94q]\xbd\xc4\xcfg\x87g\x00\x9a\x91\xf5\xe6x\x98\xcf\xa5\xb2\xd2\x9bq\x08\x9d\xa9\xa59\xc1>W\xfd\xfe\xa4\xd1l\xef\xd4\xa8\xa9\xf6\xf64%\xe7\xe8\xa2\x04\xaa\x0c^'

def bf1(secret3):
    key = hashlib.sha1('adsf' + str(token + int(secret3))).digest()
    os = ''

    for i, c in enumerate(p):
        os += chr(ord(c) ^ ord(key[(i % len(key))]))

    try:
        o = cPickle.loads(os)

    except Exception:
        return None

    if isinstance(o, list) and len(o) > 0:
        return o


def bf2(secret1, o):
    d = []
    try:
        for i, w in enumerate(o):
            r = ''
            for c in w:
                r += chr(ord(c) ^ (int(secret1)+(2*i)))
            d.append(r)
    except Exception, e:
        return None

    s = " ".join(d)

    if s != '':
        return s

if len(sys.argv) != 3:
    print "usage: %s start end" % (sys.argv[0])
    sys.exit(0)

o = None
start = int(sys.argv[1])
end = int(sys.argv[2])

for secret3 in xrange(start, end):
    if secret3 % 1000 == 0:
        sys.stdout.write(".")
        sys.stdout.flush()

    o = bf1(secret3)
    if o is not None:
        break

for secret1 in xrange(256):
    s = bf2(secret1, o)
    if s is not None:
        c = [c for c in s if c in string.printable]
        if len(c) == len(s):
            print "\nsecret1 is %d, secret3 is %d and key is %s" % (secret1,
                    secret3, repr(s))

After about 5 minutes we find the magic values for secret1 and secret3:

  • secret3 = 554433
  • secret1 = 23

which gives us the key :

'Some kids piss their name in the snow. Chuck Norris can piss his nameinto concrete.'

This challenge gives us 400 gold, yeah ! And kudos for the orgas for such a fun challenge :)