$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: Extract the flag from an intentionally unsolvable SFML Sokoban game binary. Solution: Reverse engineer the binary to find TEA-encrypted ciphertext in .rdata, derive the 4-integer decryption key from X-mark target pixel positions on the game grid (using tile size and offset formulas), and decrypt with standard TEA 32-round decryption.
Sokoban is a great logic game, just push the boxes on X marks and win the flag! Oh wait, someone placed the box outside the walls...
Challenge provides a Windows PE64 executable (SokobanHTB.exe) built with SFML (C++) and three PNG assets (player.png, X.png, box.png). The game implements a classic Sokoban box-pushing puzzle, but one box is placed outside the playable grid, making the puzzle intentionally unsolvable through normal gameplay.
Flag format: HTB{...}
file SokobanHTB.exe # PE32+ executable (console) x86-64, for MS Windows strings SokobanHTB.exe | grep -i "sokoban\|player\|box\|font\|flag" # player.png, X.png, box.png # C:\Windows\Fonts\arial.ttf # Sokoban HTB
The binary is a standard Windows console application using SFML for graphics rendering.
Disassembly revealed the Sokoban map stored at 0x1400b8e10 in the .rdata section as 32-bit integers. The grid is 7 columns x 8 rows:
Map values: 0=empty, 1=wall, 2=X-mark(target), 3=box, 4=player
Row 0: . # # # # . .
Row 1: # # P X # . . <- Player(P) at (2,1), Target(X) at (3,1)
Row 2: # . B . # . . <- Box at (2,2)
Row 3: # . B . # # # <- Box at (2,3)
Row 4: # . B # . . # <- Box at (2,4)
Row 5: # X . . . X # <- Targets at (1,5) and (5,5)
Row 6: # # . . # # #
Row 7: . # # # # . .
Grid rendering parameters:
The trick: One box sprite is placed at pixel position (1024, 410) -- far outside the grid boundaries. This makes the puzzle unsolvable through normal gameplay, hinting that the intended approach is reverse engineering.
Found the flag decryption function at 0x140003f00. Analysis of the algorithm revealed standard TEA (Tiny Encryption Algorithm):
0x9E3779B9 (golden ratio fractional part, stored as negative 0x61c88647)0xC6EF3720 (= delta * 32)Ciphertext (10 dwords = 40 bytes, embedded in .rdata):
0xea0109d5, 0xc8ee03d2, 0x553f3fc8, 0xa8b34cd9,
0x3631ad3e, 0x98689219, 0x4e883f78, 0x082e3c84,
0x9a6a6cc0, 0x5ab5c6a4
The TEA key consists of 4 integers derived from the pixel positions of the X-mark targets on the game grid:
| Key Index | Value | Derivation |
|---|---|---|
| key[0] | 154 | Y of target at (col=3, row=1): 1*64 + 90 = 154 |
| key[1] | 512 | X of target at (col=3, row=1): 3*64 + 320 = 512 |
| key[2] | 384 | X of target at (col=1, row=5): 1*64 + 320 = 384 |
| key[3] | 640 | X of target at (col=5, row=5): 5*64 + 320 = 640 |
The key is [154, 512, 384, 640].
#!/usr/bin/env python3 """ SokobanHTB -- TEA decryption with game-coordinate-derived key. """ import struct def tea_decrypt(v, key): """Standard TEA decryption (32 rounds).""" v0, v1 = v delta = 0x9E3779B9 s = (delta * 32) & 0xFFFFFFFF # 0xC6EF3720 for _ in range(32): v1 = (v1 - (((v0 << 4) + key[2]) ^ (v0 + s) ^ ((v0 >> 5) + key[3]))) & 0xFFFFFFFF v0 = (v0 - (((v1 << 4) + key[0]) ^ (v1 + s) ^ ((v1 >> 5) + key[1]))) & 0xFFFFFFFF s = (s - delta) & 0xFFFFFFFF return v0, v1 # Ciphertext from .rdata section ct = [ 0xea0109d5, 0xc8ee03d2, 0x553f3fc8, 0xa8b34cd9, 0x3631ad3e, 0x98689219, 0x4e883f78, 0x082e3c84, 0x9a6a6cc0, 0x5ab5c6a4 ] # Key derived from X-mark target pixel positions # target(3,1): Y=1*64+90=154, X=3*64+320=512 # target(1,5): X=1*64+320=384 # target(5,5): X=5*64+320=640 key = [154, 512, 384, 640] # Decrypt 8 bytes at a time (5 blocks) plaintext = b"" for i in range(0, len(ct), 2): v0, v1 = tea_decrypt((ct[i], ct[i+1]), key) plaintext += struct.pack("<II", v0, v1) flag = plaintext.decode('utf-8', errors='replace').rstrip('\x00') print(flag) # HTB{REDACTED}
0x9E3779B9)col * tile_size + x_offset / row * tile_size + y_offset is a common pattern in game pwn challenges$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar