Hack.lu 2010 CTF #16 (Rattlesnake’s Riddle) writeup

Solve this riddle to impress Captain Rattlesnake!
download

The .pyc file is a byte-compiled python code, and there is a wonderful tool called uncompyle to deal with it.

$ decompyle secret.pyc > secret.py

The decompyled file:
1. Takes 3 arguments
2. Checks 2nd to be 1337
3. Calculates ‘token‘ value, which is 11111112671
4. After some obscure checks, calculates binary sha1 of “adsf” . (token + argv[3])
5. Uses this binary hash as a key to deXOR a crypted cPickle piece of data
6. Unserializes the data, dexors it using argv[1] and prints it out.

First, we needed to get the cPickle data. Instead of bruteforcing the sha1 value, we attacked the xor key itself: it is only 20 bytes long.

*** Now cutting to hellman who will explain how to do it !

When you want to break a cipher, a good thing to know is a kind of the expected plaintext. We know, it’s a dump made with the cPickle python module. Let’s play with it a bit:

>>> import cPickle
>>> import redbeard
>>> pl = redbeard.Player("hellman")
>>> cPickle.dumps(pl)
"(ibattleships\nPlayer\np1\n(dp2\nS'rand'\np3\ncrandom\nWichmannHill
\np4\n(tRp5\n(I1\n(I16656\nI13508\nI1\ntp6\nNtbsS'name'\np7\nS'hellm
an'\np8\nsb."
>>> a = [100, "teststring", 1337]
>>> cPickle.dumps(a)
"(lp1\nI100\naS'teststring'\np2\naI1337\na."
>>> b = [a, {"x": 100, "y": 200}]
>>> cPickle.dumps(b)
"(lp1\n(lp2\nI100\naS'teststring'\np3\naI1337\naa(dp4\nS'y'\nI200\ns
S'x'\nI100\n
>>> cPickle.dumps(list)
'c__builtin__\nlist\np1\n.'

We can find here some rules:
1. if a dumping thing is an object (which is the most probably), the dump starts with ‘(‘
2. there are lots of p<N>\n, where N is growing from 1 to some number
3. S’ means start of a string
4. \n follows a string closing quote
5. the last byte is ‘.’

I wrote a small php script to simplify guessing. So, let’s begin.
Notice: ¬ is used to show newline character ( “\n” ) to make the output looking straight

  • Last byte is ‘.’ so let’s write: 10 19 .
  • Also let’s think it’s an object: 0 0 (

Now we have:

-----01234567890123456789
  0: (******************r
  1: p******************¬
  2: a******************z
  3: '******************K
  4: F******************p
  5: 9******************g
  6: F******************p
  7: 1******************G
  8: F******************p
  9: 1******************'
 10: V******************.
  • Look at 3 1. Before is ‘z’ so it’s a string’s end: 3 1 \n
  • Look at 9 18: next byte after is ‘V’, so it’s not
    a string’s end. Then, ‘S’ must prepend that: 9 18 S
-----01234567890123456789
  0: (l****************'r
  1: p*****************2: aS****************rz
  3:****************WK
  4: F'****************¬p
  5:****************'g
  6: F[****************¬p
  7: 12****************'G
  8: F\****************¬p
  9: 15****************S'
 10: VZ****************a.
  • Same trick at 0 17: 0 17 S
  • Look at the beginning of line #2 p4\naS – should be a string: 2 2 ‘

Currently we have:

-----01234567890123456789
  0: (lp**************S'r
  1: p*j**************p4¬
  2: aS'***************rz
  3: '¬p**************'WK
  4: F'¬**************'¬p
  5: 9¬a**************S'g
  6: F[[**************'¬p
  7: 12¬**************S'G
  8: F\\**************'¬p
  9: 15¬**************aS'
 10: VZ[**************¬a.
  • Start of a string at 3 16: 3 16 S
  • 0 3 – should be (lp1\n: 0 3 1,0 4 \n
  • 4 5p8 ends: 4 5 \n
  • 0 6 string beginning: 0 6 ‘

Now we have:

-----01234567890123456789
  0: (lp1¬S'*********aS'r
  1: p*j'¬p3*********¬p4¬
  2: aS'iuxt*********q*rz
  3: '¬p6¬aS*********S'WK
  4: F'¬p8¬a*********b'¬p
  5: 9¬aS'dO*********aS'g
  6: F[[@Z'¬*********E'¬p
  7: 12¬aS']*********aS'G
  8: F\\'¬p1*********T'¬p
  9: 15¬aS'Z*********¬aS'
 10: VZ[VGPA*********7¬a.

Some more easy-guessed steps:

  • 1 7 \n
  • 4 8 ‘

Also we can notice an \n before each aS’. It’s useful!

  • 0 15 \n
  • 3 14 \n
  • 2 9 \n
  • 1 10 ‘
  • 3 11 \n
  • 0 12 \n
  • 0 13 p

Finally, we got it! The final picture:

-----01234567890123456789
  0: (lp1¬S'Dxzr'¬p2¬aS'r
  1: p*j'¬p3¬aS'krhh'¬p4¬
  2: aS'iuxto'¬p5¬aS'q*rz
  3: '¬p6¬aS'HO'¬p7¬aS'WK
  4: F'¬p8¬aS'VKJR\x0b'¬p
  5: 9¬aS'dORDL'¬p10¬aS'g
  6: F[[@Z'¬p11¬aS'HJE'¬p
  7: 12¬aS']D^^'¬p13¬aS'G
  8: F\\'¬p14¬aS'_P\\T'¬p
  9: 15¬aS'Z]G\\'¬p16¬aS'
 10: VZ[VGPAP\x1b'¬p17¬a.

Enter “save” to save the key and the dump:

> save

Dump and key saved!
$ cat dump.cp
(lp1
S'Dxzr'
p2
aS'rp}j'
p3
...
p15
aS'Z]G\\'
p16
aS'VZ[VGPAP\x1b'
p17
a.

*** Now cutting back to vos!

Now as we have the correct cPickle dump, we can bruteforce argv[1] as it has only 256 meaningful values – the chars. This is what different values of argv[1] give us: out.txt.

Some kids piss their name in the snow. Chuck Norris can piss his name into concrete. looks much like an answer ;)

3 comments

    • sleepya on November 1, 2010 at 17:14
    • Reply

    Nice xor. I did bruteforcing. I noticed from the last loop after getting “o”. “o” should be list/tuple of encrypted words.

    I set “token” to be “11111111111L” then bruteforcing secret with xrange(1000000). Check if “o” is list/tuple and “len(o) > 0”.

    Then, got possible secret3 “556007” in a few minutes.

    I also tried to find the arguments to get the answer.
    python secret.pyc 23 1337 554433

    • horokey on March 23, 2013 at 02:30
    • Reply

    update .pyc decompiler link please =)

    1. uncompyle is better now. updated link

Leave a Reply to sleepya Cancel reply

Your email address will not be published.