uiuctf-2024 - Without a Trace

This challenge was solved by my teammate Andrei for the UIUCTF 2024 CTF competition.

The challenge’s description is:

Gone with the wind, can you find my flag?

ncat --ssl without-a-trace.chal.uiuc.tf 1337
import numpy as np
from Crypto.Util.number import bytes_to_long
from itertools import permutations
from SECRET import FLAG


def inputs():
    print("[WAT] Define diag(u1, u2, u3. u4, u5)")
    M = [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
    ]
    for i in range(5):
        try:
            M[i][i] = int(input(f"[WAT] u{i + 1} = "))
        except:
            return None
    return M

def handler(signum, frame):
    raise Exception("[WAT] You're trying too hard, try something simpler")

def check(M):
    def sign(sigma):
        l = 0
        for i in range(5):
            for j in range(i + 1, 5):
                if sigma[i] > sigma[j]:
                    l += 1
        return (-1)**l

    res = 0
    for sigma in permutations([0,1,2,3,4]):
        curr = 1
        for i in range(5):
            curr *= M[sigma[i]][i]
        res += sign(sigma) * curr
    return res

def fun(M):
    f = [bytes_to_long(bytes(FLAG[5*i:5*(i+1)], 'utf-8')) for i in range(5)]
    F = [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
    ]
    for i in range(5):
        F[i][i] = f[i]

    try:
        R = np.matmul(F, M)
        return np.trace(R)

    except:
        print("[WAT] You're trying too hard, try something simpler")
        return None

def main():
    print("[WAT] Welcome")
    M = inputs()
    if M is None:
        print("[WAT] You tried something weird...")
        return
    elif check(M) == 0:
        print("[WAT] It's not going to be that easy...")
        return

    res = fun(M)
    if res == None:
        print("[WAT] You tried something weird...")
        return
    print(f"[WAT] Have fun: {res}")

if __name__ == "__main__":
    main()

Analisys

If we enter the command that is given to us in the description, we’ll get prompted for every element of the diagonal of a matrix filled with 0’s.

After we define the diagonal, our matrix will get verified by getting it’s determinant calculated using the check function in the server script.

def main():
    print("[WAT] Welcome")
    M = inputs()
    if M is None:
        print("[WAT] You tried something weird...")
        return
    elif check(M) == 0:
        print("[WAT] It's not going to be that easy...")
        return

We can see that in the main function, if the determinant is 0, we’ll get kicked out from the server.

Now, the determinant will be 0 if any of the values that we input is 0. So we’ll avoid doing that :)

def fun(M):
    f = [bytes_to_long(bytes(FLAG[5*i:5*(i+1)], 'utf-8')) for i in range(5)]
    F = [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
    ]
    for i in range(5):
        F[i][i] = f[i]

    try:
        R = np.matmul(F, M)
        return np.trace(R)

    except:
        print("[WAT] You're trying too hard, try something simpler")
        return None

Looking at the fun function, we see that the flag is being split into 5 chuncks of bytes and then, every chunk is being transformed into a large number. After that, the FLAG matrix is multiplied with the matrix defined by us, returning the sum of the elements that are on the first diagonal, so that gives us some playground.

alt text

If we type only 1’s to the server, we’ll get the sum of the chunks of the flag.

Now, using the same thought process as in the picture above, but this time using one 1 and the rest of values being -1’s, we can create a system of 2 ecuations, resulting in us getting one chunk’s value.

alt text

Using the thought process presented above, we can get every chunks value. Then we will use this script in order to get the flag:

from Crypto.Util.number import long_to_bytes

a = 504280474484
b = 440157893172
c = 426031081311
d = 163852545397
e = 465806107005

strA = long_to_bytes(a).decode()
strB = long_to_bytes(b).decode()
strC = long_to_bytes(c).decode()
strD = long_to_bytes(d).decode()
strE = long_to_bytes(e).decode()

str = strA + strB + strC + strD + strE

print(str)

Flag

uiuctf{tr4c1ng_&&_mult5!}