💾 Archived View for 0x80.org › gemlog › 2015-04-05-ndh2k15-updator-writeup.gmi captured on 2022-04-28 at 17:40:53. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

NDH2k15 updator writeup

Updator[1] was an exploitation challenge worth 200 pts. A URL is given when we access it we're asked to enter a username/password, and there's a link to http://updator.challs.nuitduhack.com/update.py We can access the update.pyc file from http://updator.challs.nuitduhack.com/update.pyc which is a compiled version of the module. Reverse engineering/decompiling the bytecode and getting the following

1: http://updator.challs.nuitduhack.com/

import config
import sys
KEY = config.KEY

def xor(*args):
    if len(args) < 2:
        sys.exit(0)
    length = len(args[0])
    for arg in args:
        if len(arg) != length:
            sys.exit(0)
        length = len(arg)
    cipher = args[0]
    for arg in args[1:]:
        cipher = ''.join([ chr(ord(arg[i]) ^ ord(cipher[i])) for i in range(len(arg)) ])

    return cipher


class Crypto:

    @staticmethod
    def encrypt(file):
        with open(file, 'r') as fd:
            content = fd.read()
        content = content.ljust(len(content) + (8 - len(content) % 8), '0')
        blocks = [ content[i * 8:(i + 1) * 8] for i in range(len(content) / 8) ]
        with open('%s.encrypted' % file, 'w') as fd:
            encrypted = []
            for i in range(len(blocks)):
                if i == 0:
                    encrypted.append(xor(KEY, blocks[i]))
                else:
                    encrypted.append(xor(KEY, blocks[i], encrypted[i - 1]))

            fd.write(''.join(encrypted))

    @staticmethod
    def decrypt(file):
        with open(file, 'r') as fd:
            content = fd.read()
        blocks = [ content[i * 8:(i + 1) * 8] for i in range(len(content) / 8) ]
        with open('.'.join(file.split('.')[:-1]), 'w') as fd:
            plain = []
            for i in range(len(blocks)):
                if i == 0:
                    plain.append(xor(KEY, blocks[i]))
                else:
                    plain.append(xor(KEY, blocks[i], blocks[i - 1]))

            fd.write(''.join(plain).rstrip('0'))


print 'Content-Type: text/html'
print '\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset="UTF-8">\n    <title>Updator - Update system</title>\n    <link rel="stylesheet" href="static/font-awesome/css/font-awesome.css">\n    <link rel="stylesheet" href="static/css/style.css">\n  </head>\n  <body>\n    <div id="info">\n      The update managing system is still under construction but will be available soon.\n    </div>\n  </body>\n</html>\n'

This shows an algorithim used to encrypt and decrypt using some xoring. Anyway this doesn't help much so we search for more files in the website we find the following folder http://updator.challs.nuitduhack.com/temp/ which contains file log.py.encrypt This is an encrypted file we need to decrypt it but since we don't have the key we need to analyse the algorithim above.

This algorithim is a cyclic xoring with previous xored. Meaning

a = byte[0] ^ key[0%8]
b = byte[1] ^ key[1%8] ^ a
c = byte[2] ^ key[2%8] ^ b
d = ....
..= byte[n] ^ key[n%8] (^ x)?

and so on. A normal xor attack on analysis of key and most frequent character will not be very useful here due to the way we use the previous xored. Anyway what we know now is that key length is 8 bytes and we know that log.py.encrypted is possibly a python script which means we can assume what it starts with: it may start with something like :

import ...
#!/usr/bin/...
#!/bin/python...
#!/usr/local/bin...
def ...
if __name....

so knowing these things allows us to bruteforce the first few bytes of the encrypted file and reveal the key. We use the decrypting code. We write a bruteforce code that tries with key[0] from 0 to 0xff and decrypt with a key of length 8 and check the result file if it starts with i then key[0] = x is correct, and we move to key[1] doing the same but for m until key[7] trying all of import .

    k_idx = 0 # goes from 0 to 7
    for i in range(0xff):
        key[k_idx] = chr(i) # the key
        decrypt(key, "log.py.encrypted")
        if read_file()[k_idx] == 'i': # import the guessed word
            print "Found", hex(ord(key[k_idx]))

this will reveal that the first bytes are import d... and the log.py is

import datetime
LOG_DIR = 'logs'
class Logger():
    @staticmethod
def log(username, password):
    basename = '%s/%s_%s' % (LOG_DIR, str(datetime.date.today()), username)
    with open(basename, 'a+') as fd:
        fd.write('[%s] Login with password %s\n' % (str(datetime.datetime.today()), password))

using this we access

zeus:updator # curl http://updator.challs.nuitduhack.com/logs/2015-04-04_admin
[2015-04-04 18:49:48.839448] Login with password Mpt2P4sse2Ouf
[2015-04-04 18:49:54.044382] Login with password Mot2P4sse2Ouf

login with the mentioned password to get the flag.