Flare-On 2024: frog
frog is a neat little PyGame executable with Python source code that asks the player to walk the frog to an endpoint. However, there are walls blocking all possible paths! I’ll look at the Python source and come up with three ways to get the flag.
Challenge
The challenge prompt reads:
Welcome to Flare-On 11! Download this 7zip package, unzip it with the password ‘flare’, and read the README.txt file for launching instructions. It is written in PyGame so it may be runnable under many architectures, but also includes a pyinstaller created EXE file for easy execution on Windows.
Your mission is get the frog to the “11” statue, and the game will display the flag. Enter the flag on this page to advance to the next stage. All flags in this event are formatted as email addresses ending with the @flare-on.com domain.
The download several files and folder:
oxdf@hacky$ ls
fonts frog.7z frog.exe frog.py img orig README.txt
The README.txt
says:
This game about a frog is written in PyGame. The source code is provided, as well as a runnable pyinstaller EXE file.
To launch the game run frog.exe on a Windows computer. Otherwise, follow these basic python execution instructions:
- Install Python 3
- Install PyGame (“pip install pygame”)
- Run frog: “python frog.py”
Run It
Running the .exe
on Windows opens a GUI window:
I can move the frog around with arrow keys, but not through the walls, making it impossible to get to the statue and win (without cheating).
RE
The Python source (frog.py
) is 248 lines long. I’ll highlight the interesting bits.
The Block
and Frog
classes define the sprites on the screen:
class Block(pygame.sprite.Sprite):
def __init__(self, x, y, passable):
super().__init__()
self.image = blockimage
self.rect = self.image.get_rect()
self.x = x
self.y = y
self.passable = passable
self.rect.top = self.y * tile_size
self.rect.left = self.x * tile_size
def draw(self, surface):
surface.blit(self.image, self.rect)
class Frog(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = frogimage
self.rect = self.image.get_rect()
self.x = x
self.y = y
self.rect.top = self.y * tile_size
self.rect.left = self.x * tile_size
def draw(self, surface):
surface.blit(self.image, self.rect)
def move(self, dx, dy):
self.x += dx
self.y += dy
self.rect.top = self.y * tile_size
self.rect.left = self.x * tile_size
I’ll note that the Frog
’s move
method doesn’t check if the new space is valid, so that must be handled elsewhere. Not far below, there’s an AttemptPlayerMove
function that does just that:
def AttemptPlayerMove(dx, dy):
newx = player.x + dx
newy = player.y + dy
# Can only move within screen bounds
if newx < 0 or newx >= tiles_width or newy < 0 or newy >= tiles_height:
return False
# See if it is moving in to a NON-PASSABLE block. hint hint.
for block in blocks:
if newx == block.x and newy == block.y and not block.passable:
return False
player.move(dx, dy)
return True
Just below that is GenerateFlagText
:
def GenerateFlagText(x, y):
key = x + y*20
encoded = "\xa5\xb7\xbe\xb1\xbd\xbf\xb7\x8d\xa6\xbd\x8d\xe3\xe3\x92\xb4\xbe\xb3\xa0\xb7\xff\xbd\xbc\xfc\xb1\xbd\xbf"
return ''.join([chr(ord(c) ^ key) for c in encoded])
The rest of the code is the main
function which handles drawing the sprites onto the canvas, waiting for user input, and handling it, and a BuildBlocks
function which just initializes the blocks, returning a list of blocks.
Solutions
Decrypt Flag
The quickest way to solve this challenge is just to get the flag myself. GenerateFlagText
takes two parameters, x
and y
. It’s called in the main
function here:
if not victory_mode:
# are they on the victory tile? if so do victory
if player.x == victory_tile.x and player.y == victory_tile.y:
victory_mode = True
flag_text = GenerateFlagText(player.x, player.y)
flag_text_surface = flagfont.render(flag_text, False, pygame.Color('black'))
print("%s" % flag_text)
victory_tile
is defined at the very top:
victory_tile = pygame.Vector2(10, 10)
So to get the flag text, I just need to pass 10, 10 into the function, which I can replicate in a Python REPL:
oxdf@hacky$ python
Python 3.12.3 (main, Sep 11 2024, 14:17:37) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def GenerateFlagText(x, y):
... key = x + y*20
... encoded = "\xa5\xb7\xbe\xb1\xbd\xbf\xb7\x8d\xa6\xbd\x8d\xe3\xe3\x92\xb4\xbe\xb3\xa0\xb7\xff\xbd\xbc\xfc\xb1\xbd\xbf"
... return ''.join([chr(ord(c) ^ key) for c in encoded])
...
>>> GenerateFlagText(10,10)
'welcome_to_11@flare-on.com'
Walk Through Walls
Another way to hack this is to just allow the frog to walk through walls. I’ll copy frog.py
to frog-mod1.py
, and then edit AttemptPlayerMove
:
def AttemptPlayerMove(dx, dy):
newx = player.x + dx
newy = player.y + dy
# Can only move within screen bounds
if newx < 0 or newx >= tiles_width or newy < 0 or newy >= tiles_height:
return False
# See if it is moving in to a NON-PASSABLE block. hint hint.
#for block in blocks:
# if newx == block.x and newy == block.y and not block.passable:
# return False
player.move(dx, dy)
return True
I’ve commented out the check to see if the move is into a block. Now on running the game, the frog can pass through walls:
There Are No Walls
Alternatively, what if there were no walls? I’ll find the code at the start of main
where it initializes the Blocks list, and edit it, replacing the list of Block
objects with an empty list:
def main():
global blocks
blocks = [] # BuildBlocks()
victory_mode = False
running = True
Now the screen starts with no blocks: