babygame02 picoCTF writeup
After attending the PwnEd4 conference in Edinburgh and talking with different industry professionals in vulnerability research, I’ve been more interested in diving back into reverse engineering and binary exploitation. Over the last week I’ve been working through small CTF pwn challenges to get some hands on experience debugging binaries and creating simple exploits. Today I came across a challenge called babygame02 on picoGym and thought it looked interesting.

The challenge provided me with a single binary called game which displays a map with a player on it. The objective of the game is to move the player (@
), to the end of the map (X
). After entering some random characters, I discovered that I could move the player by entering W, A, S, or D to move up, left, down, and right respectively. If I moved the character to the end of the board, the message “You win!” would pop up. However, winning didn’t give me any flag, so I opened the program up in Ghidra to see what was going on behind the scenes.

The decompiled code was fairly trivial to read, but I decided to clean up the variable names and types to make it a bit nicer:

The code essentially initialises a map of size 2700, fills it with printable characters, and prints the results to the screen for the user. Most of the functionality is happening in the move_player
function, so I jumped into the function and cleaned up the code to see what was happening:

After cleaning up the source code two things immediately jumped out at me: the ability to change the player tile, and that it was writing the provided player tile byte to an index in the map. Since there were no checks in place to see if player->x
or player->y
were negative, it meant that I could move the character to negative coordinates, resulting in a negative index in map. For example, if I moved to location (-1, -1)
, then the code would look like map[-1 + (-1 * 90)]
or map[-91]
. This meant that I could write data to any location on the stack, and therefore change the execution flow of the program.
At this point I decided to boot up gdb to do some live debugging and see what was actually happening with my input. I put a breakpoint on 0x804953f
, which is the instruction where the player tile is being written to an index in map, and checked what the registers contained.

From this I saw that ebx
was pointing to the map on the stack, the tile character (0x40
or @
) was being placed in the edx
register, and the lower byte of the tile character was being copied into the location pointed to by eax
(the index in the map). This meant that I could only write a single byte of data to memory, and therefore would not be able to overwrite the whole eip
to change the execution flow.
However, when looking through the symbols in Ghidra, I noticed a function called win
at address 0x804975d
which prints out the contents of the flag. Fortunately, main
is located at 0x8049709
which contains the same first three bytes as the win
function address. Therefore, I could overwrite the last byte of the return address on the stack to point to win
and print out the flag.

In order to overwrite the correct value on the stack, I had to figure out which index to access. I calculated the difference between the address of the map and the return address on the stack by doing 0xffffc4f30-0xffffc4cc
which equals 0x27
, or 39
in decimal. This meant that I needed the code to look like map[-39] = 0x5d
in order to overwrite the last byte of the return address to point to win
.
Now I had all the information I needed in order to exploit the game. I started the game, attached gdb, moved to location (0, 0), and set my tile character to ]
which is the ASCII character for 0x5d
. Then I started moving the player left 39 times. However, after about 4 or 5 movements, the program crashed with SIGSEGV
. After checking on gdb, it looked like I had overwritten something important on the stack, resulting in the program attempting to write to a location it did not have access to. To get around this I tried moving the player up one more space, which would cause the program to move 90 bytes further up the stack (remember the Y value is multiplied by 90: map[player->x + player->y * 90] = player_tile;
). Then once I reached -39 on the X axis, I moved the player back down, putting me at the exact location I wanted without overwriting important data in between on the stack.
Combining all of this resulted in the following commands:
aaaa
wwwww
l]
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
s
Which resulted in the local flag.txt
file contents being read and printed to the screen!

However, when I tried the exact same commands on the remote instance, it wasn’t printing the flag. To fix this I tried jumping to other addresses in the win
function to see if any of them would execute the rest of the function correctly.

After some trial and error, I discovered jumping to 0x8049760
printed the flag on the remote service! The ASCII character for 0x60
is `
, so my final payload looked like this:
aaaa
wwwww
l`
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
s
This was a really fun and simple challenge, and it really helped me understand the reverse engineering and binary exploitation process more in-depth. I’ve attached the code I wrote for my automated local exploit below:
#!/usr/bin/env python3
from pwn import *
game = process('./game')
class Player:
def __init__(self, y, x):
self.x = x
self.y = y
def get_position(self):
return (self.y, self.x)
def get_relative_index(self):
return self.x + (self.y * 0x5a)
def move(self, direction):
match direction:
case 'a':
self.x -= 1
case 'd':
self.x += 1
case 'w':
self.y -= 1
case 's':
self.y += 1
log.info(f'Player position: {self.get_position()} | map[{self.get_relative_index()}]')
def read_map():
return game.recvrepeat(0.1)
def send_movement(player, direction, read=True):
game.sendline(direction)
str_direction = direction.decode('utf-8')
log.info(f'Sending {str_direction}')
for c in str_direction:
player.move(c)
if read: read_map()
def change_tile_character(lower_byte):
game.sendline(b'l' + lower_byte)
log.info(f'Changing tile to: {lower_byte.decode("utf-8")}')
read_map()
read_map()
player = Player(4, 4)
change_tile_character(b'`')
send_movement(player, b'aaaa')
send_movement(player, b'wwwww')
send_movement(player, b'a'*39)
send_movement(player, b's', read=False)
log.info(f'FLAG: {game.recvline().decode()}')
game.close()