$ cat writeup.md…
$ cat writeup.md…
sekai2026
Task: analyze a StarCraft II SC2Replay MPQ archive with chat fragments and hidden position-based data. Solution: decode replay messages, plot SUnitDiedEvent coordinates from tracker events, and rebuild the flag.
The provided archive was misc_deadgame2.tar.gz, containing misc_deadgame2/DeadGame2.SC2Replay.
The replay is a StarCraft II MPQ archive. Metadata identified map title Tunguska, GameVersion 5.0.15.96999, and Base96999.
The goal was to recover the complete SeKaiCTF{...} flag from replay data. The visible chat gave most of the flag, while a hint about positions pointed to hidden text encoded in tracker coordinates.
python3.11mpyq for extracting the MPQ replay archives2protocol for decoding StarCraft II replay streamsPillow for rendering coordinate plotsThe local environment used vendor311 packages because the bundled s2protocol code was compatible with Python 3.11.
After unpacking the challenge archive, treat DeadGame2.SC2Replay as an MPQ archive and extract the replay streams. The important files were:
replay.message.events - in-game chat messagesreplay.tracker.events - unit and positional tracker eventsThe message stream can be decoded directly with the matching/latest bundled s2protocol protocol module:
#!/usr/bin/env python3 from pathlib import Path from s2protocol.versions import protocol97364 as protocol data = Path("extract/replay.message.events").read_bytes() for event in protocol.decode_replay_message_events(data): if event.get("_event") != "NNet.Game.SChatMessage": continue text = event["m_string"].decode("utf-8", "replace") if text: print(event["_gameloop"], event["_userid"]["m_userId"], text)
The decoded chat stream contained direct flag fragments plus the key hint:
| Gameloop | User | Message |
|---|---|---|
| 4662 | 0 | SeKaiCTF{lol |
| 7428 | 0 | Want more? Let me be kind enough to give you a hint - in this interstellar war, everyone's "position" is crucial! |
| 11697 | 0 | Chinese hint, translated: Mysterious Eastern text: you should also send some flag out! |
| 11897 | 1 | _fl2g_ |
| 72569 | 0 | Attention!!! |
| 73100 | 1 | _ |
| 73312 | 1 | _ |
| 73588 | 1 | _ |
| 73864 | 1 | GG |
| 73931 | 1 | } |
This recovered the skeleton:
SeKaiCTF{lol_fl2g_???_???_???_GG}
The word position in the hint is the important clue: the missing chunks are not another chat encoding layer, but text drawn by positions in tracker events near the underscore-only chat messages.
In replay.tracker.events, the useful data is in NNet.Replay.Tracker.SUnitDiedEvent events. Filter events where m_killerPlayerId == 1 around the three underscore chat gameloops and plot (m_x, m_y) coordinates.
#!/usr/bin/env python3 from pathlib import Path from s2protocol.versions import protocol97364 as protocol TARGETS = { 73086: "EN", 73290: "TARO", 73563: "HERO", } groups = {g: [] for g in TARGETS} data = Path("extract/replay.tracker.events").read_bytes() for event in protocol.decode_replay_tracker_events(data): if event.get("_event") != "NNet.Replay.Tracker.SUnitDiedEvent": continue if event.get("m_killerPlayerId") != 1: continue gameloop = event.get("_gameloop") if gameloop in groups: groups[gameloop].append((event["m_x"], event["m_y"])) for gameloop, label in TARGETS.items(): pts = groups[gameloop] xs = [x for x, _ in pts] ys = [y for _, y in pts] print(f"{gameloop}: {label} points={len(pts)} bbox=x[{min(xs)},{max(xs)}], y[{min(ys)},{max(ys)}]") occupied = set(pts) for y in range(max(ys), min(ys) - 1, -1): print("".join("#" if (x, y) in occupied else "." for x in range(min(xs), max(xs) + 1))) print()
The three bursts immediately before the underscore chat messages produced readable coordinate plots:
| Gameloop | Points | Bounding box | Text |
|---|---|---|---|
| 73086 | 48 | x[115,129], y[152,161] | EN |
| 73290 | 82 | x[106,134], y[140,148] | TARO |
| 73563 | 83 | x[112,142], y[115,125] | HERO |
The helper script show_tracker_chunks.py renders these same grids and writes tracker_missing_chunks.png for visual confirmation.
Combine the visible chat fragments with the tracker text chunks. The underscore-only chat messages mark the separators around the hidden words:
SeKaiCTF{lol _fl2g_ EN _ TARO _ HERO _ GG }
So the reconstructed flag is:
SeKaiCTF{REDACTED}
The provided MD5 check matches the reconstructed flag:
#!/usr/bin/env python3 import hashlib flag = "SeKaiCTF{REDACTED}" assert hashlib.md5(flag.encode()).hexdigest() == "0b95495176f49f2dab8a2d9c26a41ecc" print(flag)
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar