Skip to main content

cipher_craft

Challenge

Imported from local notes.md.

Solution

Original Notes

cipher_craft

Challenge Summary

  • Given: a link to the CipherCraft Minetest/Mineclonia world in the DawgCTF repository.
  • Goal: recover the hidden secrets in the world and derive the final DawgCTF{...} flag.
  • Constraints: the local challenge directory initially did not contain the archive, and the installed Luanti server version could not read the world database schema directly.

Initial Recon / Triage

  • Observations:
    • starting_files/ was empty locally, so the first step was recovering the actual archive from the linked repo.
    • The extracted world is a Mineclonia/Luanti world with map.sqlite, players.sqlite, and item metadata that can be inspected offline.
    • The installed Luanti server was older than the world schema, so direct headless loading failed until a compatibility copy was made.
  • File identification:
    • starting_files/MineTest_Challenge_Archive.zip: recovered challenge archive.
    • starting_files/Mineclonia_5_15_1_TapTapTap/: extracted world.
    • artifacts/scan_output.txt: structured dumps from headless world scans.
    • artifacts/final_side_pairing.py and artifacts/analyze_machine_groups.py: helpers used to decode the final note-block machine.
  • Entry points:
    • players.sqlite for spawn/bed position.
    • map.sqlite and decompressed mapblocks for hidden books, signs, chests, and the final machine.

Hypotheses & Approach

  • Hypothesis 1: the world could be solved mostly offline by reading SQLite metadata, inventories, and mapblocks instead of playing it interactively.
  • Hypothesis 2: the final machine was a tap-code / Polybius-family puzzle, with note pitch used to split runs into count pairs.
  • Approach: recover the missing archive, build a compatibility world copy for headless scanning, walk the clue chain through metadata and item dumps, then decode the final note-block machine by grouping note blocks into physical x-bands and merging the resulting run-length streams.

Execution Steps (Reproducible)

Stage 1

Commands:

cd /tmp
git clone https://github.com/UMBCCyberDawgs/dawgctf-sp26.git
cp /tmp/dawgctf-sp26/CipherCraft/MineTest_Challenge_Archive.zip /root/dawg2026CTF/cipher_craft/starting_files/
cd /root/dawg2026CTF/cipher_craft/starting_files
unzip MineTest_Challenge_Archive.zip
sqlite3 Mineclonia_5_15_1_TapTapTap/players.sqlite 'select name, pitch, pos, metadata from player_metadata;'

Results:

  • Recovered the missing archive and extracted the world locally.
  • Player metadata exposed a bed spawn around (66,4,-38), which became the first reliable search area.

Stage 2

Commands:

cd /root/dawg2026CTF/cipher_craft/artifacts
python search_mapblocks.py
python analyze_final_machine.py
python final_side_pairing.py
python analyze_machine_groups.py

Results:

  • Built a compatibility scan workflow and dumped world objects into artifacts/scan_output.txt.
  • Solved the earlier clue chain from recovered books/chests:
    • CIPHER #1 decoded to coordinates (1312,48,92).
    • CIPHER #2 decoded to Nether coordinates (-432,-28999,68).
    • Additional mapblock scanning exposed the later movement hint, CIPHER #4, and the final machine area.
  • The FINAL CIPHER machine was found around (11497,11,3213).

Stage 3

Commands:

python -c "from analyze_machine_groups import load_rows, traverse, runs, decode; groups=load_rows(); right=traverse(groups['right'], True, 'rtl', 'alternate'); middle=traverse(groups['middle'], False, 'rtl', 'alternate'); left=traverse(groups['left'], True, 'rtl', 'alternate'); print(decode([c for _, c in runs(right + middle + left)]))"

Results:

  • The final machine decodes cleanly when split into three physical note groups by x-position and merged without resetting run boundaries:
    • Right band (x >= 11503), traversed descending by z, start rtl, alternating per row: SEEMSYOU
    • Middle band (11497 <= x <= 11501), traversed ascending by z, start rtl, alternating per row: REMININGAV
    • Left band (x <= 11495), traversed descending by z, start rtl, alternating per row: completes the phrase
  • Merging the three band sequences and decoding the run-length pairs with a 5x5 Polybius square (K omitted, matching the sign hint THERE IS NO K) yields:
SEEMSYOUREMININGAWAYAGAIN
  • Using the challenge's flag format gives the final flag.

Artifacts Produced

  • artifacts/scan_output.txt: headless scan dump of note blocks, signs, books, inventories, and metadata.
  • artifacts/search_mapblocks.py: mapblock text search helper.
  • artifacts/analyze_final_machine.py: initial final-machine summarizer.
  • artifacts/final_side_pairing.py: helper that confirmed the right/middle cluster merge.
  • artifacts/analyze_machine_groups.py: helper that split the full machine into left/middle/right x-bands and produced the final phrase.

Flag

DawgCTF{SEEMSYOUREMININGAWAYAGAIN}