No cON Name CTF Quals 2014 - imMISCible (200pts) writeup

The challenge description was: No hints :( Just go and get the flag.

So let's download and check the content of the file:

#!/usr/bin/env python
# -*- coding: rot13 -*-
vzcbeg bf
vzcbeg znefuny
vzcbeg arj

tybony synt

qrs s():
tybony synt
synt = "Abcr!".qrpbqr("rot13")

olgrpbqr = """
LjNNNNNNNNNNNjNNNRNNNNOmyjNNNTDNNTDONTjNNT0ONSbONNSxNNOxNtOfNtOgNjOnNjNOMDZN
MNZNMNDNtjVNMNHNnjVNpcZNMNLNLDDNqNDNMNpNA2RRNUDRNTDVNQquONO0ONOxPDN3LDDNqNDN
ntHNMNbNMNDNtjVNLDDNqNDNntLNMNfNtjRNLDDNMNjNMDRNqNDNtjRNntpNtjNNS2RRNT4NNTDA
NSZbQtNNNTa/////XNRNNNOmONNNNUAbLGRbNDNNNUZTNNNNM2I0MJ52pjfNNNOBG19QG05sGxSA
EKZNNNNNpjRNNNOMpmRNNNNtAGptAwttAwRtAmDtZwNtAwxtAmZtZwNtAmDtAwttAwHtZwNtAwRt
AwxtAmVtZzDtpmRNNNNtAmZtAmNtAwHtAwHtAwDtZwNtAmLtAwHtAzZtAzLtAwZtAwxtAmDtAmxt
ZwNtAzLtpmRNNNNtAwLtZwNtAwRtAzHtZwNtAmHtAzHtAzZtAwRtAwDtAwHtAzHtZwNtAmZtAmpt
AwRtpkNNNNNtAzZtAzZtAzLtAmptZ2LtpjRNNNNtpjZNNNObMKumNjNNNR5QGx4bPNNNNUZUNNNN
nTSmnTkcLaZRNNNNp2uuZKZPNNNNo3AmOtNNNTqyqTIhqaZRNNNNMzkuM3ZUNNNNpzIjoTSwMKZT
NNNNMTIwo2EypjxNNNObMKuxnJqyp3DbNNNNNPtNNNNNXNNNNNOmPNNNNQkmqUWcozp+pjtNNNN8
oJ9xqJkyCtVNNNOmRtNNNONORNRINtLOPtRXNDbORtRCND==
"""


vs __anzr__ != "__znva__".qrpbqr("rot13"):
pbqrbow = znefuny.ybnqf(olgrpbqr.qrpbqr("rot13"))
s = arj.shapgvba(pbqrbow, tybonyf(), "s".qrpbqr("rot13"), Abar, Abar)

s()

cevag synt

Interesting, we have a couple hints, the first one is that it's a python script and the second one is that it's encoded with rot13. Let's decode the file and see what is going on:

mrt:~/ctf/imm$ python
Python 2.7.3 (default, Mar 13 2014, 11:03:55)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import codecs
>>> f = open('immiscible','r').read().decode('rot13')
>>> print f
#!/hfe/ova/rai clguba
# -*- pbqvat: ebg13 -*-
import os
import marshal
import new

global flag

def f():
global flag
flag = "Nope!".decode("ebg13")

bytecode = """
YwAAAAAAAAAAAwAAAEAAAABzlwAAAGQAAGQBAGwAAG0BAFoBAAFkAABkAgBsAgBtAwBaAwABZQMA
ZAMAZAQAgwIAZAUAawIAcpMAZAYAYQQAdAQAZAcAN2EEAHQEAGQIADdhBAB0BABkCQA3YQQAdAQA
agUAZAoAZAQAgwIAYQQAdAQAagYAZAsAgwEAYQQAZAwAZQEAdAQAgwEAagcAgwAAF2EEAG4AAGQN
AFMoDgAAAGn/////KAEAAABzBAAAAHNoYTEoAQAAAHMGAAAAZ2V0ZW52cwsAAABOT19DT05fTkFN
RXMAAAAAcwEAAABZczEAAAAgNTcgNjggNjEgNzQgMjAgNjkgNzMgMjAgNzQgNjggNjUgMjAgNjEg
NjkgNzIgMmQgczEAAAAgNzMgNzAgNjUgNjUgNjQgMjAgNzYgNjUgNmMgNmYgNjMgNjkgNzQgNzkg
MjAgNmYgczEAAAAgNjYgMjAgNjEgNmUgMjAgNzUgNmUgNmMgNjEgNjQgNjUgNmUgMjAgNzMgNzcg
NjEgcxAAAAAgNmMgNmMgNmYgNzcgM2YgcwEAAAAgcwMAAABoZXhzAwAAAE5DTk4oCAAAAHMHAAAA
aGFzaGxpYnMEAAAAc2hhMXMCAAAAb3NzBgAAAGdldGVudnMEAAAAZmxhZ3MHAAAAcmVwbGFjZXMG
AAAAZGVjb2RlcwkAAABoZXhkaWdlc3QoAAAAACgAAAAAKAAAAABzCAAAADxzdHJpbmc+cwgAAAA8
bW9kdWxlPgIAAABzEgAAABABEAEVAgYBCgEKAQoBEgEPAQ==
"""


if __name__ != "__main__".decode("ebg13"):
codeobj = marshal.loads(bytecode.decode("ebg13"))
f = new.function(codeobj, globals(), "f".decode("ebg13"), None, None)

f()

print flag

So we have a global flag variable where the flag might be stored if the script is executed properly. A bytecode variable containing a base64 encoded marshal dump which is overwritting the dummy method f(). A call to the f() method and finally the output of the global variable flag. Let's decode the base64 bytecode and store it in a .pyc file to try and analyze it:

mrt:~/ctf/imm$ echo YwAAAAAAAAAAAwAAAEAAAABzlwAAAGQAAGQBAGwAAG0BAFoBAAFkAABk\
> AgBsAgBtAwBaAwABZQMAZAMAZAQAgwIAZAUAawIAcpMAZAYAYQQAdAQAZAcAN2EEAHQEAGQIADdh\
> BAB0BABkCQA3YQQAdAQAagUAZAoAZAQAgwIAYQQAdAQAagYAZAsAgwEAYQQAZAwAZQEAdAQAgwEA\
> agcAgwAAF2EEAG4AAGQNAFMoDgAAAGn/////KAEAAABzBAAAAHNoYTEoAQAAAHMGAAAAZ2V0ZW52\
> cwsAAABOT19DT05fTkFNRXMAAAAAcwEAAABZczEAAAAgNTcgNjggNjEgNzQgMjAgNjkgNzMgMjAg\
> NzQgNjggNjUgMjAgNjEgNjkgNzIgMmQgczEAAAAgNzMgNzAgNjUgNjUgNjQgMjAgNzYgNjUgNmMg\
> NmYgNjMgNjkgNzQgNzkgMjAgNmYgczEAAAAgNjYgMjAgNjEgNmUgMjAgNzUgNmUgNmMgNjEgNjQg\
> NjUgNmUgMjAgNzMgNzcgNjEgcxAAAAAgNmMgNmMgNmYgNzcgM2YgcwEAAAAgcwMAAABoZXhzAwAA\
> AE5DTk4oCAAAAHMHAAAAaGFzaGxpYnMEAAAAc2hhMXMCAAAAb3NzBgAAAGdldGVudnMEAAAAZmxh\
> Z3MHAAAAcmVwbGFjZXMGAAAAZGVjb2RlcwkAAABoZXhkaWdlc3QoAAAAACgAAAAAKAAAAABzCAAA\
> ADxzdHJpbmc+cwgAAAA8bW9kdWxlPgIAAABzEgAAABABEAEVAgYBCgEKAQoBEgEPAQ== | base64 --decode > bytecode.pyc


mrt:~/ctf/imm$ xxd bytecode.pyc
0000000: 6300 0000 0000 0000 0003 0000 0040 0000 c............@..
0000010: 0073 9700 0000 6400 0064 0100 6c00 006d .s....d..d..l..m
0000020: 0100 5a01 0001 6400 0064 0200 6c02 006d ..Z...d..d..l..m
0000030: 0300 5a03 0001 6503 0064 0300 6404 0083 ..Z...e..d..d...
0000040: 0200 6405 006b 0200 7293 0064 0600 6104 ..d..k..r..d..a.
0000050: 0074 0400 6407 0037 6104 0074 0400 6408 .t..d..7a..t..d.
0000060: 0037 6104 0074 0400 6409 0037 6104 0074 .7a..t..d..7a..t
0000070: 0400 6a05 0064 0a00 6404 0083 0200 6104 ..j..d..d.....a.
0000080: 0074 0400 6a06 0064 0b00 8301 0061 0400 .t..j..d.....a..
0000090: 640c 0065 0100 7404 0083 0100 6a07 0083 d..e..t.....j...
00000a0: 0000 1761 0400 6e00 0064 0d00 5328 0e00 ...a..n..d..S(..
00000b0: 0000 69ff ffff ff28 0100 0000 7304 0000 ..i....(....s...
00000c0: 0073 6861 3128 0100 0000 7306 0000 0067 .sha1(....s....g
00000d0: 6574 656e 7673 0b00 0000 4e4f 5f43 4f4e etenvs....NO_CON
00000e0: 5f4e 414d 4573 0000 0000 7301 0000 0059 _NAMEs....s....Y
00000f0: 7331 0000 0020 3537 2036 3820 3631 2037 s1... 57 68 61 7
0000100: 3420 3230 2036 3920 3733 2032 3020 3734 4 20 69 73 20 74
0000110: 2036 3820 3635 2032 3020 3631 2036 3920 68 65 20 61 69
0000120: 3732 2032 6420 7331 0000 0020 3733 2037 72 2d s1... 73 7
0000130: 3020 3635 2036 3520 3634 2032 3020 3736 0 65 65 64 20 76
0000140: 2036 3520 3663 2036 6620 3633 2036 3920 65 6c 6f 63 69
0000150: 3734 2037 3920 3230 2036 6620 7331 0000 74 79 20 6f s1..
0000160: 0020 3636 2032 3020 3631 2036 6520 3230 . 66 20 61 6e 20
0000170: 2037 3520 3665 2036 6320 3631 2036 3420 75 6e 6c 61 64
0000180: 3635 2036 6520 3230 2037 3320 3737 2036 65 6e 20 73 77 6
0000190: 3120 7310 0000 0020 3663 2036 6320 3666 1 s.... 6c 6c 6f
00001a0: 2037 3720 3366 2073 0100 0000 2073 0300 77 3f s.... s..
00001b0: 0000 6865 7873 0300 0000 4e43 4e4e 2808 ..hexs....NCNN(.
00001c0: 0000 0073 0700 0000 6861 7368 6c69 6273 ...s....hashlibs
00001d0: 0400 0000 7368 6131 7302 0000 006f 7373 ....sha1s....oss
00001e0: 0600 0000 6765 7465 6e76 7304 0000 0066 ....getenvs....f
00001f0: 6c61 6773 0700 0000 7265 706c 6163 6573 lags....replaces
0000200: 0600 0000 6465 636f 6465 7309 0000 0068 ....decodes....h
0000210: 6578 6469 6765 7374 2800 0000 0028 0000 exdigest(....(..
0000220: 0000 2800 0000 0073 0800 0000 3c73 7472 ..(....s....<str
0000230: 696e 673e 7308 0000 003c 6d6f 6475 6c65 ing>s....<module
0000240: 3e02 0000 0073 1200 0000 1001 1001 1502 >....s..........
0000250: 0601 0a01 0a01 0a01 1201 0f01 ............

mrt:~/ctf/imm$ strings bytecode.pyc
sha1(
getenvs
NO_CON_NAMEs
57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d s1
73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f s1
66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 s
6c 6c 6f 77 3f s
hexs
NCNN(
hashlibs
sha1s
getenvs
flags
replaces
decodes
hexdigest(
s

We notice a couple interesting things here. First the call to getenv followed by what could be the environment variable NO_CON_NAME. Second a list of what seems to be hexadecimal values. Third a call to sha1 and finally what could be our global variable flag. Let's find out what the hexadecimal values mean:

mrt:~/ctf/imm$ echo -e "\x57\x68\x61\x74\x20\x69\x73\x20\x74\x68\x65\x20\x61\
> \x69\x72\x2d\x73\x70\x65\x65\x64\x20\x76\x65\x6c\x6f\x63\x69\x74\x79\x20\x6f\
> \x66\x20\x61\x6e\x20\x75\x6e\x6c\x61\x64\x65\x6e\x20\x73\x77\x61\x6c\x6c\x6f\
> \x77\x3f"

What is the air-speed velocity of an unladen swallow?

A Monty Python and the Holy Grail quote! Maybe the flag is the sha1 of this quote? Let's try decompiling the pyc using uncompyle2 to finish our investigation:

mrt:~/ctf/imm$ uncompyle2 bytecode.pyc
# 2014.09.20 00:54:31 EDT
### Can't uncompyle bytecode.pyc
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/uncompyle2/__init__.py", line 197, in main
uncompyle_file(infile, outstream, showasm, showast, deob)
File "/usr/local/lib/python2.7/dist-packages/uncompyle2/__init__.py", line 129, in uncompyle_file
version, co = _load_module(filename)
File "/usr/local/lib/python2.7/dist-packages/uncompyle2/__init__.py", line 72, in _load_module
raise ImportError, "Unknown magic number %s in %s" % (ord(magic[0])+256*ord(magic[1]), filename)
ImportError: Unknown magic number 99 in bytecode.pyc
# decompiled 0 files: 0 okay, 1 failed, 0 verify failed
# 2014.09.20 00:54:31 EDT

Uknown magic number 99, it seems like a part of the header is missing. It's not really a problem let's make a pyc ourself and just add the missing bytes from the header:

mrt:~/ctf/imm$ python
Python 2.7.3 (default, Mar 13 2014, 11:03:55)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import py_compile
>>> py_compile.compile('immiscible.py')
>>>

mrt:~/ctf/imm$ xxd -l 16 immiscible.pyc
0000000: 03f3 0d0a a70d 1d54 6300 0000 0000 0000 .......Tc.......

mrt:~/ctf/imm$ xxd -l 16 bytecode.pyc
0000000: 6300 0000 0000 0000 0003 0000 0040 0000 c............@..

We can see our bytecode.pyc is missing the first 8 bytes and that's why it couldn't find a proper magic number. The 4 first bytes are the magic number, the 4 next bytes are the timestamp, let's edit bytecode.pyc and add these:

mrt:~/ctf/imm$ head -c 8 immiscible.pyc > newbytecode.pyc

mrt:~/ctf/imm$ cat bytecode.pyc >> newbytecode.pyc

And let's run uncompyle2 again this time:

mrt:~/ctf/imm$ uncompyle2 newbytecode.pyc
# 2014.09.20 01:26:53 EDT
#Embedded file name:
from hashlib import sha1
from os import getenv
if getenv('NO_CON_NAME', '') == 'Y':
flag = ' 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d '
flag += ' 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f '
flag += ' 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 '
flag += ' 6c 6c 6f 77 3f '
flag = flag.replace(' ', '')
flag = flag.decode('hex')
flag = 'NCN' + sha1(flag).hexdigest()
global flag ## Warning: Unused global
+++ okay decompyling newbytecode.pyc
# decompiled 1 files: 1 okay, 0 failed, 0 verify failed
# 2014.09.20 01:26:53 EDT

Much better, now we know exactly what is going on. The decompiled bytecode is looking for an environment variable as we suspected called NO_CON_NAME with a value of Y. If this value exists it stores the quote from Monty Python in the variable flag, decode the hex values and return the sha1 of it with NCN prepended. Let's run the code directly to get the flag:

mrt:~/ctf/imm$ python
Python 2.7.3 (default, Mar 13 2014, 11:03:55)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from hashlib import sha1
>>> flag = ' 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d '
>>> flag += ' 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f '
>>> flag += ' 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 '
>>> flag += ' 6c 6c 6f 77 3f '
>>> flag = flag.replace(' ', '')
>>> flag = flag.decode('hex')
>>> flag = 'NCN' + sha1(flag).hexdigest()
>>> print flag
NCN6ceeeff26e72a40b71e6029a7149ad0626fcf310

We got our flag:

NCN6ceeeff26e72a40b71e6029a7149ad0626fcf310