Logo
Overview
[SunshineCTF 2025] bits of space writeup

[SunshineCTF 2025] bits of space writeup

September 28, 2025
4 min read

TL;DR

캡처한 AES CBC 패킷의 IV를 조정해 subscriber identifier를 0xdeadbabe로 바꾸면 restricted relay가 플래그를 전달한다 원본 패킷의 첫 block이 후보 identifier를 포함한다는 가정 아래 guess XOR deadbabe 값을 IV에 XOR해 원하는 평문을 만든다

Analysis

  • voyager.bin은 48바이트 AES CBC 패킷으로 보이며 첫 16바이트가 IV, 이후 두 block이 ciphertext다
5e 60 38 3e 8e bb ee 04 aa 00 a3 be ad 86 7e f9
ed f4 f6 a2 fb 24 1b 9e 21 b4 92 6b 51 34 e3 d2
c8 6a 56 14 43 d3 12 ce b0 33 8c 66 48 81 49 6a

첫 16바이트 IV0IV_05e60383e8ebbee04aa00a3bead867ef9, 나머지 32바이트는 C1C_1, C2C_2이다. AES CBC 복호 흐름은 P1=DK(C1)IV,P2=DK(C2)C1P_1 = D_K(C_1) \oplus IV,\quad P_2 = D_K(C_2) \oplus C_1 형태이므로 IVIV를 조정하면 P1P_1의 대응 바이트를 원하는 값으로 바꿀 수 있다.

#!/usr/bin/env python3
import sys
import time
from binascii import hexlify
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto.Random import get_random_bytes
from ctf_secrets import MESSAGE
KEY = get_random_bytes(16)
NONCE = get_random_bytes(8)
WELCOME = '''
,-.
/ \
: \ ....*
| . .- \-----00''
: . ..' \''//
\ . . \/
\ . ' . NASA Deep Space Listening Posts
, . \ \ ~ Est. 1969 ~
,|,. -.\ \
'.|| `-...__..-
| | "We're always listening to you!"
|__|
/||\\
//||\\
// || \\\
__//__||__\\__
'--------------'
'''
def main():
# Print ASCII art and intro
sys.stdout.write(WELCOME)
sys.stdout.flush()
time.sleep(0.5)
sys.stdout.write("\nConnecting to remote station")
sys.stdout.flush()
for i in range(5):
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(0.5)
sys.stdout.write("\n\n== BEGINNING TRANSMISSION ==\n\n")
sys.stdout.flush()
C = 0
while True:
ctr = Counter.new(64, prefix=NONCE, initial_value=C, little_endian=False)
cipher = AES.new(KEY, AES.MODE_CTR, counter=ctr)
ct = cipher.encrypt(MESSAGE)
sys.stdout.write("%s\n" % hexlify(ct).decode())
sys.stdout.flush()
C += 1
time.sleep(0.5)
if __name__ == "__main__":
main()
  • AES CTR을 사용하지만 캡처 패킷은 CBC 구조다. 동일 이벤트에서 CTR과 CBC가 혼재되어 캡처 voyager.bin의 암호 모드는 별도 구현이라고 판단했다
  • MESSAGE 구성은 subscriber identifier를 추출한다

Exploit

  1. 캡처된 voyager.bin에서 IV와 두 개의 ciphertext block을 분리하고 AES CBC 복호식을 이용해 목표 평문을 만들기 위해 필요한 XOR 오프셋을 계산했다.
  2. 서비스가 첫 4바이트를 little endian 32-bit identifier로 비교한다고 판단해 수집한 identifier 후보를 리틀엔디언으로 변환하고 목표값 0xdeadbabe0xdeadbabe와 XOR해 delta를 얻었다.
  3. delta를 IV 첫 4바이트에만 적용하고 ciphertext는 원본 그대로 유지해 무결성 검사가 없다는 점을 악용했다.
  4. forge된 패킷을 원격 서비스에 제출해 응답을 관찰하고 success 메시지와 flag 포맷 문자열 포함 여부로 성공 여부를 판별했다.
  5. 첫 후보 실패에 대비해 후보 목록을 순회하며 동일한 delta 계산과 전송 과정을 반복하도록 설계했다.
  • CBC 첫 block 평문은 P1=DK(C1)IVP_1 = D_K(C_1) \oplus IV 관계를 따르므로 IV를 변경하면 앞부분 평문을 원하는 값으로 만들 수 있다.
  • 성공 응답은 restricted relay 안내와 flag 문자열을 포함한다.
from pwn import *
import struct
import re
# original exploit submission
context.log_level = 'debug'
VOYAGER = """
5E 60 38 3E 8E BB EE 04 AA 00 A3 BE AD 86 7E F9
ED F4 F6 A2 FB 24 1B 9E 21 B4 92 6B 51 34 E3 D2
C8 6A 56 14 43 D3 12 CE B0 33 8C 66 48 81 49 6A
"""
pkt_orig = bytes.fromhex(" ".join(VOYAGER.split()))
def forge_with_guess(pkt, guess_id):
iv = bytearray(pkt[:16])
c = pkt[16:]
delta = bytes(x ^ y for x, y in zip(struct.pack("<I", guess_id), struct.pack("<I", 0xdeadbabe)))
for i in range(4):
iv[i] ^= delta[i]
return bytes(iv) + c
def try_send(pkt):
p = remote('sunshinectf.games', 25401)
p.sendafter(b"packet:\n", pkt)
resp = p.recvuntil(b"cowboy.\n", timeout=5)
p.close()
return resp
def main():
for guess in (0x13371337, 0x1337babe, 0xdeadbeef):
forged = forge_with_guess(pkt_orig, guess)
resp = try_send(forged)
if b"relay" in resp.lower() or re.search(br"\b[a-z0-9_]{0,8}\{.*?\}", resp, re.I):
print(resp.decode())
return
print(resp)
if __name__ == "__main__":
main()

결과

[DEBUG] Received 0x31 bytes:
b'== Space Relay ==\n'
b'Send your subscription packet:\n'
[DEBUG] Sent 0x30 bytes:
00000000 d7 c9 a2 f3 8e bb ee 04 aa 00 a3 be ad 86 7e f9 │····│····│····│··~·│
00000010 ed f4 f6 a2 fb 24 1b 9e 21 b4 92 6b 51 34 e3 d2 │····│·$··│!··k│Q4··│
00000020 c8 6a 56 14 43 d3 12 ce b0 33 8c 66 48 81 49 6a │·jV·│C···│·3·f│H·Ij│
00000030
[DEBUG] Received 0x81 bytes:
b'You have reached the restricted relay... here you go.\n'
b'sun{m4yb3_4_ch3ck5um_w0uld_b3_m0r3_53cur3}\n'
b'See you next time space cowboy.\n'
[*] Closed connection to sunshinectf.games port 포트블라인드
You have reached the restricted relay... here you go.
sun{m4yb3_4_ch3ck5um_w0uld_b3_m0r3_53cur3}
See you next time space cowboy.