chore: 备份 TNG 注册/captcha 逆向与 MariBank SG bypass 进展
TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
This commit is contained in:
156
reverse/scripts/parse_vuwuuwvw_log.py
Normal file
156
reverse/scripts/parse_vuwuuwvw_log.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user