Hack.lu CTF 2013 | Internals 200 : Packed [Write Up]

We just found a dead robot. It seems there is some useful data left but somehow it got confused with other data and now we don’t know what’s useful and what’s junk. We just know there is only one way to go but there are many dead ends.

Here is the challenge: http://ctf.fluxfingers.net/static/downloads/packed/packed

The given file, packed, looked like three different files concatenated : a PDF, some crypted text, and an opendocument (base64-encoded). The first one and the third one were not interesting, The second file was in fact a rot-13 encoded Python script…

cipher="H51\\'Ux2J&+(3Z;Uxcx0Xxs\x13h\x014$V!R($R>\t/)R!\x01<.\x13,N-aP4M4aRuG1-VuU0 GuH+a@0W=3R9\x01>(_0\x01,8C0Rx GuN6\"V|\x1ezKZ3\x014$]}R!2\x1d4S?7\x1au\x1fxs\t_\x01xa\x13<Gx)R&Ip2J&\x0f93T#zj\x1c\x1ap\x13rk\x00g\x01e|\x13g\x19ju\x0ba\x18jt\x02o+xa\x13u\x01xa\x13%S1/Gu\x03\x1b.\:N7.\:N4o\x13\x0cN-3\x133M9&\x13<Rx A2WjiZ{DvaX0Xjh\x136N6\"R!\x01\x07rC0p\x138a\x1dc22ieu\x161Fw+=-@0\x1bRa\x13u\x01(3Z;UxcR\'F.s\x1c>D!s\x13<Rx,Z&R1/Tw+R"
n =0 ;import hashlib ,sys ;
try :key =sys .argv [1 ]
except IndexError :sys .exit ("x\x9c\xf3N\xadT0T\xc8\xcd,.\xce\xccKW\xc8\xccSH,J/\x03\x00M\x97\x07\".decode ("mvc"))
f =getattr (hashlib ,"x\x9c\xcbM1\x05\x00\x02G\x01\x07".decode ("mvc"))
while n <(5 *10 **6 ):key =(f (key ).digest ());n =n +1
key =key [:5 ].upper ()
while len (key )<len (cipher ):key =key *2
plain ="".join (map (chr ,[ord (a )^ord (b )for a ,b in zip (cipher ,key )]))
try :exec plain
except :print "x\x9c\x0b/\xca\xcfKW\xf0N\xadT\x04\x00\x14d\x03x".decode ("mvc"), repr(plain)

…and look terrible :D Let’s clarify all the shit. The .decode(‘mvc’)’s are zlib compressed strings.

import hashlib, sys

cipher="H51\\'Ux2J&+(3Z;Uxcx0Xxs\x13h\x014$V!R($R>\t/)R!\x01<.\x13,N-aP4M4aRuG1-VuU0 GuH+a@0W=3R9\x01>(_0\x01,8C0Rx GuN6\"V|\x1ezKZ3\x014$]}R!2\x1d4S?7\x1au\x1fxs\t_\x01xa\x13<Gx)R&Ip2J&\x0f93T#zj\x1c\x1ap\x13rk\x00g\x01e|\x13g\x19ju\x0ba\x18jt\x02o+xa\x13u\x01xa\x13%S1/Gu\x03\x1b.\:N7.\:N4o\x13\x0cN-3\x133M9&\x13<Rx A2WjiZ{DvaX0Xjh\x136N6\"R!\x01\x07rC0p\x138a\x1dc22ieu\x161Fw+=-@0\x1bRa\x13u\x01(3Z;UxcR\'F.s\x1c>D!s\x13<Rx,Z&R1/Tw+R"

try: key = sys.argv[1]
except: sys.exit('Key 1 missing in argv')

f = hashlib.md5
n = 0
while n < 5000000:
	key = f(key).digest()
	n += 1

key = key[:5].upper()
while len(key) < len(cipher):
	key = key * 2

plain = "".join(map(chr, [ord(a) ^ ord(b) for a, b in zip(cipher, key)]))
try: exec plain
except: print "Wrong Key!"

What does the code is fairly easy to understand. We have a encoded string, cipher. The program takes a password in argv and hashes it 5000000 times in md5 (we can forget about brute-forcing that, it would be like 5000000 times slower than a simple md5 brute-force). The 5 first bytes of the result are dirtily extended, and used as a xor key to decipher the cipher. The decrypted string is then executed, so we know that it’s supposed to be valid python code :)

We can retrieve the xor key with classical analysis. xortool does it well, but let’s code our own decrypter.

function break_xor($c, $lenkey) {
	$c = str_split($c, $lenkey);
	$a = array();
	for($i = 0; $i < $lenkey; $i++, $a[] = '');
	for($i = 0; $i < $lenkey; $i++)
		for($j = 0; $j < count($c); $j++)
			$a[$i] .= $c[$j][$i];
	$plaintext = array();
	$abc = str_split("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz= (.)");
	$key = '';
	foreach($a as $col)
		$p = array();
		for($k = 0; $k < 256; $k++) {
			$d = $col ^ str_repeat(chr($k), strlen($col));
			$count = 0;
			for($i = 0; $i < strlen($d); $i++)
				if(in_array($d[$i], $abc))
			$p[$d . chr($k)] = $count;
		$tmp = array_search(max($p), $p);
		$plaintext[] = substr($tmp, 0, -1);
		$key .= substr($tmp, -1);
	$strout = '';
	for($j = 0; $j < strlen($plaintext[0]); $j++)
		for($i = 0; $i < $lenkey; $i++)
			$strout .= $plaintext[$i][$j];
	return array($strout, $key);

Key found is !XA3U (strange to have a full ascii 32-127 key when it’s supposed to come from a md5 digest… but why not)
The deciphered Python code is:

import sys
print "Key 2 = leetspeak(what do you call a file that is several file types at once)?"
if len(sys.argv) > 2:
    if hash(sys.argv[2])%2**32 == 2824849251:
        print "Coooooooool. Your flag is argv2(i.e. key2) concat _3peQKyRHBjsZ0TNpu"
    print "argv2/key2 is missing"

So we must now find the second key, that is the password of the challenge once concatenated with _3peQKyRHBjsZ0TNpu. We know that key2 = leetspeak(what do you call a file that is several file types at once?). The answer is ch4m3l30n, which python hash is indeed equal to 282484951 :D

Flag: ch4m3l30n_3peQKyRHBjsZ0TNpu


Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s