Lost at Sea — Athena Trace 2026 Writeup

A four-stage chain: read vessel data off a screenshot, derive an AES key from it, pull a ZIP out of the tail of a PNG, and unlock it with the ship's own call sign.

The challenge

An illicit cargo transaction was planned mid-voyage. We have a screenshot from the coordinator’s terminal, a coastal monitoring station’s log from the same night, and three files a port authority was still serving after it pulled the paperwork. Follow what the coordinator left behind and recover the manifest.

Flag format: trace{...} — the manifest contains the flag exactly as it should be submitted.

Five artifacts:

terminal.png
radio_intercept.txt
trimmu3.png
kaz_berth.png
bqm_approach.png

This is the most technical card on the board — four distinct disciplines chained back to back, where each stage’s output is the next stage’s input. Break one link and nothing downstream works.

Stage 1 — Harvest the vessel data

terminal.png is a MarineTraffic view for a vessel called TRIMMU 3. Everything that matters later is on this screen:

The coordinator's terminal: MarineTraffic overview for TRIMMU 3. The four values that drive the whole chain — MMSI 477204900, speed 10.3 kn, call sign VRXD7, route IQ KAZ → PK BQM — are all in this one screenshot.

FieldValue
VesselTRIMMU 3
MMSI477204900
IMO9200469
Call signVRXD7
Speed (SOG)10.3 kn
RouteIQ KAZ → PK BQM

The route decodes as Khor al Zubair, Iraq → Bin Qasim, Pakistan — which is why kaz_berth.png and bqm_approach.png exist. Those two turn out to be scene dressing rather than load-bearing evidence, but they confirm the voyage endpoints and are worth checking rather than assuming.

Treat this stage as data extraction, not analysis. Copy every field verbatim; you don’t yet know which ones matter.

Stage 2 — Build the key

radio_intercept.txt contains a Base64 block followed by plaintext instructions:

KEY = MMSI_SOG
  - MMSI as printed
  - SOG in knots, one decimal
  - underscore between
AES-256-CBC
  - key is SHA-256 of that string
  - IV is first sixteen bytes

Substituting from the screenshot:

477204900_10.3

Two details decide whether this works. “MMSI as printed” means no formatting, no separators — exactly the digit string on screen. “SOG in knots, one decimal” means 10.3, not 10.30 and not 10. The instructions are pedantic because the key is a hash: one wrong character and you get noise, with no partial credit and no feedback about which character was wrong.

Stage 3 — The IV ambiguity

“IV is first sixteen bytes” is genuinely ambiguous — first sixteen bytes of what? Two conventions are common:

  • iv = key[:16] — the first half of the SHA-256 digest.
  • iv = blob[:16] — the ciphertext is prefixed with the IV, the standard practice for AES-CBC in transit.

The correct reading here is the second: the IV is prepended to the ciphertext.

What makes this worth dwelling on is the failure mode. Trying iv = key[:16] does not fail cleanly — it produces garbage in the first block and readable plaintext from block two onward. That’s inherent to CBC: each block is decrypted with the previous ciphertext block as its XOR input, so a wrong IV corrupts only the first 16 bytes. The cipher self-heals.

Lesson: in CBC, “mostly readable output” is not confirmation. If the first block is garbage and the rest is clean, your IV is wrong — not your key. A wrong key produces noise throughout; a wrong IV produces exactly one bad block. The damage pattern tells you which parameter to fix.

Working decryption:

import base64, hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

blob = base64.b64decode(b64_from_intercept)
iv   = blob[:16]
ct   = blob[16:]
key  = hashlib.sha256(b"477204900_10.3").digest()

pt = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ct), 16)
print(pt.decode())

Output — an HTTP response:

HTTP/1.1 404 Not Found
Date: Fri, 07 Aug 2026 04:18:22 GMT
Server: nginx/1.24.0
Content-Type: text/html; charset=utf-8
Content-Length: 137
X-Cargo-Manifest: /static/img/trimmu3.png
X-Archive-Note: manifest withdrawn from index 2026-08-07; asset retained

The 404 is the point. The paperwork was pulled from the index, but X-Archive-Note says the asset was retained — and X-Cargo-Manifest names it: /static/img/trimmu3.png. That’s the third artifact, and it is not just an illustration.

This is a realistic pattern worth internalising: withdrawing a document from a listing does not delete the file, and custom response headers routinely leak internal paths long after the content is “removed”.

Stage 4 — The polyglot

trimmu3.png is a valid PNG that renders normally. It also has a ZIP archive appended after the IEND chunk.

The carrier file renders as a harmless Port Qasim Authority vessel sheet — nothing about the image betrays the ZIP archive sitting after its IEND chunk.

This works because the two formats parse from opposite ends. PNG readers stop at IEND and ignore trailing bytes; ZIP readers scan backwards from the end of the file for the central directory. Both see a valid file. Nothing looks wrong.

Detection and extraction:

binwalk trimmu3.png              # flags the appended archive
# or, directly:
unzip trimmu3.png                # zip finds its own central directory

Inside: manifest.txt, password-protected.

Practical habit: any image handed to you in a forensics-flavoured challenge gets binwalk and a trailing-bytes check before anything else. Comparing the IEND offset against the file size takes seconds and catches this whole class of trick.

Stage 5 — The archive password

Nothing in the prompt states the password, but the framing does: “follow what the coordinator left behind.” Every credential in this chain has come from the terminal screenshot, so the candidate set is the identifiers on that screen:

CandidateValueReasoning
MMSI477204900Already used for the AES key — reuse is plausible but less likely
IMO9200469Permanent hull identifier
Vessel nameTRIMMU3Obvious guess, spacing ambiguous
Call signVRXD7Radio identity — thematically fits an intercept chain

The call sign is correct: VRXD7.

In hindsight it’s the thematically consistent answer. The whole chain runs on radio: an intercepted transmission, a coastal monitoring station, a vessel’s radio identity. The call sign is the ship’s identifier on the air, which is the register this challenge operates in.

Note also that the vessel name is a bad candidate precisely because it’s ambiguous — TRIMMU 3, TRIMMU3, trimmu3 are three different strings. A setter choosing a password from a screenshot will favour a value with exactly one rendering.

The manifest

PORT QASIM AUTHORITY -- CARGO MANIFEST (WITHDRAWN COPY)
Vessel      : TRIMMU 3   IMO 9200469   MMSI 477204900
Voyage      : IQ KAZ -> PK BQM
Transfer    : ship-to-ship, position withheld from filed copy
Filed       : 2026-08-07
Status      : withdrawn from public index

Declared cargo differs from the filed copy. Reconciliation reference:

trace{TR1MMU_3_SUCCE55_051NT}

Flag

trace{TR1MMU_3_SUCCE55_051NT}

Takeaways

  1. Extract exhaustively before you analyse. At stage 1 there’s no way to know the MMSI and SOG feed a hash while the call sign unlocks an archive four steps later. Copy every field; decide relevance downstream.
  2. CBC failure patterns are diagnostic. One bad block then clean text means a wrong IV. Noise throughout means a wrong key. Read the damage before changing parameters at random.
  3. Ambiguous spec language has a default. “IV is first sixteen bytes” resolves to the transit convention — IV prepended to ciphertext — because that’s how AES-CBC is actually shipped in the wild. When a spec is ambiguous, try the real-world convention first.
  4. Withdrawn ≠ deleted. A 404 with a custom header naming a retained asset is a realistic leak pattern, not just a CTF contrivance.
  5. Check trailing bytes on every image. PNG-plus-ZIP polyglots are invisible to viewers and to casual inspection, and cost nothing to rule out.
  6. Guess passwords from the artifact set, thematically. The candidates were all on one screen; the tiebreaker was which identifier fits the challenge’s own register — radio.

Sources


Image credits

All artifacts are challenge-authored files distributed with the Athena Trace 2026 CTF. The vessel identifiers are real — TRIMMU 3 is an existing LPG tanker under IMO 9200469 — and appear to be drawn from public AIS data; the manifest and the scenario built around them are the challenge’s fiction.