# -*- coding: utf-8 -*-
"""Parse vuwuuwvw attestation JSON from logcat or raw JSON file."""
import base64
import hashlib
import json
import re
import sys
from pathlib import Path

SUSPICIOUS = re.compile(
    rb"(root|hook|xposed|lsposed|magisk|frida|substrate|emulator|debug|adb|"
    rb"selinux|\bsu\b|/proc/|zygisk|riru|shamiko|tamper|integrity|"
    rb"jailbreak|virtual|mock|proxy|vpn|developer)",
    re.I,
)

KNOWN_FIELDS = [
    "root", "hook", "xposed", "lsposed", "magisk", "frida", "adb", "debug",
    "debuggable", "emulator", "simulator", "vpn", "proxy", "mock",
    "selinux", "su", "supersu", "zygisk", "riru", "shamiko", "substrate",
    "integrity", "safetynet", "playIntegrity", "deviceId", "androidId",
    "serial", "fingerprint", "model", "brand", "manufacturer", "board",
    "host", "tags", "type", "user", "display", "product", "hardware",
    "usb", "wifi", "adb_enabled", "development_settings_enabled",
    "RISK_ROOT", "RISK_HOOK", "RISK_USB_ADB", "RISK_WIFI_ADB", "RISK_ADB",
    "RISK_EMULATOR", "RISK_DEBUG", "RISK_VPN", "RISK_PROXY", "RISK_MOCK",
    "rdVerifyInfo", "deviceFingerprint", "data", "dataKey", "riskToken",
    "isRoot", "isHook", "isDebug", "isAdb", "isEmulator", "isVirtual",
    "tamper", "jailbreak", "bootloader", "verifiedbootstate", "vbmeta",
    "init.svc.adbd", "/proc/self/maps", "RealInterceptorChain",
]

# keys seen in 16:29-16:30 Pixel6 logs (from vuwuuwvw head=...)
SAMPLE_KEYS = """
2535994b 3923d741 68e69650 37132b99 1c5681ce 324f4370 4ea521fa
2236b022 5a5532da 1309e885 1bb219c0 3ade7f65 3ade7f66 1c560a56 1c560a55
3d33c1b1 1854d9b1 21e5cca2 23a20fae 36f30e66 29e2320e 2652ab1c 122c5826
269b494b 22b1f08d 169b85f 1610b055 2baf3770 5bc1a01a 37132b99
""".split()


def md5_key(name: str) -> str:
    return hashlib.md5(name.encode()).hexdigest()[:8]


def guess_keys(keys):
    table = {md5_key(n): n for n in KNOWN_FIELDS}
    out = []
    for k in keys:
        if k.lower() in table:
            out.append((k, table[k.lower()]))
    return out


def scan_value(path, val, hits):
    if isinstance(val, str):
        b = val.encode("utf-8", "replace")
        m = SUSPICIOUS.search(b)
        if m:
            hits.append(f"{path} str hit={m.group().decode()} val={val[:120]}")
        if re.fullmatch(r"[A-Za-z0-9+/=]+", val) and 8 <= len(val) <= 512:
            try:
                raw = base64.b64decode(val + "==="[: (4 - len(val) % 4) % 4])
                if sum(32 <= c < 127 for c in raw) * 100 // max(len(raw), 1) >= 85:
                    inner = raw.decode("utf-8", "replace")
                    m2 = SUSPICIOUS.search(inner.encode())
                    if m2:
                        hits.append(f"{path} b64utf8 hit={m2.group().decode()} val={inner[:120]}")
                else:
                    hx = raw[:32].hex()
                    hits.append(f"{path} b64 bin len={len(raw)} hex={hx}")
            except Exception:
                pass
    elif isinstance(val, (int, float, bool)):
        if val in (1, True):
            hits.append(f"{path} ={val} (flag?)")


def parse_json(text, label=""):
    obj = json.loads(text)
    keys = sorted(obj.keys())
    print(f"\n=== {label} keys={len(keys)} ===")
    print("first keys:", keys[:12])
    hits = []
    for k in keys:
        scan_value(k, obj[k], hits)
    if hits:
        print("SUSPICIOUS:")
        for h in hits[:30]:
            print(" ", h)
    else:
        print("no plain suspicious strings")
    matched = guess_keys(keys)
    if matched:
        print("MD5 key guesses:")
        for k, n in matched:
            print(f"  {k} => {n}")
    return obj


def extract_from_log(path):
    text = Path(path).read_text(encoding="utf-8", errors="replace")
    # MariBankCapture chunked: [vuwuuwvw.out REGISTER] 1/N ...
    chunks = {}
    current = None
    for line in text.splitlines():
        if "vuwuuwvw.out REGISTER" in line or "vuwuuwvw.out]" in line:
            m = re.search(r"\] (\d+)/(\d+) (.+)$", line)
            if m:
                idx, total, part = int(m.group(1)), int(m.group(2)), m.group(3)
                key = (total, line.split("REGISTER")[0])
                chunks.setdefault(key, {})[idx] = part
            elif " len=" in line and " parts=" not in line:
                m2 = re.search(r"\] len=\d+ (.+)$", line)
                if m2:
                    current = m2.group(1)
        elif "vuwuuwvw.out REGISTER] len=" in line and " parts=" not in line:
            m2 = re.search(r"len=\d+ (.+)$", line)
            if m2:
                current = m2.group(1)
    if current and current.startswith("{"):
        return [current]
    out = []
    for parts in chunks.values():
        if parts:
            joined = "".join(parts[i] for i in sorted(parts))
            if joined.startswith("{"):
                out.append(joined)
    # fallback: head= lines won't work for full JSON
    return out


def main():
    print("=== MD5 key table (known fields -> 8 hex) ===")
    for name in KNOWN_FIELDS[:20]:
        print(f"  {md5_key(name):8s}  {name}")
    print("  ...")
    print("\n=== sample keys from device logs ===")
    matched = guess_keys(SAMPLE_KEYS)
    if matched:
        for k, n in matched:
            print(f"  {k} => {n}")
    else:
        print("  (no MD5 match — keys may use different hash algo)")

    if len(sys.argv) > 1:
        p = Path(sys.argv[1])
        if p.suffix == ".json":
            parse_json(p.read_text(encoding="utf-8"), p.name)
        else:
            for i, blob in enumerate(extract_from_log(p)):
                parse_json(blob, f"log#{i+1}")


if __name__ == "__main__":
    main()
