Py[tc]on


Pour ce challenge, on nous donne un fichier challenge :

└─[$] <> file challenge 
challenge: Byte-compiled Python module for CPython 3.11, timestamp-based, .py timestamp: Thu Nov 28 19:48:37 2024 UTC, .py size: 1978 bytes

Grâce à ça, on peut déterminer le fait qu’on soit sur des instructions Python compilées.

Après une petite recherche sur internet, j’ai pu trouver l’outil : pycdc qui permet de décompiler les fichiers .pyc en .py.

On obtient donc :

└─[$] <> ./pycdc.x86_64 ./challenge
# Source Generated with Decompyle++
# File: challenge (Python 3.11)

import base64
import zlib
import marshal
import random
upup = None

def compute_rand(string):
    global upup
    (a, b, c) = (42, 3.14, -7)
    result = (a ** 2 + b ** 0.5) * c % 13 + (a & b.__trunc__())
    result = ((b / 3.14) ** (c + 10)).__round__(5)
Unsupported opcode: RETURN_GENERATOR (109)
    final = (lambda .0: pass# WARNING: Decompyle incomplete
)(range(1, 10)()) + result
    upup = zlib.decompress(base64.b64decode(string))
    return random.randint(1, 100)


def get_flag(upup):
    data = 'AMSI{aGFoYWhhX25vdF9kb25lX3lldF9tYW4=}'.encode('utf-8')
Unsupported opcode: RETURN_GENERATOR (109)
    encoded = (lambda .0: pass# WARNING: Decompyle incomplete
)(enumerate(data)())
Unsupported opcode: RETURN_GENERATOR (109)
    decoded = (lambda .0: pass# WARNING: Decompyle incomplete
)(enumerate(encoded)())
    upup = 7
    return base64.b64decode(data.strip(b'AMSI).strip(b'))


def loader():
    global upup
    print('Starting the challenge...')
    compute_rand('eJxtkc9LG0EUx2cTI9hdUvx56EFGb5GSFVEwKgVpC3qwggl4XMbZZ7JkM7PMvD2sGujRY28eSq8i9J/ZlBxkwZP0L/C2J2f9rTi892Xee583M4+5Ji/WiPFJ4zdHRs6Ib/UJWs9lv3Ruor9Pmb51YvnlbqnYqyksvyBHTshv8ucNbxGfNMkrrvIeURv9kX/a3GluH0cJ9wLtiUX0sLPc8raWld/P578LBEWxA7QLCUVJYxFK3r3LHIasvUazD7sRiCZo1oN85qtUCjjO0dYDQAO9RvPpfSVFuzijTlsqoazNAjFXK2WVQEQxZpVIBQJNbGvgCtArWrOy4XUxM6X5ituRPXC7LAzcb5LHPRCoXfNqKVwEjW4bBCiG4B0kRrj0oR4l2UfGMWahdxgLjoEU+eITB4KrJELw7+ENMxcL9Zf6m44xc7/eM/KTXM4sndpX1cl0anVQbQyrjdPypV399fmfPZvas1fOeDqxerFuxNjAaQydRvpo/4tibeAsDJ2F9NFuio+4BVBtm2s=')
    jk = upup
    exec(marshal.loads(jk))
    upup = None


def confuse_decompiler():
Unsupported opcode: JUMP_BACKWARD (226)
    junk_code = [
        (lambda : print("Analyzing this won't help.")),
        (lambda : random.choice([
'Flag not here!',
'Still nothing here...'])),
        (lambda : get_flag(upup))]
# WARNING: Decompyle incomplete


def main():
    print('Welcome to the obfuscated challenge!')
    confuse_decompiler()
    loader()

if __name__ == '__main__':
    main()
    return None

On peut voir deux fonctions intéressantes :

  1. loader qui semble faire quelque chose avec une longue string
  2. compute_rand qui est requise par loader.

Il ne reste plus qu’à refactorer le code :

import base64
import zlib
import marshal
import random
upup = None

def compute_rand(string):
    global upup
    (a, b, c) = (42, 3.14, -7)
    result = (a ** 2 + b ** 0.5) * c % 13 + (a & b.__trunc__())
    result = ((b / 3.14) ** (c + 10)).__round__(5)
    upup = zlib.decompress(base64.b64decode(string))
    return random.randint(1, 100)

def loader():
    global upup
    print('Starting the challenge...')
    compute_rand('eJxtkc9LG0EUx2cTI9hdUvx56EFGb5GSFVEwKgVpC3qwggl4XMbZZ7JkM7PMvD2sGujRY28eSq8i9J/ZlBxkwZP0L/C2J2f9rTi892Xee583M4+5Ji/WiPFJ4zdHRs6Ib/UJWs9lv3Ruor9Pmb51YvnlbqnYqyksvyBHTshv8ucNbxGfNMkrrvIeURv9kX/a3GluH0cJ9wLtiUX0sLPc8raWld/P578LBEWxA7QLCUVJYxFK3r3LHIasvUazD7sRiCZo1oN85qtUCjjO0dYDQAO9RvPpfSVFuzijTlsqoazNAjFXK2WVQEQxZpVIBQJNbGvgCtArWrOy4XUxM6X5ituRPXC7LAzcb5LHPRCoXfNqKVwEjW4bBCiG4B0kRrj0oR4l2UfGMWahdxgLjoEU+eITB4KrJELw7+ENMxcL9Zf6m44xc7/eM/KTXM4sndpX1cl0anVQbQyrjdPypV399fmfPZvas1fOeDqxerFuxNjAaQydRvpo/4tibeAsDJ2F9NFuio+4BVBtm2s=')
    jk = upup
    print(jk)
    exec(marshal.loads(jk))
    upup = None

loader()

Et hop, dans la console, on a le flag!

└─[$] <git:(feat/start_create_amsi_ctf_writeup*)> python test.py
Starting the challenge...
b'\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x13\x00\x00\x00\xf3z\x00\x00\x00\x97\x00d\x01}\x00t\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00}\x01|\x01d\x03k\x02\x00\x00\x00\x00r\x14t\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x00\x9b\x00\x9d\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x00S\x00t\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x05\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x00S\x00)\x06N\xfa\x1aAMSI{pyc_is_n0t_th4T_H4rd}\xfa"Enter the key to unlock the flag: \xda\nOpenSesame\xfa\x16Correct! The flag is: \xfa\x15Wrong key. Try again!)\x02\xda\x05input\xda\x05print)\x02\xda\x0bsecret_flag\xda\x03keys\x02\x00\x00\x00  \xfa5/home/kali/Documents/pycon/test/generate_byte_code.py\xda\x0factual_function\xfa0generate_encrypted_code.<locals>.actual_function\t\x00\x00\x00sR\x00\x00\x00\x80\x00\xd8\x162\x88\x0b\xdd\x0e\x13\xd0\x148\xd1\x0e9\xd4\x0e9\x88\x03\xd8\x0b\x0e\x90,\xd2\x0b\x1e\xd0\x0b\x1e\xdd\x0c\x11\xd0\x128\xa8;\xd0\x128\xd0\x128\xd1\x0c9\xd4\x0c9\xd0\x0c9\xd0\x0c9\xd0\x0c9\xe5\x0c\x11\xd0\x12)\xd1\x0c*\xd4\x0c*\xd0\x0c*\xd0\x0c*\xd0\x0c*\xf3\x00\x00\x00\x00'
Enter the key to unlock the flag: 

AMSI{pyc_is_n0t_th4T_H4rd}