Airlock Breakout [RE]

4 minute read

JavaScript flag checking functions solved using z3-solver. First RE challenge from STANDCON 2021.

Challenge Description

I’ve stolen the Nova Core, but it seems like I’ve been locked in within these 3 airlock doors. I’m not sure how much longer I can hold them off Please help me crack the password so that I can be on my merry way!

http://20.198.209.142:55030O

Solution

Navigating to the webpage, and immediately looking at the source tab, there’s only one JS file, non-obfuscated, clearly containing the flag checking functions.

You can get the script here

The first function is pretty self-explanatory. The second one is system of equations, I’ll be using z3 in python to solve for it. Get a quickstart here

Considering each character as an 8-bit vector, we can construct these quite straightforward.

s = Solver()
part = [BitVec(str(i), 8) for i in range(4)]
s.add(part[0] + 2*part[1] - 3*part[2] + 4*part[3] == 354)
s.add(2*part[0] + 2*part[1] - 2*part[2] + 3*part[3] == 383)
s.add(3*part[0] - 2*part[1] - 4*part[2] + part[3] == -106)
s.add(2*part[0]*part[0]*part[0] + 3*part[1]*part[1] - 2*part[2]*part[2]*part[2] - 4*part[3]*part[3] == 59284)
if s.check() == sat:
    result = s.model()
    return ''.join([chr(int(str(result[p]))) for p in part])

The third function was also quite easy (you just construct everything as it is checked, and throw it into z3’s solver). Only thing interesting is the magic value and the length of the final part of the flag. We can fully utilize the power of z3 and just construct magic as it is, as for the length, I tested 6, which gave unsat, which I then tried 7 and it worked.

s = Solver()
str3 = [BitVec("3-%d" % i, 32) for i in range(3)]
str4 = [BitVec("4-%d" % i, 32) for i in range(2)]
str5 = [BitVec("5-%d" % i, 32) for i in range(3)]
str6 = [BitVec("6-%d" % i, 32) for i in range(7)]

magic = BitVecVal(0, 32) # constant number
for strs in [str3, str4, str5, str6]:
    s.add(And([\
        Or([ strs[i] == ord(ch) for ch in "01347CFHKLNRUX" ]) for i in range(len(strs))\
    ]))

    for char in strs:
        magic = (magic << 3) + char - magic

s.add(str3[0] == ord("0"))
s.add(str5[0] == ord("7"))
s.add(str3[0] + str5[0] - str6[0] == 51)
s.add(str3[0] == str4[0])
s.add(str3[2] - str3[1] == -30)
s.add((str4[1] / 7) * str5[1] == 720)
s.add(str5[0] + str5[2] == 106)
s.add(str6[3] - str6[2] == -6)
s.add(str6[2] * str6[4] == 3936)
s.add(magic == (-859895409 & 0xffffffff))

if s.check() == sat:
    result = s.model()
    return '_'.join([''.join([ chr(int(str(result[p]))) for p in strs]) for strs in [str3, str4, str5, str6]])

else:
    print("unsat")

Then we just piece everything together and get the flag.

Final Script

def part1():
    return ''.join(map(chr, [51, 74, 51, 67, 55]))

from z3 import *

def part2():
    s = Solver()
    part = [BitVec(str(i), 8) for i in range(4)]
    s.add(part[0] + 2*part[1] - 3*part[2] + 4*part[3] == 354)
    s.add(2*part[0] + 2*part[1] - 2*part[2] + 3*part[3] == 383)
    s.add(3*part[0] - 2*part[1] - 4*part[2] + part[3] == -106)
    s.add(2*part[0]*part[0]*part[0] + 3*part[1]*part[1] - 2*part[2]*part[2]*part[2] - 4*part[3]*part[3] == 59284)
    if s.check() == sat:
        result = s.model()
        return ''.join([chr(int(str(result[p]))) for p in part])


def part3():
    s = Solver()
    str3 = [BitVec("3-%d" % i, 32) for i in range(3)]
    str4 = [BitVec("4-%d" % i, 32) for i in range(2)]
    str5 = [BitVec("5-%d" % i, 32) for i in range(3)]
    str6 = [BitVec("6-%d" % i, 32) for i in range(7)]

    magic = BitVecVal(0, 32) # constant number
    for strs in [str3, str4, str5, str6]:
        s.add(And([\
            Or([ strs[i] == ord(ch) for ch in "01347CFHKLNRUX" ]) for i in range(len(strs))\
        ]))

        for char in strs:
            magic = (magic << 3) + char - magic

    s.add(str3[0] == ord("0"))
    s.add(str5[0] == ord("7"))
    s.add(str3[0] + str5[0] - str6[0] == 51)
    s.add(str3[0] == str4[0])
    s.add(str3[2] - str3[1] == -30)
    s.add((str4[1] / 7) * str5[1] == 720)
    s.add(str5[0] + str5[2] == 106)
    s.add(str6[3] - str6[2] == -6)
    s.add(str6[2] * str6[4] == 3936)
    s.add(magic == (-859895409 & 0xffffffff))

    if s.check() == sat:
        result = s.model()
        return '_'.join([''.join([ chr(int(str(result[p]))) for p in strs]) for strs in [str3, str4, str5, str6]])
    else:
        print("unsat")

print("STC{%s_%s_%s}" % (part1(), part2(), part3()))

Flag: STC{3J3C7_7H3M_0U7_0F_7H3_41RL0CK}