Skip to main content
  1. CTF Writeups/
  2. CSAW Quals 2025/

Space Portal

·
rev 474 pts protocol trauma
subzcuber
Author
subzcuber
i like to imagine i’m funny

There was so much going on this challenge and it got so annoying so fast. I’m just going to clean up and drop my solve script. Please don’t ask me how long this chal took, and definitely don’t ask me how many silly mistakes i made in that time

actually nvm i’m not going to clean my solve script, here’s my baby in all her glory

solve.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
spaceship_name = b"my_ship"
access_code = b"my_code"
md5_hash = hashlib.md5(spaceship_name + access_code).digest()

des = DES.new(md5_hash[0:8], DES.MODE_ECB)
enc = DES.new(md5_hash[0:8], DES.MODE_ECB)

def extract_hash(packet):
    hash = bytes(packet[0x08:0x18])
    check = packet[0:0x08] + b"\x00"*0x10 + des.decrypt(packet[0x18:])
    if hash == hashlib.md5(check).digest():
        log.success(f"hash check PASS")
        log.warning(f"Packet: {check.hex()}")
    else:
        log.warning(f"hash check FAIL")
        log.warning(f"packet: {packet.hex()}")
        log.warning(f"Packet: {check.hex()}")
        log.warning(f"hash: {hash.hex()}")
        log.warning(f"Hash: {hashlib.md5(check).hexdigest()}")

def reg_spaceship():
    io.recvuntil(b"spaceship name: ")
    io.sendline(spaceship_name)
    io.recvuntil(b"access code: ")
    io.sendline(access_code)
    io.recvuntil(b"enrolled!")
    log.info(f"{md5_hash=}")

def auth(message):
        io.recvuntil(b"entry...")
        f1 = 0x01
        f2 = 0x05
        f3 = 0x01
        f4 = 0
        f5 = len(message)
        f6 = libc.rand() & 0xff
        payload = flat(
            p8(f1),
            p8(f2),
            p8(f3),
            p8(0x00),
            p8(f4),
            p8(0x00),
            p8(f5),
            p8(f6),
            cyclic(0x10),
            message
        )
        io.send(payload)
        log.info(f"{payload=}")
        io.recvline()

def calc_entropy(sign_bytes):
    log.info(f"{sign_bytes.hex()=}")
    entropy = bytearray([(~i & 0xff) for i in des.decrypt(sign_bytes)[::-1]])
    log.info(f"{entropy.hex()=}")
    return entropy

def sign(entropy):
    io.recvuntil(b"Validating signature...")
    f1 = 0x02
    f2 = 0x4c
    f3 = 0x01
    f4 = 2
    f5 = 8
    f6 = libc.rand() & 0xff
    msg = (bytearray([i^f6 for i in entropy]))
    payload = flat(
        p8(f1),
        p8(f2),
        p8(f3),
        p8(0x00),
        p8(f4),
        p8(0x00),
        p8(f5),
        p8(f6),
        b"\x00"*0x10,
        msg
    )
    log.warning(f"packet: {payload.hex()}")
    sign_hash = hashlib.md5(payload).digest()
    payload = flat(
        p8(f1),
        p8(f2),
        p8(f3),
        p8(0x00),
        p8(f4),
        p8(0x00),
        p8(f5),
        p8(f6),
        sign_hash,
        enc.encrypt(msg)
    )
    log.warning(f"Packet: {payload.hex()}")
    extract_hash(payload)
    log.info(f"{payload=}")
    if entropy == bytearray(i^f6 for i in des.decrypt(payload[0x18:])):
        log.success(f"entropy check")
    else:
        log.warning("entropy fail")
    io.send(payload)

delay = 0
authenticated = False
while delay <= 10 and not authenticated:
    log.info(f"{delay=}")
    
    io = start()
    time_x = int(time.time() + delay)
    libc.srand(time_x)
    log.info(f"seeding with time: {time_x}")
    delay += 1

    io.recvuntil(b"Welcome to the intelligent portal in space!")
    reg_spaceship()
    io.recvuntil(b"special way!")

    auth(b"")
    msg = io.recvline()
    log.info(msg)
    if b"authenticated" in msg:
        authenticated = True
        log.success(f"authenticated!!!")
    else:
        log.warning(f"not authorised :(")
        io.close()
        continue

    io.recvuntil(b"Leaking signature...")
    io.recvline()

    signature = io.recvn(0x20)
    log.info([hex(i) for i in signature])
    if signature[7] == (libc.rand() & 0xff):
        log.success("seed matches!")
    else:
        log.warning("seed doesn't match")
    extract_hash(signature)
    
    entropy = calc_entropy(signature[:0x18])
    log.info(f"{entropy=}")

    sign(entropy)

    io.recvuntil(b"coordinate...\n")
    flag_packet = io.recvuntil(b"Coordinate", drop=True)
    log.info([hex(i) for i in flag_packet])
    
    extract_hash(flag_packet)
    dec = DES.new(entropy, DES.MODE_ECB)
    log.success(dec.decrypt(flag_packet[0x18:]))

    io.recvline()
    io.interactive()

fuck it i don’t think this is even the working version idfc i hate this chal and myself so much

Reply by Email

Related

Whitespace Compiler
rev 463 pts vm go patch whitespace
this was so so so fun
Rev From the Past
rev 100pts 118 solves dosbox 8086
PlasticShield
rev 100pts aes hash
should not have taken that long