Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник Assets decryptor

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
18 Май 2025
Сообщения
51
Реакции
2
Я не нашел ничего подобного так что решил написать сам, использование скрипта "py decrypt.py "C:\VK Play\Warface\Game\GameData.pak" out_dir"

Код:

Python:
Expand Collapse Copy
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
 
Последнее редактирование:
Назад
Сверху Снизу