# -*- 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()