My First Game
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 Windowsstrings: Godot markers,res://paths, GDScript filenames,sigma.png, andCODEBY_*.svg7z l: a PE section namedpck
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 read-only • copy allowed# 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 read-only • copy allowed# 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 read-only • copy allowedfrom 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\x00signature appeared at offset0x80
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 read-only • copy allowedCODEBY{REDACTED
The rest of the flag was checked in function fcn.00401890. Each input byte x was transformed as follows before comparison:
c read-only • copy allowedy = 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 read-only • copy allowed59 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 read-only • copy allowed59 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 read-only • copy allowedREDACTED
Combining both parts gave the final flag:
text read-only • copy allowedCODEBY{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
- Download the ZIP archive and extract
flappy_bird_forest_edition.exe. - Confirm it is a PE32+ Windows GUI executable and inspect strings for Godot indicators.
- List PE sections with
7zand extract the embeddedpcksection. - Parse the
GDPCPCK header and correct file offsets by subtracting the raw PE offset of thepcksection. - Unpack the Godot project and inspect
Player.gdandGround.gd. - Identify
sigma.pngas the fake 20 KB asset and XOR it withSupEr_S2crET_C0deBY_X0r_Key. - Repair the partially corrupted PE by zeroing bytes equal to the repeating key byte.
- Reverse the repaired PE: read the first flag fragment from
.data, then invert the suffix transform fromfcn.00401890. - Resolve the two ambiguous transformed bytes with the nonzero alternatives and assemble the final flag.
python read-only • copy allowed#!/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