fix(tng): 适配 Promon 1.9.10 vhvlnqgy 包名并加固 Zygisk 信号跳过
1.9.10 将 xwwqazamx 重命名为 vhvlnqgy,Login 闪退为 vhvlnqgy.bd:16;Xposed 双包名解析并 short-circuit R/bl、拦截 bd 异常。Zygisk 扩展 non-promon SEGV pc+4 与 ABRT/TRAP 一律 pc+4,修复 Login 稳定;注册页 stack_chk abort 待续攻。
This commit is contained in:
14
reverse/scripts/_scan_tng_vhv.py
Normal file
14
reverse/scripts/_scan_tng_vhv.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import zipfile
|
||||
import re
|
||||
|
||||
z = zipfile.ZipFile(r"reverse/dumps/tng_1.9.10_base.apk")
|
||||
names = [n for n in z.namelist() if n.endswith(".dex")]
|
||||
pat = re.compile(rb"Lvhvlnqgy/([^;\s]{1,80});")
|
||||
found = set()
|
||||
for n in names:
|
||||
data = z.read(n)
|
||||
for m in pat.findall(data):
|
||||
found.add(m.decode("ascii", errors="ignore"))
|
||||
print("vhvlnqgy classes", len(found))
|
||||
for c in sorted(found):
|
||||
print(f"vhvlnqgy.{c.replace('/', '.')}")
|
||||
24
reverse/scripts/_scan_tng_vhv_bl.py
Normal file
24
reverse/scripts/_scan_tng_vhv_bl.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Dump vhvlnqgy bl/R/bd method refs from TNG 1.9.10 dex."""
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"reverse/dumps/tng_1.9.10_base.apk"
|
||||
with zipfile.ZipFile(APK) as z:
|
||||
data = b"".join(z.read(n) for n in z.namelist() if n.endswith(".dex"))
|
||||
|
||||
for cls in ["Lvhvlnqgy/bl;", "Lvhvlnqgy/R;", "Lvhvlnqgy/bd;", "Lvhvlnqgy/w;", "Lvhvlnqgy/W;", "Lvhvlnqgy/a;"]:
|
||||
print("===", cls, "===")
|
||||
refs = sorted(set(re.findall(cls.encode() + rb"->[^\x00]{1,80}", data)))
|
||||
for r in refs[:30]:
|
||||
print(r.decode("ascii", "ignore"))
|
||||
print()
|
||||
|
||||
print("=== R callers (who invokes R.a/R.b) ===")
|
||||
for m in sorted(set(re.findall(rb"Lvhvlnqgy/[^;]+;->[a-zA-Z]+[^\x00]{0,40}Lvhvlnqgy/R;", data))):
|
||||
print(m.decode("ascii", "ignore"))
|
||||
|
||||
print("\n=== bd throw sites ===")
|
||||
for m in sorted(set(re.findall(rb"[^\x00]{0,40}Lvhvlnqgy/bd;", data))):
|
||||
s = m.decode("ascii", "ignore")
|
||||
if "vhvlnqgy" in s:
|
||||
print(s)
|
||||
20
reverse/scripts/parse_ui_dump.py
Normal file
20
reverse/scripts/parse_ui_dump.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else "reverse/dumps/ui_login.xml"
|
||||
root = ET.parse(path).getroot()
|
||||
keywords = [
|
||||
"register", "sign", "login", "mobile", "phone", "continue", "next",
|
||||
"create", "otp", "skip", "started", "email", "password", "pin",
|
||||
]
|
||||
for node in root.iter("node"):
|
||||
text = node.get("text", "")
|
||||
desc = node.get("content-desc", "")
|
||||
clickable = node.get("clickable", "")
|
||||
bounds = node.get("bounds", "")
|
||||
label = (text or desc).strip()
|
||||
if not label and clickable != "true":
|
||||
continue
|
||||
hay = (text + " " + desc).lower()
|
||||
if clickable == "true" or any(k in hay for k in keywords):
|
||||
print(f"{label!r} bounds={bounds} clickable={clickable}")
|
||||
13
reverse/scripts/parse_ui_dump_all.py
Normal file
13
reverse/scripts/parse_ui_dump_all.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
path = sys.argv[1]
|
||||
root = ET.parse(path).getroot()
|
||||
for node in root.iter("node"):
|
||||
text = (node.get("text") or "").strip()
|
||||
desc = (node.get("content-desc") or "").strip()
|
||||
rid = node.get("resource-id") or ""
|
||||
bounds = node.get("bounds") or ""
|
||||
clickable = node.get("clickable") or "false"
|
||||
if text or desc or "login" in rid.lower() or "register" in rid.lower():
|
||||
print(f"text={text!r} desc={desc!r} id={rid} bounds={bounds} click={clickable}")
|
||||
110
reverse/scripts/test_tng_register_flow.py
Normal file
110
reverse/scripts/test_tng_register_flow.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Automate TNG login/register tap test and report crashes."""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
PKG = "my.com.tngdigital.ewallet"
|
||||
TAGS = re.compile(
|
||||
r"FATAL EXCEPTION|AndroidRuntime.*Process: " + PKG
|
||||
+ r"|has died|exited due to signal|vhvlnqgy\.bd|blocked killProcess|blocked System\.exit"
|
||||
+ r"|UserRegistration|UserOtp|UserLogin|Displayed.*tngdigital|ACT on(Create|Resume)"
|
||||
+ r"|TngExitGuard.*ABRT|TngRoot hooked 6 vhvlnqgy\.R",
|
||||
re.I,
|
||||
)
|
||||
|
||||
TAPS = [
|
||||
("register_continue", 540, 959, 8),
|
||||
("maybe_otp_continue", 540, 959, 6),
|
||||
("back_to_login", None, None, 2),
|
||||
("login_tab", 540, 1168, 5),
|
||||
]
|
||||
|
||||
|
||||
def adb(*args, timeout=30):
|
||||
cmd = ["adb"] + list(args)
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
out = (r.stdout or "") + (r.stderr or "")
|
||||
return r.returncode, out.strip()
|
||||
|
||||
|
||||
def pid_alive():
|
||||
code, out = adb("shell", "pidof", PKG)
|
||||
return code == 0 and out.strip().split()
|
||||
|
||||
|
||||
def top_activity():
|
||||
code, out = adb("shell", "dumpsys", "activity", "activities")
|
||||
if code != 0:
|
||||
return "?"
|
||||
for line in out.splitlines():
|
||||
if "topResumedActivity=" in line:
|
||||
m = re.search(r"/([^/]+)$", line.strip())
|
||||
if m:
|
||||
return m.group(1).rstrip("}")
|
||||
return line.strip()
|
||||
return "?"
|
||||
|
||||
|
||||
def logcat_since(start):
|
||||
code, out = adb("shell", "logcat", "-d", "-t", start)
|
||||
hits = []
|
||||
if code == 0:
|
||||
for line in out.splitlines():
|
||||
if TAGS.search(line):
|
||||
hits.append(line)
|
||||
return hits
|
||||
|
||||
|
||||
def main():
|
||||
adb("logcat", "-c")
|
||||
adb("shell", "am", "force-stop", PKG)
|
||||
time.sleep(1)
|
||||
adb("shell", "am", "start", "-n", f"{PKG}/.ui.SplashActivity")
|
||||
time.sleep(12)
|
||||
print("=== after cold start ===")
|
||||
print("pids:", pid_alive())
|
||||
print("activity:", top_activity())
|
||||
|
||||
# dismiss country picker if open
|
||||
adb("shell", "input", "keyevent", "KEYCODE_BACK")
|
||||
time.sleep(1)
|
||||
|
||||
results = []
|
||||
for name, x, y, wait_s in TAPS:
|
||||
if x is not None:
|
||||
adb("shell", "input", "tap", str(x), str(y))
|
||||
print(f"\n=== tap {name} ({x},{y}) ===")
|
||||
else:
|
||||
adb("shell", "input", "keyevent", "KEYCODE_BACK")
|
||||
print(f"\n=== {name} ===")
|
||||
time.sleep(wait_s)
|
||||
pids = pid_alive()
|
||||
act = top_activity()
|
||||
crash_buf = adb("shell", "logcat", "-d", "-b", "crash", "-t", "50")[1]
|
||||
fatal = [l for l in crash_buf.splitlines() if PKG in l or "vhvlnqgy" in l]
|
||||
results.append((name, pids, act, fatal))
|
||||
print("pids:", pids or "DEAD")
|
||||
print("activity:", act)
|
||||
if fatal:
|
||||
print("CRASH:", fatal[-3:])
|
||||
|
||||
print("\n=== summary ===")
|
||||
ok = True
|
||||
for name, pids, act, fatal in results:
|
||||
status = "OK" if pids and not fatal else "FAIL"
|
||||
if status == "FAIL":
|
||||
ok = False
|
||||
print(f"{status} {name}: pids={pids} activity={act} crash_lines={len(fatal)}")
|
||||
|
||||
hits = logcat_since("2000")
|
||||
print(f"\n=== key log lines ({len(hits)}) ===")
|
||||
for line in hits[-40:]:
|
||||
print(line)
|
||||
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user