Files
beerpsi_x/chuni/calc_title_server_enc_key.py
2026-04-19 23:43:43 +07:00

129 lines
4.0 KiB
Python

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pefile",
# ]
# ///
from hashlib import pbkdf2_hmac
import os
import sys
import re
import pefile
def rva2offset(pe: pefile.PE, rva: int):
for section in pe.sections:
if section.contains_rva(rva):
return section.get_offset_from_rva(rva)
return -1
def bytes_to_int32_le(input: bytes):
return input[0] | (input[1] << 8) | (input[2] << 16) | (input[3] << 24)
def read_c_string(input: bytes, offset: int):
end = offset + input[offset:].index(0)
return input[offset:end]
if len(sys.argv) < 2:
print(f"Usage: python {os.path.basename(__file__)} <PATH TO EXE>")
exit(1)
exe_path = sys.argv[1]
INITIALIZE_ENCRYPTION_CALL_RE = re.compile(
rb"\xFF\xB3(?P<iter_count_offset>....)\xFF\xB3(?P<salt_password_offset>....)\xFF\x35(?P<key_password_offset>....)\xFF\xB3(?P<encryption_version_offset>....)\x8D\x4F.\xE8"
)
KEY_SALT_RE = re.compile(
rb"\x50\x6A(?P<keylen>\x20)\x6A(?P<iterations>.)\x6A(?P<saltlen>\x10)\x2B\xCA\x68(?P<offset>....)\x51\x55\xE8...."
)
SALT_SALT_RE = re.compile(
rb"\x52\x6A(?P<keylen>\x08)\x6A(?P<iterations>.)\x6A(?P<saltlen>\x10)\x68(?P<offset>....)\x51\x53\xE8...."
)
IV_RE_1 = re.compile(rb"\xF3\x0F\x7E\x05(?P<offset>....)\x8B\x74\x24\x24\x6A\x01")
IV_RE_2 = re.compile(rb"\xE8....\xF3\x0F\x7E\x05(?P<offset>....)\x6A\x01")
ITER_COUNT_RE = re.compile(
rb"\xC7\x86....(?P<count>....)\x0F\x8C....\x85\xED\x0F\x84....\x85\xDB\x0F\x84...."
)
with open(exe_path, "rb") as f:
exe = f.read()
pe = pefile.PE(data=exe, fast_load=True)
base_address = pe.OPTIONAL_HEADER.ImageBase
if (
(match := INITIALIZE_ENCRYPTION_CALL_RE.search(exe))
and (ksmatch := KEY_SALT_RE.search(exe))
and (ssmatch := SALT_SALT_RE.search(exe))
and ((ivmatch := IV_RE_1.search(exe)) or (ivmatch := IV_RE_2.search(exe)))
):
iter_count_offset = rva2offset(
pe, bytes_to_int32_le(match.group("iter_count_offset")) - base_address
)
iter_count = bytes_to_int32_le(exe[iter_count_offset:])
salt_password_offset_offset = rva2offset(
pe, bytes_to_int32_le(match.group("salt_password_offset")) - base_address
)
salt_password_offset = rva2offset(
pe, bytes_to_int32_le(exe[salt_password_offset_offset:]) - base_address
)
salt_password = read_c_string(exe, salt_password_offset)
salt_salt_offset = rva2offset(
pe, bytes_to_int32_le(ssmatch.group("offset")) - base_address
)
salt_salt = exe[salt_salt_offset : salt_salt_offset + ssmatch.group("saltlen")[0]]
salt = pbkdf2_hmac(
"sha1", salt_password, salt_salt, ssmatch.group("iterations")[0], dklen=8
)
key_password_offset_offset = rva2offset(
pe, bytes_to_int32_le(match.group("key_password_offset")) - base_address
)
key_password_offset = rva2offset(
pe, bytes_to_int32_le(exe[key_password_offset_offset:]) - base_address
)
key_password = read_c_string(exe, key_password_offset)
key_salt_offset = rva2offset(pe, bytes_to_int32_le(ksmatch.group("offset")) - base_address)
key_salt = exe[key_salt_offset:key_salt_offset + ksmatch.group("saltlen")[0]]
key = pbkdf2_hmac(
"sha1", key_password, key_salt, ksmatch.group("iterations")[0], dklen=32
)
key = bytes((x % 0x5E) + 0x21 for x in key)
encryption_version_offset = rva2offset(
pe, bytes_to_int32_le(match.group("encryption_version_offset")) - base_address
)
encryption_version = exe[encryption_version_offset]
ivoffset = rva2offset(pe, bytes_to_int32_le(ivmatch.group("offset")) - base_address)
iv = exe[ivoffset : ivoffset + 16]
print("=> Encryption version:", encryption_version)
print()
print("Encryption key password:", key_password)
print("Encryption key salt:", key_salt.hex())
print("=> Key:", key.hex())
print()
print("=> IV:", iv.hex())
print()
print("Endpoint salt password:", salt_password)
print("Endpoint salt salt:", salt_salt.hex())
print("=> Endpoint salt:", salt.hex())
print()
print("=> Iterations:", iter_count)