#!/usr/bin/env python3
"""Extract the per-platform native payloads bundled in dist/intro.js.

Reimplements in Python the container format read by the malicious IIFE
embedded in index.js/bin/jscrambler.js (and by dist/setup.js in 8.14.0):

    [5 bytes magic: 1b 43 53 49 01] [1 byte count]
    repeat count times:
        [1 byte platform id: 0=linux 1=win32 2=darwin]
        [8 bytes reserved, never read by the loader itself]
        [8 bytes LE uint64: compressed size]
        [compressed_size bytes: gzip-compressed payload]

Never executes the extracted binaries. Writes each decompressed payload to
disk with the executable bit stripped and prints its SHA256.
"""
import gzip
import hashlib
import os
import struct
import sys

MAGIC = bytes([0x1B, 0x43, 0x53, 0x49, 0x01])
PLATFORM_NAMES = {0: "linux", 1: "win32", 2: "darwin"}


def extract(intro_path: str, out_dir: str) -> None:
    data = open(intro_path, "rb").read()
    if data[:5] != MAGIC:
        raise ValueError(f"bad magic: {data[:5].hex()}")

    count = data[5]
    offset = 6
    os.makedirs(out_dir, exist_ok=True)

    for i in range(count):
        platform = data[offset]; offset += 1
        reserved = data[offset:offset + 8]; offset += 8
        compressed_size = struct.unpack_from("<Q", data, offset)[0]; offset += 8
        chunk = data[offset:offset + compressed_size]; offset += compressed_size

        name = PLATFORM_NAMES.get(platform, f"unknown{platform}")
        payload = gzip.decompress(chunk)
        ext = ".exe" if platform == 1 else ""
        out_path = os.path.join(out_dir, f"payload_{name}{ext}")

        with open(out_path, "wb") as f:
            f.write(payload)
        os.chmod(out_path, 0o644)

        sha256 = hashlib.sha256(payload).hexdigest()
        print(f"[{i}] platform={name:8s} reserved={reserved.hex()} "
              f"compressed={compressed_size:>9} decompressed={len(payload):>9} "
              f"sha256={sha256} -> {out_path}")

    trailing = len(data) - offset
    if trailing:
        print(f"WARNING: {trailing} trailing byte(s) after last entry, unparsed")


def main() -> None:
    intro_path = sys.argv[1] if len(sys.argv) > 1 else "intro.js"
    out_dir = sys.argv[2] if len(sys.argv) > 2 else "binaries"
    extract(intro_path, out_dir)


if __name__ == "__main__":
    main()
