Writeup: Minecraft Enterprise Edition - upCTF (Reverse)
Challenge Description
My company recently acquired a limited number of Minecraft Enterprise Edition keys to reward top-performing employees. Sadly, I didn't make the cut. I managed to get my hands on the internal activation program they use to validate these licenses. Will you help me go around management and generate myself a valid key?
Initial Analysis
$ file minecraft-enterprise
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
Statically linked, stripped binary. Searching for strings reveals the key format and validation flow:
Enter Key (format: XXXXX-XXXXX-XXXXX-XXXXX):
Key parsing error.
Invalid Key.
flag.txt
Flag: %s
@@IMNOTTHEKEY
ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 <-- Base32 alphabet
Reverse Engineering the Validation
The main function lives at 0x402cd0. The validation pipeline has 5 stages:
Stage 1: Parse Key (0x4031d0)
- Verifies input length is exactly 23 characters
- Checks dashes at positions 6, 12, 18 (1-indexed)
- Validates each non-dash character is alphanumeric
- Applies
toupper()to each character (confirmed via GDB - chars stay uppercase) - Outputs 20 uppercase alphanumeric characters:
p[0..19]
Stage 2: Fixed Seed Computation (lines 0x402d87-0x402dd4)
A fixed computation using the AES S-box table at 0x7e24c0:
seed = 0x12345678
for i in range(64):
a = ROL32(seed, i)
a ^= seed
a = (a >> (i & 7)) & 0xFF
a = SBOX[a]
seed = a + seed
Result: seed = 0xde43e410 (constant, independent of input).
Stage 3: Transform Key (0x403170)
Two byte-shuffling operations on the 20 parsed characters:
- Swap halves:
chars[0:10] <-> chars[10:20] - Swap adjacent pairs: For each pair
(i, i+1), swap the two bytes
Final mapping (original position -> transformed position):
transformed = [p11,p10,p13,p12,p15,p14,p17,p16,p19,p18, p1,p0,p3,p2,p5,p4,p7,p6,p9,p8]
|__________ first_10 __________________| |_________ last_10 ______________|
Stage 4: HMAC-SHA256 + Base32 (0x4030a0)
The first 10 characters of the transformed key are hashed:
digest = HMAC-SHA256(key="IMNOTTHEKEY", data=first_10_transformed)
The hash function was identified as SHA256 by:
0x4037a0returns anEVP_MDpointer at0x88d340- Brute-forcing all common hash functions against a known input/output pair from GDB
The first 7 bytes of the HMAC digest are converted to a 10-character Base32 string:
val = int.from_bytes(digest[:7], 'big') >> 6 # 50 bits
for i in range(10): # 10 chars x 5 bits = 50 bits
char = BASE32_ALPHA[val & 0x1f]
val >>= 5
# Written right-to-left (big-endian base32)
Stage 5: Comparison (0x402e54)
The Base32-encoded HMAC result must equal the last 10 characters of the transformed key:
HMAC_base32(first_10_transformed) == last_10_transformed
Key Generation
To forge a valid key:
- Choose any 10 uppercase alphanumeric characters for
first_10_transformed - Compute
HMAC-SHA256("IMNOTTHEKEY", first_10)and Base32-encode to getlast_10 - Reverse the byte shuffling to recover
p[0..19] - Format as
XXXXX-XXXXX-XXXXX-XXXXX
import hmac, hashlib
BASE32_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
def hmac_to_base32_10(data):
h = hmac.new(b"IMNOTTHEKEY", data, hashlib.sha256).digest()
val = int.from_bytes(h[:7], 'big') >> 6
result = []
for _ in range(10):
result.append(BASE32_ALPHA[val & 0x1f])
val >>= 5
result.reverse()
return ''.join(result)
first_10 = "AAAAAAAAAA"
last_10 = hmac_to_base32_10(first_10.encode()) # -> "3CX463HTMX"
t, h = list(first_10), list(last_10)
p = [''] * 20
p[11],p[10],p[13],p[12],p[15],p[14],p[17],p[16],p[19],p[18] = t
p[1],p[0],p[3],p[2],p[5],p[4],p[7],p[6],p[9],p[8] = h
key = '-'.join([''.join(p[i:i+5]) for i in range(0, 20, 5)])
print(f"Key: {key}") # C34X3-6THXM-AAAAA-AAAAA
Solution
$ echo 'C34X3-6THXM-AAAAA-AAAAA' | nc <host> <port>
Enter Key (format: XXXXX-XXXXX-XXXXX-XXXXX): Flag: UPCTF{...}
Key Debugging Moment
Initial attempt used HMAC-SHA1 (misidentified from nearby "SHA1" strings in the EVP_MD struct). Using QEMU + GDB to inspect the actual HMAC output at the comparison breakpoint (0x402e54) revealed the mismatch, and testing all common hash functions against the known pair confirmed SHA256.
The binary also uses x32 mode switching (visible via addr32 instruction prefixes and strace showing alternating x32/64-bit mode), which prevented native GDB debugging - QEMU user-mode emulation with gdb-multiarch was required.
Flag
upCTF{m1n3cr4ft_0n_th3_b4nks-7jWw48VRc83b39fd}