babygame01 picoCTF 2023 Solution

Published: April 26, 2023

Description

Navigate the BabyGame binary to obtain the flag. Move to the top-left corner of the map and walk off the left edge to write your player character's value into the win variable. Press P to auto-navigate to the exit and collect the flag.

Run the binary locally to understand the map mechanics (chmod +x game && ./game).

When ready, connect to nc saturn.picoctf.net 52987 to obtain the real flag.

bash
chmod +x game && ./game
bash
nc saturn.picoctf.net 52987

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Move to the top-left corner of the map
    Observation
    I noticed the win variable sits at a negative offset from the map array base, which suggested that reaching column 0 first was necessary before any out-of-bounds write could land on the correct memory location.
    Use 'w' to move up and 'a' to move left until you reach position (0, 0) - the top-left corner. The player starts at (4, 4), so press 'w' four times and 'a' four times to reach the corner. Your default player character is '@' (ASCII 0x40 = 64), which is exactly the value needed to satisfy the win condition.
    bash
    # After connecting, navigate to the top-left corner:
    bash
    wwwwaaaa
    What didn't work first

    Tried: Modify the player character to a different value (e.g. press p to change it) before navigating to the corner.

    The win condition checks for exactly 64 (0x40), which is the ASCII value of '@', the default character. Changing the player character to anything else (e.g. 'p' = 0x70 = 112) means the out-of-bounds write stamps the wrong value into the win variable and the game never grants the flag. No modification is needed - the default '@' already satisfies the check.

    Tried: Navigate to the bottom-right corner or some other edge instead of the top-left.

    The out-of-bounds write only triggers when moving left past column 0, which requires being in the leftmost column (x = 0). The top-left corner is required because the win variable sits at a negative x offset from the map array base. Moving off other edges either has bounds checks or writes into irrelevant memory, so no flag condition is triggered.

    Learn more

    Reverse-engineering the binary in Ghidra shows that the player character is initialized to @ (0x40 = 64) by default. The win condition checks whether a variable on the stack equals 64, so no modification of the player character is needed at all - the default value already satisfies the check.

  2. Step 2
    Walk off the left edge of the map
    Observation
    I noticed the game has no bounds check on the left edge and the win variable sits exactly 4 bytes before the map array base, which suggested pressing 'a' four more times from (0, 0) would stamp the player character value directly onto the win variable.
    From the top-left corner at (0, 0), press 'a' four more times. The game has no bounds check on the left edge, so your position goes negative and the player stamp writes your character value (64) into the win variable, which sits 4 bytes before the start of the map array. The game prints Player has flag: 64 to confirm.
    bash
    # From the top-left corner, press 'a' four more times:
    bash
    aaaa

    Expected output

    Player has flag: 64
    What didn't work first

    Tried: Press 'a' only once or twice after reaching (0, 0) to trigger the win condition sooner.

    The win variable sits exactly 4 bytes before the map array base, so the write only reaches it on the fourth left press (x = -4). Stopping at -1, -2, or -3 writes the player byte into a different stack location and the win check still evaluates to 0. All four presses are required.

    Tried: Press 'a' five or more times after reaching (0, 0) to be safe.

    A fifth left press moves x to -5, writing outside the win variable into adjacent stack memory and typically triggering a segmentation fault that crashes the server connection. The game has no recovery from this crash, so the session must be restarted. Exactly four left presses are needed - no more, no less.

    Learn more

    The stack layout places the win variable 4 bytes before the start of the map array. Moving left from column 0 without a bounds check decrements the x coordinate to -1, -2, -3, -4, and at each step writes the player character byte to that negative offset relative to the map base. After 4 left presses, the write lands on the win variable and sets it to 0x40 (64). A fifth press causes a segmentation fault.

    This is an out-of-bounds write bug. The fix is a bounds check: if (x > 0) x--;. Without it, the player coordinates can go negative and clobber adjacent stack memory.

  3. Step 3
    Press P to auto-solve and reach the exit
    Observation
    I noticed the game printed 'Player has flag: 64' after the out-of-bounds write succeeded, which suggested the win condition was satisfied and the built-in P auto-navigate shortcut could now complete the path to the exit and retrieve the flag from the remote server.
    Once the win variable is set, press P to auto-navigate to the exit. Locally you will see "flag.txt not found", so repeat the moves remotely via netcat to have the server print the flag.
    Learn more

    Netcat (nc) is a raw TCP/UDP utility that connects stdin/stdout to a network socket. When a CTF challenge runs a binary on a remote server, nc host port lets you interact with it exactly as if you were at a local terminal - every keypress you type is sent over the wire and the server's output is streamed back.

    The reason the flag only appears remotely is that flag.txt exists only on the server's filesystem. Local runs teach you the mechanics; the remote session is where the actual secret is stored. This pattern is extremely common in CTF binary challenges: download and analyze locally, exploit remotely.

    For challenges that require many precise keystrokes, players often script the interaction with pwntools in Python (e.g., p.send(b'a' * 4)), which provides reliable, repeatable input over the same TCP connection.

Interactive tools
  • Cyclic Pattern GeneratorGenerate de Bruijn cyclic patterns and find buffer overflow offsets. The browser equivalent of pwntools cyclic and cyclic_find.
  • pwntools Payload BuilderPack integers into little-endian bytes (p32 / p64), unpack bytes back to integers, and build flat ROP payloads with offset-based insertion.

Flag

Reveal flag

picoCTF{gamer_m0d3_enabl...8e6}

The netcat session is required; local runs simply teach you the map layout.

Key takeaway

Out-of-bounds array writes occur when a program updates an index without checking that it stays within the array's allocated range. Adjacent stack variables sit at predictable offsets from the array base, so an unchecked negative or oversized index can clobber them with attacker-controlled data. The same root cause appears in real-world bugs like the 2014 Heartbleed read overrun and countless CVEs in game engines, image parsers, and network daemons where index arithmetic is trusted without validation.

Related reading

Want more picoCTF 2023 writeups?

Useful tools for Binary Exploitation

What to try next