TCP1P 2023 – Lock the Lock

13 October 2023 – Written by Valentin Huber – in ctf, decompile, pyc, python, rev, and tree


Challenge

Unlock the Unlocked

chall.pcy

Solution

We are presented with a pyc file. It is a compiled binary of a python script, so the first step is to decompile it. This turned out to be harder than expected, but Decompyle++ worked and produced this python script.

At this point I tried to get this to run by deleting all the GUI instructions and replacing them with fixed calls. But I didn’t manage to achieve this and instead opted to write a second implementation that mirrored the logic I observed in the original. To do this, I first had to understand what the code does. The snippets below are from the decompiled code and still have the errors in them.

A few observations

Flag and decryption

FLAG = [ 90, 19, 95, 37, 58, 144, 131, 222, 253, 162, 107, 96, 98, 128, ...

# ---

def decrypt(key, plain):
    dec = ''
    key = long_to_bytes(int(''.join(key), 2))
    for i in range(len(key)):
        dec += chr(key[i] ^ plain[i])
    return dec

Random

import random
turn = 0
random.seed(199)

# ---

num = (lambda .0: [ i for i in .0 ])(range(1, 1001))
init = num.copy()
random.shuffle(init)
random.shuffle(init)
random.shuffle(init)

# ---

target = init.copy()
target.remove(troot.data)
random.shuffle(target)

Trees

class Node:
    __qualname__ = 'main.<locals>.Node'
    
    def __init__(self, data):
        self.data = data
        self.l = None
        self.r = None
        self.height = 1
def insert(self = None, root = None, key = None):
    if not root:
        return Node(key)
    if None < root.data:
        root.l = self.insert(root.l, key)
    else:
        root.r = self.insert(root.r, key)
    root.height = 1 + max(self.getHeight(root.l), self.getHeight(root.r))
    b = self.getBal(root)
    if b > 1 and key < root.l.data:
        return self.rRotate(root)
    if None < -1 and key > root.r.data:
        return self.lRotate(root)
    if None > 1 and key > root.l.data:
        root.l = self.lRotate(root.l)
        return self.rRotate(root)
    if None < -1 and key < root.r.data:
        root.r = self.rRotate(root.r)
        return self.lRotate(root)

Figuring out what is happening

Submit

def submit(root = None):
    global turn, turn
    
    try:
        u = inp.get()
        if not tr.check(0, root, u, target[turn]):
            turn = 0
            validatedkey.clear()
        else:
            showinfo('W rizz', 'Correct!', **('title', 'message'))
            validatedkey.append(u)
            inp.delete(0, 'end')
            turn += 1
        if turn == len(target):
            showinfo('dayummm', decrypt(validatedkey, FLAG), **('title', 'message'))
    finally:
        pass
    except SyntaxError:
        showerror('Error', 'Invalid input!', **('title', 'message'))

Check

def check(self, state, root, n, x):
    state = root
    for i in n:
        if i == '0':
            state = state.l
        elif i == '1':
            state = state.r
        if state == None:
            showwarning('sike', 'Error invalid node!\nResetting level...', **('title', 'message'))
            return False
        if state.data == x:
            return True
        None('wuat de hell', 'Wrong answer! \nResetting level...', **('title', 'message'))
        return False
        return None

Solving the challenge

I pulled a working implementation of an AVL tree in python from the internet. It’s a fairly standard data structure so implementations are easy to find.

def getPath(self, value, root):
    if not root or value == root.data:
        return ''
    if value > root.data:
        return '1' + self.getPath(value, root.r)
    else:
        return '0' + self.getPath(value, root.l)
# Setting up the random array
init = list(range(1, 1001))
random.seed(199)
random.shuffle(init)
random.shuffle(init)
random.shuffle(init)

# Creating the tree and feeding it the shuffled array.
tr = AVLTree()
root = None
for i in init:
    root = tr.insert(root, i)

# Creating target
target = init.copy()
target.remove(root.data)
random.shuffle(target)
# Calculating paths
input = [list(tr.getPath(e, root)) for e in target]
# Calling check repeatedly
turn = 0
validatedKey = []

while True:
    if not tr.check(0, root, input[turn], target[turn]):
        print('error at', turn)
        exit(1)
    else:
        validatedKey.append(input[turn])
        turn += 1
    if turn == len(target):
        print(decrypt([''.join(e) for e in validatedKey], FLAG))
        exit(0)

You can download the full solver script here.

The flag

Your remarkable accomplishment is a testament to your unwavering determination, relentless pursuit of excellence, and unwavering spirit. You have conquered every obstacle with sheer grit, transforming challenges into stepping stones towards triumph. Your efforts, unwavering focus, and boundless passion have propelled you to new heights of success, leaving an indelible mark on the world. Your commitment to excellence serves as an inspiration to all who have had the privilege of witnessing your remarkable journey. As you stand at this pinnacle of achievement, take a moment to reflect on the incredible journey that brought you here. Embrace this milestone as a testament to your unyielding dedication and the incredible potential that resides within you. Here’s the flag : TCP1P{where_the_skies_are_blue_to_see_you_once_again}. The world eagerly awaits the remarkable contributions you will undoubtedly make in the future. Your accomplishment has not only made us proud but also redefined the boundaries of what is possible

TCP1P{where_the_skies_are_blue_to_see_you_once_again}