imMISCible – No cON Name CTF Qualifiers 2014

This is one of the challenges that I managed to solve in NCN 2014 qualifiers and decided to write about it as a lot of the knowledge involved here about how Python works was new to me. You were supposed to find a flag, given the file below:

#!/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()


While the supplied file was illegible, you could figure out that it was a python script. The second line also explained why the file contents were illegible: it had been ROT13’d! After performing another ROT13 on the file, shifting by 26 times brings you back to the same character, we get the following source:

#!/usr/bin/env python
import os
import marshal
import new
from base64 import b64decode
global flag
def f():
    global flag
    flag = 'Nope!'
bytecode = '''
    YwAAAAAAAAAAAwAAAEAAAABzlwAAAGQAAGQBAGwAAG0BAFoBAAFkAABkAgBsAgBtAwBaAwABZQMA
    ZAMAZAQAgwIAZAUAawIAcpMAZAYAYQQAdAQAZAcAN2EEAHQEAGQIADdhBAB0BABkCQA3YQQAdAQA
    agUAZAoAZAQAgwIAYQQAdAQAagYAZAsAgwEAYQQAZAwAZQEAdAQAgwEAagcAgwAAF2EEAG4AAGQN
    AFMoDgAAAGn/////KAEAAABzBAAAAHNoYTEoAQAAAHMGAAAAZ2V0ZW52cwsAAABOT19DT05fTkFN
    RXMAAAAAcwEAAABZczEAAAAgNTcgNjggNjEgNzQgMjAgNjkgNzMgMjAgNzQgNjggNjUgMjAgNjEg
    NjkgNzIgMmQgczEAAAAgNzMgNzAgNjUgNjUgNjQgMjAgNzYgNjUgNmMgNmYgNjMgNjkgNzQgNzkg
    MjAgNmYgczEAAAAgNjYgMjAgNjEgNmUgMjAgNzUgNmUgNmMgNjEgNjQgNjUgNmUgMjAgNzMgNzcg
    NjEgcxAAAAAgNmMgNmMgNmYgNzcgM2YgcwEAAAAgcwMAAABoZXhzAwAAAE5DTk4oCAAAAHMHAAAA
    aGFzaGxpYnMEAAAAc2hhMXMCAAAAb3NzBgAAAGdldGVudnMEAAAAZmxhZ3MHAAAAcmVwbGFjZXMG
    AAAAZGVjb2RlcwkAAABoZXhkaWdlc3QoAAAAACgAAAAAKAAAAABzCAAAADxzdHJpbmc+cwgAAAA8
    bW9kdWxlPgIAAABzEgAAABABEAEVAgYBCgEKAQoBEgEPAQ==
'''
if __name__ != '__main__':
    codeobj = marshal.loads(b64decode(bytecode))
    f = new.function(codeobj, globals(), 'f', None, None)
f() 

From this it is clear to see that the python file has embedded python byte-code in it, and also that a function is defined in that byte-code (observe the new.function call). We can also see a variable called flag. This is a lot more manageable at this stage, but we can make a further modification in order to see what that byte-code actually does, as function f() seems to be directly interacting with our flag. The new entry point now looks like this:

import dis
if __name__ == '__main__':
    codeobj = marshal.loads(b64decode(bytecode))
    f = new.function(codeobj, globals(), 'f', None, None)
    print dis.dis(f.func_code)

I changed the != check to an == in order to run our file directly, and added the dis.dis function call so that the decompiled byte-code would be printed out. I chose to go with dis, as opposed to other disassemblers out there, as it was the easiest to implement and time matters in the context of a CTF. The resulting assembler looks like this:

  2           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (('sha1',))
              6 IMPORT_NAME              0 (hashlib)
              9 IMPORT_FROM              1 (sha1)
             12 STORE_NAME               1 (sha1)
             15 POP_TOP
  3          16 LOAD_CONST               0 (-1)
             19 LOAD_CONST               2 (('getenv',))
             22 IMPORT_NAME              2 (os)
             25 IMPORT_FROM              3 (getenv)
             28 STORE_NAME               3 (getenv)
             31 POP_TOP
  4          32 LOAD_NAME                3 (getenv)
             35 LOAD_CONST               3 ('NO_CON_NAME')
             38 LOAD_CONST               4 ('')
             41 CALL_FUNCTION            2
             44 LOAD_CONST               5 ('Y')
             47 COMPARE_OP               2 (==)
             50 POP_JUMP_IF_FALSE      147
  6          53 LOAD_CONST               6 (' 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d ')
             56 STORE_GLOBAL             4 (flag)
  7          59 LOAD_GLOBAL              4 (flag)
             62 LOAD_CONST               7 (' 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f ')
             65 INPLACE_ADD
             66 STORE_GLOBAL             4 (flag)
  8          69 LOAD_GLOBAL              4 (flag)
             72 LOAD_CONST               8 (' 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 ')
             75 INPLACE_ADD
             76 STORE_GLOBAL             4 (flag)
  9          79 LOAD_GLOBAL              4 (flag)
             82 LOAD_CONST               9 (' 6c 6c 6f 77 3f ')
             85 INPLACE_ADD
             86 STORE_GLOBAL             4 (flag)
 10          89 LOAD_GLOBAL              4 (flag)
             92 LOAD_ATTR                5 (replace)
             95 LOAD_CONST              10 (' ')
             98 LOAD_CONST               4 ('')
            101 CALL_FUNCTION            2
            104 STORE_GLOBAL             4 (flag)
 11         107 LOAD_GLOBAL              4 (flag)
            110 LOAD_ATTR                6 (decode)
            113 LOAD_CONST              11 ('hex')
            116 CALL_FUNCTION            1
            119 STORE_GLOBAL             4 (flag)
 12         122 LOAD_CONST              12 ('NCN')
            125 LOAD_NAME                1 (sha1)
            128 LOAD_GLOBAL              4 (flag)
            131 CALL_FUNCTION            1
            134 LOAD_ATTR                7 (hexdigest)
            137 CALL_FUNCTION            0
            140 BINARY_ADD
            141 STORE_GLOBAL             4 (flag)
            144 JUMP_FORWARD             0 (to 147)
        >>  147 LOAD_CONST              13 (None)
            150 RETURN_VALUE

The output of dis is very helpful. It turned out that the python interpreter was implemented with a stack, as all the LOAD and STORE instructions indicate (some of you might have seen similar op-codes in Java byte-code). The first column, on the far left, is the number of the line in the source code that generated the instruction sequence, the second column from the left is the instruction’s offset from the top of the function, the third column is the instruction and the third fourth indicates the instruction’s arguments (I’m assuming the fourth column is the symbolic reference of the argument in the stack).

Just by visually inspecting the disassembled code, we can immediately see modules and functions that are fairly popular among python programmers, as well as a reference to our flag global variable (STORE_GLOBAL 4 (flag)). It would appear that between (source) lines 6-9 an alphanumeric string is assigned to the flag, while lines 10-11 remove the spaces and hex-decode the string. Source line 12 would take the SHA-1 hash of the string from the previous stage, and append it to the ‘NCN’ constant. If we translate all of that into code, we get:

#!/usr/bin/python
import hashlib
a='5768617420697320746865206169722d73706565642076656c6f63697479206f6620616e20756e6c6164656e207377616c6c6f773f'
print a
a= a.decode('hex')
sha1_h = hashlib.sha1()
sha1_h.update(a)
print 'NCN'+sha1_h.hexdigest()

The following produces the string NCN6ceeeff26e72a40b71e6029a7149ad0626fcf310, which is the accepted solution 🙂 200 points, ca-ching!

As a side note, if you observe source-line 4 in the disassembly, an environment variable NO_CON_NAME is being compared with ‘Y’. I didn’t manage to get past this, but I’m sure it’s me doing something wrong and that someone else solved it like this instead.

Advertisements
imMISCible – No cON Name CTF Qualifiers 2014

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s