Files
notiMessage/reverse/scripts/parse_vuwuuwvw_log.py
mars 609635aba1 chore: 备份 TNG 注册/captcha 逆向与 MariBank SG bypass 进展
TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
2026-08-03 15:23:02 +08:00

157 lines
5.5 KiB
Python

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