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:
mars
2026-08-03 11:02:46 +08:00
parent 818b2f4f51
commit 18ae42ec63
8 changed files with 329 additions and 95 deletions

View 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('/', '.')}")

View 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)

View 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}")

View 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}")

View 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())