Tags: godot reverse 

Rating: 5.0

By opening the game from a terminal, it can be seen that the game has been developed with [Godot Engine](https://godotengine.org/), an open source game engine:

Godot Engine v4.2.1.stable.custom_build - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 517.48 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce MX250

[gdsdecomp ](https://github.com/bruvzg/gdsdecomp)is a reverse engineering tools for games written with Godot Engine. It is able to recover a full project from the executable. Executables produced by Godot embed a "PCK" (packed) archive. gdsdecomp is able to locate that archive, extract it, and even decompile its embedded GDScripts.

Opening game.exe with gdsdecomp gives the following error message:

> Error opening encrypted PCK file: C:/Users/xx/Downloads/game.exe
> Set correct encryption key and try again.

The PCK archive is encrypted. Its encryption key must be retrieved. As Godot Engine has to extract this archive to run the game, the encryption key must be present somewhere in the binary. By quickly looking at the project source code, I found a location where thus key is manipulated and easy to retrieve with a debugger:

bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
if (f.is_null()) {
return false;

bool pck_header_found = false;

// Search for the header at the start offset - standalone PCK file.
// ...
if (enc_directory) {
Ref<FileAccessEncrypted> fae;
ERR_FAIL_COND_V_MSG(fae.is_null(), false, "Can't open encrypted pack directory.");

Vector<uint8_t> key;
for (int i = 0; i < key.size(); i++) {
key.write[i] = script_encryption_key[i];

Godot Engine has a verbose logging system. Error strings can be easily located in the binary. Breaking just after the reference to the "Can't open encrypted pack directory." message allows to directly dump the value of `script_encryption_key`.

Key value is DFE5729E1243D4F7778F546C0E1EE8CC532104963FF73604FFB3234CE5052B27.

Enter key in "RE Tools" --> "Set encryption key", then extract project.

Project contains 6 GD files: Beach.gd, Bullet.gd, Enemy.gd, EnemySpawner.gd, Main.gd and Player.gd. They are small and can be quickly analyzed. The interesting function for the challenge is `_unhandled_input`, located in Main.gd:

func teleport():

func _unhandled_input(event):
if event is InputEventKey:
if event.pressed and event.keycode == KEY_ESCAPE:
if event.pressed and event.keycode == KEY_G and power == 0:
power += 1
elif event.pressed and event.keycode == KEY_M and power == 1:
power += 1
elif event.pressed and event.keycode == KEY_T and power == 2:
power += 1
elif event.pressed and event.keycode == KEY_F and power == 3:
power += 1
elif event.pressed and event.keycode == KEY_L and power == 4:
power += 1
elif event.pressed and event.keycode == KEY_A and power == 5:
power += 1
elif event.pressed and event.keycode == KEY_G and power == 6:
elif event.pressed:
power = 0

It checks for keyboard input events. If the good combination is entered, the main scene is replaced with the "Beach" scene thanks to the `teleport` function. The `_ready` function prints with a timer each char of the flag. As each char is put in a specific location, difficult to identify by just reading the source code, it is preferable to get the flag directly from the game, rather than by reading the code.

Open game, enter "gmtflag" and a new screen displays the flag.

Flag is THCON{60d07_D3cryp7}