Skip to content

Instantly share code, notes, and snippets.

@leegao
Created April 24, 2026 14:57
Show Gist options
  • Select an option

  • Save leegao/bb2a87cca36c39052e8ef3ca9fb890cb to your computer and use it in GitHub Desktop.

Select an option

Save leegao/bb2a87cca36c39052e8ef3ca9fb890cb to your computer and use it in GitHub Desktop.
Unpacking gamblers-table.pck (reverse engineering)
import hashlib
import io
import os
import struct
from Crypto.Cipher import AES
def unpack_and_decrypt_pck(pck_path, hex_key):
key = bytes.fromhex(hex_key)
# key = 'generate_aes_key(b"\0" * 16)'
print("key", key)
output_dir = "decrypted_assets"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
with open(pck_path, "rb") as f:
f.seek(0x18)
file_offset = struct.unpack("<I", f.read(4))[0]
f.seek(0x20)
dir_offset = struct.unpack("<Q", f.read(8))[0]
total_size = os.path.getsize(pck_path)
f.seek(dir_offset)
entries = []
for _ in range(10000):
if f.tell() + 4 >= total_size:
break
file_id = struct.unpack("<I", f.read(4))[0]
path_len = struct.unpack("<I", f.read(4))[0]
path = f.read(path_len).decode("utf-8", errors="ignore").strip("\x00")
offset, size = struct.unpack("<QQ", f.read(16))
f.seek(16, 1)
entries.append((path, offset, size))
entries.sort(key=lambda x: x[1])
for i, (path, offset, size) in enumerate(entries):
print(path, offset + file_offset)
f.seek(offset + file_offset)
end = dir_offset if i + 1 >= len(entries) else entries[i + 1][1]
raw_data = f.read(end - offset)
decrypted_data = decrypt_file_payload(raw_data, key)
clean_path = path.replace("res://", "").replace("/", os.sep)
full_out_path = os.path.join(output_dir, clean_path)
os.makedirs(os.path.dirname(full_out_path), exist_ok=True)
print(
"At: ",
hex(offset + file_offset),
"rs",
size,
"ds",
len(decrypted_data),
full_out_path,
)
with open(full_out_path, "wb") as out_f:
out_f.write(decrypted_data)
print(f"[+] Extracted: {path}")
# break
def decrypt_file_payload(raw_payload, key):
iv = raw_payload[0:16]
print(iv)
length = int.from_bytes(raw_payload[16:24], "little")
print(length)
ciphertext = raw_payload[
24: # 24 + 16 + length
]
cipher = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128)
plaintext = cipher.decrypt(ciphertext)
print(len(plaintext), len(ciphertext))
return plaintext[16 : length + 16]
MY_PCK_FILE = "sample.pck"
MY_SECRET_KEY = "10 8A C8 C5 A2 93 6A 41 0C 92 67 DE D4 24 0D BB 62 5E 1B 52 3C A9 9A 5E F7 B2 E7 41 33 B1 2F 1D".replace(
" ", ""
)
# Look for 0x64646464 followed by 0x47474747 in the disassembly
unpack_and_decrypt_pck(MY_PCK_FILE, MY_SECRET_KEY)
@leegao

leegao commented Apr 24, 2026

Copy link
Copy Markdown
Author

Changes to the .pck:

  1. the central directory is located at the end (dir_offset is at +0x20)
  2. each file is custom encoded
  3. the encryption key is no longer hard-coded, instead it's deterministically calculated, but it stays the same for each entry

There's a way to just port the algorithm to calculate the key over, but it's far easier to just dump it from memory.

bool FUN_142730e20(undefined8 *param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4,
                  undefined8 param_5)

{
  undefined8 uVar1;
  undefined8 uVar2;
  int iVar3;
  undefined1 (*_Memory) [16];
  undefined1 (*_Memory_00) [16];
  undefined1 auVar4 [16];
  undefined1 auVar5 [16];
  undefined1 auVar6 [16];
  undefined1 auVar7 [16];
  undefined1 auVar8 [16];
  undefined1 auVar9 [16];
  undefined1 auVar10 [16];
  undefined1 auVar11 [16];
  undefined8 local_50 [4];
  
  uVar1 = param_1[1];
  uVar2 = param_1[2];
  local_50[0] = 0;
  _Memory = (undefined1 (*) [16])FUN_144006110(0x20);
  *(undefined8 *)*_Memory = uVar1;
  *(undefined8 *)(*_Memory + 8) = uVar2;
  *(undefined8 *)(_Memory[1] + 8) = 0xa973d7143cda0d77;
  *(undefined8 *)_Memory[1] = 0x42ee09cd50646d07;
  _Memory_00 = (undefined1 (*) [16])FUN_144006110(0x20);
  uVar1 = *param_1;
  auVar4._8_4_ = 0x64646464;
  auVar4._0_8_ = 0x6464646464646464;
  auVar4._12_4_ = 0x64646464;
  auVar4 = *_Memory ^ auVar4;
  auVar5._8_4_ = 0x47474747;
  auVar5._0_8_ = 0x4747474747474747;
  auVar5._12_4_ = 0x47474747;
  auVar5 = _Memory[1] ^ auVar5;
  auVar8._8_4_ = 0xc0c0c0c0;
  auVar8._0_8_ = 0xc0c0c0c0c0c0c0c0;
  auVar8._12_4_ = 0xc0c0c0c0;
  auVar6._0_2_ = auVar5._0_2_ >> 2;
  auVar6._2_2_ = auVar5._2_2_ >> 2;
  auVar6._4_2_ = auVar5._4_2_ >> 2;
  auVar6._6_2_ = auVar5._6_2_ >> 2;
  auVar6._8_2_ = auVar5._8_2_ >> 2;
  auVar6._10_2_ = auVar5._10_2_ >> 2;
  auVar6._12_2_ = auVar5._12_2_ >> 2;
  auVar6._14_2_ = auVar5._14_2_ >> 2;
  auVar10 = psllw(auVar5,6);
  auVar11._8_4_ = 0x3f3f3f3f;
  auVar11._0_8_ = 0x3f3f3f3f3f3f3f3f;
  auVar11._12_4_ = 0x3f3f3f3f;
  auVar9[1] = auVar4[1] * '\x02';
  auVar9[0] = auVar4[0] * '\x02';
  auVar9[2] = auVar4[2] * '\x02';
  auVar9[3] = auVar4[3] * '\x02';
  auVar9[4] = auVar4[4] * '\x02';
  auVar9[5] = auVar4[5] * '\x02';
  auVar9[6] = auVar4[6] * '\x02';
  auVar9[7] = auVar4[7] * '\x02';
  auVar9[8] = auVar4[8] * '\x02';
  auVar9[9] = auVar4[9] * '\x02';
  auVar9[10] = auVar4[10] * '\x02';
  auVar9[0xb] = auVar4[0xb] * '\x02';
  auVar9[0xc] = auVar4[0xc] * '\x02';
  auVar9[0xd] = auVar4[0xd] * '\x02';
  auVar9[0xe] = auVar4[0xe] * '\x02';
  auVar9[0xf] = auVar4[0xf] * '\x02';
  *_Memory_00 = auVar6 & auVar11 | auVar8 & auVar10;
  auVar10._0_2_ = auVar4._0_2_ >> 7;
  auVar10._2_2_ = auVar4._2_2_ >> 7;
  auVar10._4_2_ = auVar4._4_2_ >> 7;
  auVar10._6_2_ = auVar4._6_2_ >> 7;
  auVar10._8_2_ = auVar4._8_2_ >> 7;
  auVar10._10_2_ = auVar4._10_2_ >> 7;
  auVar10._12_2_ = auVar4._12_2_ >> 7;
  auVar10._14_2_ = auVar4._14_2_ >> 7;
  auVar7._8_4_ = 0x1010101;
  auVar7._0_8_ = 0x101010101010101;
  auVar7._12_4_ = 0x1010101;
  _Memory_00[1] = auVar10 & auVar7 | auVar9;
  // <<< break here and dump _Memory_00 (at RSI) to get the real AES key
  FUN_140c69380(uVar1,_Memory_00,0x100); // Set the aes key for the aes context
  // The following is the actual decrypt call
  iVar3 = FUN_140c6b4f0(*param_1,0,param_2,local_50,param_3,param_4,param_5);
  free(_Memory_00);
  free(_Memory);
  return iVar3 != 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment