Entropic Labyrinth

Eh4x CTFby smothy

Entropic Labyrinth - EHAX CTF 2026 Writeup

Category: Miscellaneous | Points: 500 | Solves: 17 | Author: Ravager

Note: We solved this challenge after the CTF ended as we were participating in two CTFs simultaneously and couldn't allocate time during the live event.


Challenge Description

I made my first game today but once i compiled it i lost track of all of it. Now it's just full of brainrot and I don't wish to play it anymore ;-;

Hint (Free): Cake or fake

Attachment: game.exe (PyInstaller-packed Windows executable)


Overview

The challenge provides a PyInstaller-packed Pygame application. The goal is to reverse engineer the binary, extract the Python source code, and recover a hidden flag encrypted within the game's code.

Solution Flow

game.exe ──> pyinstxtractor ──> game.pyc ──> bytecode analysis ──> XOR decrypt ──> FLAG

Step 1: Identifying the Binary

Running file on the binary reveals it's a PyInstaller-packed PE32+ executable:

bash
$ file game.exe
game.exe: PE32+ executable (GUI) x86-64, for MS Windows

PyInstaller bundles Python applications into standalone executables. The binary contains a bootloader + a zlib-compressed archive of all Python modules.

PyInstaller Structure PyInstaller packages Python scripts, dependencies, and a bootloader into a single executable.


Step 2: Extracting the Python Bytecode

We use pyinstxtractor to unpack the .exe and recover the embedded .pyc files:

bash
$ python3 pyinstxtractor.py game.exe
[+] Processing game.exe
[+] Found PYZ archive
[+] Extracting PYZ archive
[+] Successfully extracted pyinstaller archive: game.exe_extracted

This gives us the main game bytecode:

game.exe_extracted/ game.pyc <-- Main game logic PYZ.pyz_extracted/ <-- Standard library modules pygame/ <-- Pygame assets python3XX.dll <-- Python runtime ...

Step 3: Decompiling the Bytecode

Since the binary uses Python 3.11/3.12, standard decompilers may have limited support. We use Python's built-in dis module to disassemble the bytecode and manually reconstruct the source:

python
import marshal, dis

with open('game.pyc', 'rb') as f:
    f.read(16)  # Skip pyc header
    code = marshal.loads(f.read())

dis.dis(code)  # Full disassembly

Reconstructed Source (Key Parts)

The decompiled code reveals a Pygame game with "brainrot" variable names (Gen-Z memes):

python
import pygame, sys, random

DOCTOR_WHO = '525f234f6c50235a24482644485545275c24596a'
Alpha_wolf = 'VGhlIEZsYWcgYWluJ3Qg...'  # Base64: "The Flag ain't gonna be this easy to find vro"
Beta_wolf = "Don't look here...EH4X(F4K3_FL4G)...You scrolled too far"

class Ballin:
    def __init__(self, x, y, wilted_rose, homies):
        self.rect = pygame.Rect(x, y, wilted_rose, homies)

    def Siks_Sayven(self):
        return self.rect.width // (self.rect.height or 1)

def Ohio_Skibidi_brainrot_decryption(fine_shyte, sigma):
    """The REAL decryption function - simple XOR!"""
    raw_bytes = bytes.fromhex(fine_shyte)
    return ''.join(chr(b ^ sigma) for b in raw_bytes)

def cake_or_fake(fine_shyte, Ballins):
    """RED HERRING - produces non-deterministic fake output"""
    random.shuffle(Ballins)
    for boomer in Ballins:
        fine_shyte = (fine_shyte - boomer.Siks_Sayven()) % 256
        fine_shyte ^= boomer.Siks_Sayven()
    return fine_shyte

def main():
    # ... game initialization ...
    skibidi_key = 23  # <-- XOR key hidden in constants!

    # When player reaches goal:
    fake_flag = ''.join(chr(cake_or_fake(b, Ballins.copy())) for b in raw)
    # Displays: "FLAG: {fake_flag}" + "Reality drift detected."

Step 4: Understanding the Cipher

The challenge contains two decryption paths - a red herring and the real one:

Red Herring: cake_or_fake()

This function uses random.shuffle() making the output non-deterministic. It's called in the game to produce a "fake flag" every time the player reaches the goal. The variable is literally named fake_flag and the game displays "Reality drift detected."

Real Decoder: Ohio_Skibidi_brainrot_decryption()

This function performs a simple single-byte XOR decryption:

Ciphertext (hex) ──> bytes.fromhex() ──> XOR each byte with key ──> Plaintext

XOR Symmetric Encryption XOR encryption is symmetric: the same key encrypts AND decrypts. plaintext XOR key = ciphertext and ciphertext XOR key = plaintext.

XOR Key Operation Each byte of the ciphertext is XOR'd with the same key byte (23) to recover the plaintext.


Step 5: Extracting the Flag

From the bytecode constants, we extract:

VariableValue
DOCTOR_WHO525f234f6c50235a24482644485545275c24596a
skibidi_key23

Decryption Script

python
#!/usr/bin/env python3
DOCTOR_WHO = '525f234f6c50235a24482644485545275c24596a'
skibidi_key = 23

raw = bytes.fromhex(DOCTOR_WHO)
flag = ''.join(chr(b ^ skibidi_key) for b in raw)
print(f'Flag: {flag}')

XOR Breakdown

Hex: 52 5f 23 4f 6c 50 23 5a 24 48 26 44 48 55 45 27 5c 24 59 6a Key: 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 XOR: 45 48 34 58 7b 47 34 4d 33 5f 31 53 5f 42 52 30 4b 33 4e 7d ASCII: E H 4 X { G 4 M 3 _ 1 S _ B R 0 K 3 N }

Flag

EH4X{G4M3_1S_BR0K3N}

The flag itself is self-referential: "G4M3 1S BR0K3N" (Game is broken) - a nod to the fact that the game's cake_or_fake function is intentionally broken and can never produce a valid flag!


Key Takeaways

  1. Don't fall for red herrings: The cake_or_fake() function with random.shuffle() is intentionally non-deterministic - it can NEVER produce a consistent flag. The hint "Cake or fake" is telling you the output is fake.

  2. Check all code paths: The real decryption function Ohio_Skibidi_brainrot_decryption() was defined but NOT called in the game loop. The flag was hidden in the unused function.

  3. Brainrot naming is just obfuscation: All variable names (Ohio_Skibidi, Siks_Sayven, giggity, etc.) are Gen-Z meme terms used purely for confusion.

  4. The game is an impossible escape room: The player starts INSIDE a closed rectangular box made of invisible walls. The walls randomly mutate, making escape nearly impossible - hence "I don't wish to play it anymore."


Tools Used

  • pyinstxtractor - PyInstaller archive extractor
  • Python dis module - Bytecode disassembler
  • Python marshal module - Code object loader

Solved post-CTF. We were simultaneously competing in two CTFs and couldn't dedicate time to this challenge during the live event.