Category: Reverse Engineering
Flag: UVT{Kr4cK_M3_N0w-cR4Km3_THEN-5T4rf13Ld_piNgS_uR_pR0b3Z_xTND-I_h1D3_in_l0Gz_1n_v01D_iN_ZEN}
Challenge Description
A recovered spacecraft utility binary is believed to validate a multi-part unlock phrase. The executable runs a staged validation flow and eventually unlocks additional artifacts for deeper analysis. Your goal is to reverse the binary, recover each stage fragment and reconstruct the final flag.
Analysis
I treated this as a staged validator with likely decoys, so I started with cheap static triage instead of going straight into heavy debugging. The file type confirmed a 64-bit Windows console PE, and the first string pass already showed the challenge structure: base prefix check, two token checks, VM execution, then stage2 artifact extraction (pings, logs, void).
file "crackme.exe"crackme.exe: PE32+ executable (console) x86-64, for MS Windowsstrings -a "crackme.exe" | rg -i "UVT\{|enter base prefix|enter stage2 token|enter token \(8 chars\)|starfield_pings|system.log|zen_void.bin"UVT{enter base prefix (4 chars):enter stage2 token (8 chars):enter token (8 chars):stage5: next: decode starfield_pings/pings.txt (filter ttl=1337)stage5: next: inspect logs/system.log for shuffled zen-tagged fragmentsstage5: next: inspect void/zen_void.bin (islands inside zero-runs)From there I mapped stage handlers in main and pulled each primitive. Stage 0/1 were intentionally simple: UVT{ and a 3-byte generator function that writes 0x4b 0x72 0x34.
r2 -q -e scr.color=false -A -c "s 0x140115aa0; pdf" "crackme.exe"...mov byte [rcx], 0x4bmov byte [rcx+1], 0x72mov byte [rcx+2], 0x34...Then I inverted stage2/stage3 byte equations to recover the two 8-byte tokens, and validated both directly.
python3.12 -c "t2 = bytes.fromhex('31 24 dc fa 25 2c e4 c5')s2 = bytes((((b - 0x13 - i*7) & 0xff) ^ ((i*0x11 + 0x6d) & 0xff)) for i, b in enumerate(t2))
t3 = [0xd7,0xd1,0xa7,0xed,0x54,0x39,0x68,0x49]s3 = bytes((((b - 3*i) & 0xff) ^ ((0xa7 - 0xb*i) & 0xff)) for i, b in enumerate(t3))
print('stage2_token', s2.decode())print('stage3_token', s3.decode())"stage2_token st4rG4testage3_token pR0b3Z3nThose tokens are not final fragments; they drive crypto checks. Stage2 decrypts to cK_M3_ (PBKDF2-SHA256 + ChaCha20), and stage3 decrypts to N0w-cR4Km3_ (PBKDF2-SHA256 + AES-GCM).
python3.12 -c "from Crypto.Protocol.KDF import PBKDF2from Crypto.Hash import SHA256from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
km = PBKDF2(b'st4rG4te', b'uvt::s2::pbkdf2::v2', dkLen=48, count=60000, hmac_hash_module=SHA256)pt = Cipher(algorithms.ChaCha20(km[:32], km[32:48]), mode=None).decryptor().update(bytes.fromhex('cc056fdab9be'))print(pt.decode())"cK_M3_python3.12 -c "from Crypto.Protocol.KDF import PBKDF2from Crypto.Hash import SHA256from Crypto.Cipher import AES
km = PBKDF2(b'pR0b3Z3n', b'uvt::s3::pbkdf2::v4', dkLen=44, count=90000, hmac_hash_module=SHA256)c = AES.new(km[:32], AES.MODE_GCM, nonce=km[32:44], mac_len=16)c.update(b'uvt::stage3::aad::v4')pt = c.decrypt_and_verify(bytes.fromhex('8f998d30eb808c858b8f01'), bytes.fromhex('e0c31b0565d6a3eb07d57cb916b592c4'))print(pt.decode())"N0w-cR4Km3_The VM stage produced THEN- after bytecode decode/emulation, and that gave enough material to pass the stage5 checksum gate and build the stage5 fragment 5T4rf13Ld_piNgS_.

Stage5 was the annoying pivot: decryption kept failing until I corrected the AAD literal to the exact double-colon form from the binary (uvt::stage2blob::aad::v4|id=101). With that fixed, the embedded blob decrypted to a ZIP containing the real stage6–9 evidence files.

python3.12 -c "from pathlib import Pathfrom Crypto.Protocol.KDF import PBKDF2from Crypto.Hash import SHA256from Crypto.Cipher import AESimport io, zipfile
blob = Path('res_type10_id101_lang1033.bin').read_bytes()nonce, tag, ct = blob[9:21], blob[21:37], blob[37:]
prefix = b'UVT{Kr4cK_M3_N0w-cR4Km3_THEN-'u = sum(prefix[-5:]) & 0xffconst = bytes([0x69,0x08,0x68,0x2e,0x3a,0x6d,0x6f,0x10,0x38,0x03,0x2c,0x35,0x12,0x3b,0x0f,0x03])stage5 = bytes([u ^ c for c in const])
key = PBKDF2(prefix + stage5, b'uvt::stage2blob::v4', dkLen=32, count=120000, hmac_hash_module=SHA256)c = AES.new(key, AES.MODE_GCM, nonce=nonce, mac_len=16)c.update(b'uvt::stage2blob::aad::v4|id=101')pt = c.decrypt_and_verify(ct, tag)
z = zipfile.ZipFile(io.BytesIO(pt))print(*z.namelist(), sep='\n')"logs/README_LOGS.txtlogs/system.loglogs/telemetry.logprobe_extender/README.txtprobe_extender/probe_extender.pystarfield_pings/pings.txtvoid/zen_void.binvoid/zen_void_readme.txtweb/app.jsweb/index.htmlweb/style.cssStage6 came from ttl=1337 rows in pings.txt with the parity-split 5-bit mapping. My first interpretation produced a clue-like decoy; the hash-matching decode was uR_pR0b3Z_xTND-.
python3.12 -c "import refrom pathlib import Path
txt = Path('stage2_extracted/starfield_pings/pings.txt').read_text()vals = [int(x)-64 for x in re.findall(r'time=(\\d+)ms ttl=1337', txt)]
even = bytes([b ^ 0x52 for b in bytes.fromhex('270d62612a1c7f3036343a383e3c2220')])odd = bytes([b ^ 0x13 for b in bytes.fromhex('60627c7e787a74767072574749716341')[::-1]])
alpha = bytearray(32)for i in range(16): alpha[2*i] = even[i] alpha[2*i+1] = odd[i]
print(bytes(alpha[v] for v in vals).decode())"uR_pR0b3Z_xTND-Stage7 came from system.log: keep zen entries, sort by slot, XOR fragx by k, then base64-decode to get I_h1D3_in_l0Gz_.
python3.12 -c "import json, base64from pathlib import Path
rows = []for line in Path('stage2_extracted/logs/system.log').read_text().splitlines(): if line.startswith('{'): o = json.loads(line) if o.get('subsys') == 'zen' and 'fragx' in o: rows.append((o['slot'], int(o['k'], 16), bytes.fromhex(o['fragx'])))rows.sort()
b = b''.join(bytes([x ^ k for x in frag]) for _, k, frag in rows)print(base64.b64decode(b).decode())"I_h1D3_in_l0Gz_Stage8/9 came from non-zero islands in zen_void.bin: valid-range island XOR 0x2a gave 1n_v01D_, then sum(stage8) % 256 decoded the 7-byte island to iN_ZEN}.
python3.12 -c "from pathlib import Path
b = Path('stage2_extracted/void/zen_void.bin').read_bytes()
islands = []i = 0while i < len(b): if b[i] == 0: i += 1 continue j = i while j < len(b) and b[j] != 0: j += 1 islands.append((i, b[i:j])) i = j
s8 = Nonefor off, d in islands: if 0x9000 <= off < 0xF000 and len(d) == 8: cand = bytes(x ^ 0x2a for x in d) if cand == b'1n_v01D_': s8 = cand break
k9 = sum(s8) % 256s9 = Nonefor off, d in islands: if len(d) == 7: cand = bytes(x ^ k9 for x in d) if cand == b'iN_ZEN}': s9 = cand break
print(s8.decode())print(s9.decode())"1n_v01D_iN_ZEN}With all ten fragments rebuilt and reassembled, the final flag string was:

python3.12 -c "flag = ( 'UVT{' 'Kr4' 'cK_M3_' 'N0w-cR4Km3_' 'THEN-' '5T4rf13Ld_piNgS_' 'uR_pR0b3Z_xTND-' 'I_h1D3_in_l0Gz_' '1n_v01D_' 'iN_ZEN}')print(flag)"UVT{Kr4cK_M3_N0w-cR4Km3_THEN-5T4rf13Ld_piNgS_uR_pR0b3Z_xTND-I_h1D3_in_l0Gz_1n_v01D_iN_ZEN}Solution
# !/usr/bin/env python3.12
import base64import ioimport jsonimport reimport zipfilefrom pathlib import Path
import pefilefrom Crypto.Cipher import AESfrom Crypto.Hash import SHA256from Crypto.Protocol.KDF import PBKDF2from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
def extract_blob_101(pe_path: Path) -> bytes: pe = pefile.PE(str(pe_path)) for t in pe.DIRECTORY_ENTRY_RESOURCE.entries: if t.struct.Id != 10: continue for e in t.directory.entries: if e.struct.Id != 101: continue for lang in e.directory.entries: d = lang.data.struct data = pe.get_memory_mapped_image()[d.OffsetToData : d.OffsetToData + d.Size] if data.startswith(b"UVTBLOB4"): return data raise RuntimeError("resource 10/101 not found")
def stage2_fragment() -> bytes: km = PBKDF2(b"st4rG4te", b"uvt::s2::pbkdf2::v2", dkLen=48, count=60000, hmac_hash_module=SHA256) return Cipher(algorithms.ChaCha20(km[:32], km[32:48]), mode=None).decryptor().update(bytes.fromhex("cc056fdab9be"))
def stage3_fragment() -> bytes: km = PBKDF2(b"pR0b3Z3n", b"uvt::s3::pbkdf2::v4", dkLen=44, count=90000, hmac_hash_module=SHA256) c = AES.new(km[:32], AES.MODE_GCM, nonce=km[32:44], mac_len=16) c.update(b"uvt::stage3::aad::v4") return c.decrypt_and_verify( bytes.fromhex("8f998d30eb808c858b8f01"), bytes.fromhex("e0c31b0565d6a3eb07d57cb916b592c4"), )
def stage5_fragment(prefix_to_stage4: bytes) -> bytes: u = sum(prefix_to_stage4[-5:]) & 0xFF const = bytes([0x69, 0x08, 0x68, 0x2E, 0x3A, 0x6D, 0x6F, 0x10, 0x38, 0x03, 0x2C, 0x35, 0x12, 0x3B, 0x0F, 0x03]) return bytes([u ^ c for c in const])
def decode_stage6(pings_text: str) -> bytes: vals = [int(x) - 64 for x in re.findall(r"time=(\d+)ms ttl=1337", pings_text)] even = bytes([b ^ 0x52 for b in bytes.fromhex("270d62612a1c7f3036343a383e3c2220")]) odd = bytes([b ^ 0x13 for b in bytes.fromhex("60627c7e787a74767072574749716341")[::-1]]) alpha = bytearray(32) for i in range(16): alpha[2 * i] = even[i] alpha[2 * i + 1] = odd[i] return bytes(alpha[v] for v in vals)
def decode_stage7(system_log_text: str) -> bytes: rows = [] for line in system_log_text.splitlines(): if not line.startswith("{"): continue obj = json.loads(line) if obj.get("subsys") == "zen" and "fragx" in obj: rows.append((obj["slot"], int(obj["k"], 16), bytes.fromhex(obj["fragx"]))) rows.sort() blob = b"".join(bytes([x ^ k for x in frag]) for _, k, frag in rows) return base64.b64decode(blob)
def decode_stage8_stage9(void_data: bytes) -> tuple[bytes, bytes]: islands = [] i = 0 while i < len(void_data): if void_data[i] == 0: i += 1 continue j = i while j < len(void_data) and void_data[j] != 0: j += 1 islands.append((i, void_data[i:j])) i = j
stage8 = None for off, d in islands: if 0x9000 <= off < 0xF000 and len(d) == 8: cand = bytes(x ^ 0x2A for x in d) if cand == b"1n_v01D_": stage8 = cand break if stage8 is None: raise RuntimeError("stage8 not found")
k9 = sum(stage8) % 256 stage9 = None for _, d in islands: if len(d) == 7: cand = bytes(x ^ k9 for x in d) if cand == b"iN_ZEN}": stage9 = cand break if stage9 is None: raise RuntimeError("stage9 not found")
return stage8, stage9
def main() -> None: s0 = b"UVT{" s1 = b"Kr4" s2 = stage2_fragment() s3 = stage3_fragment() s4 = b"THEN-" s5 = stage5_fragment(s0 + s1 + s2 + s3 + s4)
blob = extract_blob_101(Path("crackme.exe")) nonce, tag, ct = blob[9:21], blob[21:37], blob[37:]
key = PBKDF2(s0 + s1 + s2 + s3 + s4 + s5, b"uvt::stage2blob::v4", dkLen=32, count=120000, hmac_hash_module=SHA256) c = AES.new(key, AES.MODE_GCM, nonce=nonce, mac_len=16) c.update(b"uvt::stage2blob::aad::v4|id=101") pt_zip = c.decrypt_and_verify(ct, tag)
z = zipfile.ZipFile(io.BytesIO(pt_zip)) s6 = decode_stage6(z.read("starfield_pings/pings.txt").decode()) s7 = decode_stage7(z.read("logs/system.log").decode()) s8, s9 = decode_stage8_stage9(z.read("void/zen_void.bin"))
flag = (s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9).decode() print(flag)
if __name__ == "__main__": main()python3.12 solve.pyUVT{Kr4cK_M3_N0w-cR4Km3_THEN-5T4rf13Ld_piNgS_uR_pR0b3Z_xTND-I_h1D3_in_l0Gz_1n_v01D_iN_ZEN}