flare24-frog-cover

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:

  1. Install Python 3
  2. Install PyGame (“pip install pygame”)
  3. Run frog: “python frog.py”

Run It

Running the .exe on Windows opens a GUI window:

image-20241101145439055

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: