← Back to Writeups
CUSTOMMediumReversing

My First Game

XESXOR9/2/20266 min read
#reversing#custom#medium#godot_pck_extraction#xor_payload_recovery#pe_header_restoration#inverse_byte_transform

My First Game

Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: godot_pck_extraction, xor_payload_recovery, pe_header_restoration, inverse_byte_transform

Summary

Walkthrough of the CUSTOM Reversing My First Game. Task: a Godot 4 Windows game executable with an embedded PCK archive, misleading assets, and a fake PNG hiding another PE file. Solution: extracted the PCK with corrected offsets, recovered the hidden

Description

Моя первая игра

English summary: we were given a URL to a ZIP archive. The archive contained a single file, flappy_bird_forest_edition.exe, which turned out to be a Godot 4.0.1 self-contained Windows executable with an embedded PCK resource section.

Analysis

Step 1: Preparation and initial recon

The archive was downloaded into resources/hackerlab/moya_pervaya_igra.zip and extracted into tasks/hackerlab/moya_pervaya_igra/.

Basic triage showed:

  • file: PE32+ executable GUI x86-64 for Windows
  • strings: Godot markers, res:// paths, GDScript filenames, sigma.png, and CODEBY_*.svg
  • 7z l: a PE section named pck ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

That immediately suggested a Godot game with the original project data still embedded inside the executable.

Step 2: Extracting the Godot PCK

The pck PE section was extracted with 7z. Its header began with GDPC, which confirmed a Godot PCK container. The header also showed:

  • format version: 2
  • engine version: 4.0.1
  • flags: 0 (not encrypted)
  • file count: 42

Normal archive tools could not unpack it correctly, so a custom Python parser was used.

The important implementation detail was offset handling. The PCK file entries used offsets relative to the embedded section layout, so file_base had to be corrected by subtracting the raw file offset of the PE pck section, obtained with pefile. Without that correction, extracted files were empty or wrong. After applying the correction, the full Godot project unpacked cleanly. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 3: Source clues inside the unpacked project

The extracted project contained the most useful hints.

Player.gd included:

gdscript
# Use this key: "SupEr_S2crET_C0deBY_X0r_Key"
# https://cyberchef.org/#recipe=XOR(...)

The same script also referenced res://sigma.png and, after a score threshold, displayed the message: Not bad. The key will be in the EXE file.

Ground.gd added another clue:

gdscript
# The size of the required file is about 20 KB

The suspicious file was sigma.png. It was exactly 20480 bytes, matching the hint, but it was not a valid PNG. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 4: Recovering the hidden payload

sigma.png was XORed with the repeating key SupEr_S2crET_C0deBY_X0r_Key. The result, xor_sigma.bin, started with MZ, so it clearly hid a Windows executable, but it was still corrupted and would not parse as a valid PE.

The repair trick was to zero every byte that matched the repeating key byte at the same position:

python
from pathlib import Path

key = b"SupEr_S2crET_C0deBY_X0r_Key"
data = bytearray(Path("xor_sigma.bin").read_bytes())
for i, b in enumerate(data):
    if b == key[i % len(key)]:
        data[i] = 0
Path("patched_zero.bin").write_bytes(data)

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

After this repair, the payload became a valid PE32+ x86-64 console program. In particular:

  • e_lfanew = 0x80
  • the PE\x00\x00 signature appeared at offset 0x80

This repair is slightly lossy: if an original byte was already equal to the keystream byte, the procedure cannot distinguish it from a byte that should be restored to zero. That ambiguity mattered later.

Step 5: Reversing the restored PE

strings and disassembly of patched_zero.bin showed the first part of the flag directly in .data at virtual address 0x404060: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

text
CODEBY{REDACTED

The rest of the flag was checked in function fcn.00401890. Each input byte x was transformed as follows before comparison:

c
y = x;
y ^= 0x27;
y += 0x57;
y ^= 0x64;
y -= 0x19;
y ^= 0x22;
y -= 0x41;
y ^= 0x36;

The transformed 22-byte suffix was compared against constants stored at:

  • 0x404080 (qword)
  • 0x404088 (qword)
  • 0x404090 (dword)
  • 0x404094 (word)

The raw recovered comparison bytes were:

text
59 a7 45 59 66 ba 5a a3 59 b5 a9 a4 a7 59 40 7a ac 00 60 00 5b ba

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Two bytes were ambiguous because of the zero-restoration step described above. Using the nonzero alternatives at those positions produced the coherent transformed suffix bytes:

text
59 a7 45 59 66 ba 5a a3 59 b5 a9 a4 a7 59 40 7a ac 65 60 59 5b ba

Inverting the byte transformation decoded the suffix:

text
REDACTED

Combining both parts gave the final flag:

text
CODEBY{REDACTED}

Note on references

The local knowledge base was checked for a matching Godot/PCK reverse writeup, but there was no exact match. HackTricks reversing references were also reviewed, but the solve itself required custom PCK extraction and manual reverse engineering of the recovered PE. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

  1. Download the ZIP archive and extract flappy_bird_forest_edition.exe.
  2. Confirm it is a PE32+ Windows GUI executable and inspect strings for Godot indicators.
  3. List PE sections with 7z and extract the embedded pck section.
  4. Parse the GDPC PCK header and correct file offsets by subtracting the raw PE offset of the pck section.
  5. Unpack the Godot project and inspect Player.gd and Ground.gd.
  6. Identify sigma.png as the fake 20 KB asset and XOR it with SupEr_S2crET_C0deBY_X0r_Key.
  7. Repair the partially corrupted PE by zeroing bytes equal to the repeating key byte.
  8. Reverse the repaired PE: read the first flag fragment from .data, then invert the suffix transform from fcn.00401890.
  9. Resolve the two ambiguous transformed bytes with the nonzero alternatives and assemble the final flag. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
python
#!/usr/bin/env python3
from pathlib import Path

KEY = b"SupEr_S2crET_C0deBY_X0r_Key"
SIGMA = Path("unpacked/sigma.png")


def xor_repeat(data: bytes, key: bytes) -> bytes:
    return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))


def repair_zero_bytes(data: bytes, key: bytes) -> bytes:
    out = bytearray(data)
    for i, b in enumerate(out):
        if b == key[i % len(key)]:
            out[i] = 0
    return bytes(out)


def inverse_transform(y: int) -> int:
    y ^= 0x36
    y = (y + 0x41) & 0xFF
    y ^= 0x22
    y = (y + 0x19) & 0xFF
    y ^= 0x64
    y = (y - 0x57) & 0xFF
    y ^= 0x27
    return y
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍


def main() -> None:
    sigma = SIGMA.read_bytes()
    xor_sigma = xor_repeat(sigma, KEY)
    Path("xor_sigma.bin").write_bytes(xor_sigma)

    repaired = repair_zero_bytes(xor_sigma, KEY)
    Path("patched_zero.bin").write_bytes(repaired)

    first_part = b"CODEBY{REDACTED"

    # Raw compare bytes recovered from the restored PE had two ambiguous positions.
    # Using the nonzero alternatives gives the real transformed suffix bytes.
    transformed_suffix = bytes.fromhex(
        "59 a7 45 59 66 ba 5a a3 59 b5 a9 a4 a7 59 40 7a ac 65 60 59 5b ba"
    )
    suffix = bytes(inverse_transform(b) for b in transformed_suffix)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    flag = first_part + suffix + b"}"
    print(flag.decode())


if __name__ == "__main__":
    main()

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Takeaways / Lessons

  • Mastering godot_pck_extraction, xor_payload_recovery, pe_header_restoration, inverse_byte_transform was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-04-06 • CUSTOM • My First Game • Difficulty: Medium