Files
2026-08-09 21:57:08 +08:00

63 lines
1.9 KiB
Python

"""Encrypt/decrypt Coruna secondary type-0x01 .min.js packs."""
from __future__ import annotations
import lzma
import struct
from Crypto.Cipher import ChaCha20
WRAP_MAGIC = b"\x0d\xf0\xed\x0b" # 0x0BEDF00D LE
F00D_MAGIC = 0xF00DBEEF
def build_f00dbeef_type01(dylib: bytes) -> bytes:
"""Single-entry F00DBEEF used by this campaign's secondary packs."""
header = struct.pack(
"<6I",
F00D_MAGIC,
1, # version / entry-count field as in sample
0x00010000, # type 0x01
3,
0x18, # payload offset
len(dylib),
)
return header + dylib
def wrap_xz(plaintext: bytes) -> bytes:
compressed = lzma.compress(plaintext, format=lzma.FORMAT_XZ)
return WRAP_MAGIC + struct.pack("<I", len(plaintext)) + compressed
def unwrap_xz(blob: bytes) -> bytes:
if blob[:4] != WRAP_MAGIC:
raise ValueError(f"bad wrap magic: {blob[:4]!r}")
expected = struct.unpack_from("<I", blob, 4)[0]
plain = lzma.decompress(blob[8:])
if len(plain) != expected:
raise ValueError(f"xz size mismatch: {len(plain)} != {expected}")
return plain
def chacha_crypt(data: bytes, key: bytes) -> bytes:
if len(key) != 32:
raise ValueError("ChaCha20 key must be 32 bytes")
return ChaCha20.new(key=key, nonce=b"\x00" * 8).encrypt(data)
def encrypt_secondary_minjs(dylib: bytes, key: bytes) -> bytes:
return chacha_crypt(wrap_xz(build_f00dbeef_type01(dylib)), key)
def decrypt_secondary_minjs(blob: bytes, key: bytes) -> bytes:
plain = unwrap_xz(chacha_crypt(blob, key))
if struct.unpack_from("<I", plain, 0)[0] != F00D_MAGIC:
raise ValueError("not F00DBEEF after decrypt")
offset = struct.unpack_from("<I", plain, 16)[0]
size = struct.unpack_from("<I", plain, 20)[0]
dylib = plain[offset : offset + size]
if len(dylib) != size:
raise ValueError("truncated dylib in F00DBEEF")
return dylib