The Challenge

So this challenge was fun, easy and had lots of was to complete. First we are greeted with:

Challenge 01 briefing

After downloading frog.7z, we can list the file contents:

$7z l frog.7z 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,22 CPUs Intel(R) Core(TM) Ultra 9 185H (A06A4),ASM,AES-NI)

Scanning the drive for archives:
1 file, 10653489 bytes (11 MiB)

Listing archive: frog.7z


Enter password (will not be echoed):
--
Path = frog.7z
Type = 7z
Physical Size = 10653489
Headers Size = 529
Method = LZMA2:24 BCJ 7zAES
Solid = +
Blocks = 2

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2024-09-25 21:44:33 D....            0            0  fonts
2024-09-25 21:44:33 D....            0            0  img
2024-09-25 21:50:25 ....A          334       450752  README.txt
2024-09-25 18:39:48 ....A         4367               fonts/SIL Open Font License.txt
2024-09-25 18:39:48 ....A       153116               fonts/VT323-Regular.ttf
2024-09-25 21:38:02 ....A         7776               frog.py
2024-09-25 15:37:07 ....A          380               img/block.png
2024-09-25 16:03:26 ....A        61714               img/f11_statue.png
2024-09-25 16:36:39 ....A          266               img/floor.png
2024-09-25 16:04:49 ....A         3800               img/frog.png
2024-09-25 19:38:34 ....A       344691               img/win.png
2024-09-25 21:42:32 ....A     12966519     10202208  frog.exe
------------------- ----- ------------ ------------  ------------------------
2024-09-25 21:50:25           13542963     10652960  10 files, 2 folders

So we can solve this challenge either by using python, or by looking at a binary? Cool.

Frog.py

Let’s start by using python. When we start the game we see, just like described in the introduction, a frog on a mission to get to the statue.

Start of Game

Start of Game

However, there is no apparant way to get there because of the walls around the trophy.

The python code

Alright, now let’s look at frog.py:

import pygame

pygame.init()
pygame.font.init()
screen_width = 800
screen_height = 600
tile_size = 40
tiles_width = screen_width // tile_size
tiles_height = screen_height // tile_size
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
victory_tile = pygame.Vector2(10, 10)

pygame.key.set_repeat(500, 100)
pygame.display.set_caption('Non-Trademarked Yellow Frog Adventure Game: Chapter 0: Prelude')
dt = 0

floorimage = pygame.image.load("img/floor.png")
blockimage = pygame.image.load("img/block.png")
frogimage = pygame.image.load("img/frog.png")
statueimage = pygame.image.load("img/f11_statue.png")
winimage = pygame.image.load("img/win.png")

gamefont = pygame.font.Font("fonts/VT323-Regular.ttf", 24)
text_surface = gamefont.render("instruct: Use arrow keys or wasd to move frog. Get to statue. Win game.",
                               False, pygame.Color('gray'))
flagfont = pygame.font.Font("fonts/VT323-Regular.ttf", 32)
flag_text_surface = flagfont.render("nope@nope.nope", False, pygame.Color('black'))

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

blocks = []



player = Frog(0, 1)

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


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])

def main():
    global blocks
    blocks = BuildBlocks()
    victory_mode = False
    running = True
    while running:
        # poll for events
        # pygame.QUIT event means the user clicked X to close your window
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w or event.key == pygame.K_UP:
                    AttemptPlayerMove(0, -1)
                elif event.key == pygame.K_s or event.key == pygame.K_DOWN:
                    AttemptPlayerMove(0, 1)
                elif event.key == pygame.K_a or event.key == pygame.K_LEFT:
                    AttemptPlayerMove(-1, 0)
                elif event.key == pygame.K_d or event.key == pygame.K_RIGHT:
                    AttemptPlayerMove(1, 0)

        # draw the ground
        for i in range(tiles_width):
            for j in range(tiles_height):
                screen.blit(floorimage, (i*tile_size, j*tile_size))

        # display the instructions
        screen.blit(text_surface, (0, 0))

        # draw the blocks
        for block in blocks:
            block.draw(screen)

        # draw the statue
        screen.blit(statueimage, (240, 240))

        # draw the frog
        player.draw(screen)

        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)
        else:
            screen.blit(winimage, (150, 50))
            screen.blit(flag_text_surface, (239, 320))

        # flip() the display to put your work on screen
        pygame.display.flip()

        # limits FPS to 60
        # dt is delta time in seconds since last frame, used for framerate-
        # independent physics.
        dt = clock.tick(60) / 1000

    pygame.quit()
    return

def BuildBlocks():
    blockset = [
        Block(3, 2, False),
        Block(4, 2, False),
        Block(5, 2, False),
        Block(6, 2, False),
        Block(7, 2, False),
        Block(8, 2, False),
        Block(9, 2, False),
        Block(10, 2, False),
        Block(11, 2, False),
        Block(12, 2, False),
        Block(13, 2, False),
        Block(14, 2, False),
        Block(15, 2, False),
        Block(16, 2, False),
        Block(17, 2, False),
        Block(3, 3, False),
        Block(17, 3, False),
        Block(3, 4, False),
        Block(5, 4, False),
        Block(6, 4, False),
        Block(7, 4, False),
        Block(8, 4, False),
        Block(9, 4, False),
        Block(10, 4, False),
        Block(11, 4, False),
        Block(14, 4, False),
        Block(15, 4, True),
        Block(16, 4, False),
        Block(17, 4, False),
        Block(3, 5, False),
        Block(5, 5, False),
        Block(11, 5, False),
        Block(14, 5, False),
        Block(3, 6, False),
        Block(5, 6, False),
        Block(11, 6, False),
        Block(14, 6, False),
        Block(15, 6, False),
        Block(16, 6, False),
        Block(17, 6, False),
        Block(3, 7, False),
        Block(5, 7, False),
        Block(11, 7, False),
        Block(17, 7, False),
        Block(3, 8, False),
        Block(5, 8, False),
        Block(11, 8, False),
        Block(15, 8, False),
        Block(16, 8, False),
        Block(17, 8, False),
        Block(3, 9, False),
        Block(5, 9, False),
        Block(11, 9, False),
        Block(12, 9, False),
        Block(13, 9, False),
        Block(15, 9, False),
        Block(3, 10, False),
        Block(5, 10, False),
        Block(13, 10, True),
        Block(15, 10, False),
        Block(16, 10, False),
        Block(17, 10, False),
        Block(3, 11, False),
        Block(5, 11, False),
        Block(6, 11, False),
        Block(7, 11, False),
        Block(8, 11, False),
        Block(9, 11, False),
        Block(10, 11, False),
        Block(11, 11, False),
        Block(12, 11, False),
        Block(13, 11, False),
        Block(17, 11, False),
        Block(3, 12, False),
        Block(17, 12, False),
        Block(3, 13, False),
        Block(4, 13, False),
        Block(5, 13, False),
        Block(6, 13, False),
        Block(7, 13, False),
        Block(8, 13, False),
        Block(9, 13, False),
        Block(10, 13, False),
        Block(11, 13, False),
        Block(12, 13, False),
        Block(13, 13, False),
        Block(14, 13, False),
        Block(15, 13, False),
        Block(16, 13, False),
        Block(17, 13, False)
    ]
    return blockset


if __name__ == '__main__':
    main()
    

So we see that this is indeed a Python script creating a simple game using the Pygame library. The game features a player-controlled frog that must navigate to a statue to win the game. Here’s a breakdown of the code:

Imports and Initialization

The script imports the pygame module and initializes it along with the font module.

import pygame

pygame.init()
pygame.font.init()

Constants and Setup

Screen dimensions and tile size are defined. The game window is created with the specified width and height. A clock is created to manage the game’s frame rate. The victory tile’s position is set using a pygame.Vector2 object. Keyboard repeat settings are configured to allow holding down keys for movement. The window’s caption is set.

screen_width = 800
screen_height = 600
tile_size = 40
tiles_width = screen_width // tile_size
tiles_height = screen_height // tile_size
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
victory_tile = pygame.Vector2(10, 10)

pygame.key.set_repeat(500, 100)
pygame.display.set_caption('Non-Trademarked Yellow Frog Adventure Game: Chapter 0: Prelude')
dt = 0

Images for the floor, blocks, frog, statue, and win screen are loaded.

floorimage = pygame.image.load("img/floor.png")
blockimage = pygame.image.load("img/block.png")
frogimage = pygame.image.load("img/frog.png")
statueimage = pygame.image.load("img/f11_statue.png")
winimage = pygame.image.load("img/win.png")

Two fonts are initialized, and text surfaces for instructions and an email address are created.

gamefont = pygame.font.Font("fonts/VT323-Regular.ttf", 24)
text_surface = gamefont.render("instruct: Use arrow keys or wasd to move frog. Get to statue. Win game.",
                               False, pygame.Color('gray'))
flagfont = pygame.font.Font("fonts/VT323-Regular.ttf", 32)
flag_text_surface = flagfont.render("nope@nope.nope", False, pygame.Color('black'))

Classes

Block: Represents an obstacle in the game. It has an image, a position, and a flag indicating whether it is passable. Frog: Represents the player’s character. It has an image, a position, and methods for drawing and moving.

class Block(pygame.sprite.Sprite):
    # ... class definition ...

class Frog(pygame.sprite.Sprite):
    # ... class definition ...

Game Variables

An empty list blocks is initialized to store block objects. A Frog object player is created, representing the player’s character.

blocks = []
player = Frog(0, 1)

Functions

AttemptPlayerMove(dx, dy): Attempts to move the player in the specified direction, checking for screen bounds and collisions with non-passable blocks. GenerateFlagText(x, y): Generates a string by XORing a hardcoded encoded string with a key derived from the player’s position. This is likely used for some sort of puzzle or secret within the game. main(): The main game loop that handles events, updates the game state, and renders the game to the screen. It also checks for victory conditions and displays the win screen if the player reaches the victory tile. BuildBlocks(): Creates and returns a list of Block objects that represent the level’s layout.

def AttemptPlayerMove(dx, dy):
    # ... function definition ...

def GenerateFlagText(x, y):
    # ... function definition ...

def main():
    # ... function definition ...

def BuildBlocks():
    # ... function definition ...

Main Game Loop

The game loop handles user input, updates the game state, and renders the game. It checks for QUIT events to close the game and key presses to move the player. This part of the loop checks for events such as the user clicking the window’s close button (QUIT event) or pressing a key (KEYDOWN event). If the close button is clicked, the game will stop running. If a key is pressed, the game checks which key it is and calls AttemptPlayerMove with the appropriate direction to move the player.

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        running = False
    if event.type == pygame.KEYDOWN:
        if event.key in (pygame.K_w, pygame.K_UP):
            AttemptPlayerMove(0, -1)
        elif event.key in (pygame.K_s, pygame.K_DOWN):
            AttemptPlayerMove(0, 1)
        elif event.key in (pygame.K_a, pygame.K_LEFT):
            AttemptPlayerMove(-1, 0)
        elif event.key in (pygame.K_d, pygame.K_RIGHT):
            AttemptPlayerMove(1, 0)

The game world is drawn, including the floor, blocks, statue, and frog. The screen is first cleared with a black color. Then, the game world is drawn tile by tile, starting with the floor. After that, obstacles (blocks) and the victory tile (statue) are drawn. Finally, the player’s character (frog) is drawn on top.

screen.fill((0, 0, 0))  # Clear the screen with a black color
for y in range(tiles_height):
    for x in range(tiles_width):
        screen.blit(floorimage, (x * tile_size, y * tile_size))
for block in blocks:
    screen.blit(block.image, block.rect)
screen.blit(statueimage, (victory_tile.x * tile_size, victory_tile.y * tile_size))
screen.blit(player.image, player.rect)

If the player reaches the victory tile, the game enters victory mode, and a flag text is generated and printed to the console. The game checks if the player’s position matches the victory tile’s position. If it does, the victory screen is displayed, a flag text is generated using the GenerateFlagText function, and the flag text is printed to the console. The game loop is then stopped.

if player.rect.topleft == (victory_tile.x * tile_size, victory_tile.y * tile_size):
    screen.blit(winimage, (0, 0))
    flag_text = GenerateFlagText(player.rect.x, player.rect.y)
    print(flag_text)
    running = False

The display is updated with pygame.display.flip(), and the frame rate is capped at 60 FPS. pygame.display.flip() updates the entire screen with everything that’s been drawn during this iteration of the loop. The clock.tick(60) call ensures that the game runs at no more than 60 frames per second (FPS), which keeps the game running at a consistent speed across different hardware.

pygame.display.flip()
dt = clock.tick(60)

This code provides a basic framework for a tile-based puzzle game. The player can move the frog around the screen, avoiding blocks and trying to reach the victory tile. The game includes a simple victory condition and a secret message. Cool, now that we have spent way too much looking at the code, let’ s try to solve it.

Obtaining the flag

So there are many ways to solve this one, when solving puzzles I like to think of different approaches and solutions. I will provide a couple of possible solutions, some more creative than others.

Solution 1: No game needed

For example, we see the function 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])

This expects two arguments, when we look where this function is called:

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)

So we need to figure out what the values of victory_tile are. We can see that they are set by:

victory_tile = pygame.Vector2(10, 10)

So you can create your stand alone program to get the flag.

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])

print(GenerateFlagText(10,10))

Solution 2: Blocks-IT

Another way to solve this is to remove the walls, we see a big block of walls:

    blockset = [
        Block(3, 2, False),
        Block(4, 2, False),
        Block(5, 2, False),
        ...
        Block(15, 13, False),
        Block(16, 13, False),
        Block(17, 13, False)
    ]

They represent the gray blocks where the frog can’t go to. We can just remove most of the walls to make sure the frog is able to walk to the trophy. We can slightly modify the original code of the main function:

Getting the flag was never this easy:

Challenge 01 Solution 2
def main():

    custom_blocks = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 9], [3, 4], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [3, 10], [4, 4], [4, 5], [4, 6], [4, 7], [4, 8], [4, 9], [4, 10], [4, 11], [5, 4], [5, 5], [5, 6], [5, 7], [5, 8], [5, 9], [5, 10], [5, 11], [5, 12], [6, 4], [6, 5], [6, 6], [6, 7], [6, 8], [6, 9], [6, 10], [6, 11], [6, 12], [6, 13], [7, 4], [7, 5], [7, 6], [7, 7], [7, 8], [7, 9], [7, 10], [7, 11], [7, 12], [7, 13], [7,14], [8, 4], [8, 5], [8, 6], [8, 7], [8, 8], [8, 9], [8, 10], [8, 11], [8, 12], [8, 13], [8,14], [9, 4], [9, 5], [9, 6], [9, 7], [9, 9], [9, 10], [9, 11], [9, 12], [9,13], [9,14], [10, 4], [10, 5], [10, 6], [10, 11], [10,12], [10,13], [11, 4], [11, 5], [11, 6], [11,11], [11,12], [12, 4], [12, 5], [12, 6], [12, 7], [13, 4], [13, 5], [13, 6], [13, 7], [13, 8], [13, 9], [14, 3], [14, 4], [14, 5], [14, 6], [14, 7], [14, 8], [14, 9], [15,2], [15, 3], [15, 4], [15, 5], [15, 6], [15, 7], [16,1], [16, 2], [16, 3]]
    global blocks
    blocks = BuildBlocks(custom_blocks)
    ...

def BuildBlocks(coordinates):
    new_blocks = []  # Start with an empty list for the new blocks
    for x, y in coordinates:  # Unpack each pair of coordinates
        new_block = Block(x, y, False)  # Create a new Block
        new_blocks.append(new_block)  # Append the new Block to the list
    return new_blocks  # Return the list of new Block objects


if __name__ == '__main__':
    main()

Making it easy to just walk towards the flag.

Solution 3: Play to win

In the first solution we read the position the player needs to be in to trigger the flag decryption. We can also set our starting point to that location:

victory_tile = Frog(10,10)

Solution 4: No blocks? No problem

We can also remove all the blocks, making it easier for the frog to get to the statue:


def BuildBlocks():
    blockset = [
    ]
    return blockset


if __name__ == '__main__':
    main()

And there we go:

Challenge 01 Solution 4

Solution 5: Not all blocks are False

When we take another look at the original code, especially the blockset, we see that there are two blocks (13,10) and (15,4) which are set to True instead of False, i.e. you can just walk through them!

And so without the game code we can just walk towards the statue:

Challenge 01 Solution 5

Solution 6: Cheatcodes enabled

We can use some GTA2 love to unlock all levels win the game. So when you type ’tumyfrog’ while playing the game, you will see the flag:

def main():
    global blocks
    blocks = BuildBlocks()
    victory_mode = False
    running = True
    entered_text = ""
    while running:
        # poll for events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w or event.key == pygame.K_UP:
                    AttemptPlayerMove(0, -1)
                elif event.key == pygame.K_s or event.key == pygame.K_DOWN:
                    AttemptPlayerMove(0, 1)
                elif event.key == pygame.K_a or event.key == pygame.K_LEFT:
                    AttemptPlayerMove(-1, 0)
                elif event.key == pygame.K_d or event.key == pygame.K_RIGHT:
                    AttemptPlayerMove(1, 0)
                elif event.unicode.isalpha():  # Check if the key pressed is a letter
                    entered_text += event.unicode  # Add the letter to the entered text
                    if entered_text.lower().endswith("tumyfrog"):  
                      print(entered_text)
                      player.x = int(victory_tile.x)  # Teleport player to victory tile x position
                      player.y = int(victory_tile.y)  # Teleport player to victory tile y position
                      player.rect.topleft = (player.x * tile_size, player.y * tile_size)  # Update player rect position

And so, if we use the classic frog themed cheatcode:

Challenge 01 Solution 6

Solution 7: Press t to teleport

We can modify the game, such that when a key is pressed, the player is teleports to a random location:

def TeleportPlayer():
    while True:
        # Generate a random position within the bounds of the screen
        random_x = random.randint(0, tiles_width - 1)
        random_y = random.randint(0, tiles_height - 1)
        
        # Check if the random position is not occupied by a block
        position_occupied = any(block.x == random_x and block.y == random_y and not block.passable for block in blocks)
        
        # If the position is free, move the player to that position
        if not position_occupied:
            player.x = random_x
            player.y = random_y
            player.rect.top = player.y * tile_size
            player.rect.left = player.x * tile_size
            break  # Exit the loop once a valid position is found

def main():
    global blocks
    blocks = BuildBlocks()
    victory_mode = False
    running = True
    print(victory_tile.x)
    while running:
        # poll for events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w or event.key == pygame.K_UP:
                    AttemptPlayerMove(0, -1)
                elif event.key == pygame.K_s or event.key == pygame.K_DOWN:
                    AttemptPlayerMove(0, 1)
                elif event.key == pygame.K_a or event.key == pygame.K_LEFT:
                    AttemptPlayerMove(-1, 0)
                elif event.key == pygame.K_d or event.key == pygame.K_RIGHT:
                    AttemptPlayerMove(1, 0)
                elif event.key == pygame.K_t:  # Check if 'T' key is pressed
                    TeleportPlayer()

It only takes 380 key presses:

Challenge 01 Solution 7

And there are many, many more solutions to think of. We can also see if we can play around with the binary.

Frog.exe

Now, let’ s look at the executable.

Determining the used language

$ file frog.exe
frog.exe: PE32 executable (console) Intel 80386, for MS Windows

That does not tell us that much, did the creator by any chance use py2exe or a similar tool to create an executable? Let’s hunt for references to python:

$ strings frog.exe | grep -i python
pyi-python-flag
Reported length (%d) of Python shared library name (%s) exceeds buffer size (%d)
Path of Python shared library (%s) and its name (%s) exceed buffer size (%d)
Failed to pre-initialize embedded python interpreter!
Failed to allocate PyConfig structure! Unsupported python version?
Failed to set python home path!
Failed to start embedded python interpreter!
bpython38.dll
4python38.dll

Cool. We can now use pyinstxtractor.py to verify this claim.

Still python?

$ python3 pyinstxtractor.py frog.exe
[+] Processing frog.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.8
[+] Length of package: 12677239 bytes
[+] Found 220 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: pyi_rth_setuptools.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: game.pyc
[+] Found 493 files in PYZ archive
[+] Successfully extracted pyinstaller archive: frog.exe

When we look at the produced files:

$ ls
_asyncio.pyd                        libcrypto-1_1.dll  libtiff-5.dll            pyimod01_archive.pyc         python38.dll          setuptools
base_library.zip                    libffi-7.dll       libwebp-7.dll            pyimod02_importers.pyc       PYZ-00.pyz            _socket.pyd
_bz2.pyd                            libjpeg-9.dll      _lzma.pyd                pyimod03_ctypes.pyc          PYZ-00.pyz_extracted  _ssl.pyd
_ctypes.pyd                         libmodplug-1.dll   _multiprocessing.pyd     pyimod04_pywin32.pyc         _queue.pyd            struct.pyc
_decimal.pyd                        libogg-0.dll       _overlapped.pyd          pyi_rth_inspect.pyc          SDL2.dll              typeguard-4.3.0.dist-info
freetype.dll                        libopus-0.dll      portmidi.dll             pyi_rth_multiprocessing.pyc  SDL2_image.dll        unicodedata.pyd
game.pyc                            libopusfile-0.dll  pyexpat.pyd              pyi_rth_pkgres.pyc           SDL2_mixer.dll        VCRUNTIME140.dll
_hashlib.pyd                        libpng16-16.dll    pygame                   pyi_rth_pkgutil.pyc          SDL2_ttf.dll          wheel-0.43.0.dist-info
importlib_metadata-8.5.0.dist-info  libssl-1_1.dll     pyiboot01_bootstrap.pyc  pyi_rth_setuptools.pyc       select.pyd            zlib1.dll

Nice, we can now use a python decompiler on the pyc files within the extracted directory. For example by using uncompyle6:

$ uncompyle6 -o ../ game.pyc

-- Stacks of completed symbols:
START ::= |- stmts . 
_come_froms ::= \e__come_froms . COME_FROM
_come_froms ::= \e__come_froms . COME_FROM_LOOP
_come_froms ::= \e__come_froms COME_FROM . 


...
# file game.pyc
# Deparsing stopped due to parse error
game.pyc -- 
# decompile failed

Even though it states it failed, we are presented with:

# uncompyle6 version 3.9.2
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.8.13 (default, Sep 29 2024, 13:55:46) 
# [GCC 11.4.0]
# Embedded file name: game.py
import pygame
pygame.init()
pygame.font.init()
screen_width = 800
screen_height = 600
tile_size = 40
tiles_width = screen_width // tile_size
tiles_height = screen_height // tile_size
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
victory_tile = pygame.Vector2(10, 10)
pygame.key.set_repeat(500, 100)
pygame.display.set_caption("Non-Trademarked Yellow Frog Adventure Game: Chapter 0: Prelude")
dt = 0
floorimage = pygame.image.load("img/floor.png")
blockimage = pygame.image.load("img/block.png")
frogimage = pygame.image.load("img/frog.png")
statueimage = pygame.image.load("img/f11_statue.png")
winimage = pygame.image.load("img/win.png")
gamefont = pygame.font.Font("fonts/VT323-Regular.ttf", 24)
text_surface = gamefont.render("instruct: Use arrow keys or wasd to move frog. Get to statue. Win game.", False, pygame.Color("gray"))
flagfont = pygame.font.Font("fonts/VT323-Regular.ttf", 32)
flag_text_surface = flagfont.render("nope@nope.nope", False, pygame.Color("black"))

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


blocks = []
player = Frog(0, 1)

def AttemptPlayerMoveParse error at or near `RETURN_VALUE' instruction at offset 112


def GenerateFlagText(x, y):
    key = x + y * 20
    encoded = "¥·¾±½¿·\x8d¦½\x8dãã\x92´¾³\xa0·ÿ½¼ü±½¿"
    return "".join([chr(ord(c) ^ key) for c in encoded])


def main():
    global blocks
    blocks = BuildBlocks()
    victory_mode = False
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_w or event.key == pygame.K_UP:
                AttemptPlayerMove(0, -1)
            elif event.key == pygame.K_s or event.key == pygame.K_DOWN:
                AttemptPlayerMove(0, 1)
            elif event.key == pygame.K_a or event.key == pygame.K_LEFT:
                AttemptPlayerMove(-1, 0)
            elif event.key == pygame.K_d or event.key == pygame.K_RIGHT:
                AttemptPlayerMove(1, 0)
        for i in range(tiles_width):
            for j in range(tiles_height):
                screen.blit(floorimage, (i * tile_size, j * tile_size))
            else:
                screen.blit(text_surface, (0, 0))
                for block in blocks:
                    block.draw(screen)
                else:
                    screen.blit(statueimage, (240, 240))
                    player.draw(screen)

            if not victory_mode:
                if player.x == victory_tile.x:
                    if 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)
                    else:
                        screen.blit(winimage, (150, 50))
                        screen.blit(flag_text_surface, (239, 320))
                pygame.display.flip()
                dt = clock.tick(60) / 1000

    pygame.quit()


def BuildBlocks():
    blockset = [
     Block(3, 2, False),
     Block(4, 2, False),
     Block(5, 2, False),
     Block(6, 2, False),
     Block(7, 2, False),
     Block(8, 2, False),
     Block(9, 2, False),
     Block(10, 2, False),
     Block(11, 2, False),
     Block(12, 2, False),
     Block(13, 2, False),
     Block(14, 2, False),
     Block(15, 2, False),
     Block(16, 2, False),
     Block(17, 2, False),
     Block(3, 3, False),
     Block(17, 3, False),
     Block(3, 4, False),
     Block(5, 4, False),
     Block(6, 4, False),
     Block(7, 4, False),
     Block(8, 4, False),
     Block(9, 4, False),
     Block(10, 4, False),
     Block(11, 4, False),
     Block(14, 4, False),
     Block(15, 4, True),
     Block(16, 4, False),
     Block(17, 4, False),
     Block(3, 5, False),
     Block(5, 5, False),
     Block(11, 5, False),
     Block(14, 5, False),
     Block(3, 6, False),
     Block(5, 6, False),
     Block(11, 6, False),
     Block(14, 6, False),
     Block(15, 6, False),
     Block(16, 6, False),
     Block(17, 6, False),
     Block(3, 7, False),
     Block(5, 7, False),
     Block(11, 7, False),
     Block(17, 7, False),
     Block(3, 8, False),
     Block(5, 8, False),
     Block(11, 8, False),
     Block(15, 8, False),
     Block(16, 8, False),
     Block(17, 8, False),
     Block(3, 9, False),
     Block(5, 9, False),
     Block(11, 9, False),
     Block(12, 9, False),
     Block(13, 9, False),
     Block(15, 9, False),
     Block(3, 10, False),
     Block(5, 10, False),
     Block(13, 10, True),
     Block(15, 10, False),
     Block(16, 10, False),
     Block(17, 10, False),
     Block(3, 11, False),
     Block(5, 11, False),
     Block(6, 11, False),
     Block(7, 11, False),
     Block(8, 11, False),
     Block(9, 11, False),
     Block(10, 11, False),
     Block(11, 11, False),
     Block(12, 11, False),
     Block(13, 11, False),
     Block(17, 11, False),
     Block(3, 12, False),
     Block(17, 12, False),
     Block(3, 13, False),
     Block(4, 13, False),
     Block(5, 13, False),
     Block(6, 13, False),
     Block(7, 13, False),
     Block(8, 13, False),
     Block(9, 13, False),
     Block(10, 13, False),
     Block(11, 13, False),
     Block(12, 13, False),
     Block(13, 13, False),
     Block(14, 13, False),
     Block(15, 13, False),
     Block(16, 13, False),
     Block(17, 13, False)]
    return blockset


if __name__ == "__main__":
    main() 

This looks very much like the version we have been playing before. So we can insert the same hacks here as we did with the python version. Note though, this retrieved version is not playable, since it contains (fixable) errors, for example:

def AttemptPlayerMoveParse error at or near `RETURN_VALUE' instruction at offset 112


def GenerateFlagText(x, y):
    key = x + y * 20
    encoded = "¥·¾±½¿·\x8d¦½\x8dãã\x92´¾³\xa0·ÿ½¼ü±½¿"
    return "".join([chr(ord(c) ^ key) for c in encoded])

That’s not a real problem, we can work that out if we wanted to. More important is that we confirmed that the python version and the executable version are probably based on the same source. So, all in all, many ways to obtain the flag.