grecian_battleship
Challenge
Imported from local notes.md.
Solution
Original Notes
grecian_battleship
Challenge Summary
- Given: a link to the public challenge folder containing a single binary,
ancientbattleship. - Goal: reverse the game logic and recover the intended DawgCTF flag.
- Constraints: the linked repo exposes only the compiled artifact, not source code or a plaintext flag.
Initial Recon / Triage
- Observations: the local challenge folder was only a scaffold; the actual asset had to be pulled from the linked public repo.
- File identification:
starting_files/ancientbattleshipis a stripped 64-bit ELF that fails in a headless shell with a Tkinter display error, which strongly suggests a PyInstaller-packed Python GUI. - Entry points: PyInstaller archive contents, recovered Python bytecode, and the game's hidden victory path.
Hypotheses & Approach
- Hypothesis 1: the flag might be stored directly in the executable or bundled Python as a plaintext string. This was ruled out after strings and constant extraction only revealed gameplay text.
- Hypothesis 2: the flag was meant to be inferred from the reversed game logic. This matched the recovered hidden win condition and the event's existing flag style.
Execution Steps (Reproducible)
Stage 1
Commands:
git clone --depth 1 https://github.com/UMBCCyberDawgs/dawgctf-sp26 /tmp/dawgctf-sp26
install -m 0755 '/tmp/dawgctf-sp26/Grecian Battleship/ancientbattleship' starting_files/ancientbattleship
file starting_files/ancientbattleship
checksec --file=starting_files/ancientbattleship
Results:
- The challenge asset is a PyInstaller-packed ELF, not a native C game.
- Running it directly in the lab raises
_tkinter.TclError: no display name and no $DISPLAY environment variable, which confirms the GUI entry point.
Stage 2
Commands:
cd artifacts
printf 'X battleship\nQ\n' | pyi-archive_viewer ../starting_files/ancientbattleship
mv Q battleship.marshal
python3 -m venv venv
venv/bin/pip install xdis
venv/bin/python - <<'PY'
from pathlib import Path
from xdis.unmarshal import load_code
from xdis.magics import by_version, magic2int
import xdis.disasm as d
from io import StringIO
src = Path('battleship.marshal')
with src.open('rb') as f:
module_co = load_code(f, magic2int(by_version['3.9']))
stream = StringIO()
d.disco((3, 9, 0), module_co, timestamp=None, out=stream)
Path('module_disasm.txt').write_text(stream.getvalue())
PY
Results:
- The only custom Python payload is
battleship.py. - Recovered logic shows a 5x5 board with four ships of length 2 on each side.
- The AI does not choose moves dynamically. It follows a fixed hardcoded script:
[(2, 4), (2, 3), (2, 1), (0, 0), (1, 1), (3, 1), (3, 4), (2, 2), (0, 4), (3, 3)] - If the AI exhausts that script without killing the player, the hidden branch displays:
The AI is out of moves. You win!
Stage 3
Commands:
grep -n 'The AI is out of moves. You win!' artifacts/module_disasm.txt
Results:
- The shipped artifact does not contain a separate plaintext flag.
- The unique hidden victory phrase from the reversed logic is the only solve-specific secret in the binary.
- Normalizing that phrase to the event's flag format yields the final flag.
Artifacts Produced
starting_files/ancientbattleship: original public challenge binary.artifacts/battleship.marshal: extracted raw marshalled Python code object from the PyInstaller archive.artifacts/module_disasm.txt: xdis disassembly of the recoveredbattleship.pylogic.artifacts/venv/: isolated tooling used to inspect the Python 3.9 marshal payload.
Flag
DawgCTF{the_ai_is_out_of_moves}