Начинающий
Начинающий
- Статус
- Оффлайн
- Регистрация
- 18 Май 2025
- Сообщения
- 51
- Реакции
- 2
Я не нашел ничего подобного так что решил написать сам, использование скрипта "py decrypt.py "C:\VK Play\Warface\Game\GameData.pak" out_dir"
Код:
Зависимости:
pip install pycryptodome twofish
Код:
Python:
import os
import struct
import sys
import zlib
import hashlib
from Crypto.PublicKey import RSA
import twofish
RSA_PUBLIC_KEY = bytes.fromhex(
"308189028181009b606931dcf7027a4dc0e5263b4ad0d8f4a492a16e4b5ec0850f074b4c3da627ff"
"96676d2379f89062de6c917f268cbd822404d26d9d79bcb0182d4c96eeaf2b918a0300bfb8161962"
"2d1556b4e02d16fe0c7ed72c01ee429c4c849c6a786bcec44d6c50cb914648bb662d0ba235680002"
"d4605058d1c30da11237822a01f2ef0203010001"
)
XXTEA_KEY = [0xC968FB67, 0x8F9B4267, 0x85399E84, 0xF9B99DC4]
XXTEA_DELTA = 0x9E3779B9
MASK32 = 0xFFFFFFFF
def _bswap32(v):
return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) | ((v & 0xFF0000) >> 8) | ((v >> 24) & 0xFF)
def xxtea_decrypt(data):
if len(data) < 8 or len(data) % 4 != 0:
return data
n = len(data) // 4
v = [_bswap32(x) for x in struct.unpack(f"<{n}I", data)]
rounds = 52 // n + 6
total = (rounds * XXTEA_DELTA) & MASK32
y = v[0]
for _ in range(rounds):
e = (total >> 2) & 3
for p in range(n - 1, 0, -1):
z = v[p - 1]
mx = (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((total ^ y) + (XXTEA_KEY[(p & 3) ^ e] ^ z))) & MASK32
v[p] = (v[p] - mx) & MASK32
y = v[p]
z = v[n - 1]
mx = (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((total ^ y) + (XXTEA_KEY[e] ^ z))) & MASK32
v[0] = (v[0] - mx) & MASK32
y = v[0]
total = (total - XXTEA_DELTA) & MASK32
return struct.pack(f"<{n}I", *[_bswap32(x) for x in v])
def twofish_ctr_decrypt(data, key, iv):
tf = twofish.Twofish(key)
ctr = bytearray(iv)
out = bytearray()
for i in range(0, len(data), 16):
ks = tf.encrypt(bytes(ctr))
chunk = data[i:i + 16]
out.extend(a ^ b for a, b in zip(chunk, ks))
for j in range(16):
ctr[j] = (ctr[j] + 1) & 0xFF
if ctr[j]:
break
return bytes(out)
def _mgf1_sha1(seed, length):
out = b""
for i in range((length + 19) // 20):
out += hashlib.sha1(seed + struct.pack(">I", i)).digest()
return out[:length]
def _oaep_decode(em):
hlen = 20
if len(em) < 2 * hlen + 2:
return None
seed = bytes(a ^ b for a, b in zip(em[1:1 + hlen], _mgf1_sha1(em[1 + hlen:], hlen)))
db = bytes(a ^ b for a, b in zip(em[1 + hlen:], _mgf1_sha1(seed, len(em) - hlen - 1)))
if db[:hlen] != hashlib.sha1(b"").digest():
return None
idx = hlen
while idx < len(db) and db[idx] == 0x00:
idx += 1
if idx >= len(db) or db[idx] != 0x01:
return None
return db[idx + 1:]
def rsa_verify_and_unwrap(blob, key):
modulus_size = (key.n.bit_length() + 7) // 8
em = pow(int.from_bytes(blob, "big"), key.e, key.n).to_bytes(modulus_size, "big")
return _oaep_decode(em)
def build_file_iv(compressed, uncompressed, crc):
iv = bytearray(16)
struct.pack_into("<I", iv, 0, (uncompressed ^ (compressed << 12)) & MASK32)
struct.pack_into("<I", iv, 4, 0 if compressed else 1)
struct.pack_into("<I", iv, 8, (crc ^ (compressed << 12)) & MASK32)
struct.pack_into("<I", iv, 12, (compressed ^ (0 if uncompressed else 1)) & MASK32)
return bytes(iv)
CDR_SIG = 0x02014B50
EOCD_SIG = b"\x50\x4B\x05\x06"
ENC_TYPE2 = 2
class PakFile:
def __init__(self, path):
with open(path, "rb") as f:
self.data = f.read()
self.enc_type = 0
self.enc_headers = False
self.cdr_iv = None
self.cdr_key = None
self.block_keys = [None] * 16
self._eocd = None
self._eocd_off = 0
self._enc_cdr_blob = None
self._enc_block_blobs = []
def parse(self):
if not self._find_eocd():
print("eocd not found")
return False
if not self._parse_extended_header():
print("bad extended header")
return False
if self.enc_type == ENC_TYPE2:
if not self._decrypt_keys():
return False
return True
def _find_eocd(self):
search_from = max(0, len(self.data) - 65557)
for i in range(len(self.data) - 22, search_from - 1, -1):
if self.data[i:i + 4] != EOCD_SIG:
continue
comment_len = struct.unpack_from("<H", self.data, i + 20)[0]
if i + 22 + comment_len != len(self.data):
continue
fields = struct.unpack_from("<IHHHHIIH", self.data, i)
disk = fields[1]
self.enc_headers = bool(disk >> 15)
self._eocd = {
"disk": disk & 0x7FFF,
"cdr_disk": fields[2],
"entries_disk": fields[3],
"entries": fields[4],
"cdr_size": fields[5],
"cdr_offset": fields[6],
"comment_len": fields[7],
}
self._eocd_off = i
return True
return False
def _parse_extended_header(self):
base = self._eocd_off + 22
if self._eocd["comment_len"] < 8:
return True
ext_size, enc_type = struct.unpack_from("<II", self.data, base)
if ext_size != 8:
return False
self.enc_type = enc_type
if self._eocd["comment_len"] < 0x93C:
return True
hdr_size = struct.unpack_from("<I", self.data, base + 8)[0]
if hdr_size != 2356:
return False
h = base + 8
self.cdr_iv = self.data[h + 0xA4 : h + 0xA4 + 16]
self._enc_cdr_blob = self.data[h + 0xB4 : h + 0xB4 + 128]
keys_base = h + 0x134
self._enc_block_blobs = [self.data[keys_base + i * 128 : keys_base + i * 128 + 128] for i in range(16)]
return True
def _decrypt_keys(self):
rsa = RSA.import_key(RSA_PUBLIC_KEY)
self.cdr_key = rsa_verify_and_unwrap(self._enc_cdr_blob, rsa)
if not self.cdr_key or len(self.cdr_key) != 16:
print(f"cdr key unwrap failed ({len(self.cdr_key) if self.cdr_key else 0} bytes)")
return False
for i, blob in enumerate(self._enc_block_blobs):
key = rsa_verify_and_unwrap(blob, rsa)
if not key or len(key) != 16:
print(f"block key {i} unwrap failed")
return False
self.block_keys[i] = key
return True
def entries(self):
raw = bytearray(self.data[self._eocd["cdr_offset"] : self._eocd["cdr_offset"] + self._eocd["cdr_size"]])
if self.enc_headers:
if self.enc_type == ENC_TYPE2:
raw = bytearray(twofish_ctr_decrypt(bytes(raw), self.block_keys[0], self.cdr_iv))
else:
raw = bytearray(xxtea_decrypt(bytes(raw)))
result = []
pos = 0
for _ in range(self._eocd["entries"]):
if pos + 46 > len(raw):
break
sig = struct.unpack_from("<I", raw, pos)[0]
if sig != CDR_SIG:
print(f"bad cdr sig at {pos:#x}: {sig:#010x}")
break
f = struct.unpack_from("<IHHHHHHIIIHHHHHII", raw, pos)
fname_len = f[10]
extra_len = f[11]
comment_len = f[12]
local_off = f[16]
name = raw[pos + 46 : pos + 46 + fname_len].decode("utf-8", errors="replace")
data_off = local_off + 30 + fname_len + extra_len
result.append({
"name": name,
"method": f[4],
"comp_size": f[8],
"uncomp_size": f[9],
"crc32": f[7],
"data_off": data_off,
})
pos += 46 + fname_len + extra_len + comment_len
return result
def extract(self, entry):
if entry["comp_size"] == 0:
return b""
raw = self.data[entry["data_off"] : entry["data_off"] + entry["comp_size"]]
method = entry["method"]
if self.enc_type == ENC_TYPE2:
if method in (11, 12):
iv = build_file_iv(entry["comp_size"], entry["uncomp_size"], entry["crc32"])
idx = (~((entry["crc32"] >> 2) & 0xFF)) & 0x0F
raw = twofish_ctr_decrypt(raw, self.block_keys[idx], iv)
elif method == 11:
raw = xxtea_decrypt(raw)
if method == 0:
return raw
if entry["comp_size"] == entry["uncomp_size"]:
return raw
try:
return zlib.decompress(raw, -15)
except zlib.error as e:
print(f"decompress failed for {entry['name']}: {e}")
return None
def main():
if len(sys.argv) < 2:
print("usage: pak_decrypt.py <file.pak> [output_dir]")
return
input_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) >= 3 else "output"
pak = PakFile(input_path)
if not pak.parse():
return
print(f"enc_type={pak.enc_type} enc_headers={pak.enc_headers}")
entries = pak.entries()
print(f"{len(entries)} entries")
os.makedirs(output_dir, exist_ok=True)
ok = 0
for e in entries:
data = pak.extract(e)
if data is None:
continue
rel = e["name"].replace("/", os.sep).replace("\\", os.sep)
out = os.path.join(output_dir, rel)
os.makedirs(os.path.dirname(out) or ".", exist_ok=True)
with open(out, "wb") as f:
f.write(data)
ok += 1
print(f"extracted {ok}/{len(entries)}")
if __name__ == "__main__":
main()
Зависимости:
pip install pycryptodome twofish
Последнее редактирование: