Revese Engineering

Malayo

Points 100

Solves 101

Under the sound of the wild, The Malayopython appears


So this challenge provides us with a .pyc file that we can decompile by using pylingual or other decompilers. After decompiling by pylingual, we get some like reconstructed bytecode, which is some of function is failing to decompile properly. However, we can still use the original bytecode to reconstruct by using ChatGPT or other LLMs. After reconstructing, we get the following code:

def Uu(f):
    try:
        if f[0] != 102: return False    # 'f'
        if f[1] != 108: return False    # 'l'
        if f[2] != 97:  return False    # 'a'
        if f[3] != 103: return False    # 'g'
        if f[4] != 123: return False    # '{'
        if f[5] != 85:  return False    # 'U'
        if f[6] != 95:  return False    # '_'
        return True
    except:
        return False

def uU(f):
    try:
        # f[7]*7 - 525 == 0
        if f[7] * 7 != 525: return False   # f[7] = 75 ('K')

        # f[8]*17 - 10 XOR 933 == 252
        if ((f[8] * 17 - 10) ^ 933) != 252: return False

        # three squared-diff sums = 0
        if ((f[9] - f[8])**2 +
            (f[28] - f[8])**2 +
            (f[31] - f[8])**2) != 0:
            return False

        # (f[10] -112)*1000 + (f[11] - 95) == 0
        if (f[10] - 112) * 1000 + (f[11] - 95) != 0:
            return False

        # big multi squared diff for indices 14,19,23,26,32 relative to f[11]
        base = f[11]
        s11 = ((f[14] - base)**2 +
               (f[19] - base)**2 +
               (f[23] - base)**2 +
               (f[26] - base)**2 +
               (f[32] - base)**2)
        if s11 != 0: return False

        # weird invert check
        if ((~f[12] + 0) ^ -1) - 49 != 0: return False

        # f[33] - f[12] == 0
        if f[33] != f[12]: return False

        # (f[13]+10)*5 XOR 95 == 553
        if ((f[13] + 10) * 5) ^ 95 != 553: return False

        # (f[15] - f[13])**2 + (f[18] - f[13])**2 + (f[34] - f[13])**2 == 0
        if ((f[15] - f[13])**2 +
            (f[18] - f[13])**2 +
            (f[34] - f[13])**2) != 0:
            return False

        return True
    except:
        return False

def UU(f):
    try:
        if f[16]*2 - 12 - 196 != 0: return False    # f[16] = 104 'h'
        if (f[17] << 0) ^ 0 != 52: return False     # f[17] = '4'
        if ((f[21] - f[17])**2 + (f[29] - f[17])**2) != 0: return False
        if 3*f[20] - 357 != 0: return False         # f[20] = 119 'w'
        if f[22] - f[20] - 2 != 0: return False
        if (f[24] ^ 0) - 48 != 0: return False
        if f[25] - (f[22] - 7) != 0: return False
        if (f[27] - 76) * (f[30] - 118) != 0: return False
        return True
    except:
        return False

def UUu(f):
    try:
        flag_content = "".join(chr(c) for c in f[7:35])

        key_part = flag_content[:17]
        key = hashlib.sha256(key_part.encode()).digest()

        plaintext = flag_content[17:]

        expected_ciphertext = base64.b64decode("jNtv1ielcDMRvnTLzB2hrg==")
        iv = b"PWNSEC_CHALLENGE"

        cipher = AES.new(key, AES.MODE_CBC, iv)
        padded = pad(plaintext.encode(), AES.block_size)
        if cipher.encrypt(padded) != expected_ciphertext:
            return False

        if sum(f) != 3217:
            return False

        if f[35] != 125: return False   # '}'

        return True
    except:
        return False

def you(user_flag):
    if len(user_flag) != 36:
        return False
    f = list(map(ord, user_flag))
    if not Uu(f): return False
    if not uU(f): return False
    if not UU(f): return False
    if not UUu(f): return False
    return True

Now, we can see that the flag length is 36 characters, and there are four functions that validate different parts of the flag. We can use Z3 to solve the constraints given in these functions. Here’s a Z3 script that does just that:

from z3 import *

FLAG_LEN = 36

f = [BitVec(f'f[{i}]', 32) for i in range(FLAG_LEN)]
s = Solver()

for b in f:
    s.add(b >= 0, b <= 255)

# Uu
s.add(f[0] == 102)
s.add(f[1] == 108)
s.add(f[2] == 97)
s.add(f[3] == 103)
s.add(f[4] == 123)
s.add(f[5] == 85)
s.add(f[6] == 95)

# uU
s.add(f[7] * 7 == 525)
s.add(((f[8] * 17 - 10) ^ BitVecVal(933, 32)) == 252)

# s.add((f[9] - f[8]) * (f[9] - f[8]) +
#       (f[28] - f[8]) * (f[28] - f[8]) +
#       (f[31] - f[8]) * (f[31] - f[8]) == 0)
s.add(f[9] == f[8])
s.add(f[28] == f[8])
s.add(f[31] == f[8])

s.add((f[10] - 112) * 1000 + (f[11] - 95) == 0)

base = f[11]
# s.add((f[14] - base)*(f[14] - base) +
#       (f[19] - base)*(f[19] - base) +
#       (f[23] - base)*(f[23] - base) +
#       (f[26] - base)*(f[26] - base) +
#       (f[32] - base)*(f[32] - base) == 0)
s.add(f[14] == base)
s.add(f[19] == base)
s.add(f[23] == base)
s.add(f[26] == base)
s.add(f[32] == base)

# s.add(((~f[12]) ^ BitVecVal(-1, 32)) - 49 == 0)
s.add(f[12] == 49)
s.add(f[33] == f[12])

s.add((((f[13] + 10) * 5) ^ BitVecVal(95, 32)) == 553)

# s.add((f[15] - f[13])*(f[15] - f[13]) +
#       (f[18] - f[13])*(f[18] - f[13]) +
#       (f[34] - f[13])*(f[34] - f[13]) == 0)
s.add(f[15] == f[13])
s.add(f[18] == f[13])
s.add(f[34] == f[13])

# UU
s.add(f[16] * 2 - 12 - 196 == 0)
# s.add((f[17] << 0) == 52)
s.add(f[17] == 52)

# s.add((f[21] - f[17])*(f[21] - f[17]) +
#       (f[29] - f[17])*(f[29] - f[17]) == 0)
s.add(f[21] == f[17])
s.add(f[29] == f[17])

s.add(3 * f[20] == 357)
s.add(f[22] == f[20] + 2)
s.add((f[24] ^ BitVecVal(0, 32)) == 48)
s.add(f[25] == f[22] - 7)

s.add(Or(f[27] == 76, f[30] == 118))

# UUu
s.add(Sum([ZeroExt(24, b) for b in f]) == 3217)
s.add(f[35] == ord('}'))

if s.check() == sat:
    m = s.model()
    flag = ''.join(chr(m[f[i]].as_long()) for i in range(FLAG_LEN))
    print("Flag:", flag)
else:
    print("No solution found.")
Malayo Flag: flag{U_K33p_1t_th4t_w4y_0r_L34v3_1t}