Skip to main content

cheater_cheater

Challenge

Imported from local notes.md.

Solution

Original Notes

cheater_cheater

Challenge Summary

  • Given: A downloaded Java archive, starting_files/PacManForCTF.jar, for a Pac-Man clone called Hac-Man.
  • Goal: Beat the impossible high score and recover the flag.
  • Constraints: Normal gameplay appears capped well below the displayed high score, so the intended path is to cheat or reverse the game logic.

Initial Recon / Triage

  • Observations: The JAR only contains SimplePacMan.class and JTextBasket.class, which keeps the reversing surface small.
  • File identification: META-INF/MANIFEST.MF sets Main-Class: SimplePacMan.
  • Entry points: SimplePacMan.actionPerformed controls score progression, and SimplePacMan.paintComponent handles the win/lose screens.

Hypotheses & Approach

  • Hypothesis 1: The game hardcodes a losing condition that prevents the player from legitimately reaching the displayed high score.
  • Hypothesis 2: The flag is embedded in the JAR and revealed through a hidden UI path once the score is forced into the winning state.

Execution Steps (Reproducible)

Stage 1

Commands:

cd /root/dawg2026CTF/cheater_cheater/starting_files
jar tf PacManForCTF.jar
unzip -p PacManForCTF.jar META-INF/MANIFEST.MF
javap -classpath PacManForCTF.jar -p -c SimplePacMan
javap -classpath PacManForCTF.jar -p -c JTextBasket

Results:

  • SimplePacMan.actionPerformed increments score by 10 for each pellet.
  • If score reaches 64000, the game sets loser = true, so normal play is intentionally capped.
  • If score is >= 6942069, the game sets winner = true and clamps score to 6942069.
  • In the win screen, paintComponent sets the panel name to the score string and calls getComponents()[0].revalidate().
  • JTextBasket.revalidate() uses the parent component name as input to derive an AES key and IV, decrypts a hardcoded Base64 ciphertext, and stores the plaintext in the hidden component name.

Stage 2

Commands:

cd /root/dawg2026CTF/cheater_cheater/artifacts
jshell -q recover_flag.jsh

Results:

  • The winning score is the exact string 6942069.
  • JTextBasket.revalidate() derives the decrypt material as (6942069 * 10 + 1)^4.
  • The decimal result is interpreted as hex bytes for the AES key, and its reversed decimal string is interpreted as hex bytes for the IV.
  • Decrypting the embedded ciphertext reveals the flag: DawgCTF{ch3at3R_ch34t3r_pumk1n_34t3r!}.

Artifacts Produced

  • artifacts/recover_flag.jsh: JShell script that reproduces the game's decryption logic and prints the flag.

Flag

DawgCTF{ch3at3R_ch34t3r_pumk1n_34t3r!}