Skip to main content

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/ancientbattleship is 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 recovered battleship.py logic.
  • artifacts/venv/: isolated tooling used to inspect the Python 3.9 marshal payload.

Flag

DawgCTF{the_ai_is_out_of_moves}