110 lines
4.6 KiB
Python
110 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""通用 bypass 健康检查器 — 三层面标准 (唯一判定工具).
|
|
|
|
层面1 进程: pid 不被杀、不换新 (换新 = 被静默杀后重启)
|
|
层面2 界面: Activity 轨迹持续推进, 通过 Splash 且最终停在主界面 (轨迹停滞 = 卡死)
|
|
层面3 崩溃: logcat 无 FATAL / 无 ANR
|
|
|
|
用法:
|
|
bypass_healthcheck.py [attempts] [--recipe merged|original] [--watch 秒]
|
|
判定: 每次 attempt 三层全过 → 该次 PASS; 所有 attempt 都 PASS → 总 PASS.
|
|
"""
|
|
import subprocess, sys, time, re
|
|
from pathlib import Path
|
|
import frida
|
|
|
|
ADB="5dd8c93f"; PKG="com.duowan.kiwi"
|
|
RE=Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
|
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
from bypass_loader import load_bypass
|
|
|
|
def sh(*a): return subprocess.run(list(a),capture_output=True,text=True).stdout
|
|
|
|
def pid_of():
|
|
for l in sh("adb","-s",ADB,"shell","ps","-A").splitlines():
|
|
if l.rstrip().endswith(PKG): return l.split()[1]
|
|
return None
|
|
|
|
def main_thread_frozen(pid):
|
|
"""6s 采样主线程 utime 增量: 0 = 冻结 (前台卡死铁证)"""
|
|
if not pid: return None
|
|
def utime():
|
|
out=sh("adb","-s",ADB,"shell","su","-c",f"cat /proc/{pid}/task/{pid}/stat")
|
|
f=out.split()
|
|
return int(f[13])+int(f[14]) if len(f)>14 else None
|
|
a=utime()
|
|
if a is None: return None
|
|
time.sleep(6)
|
|
b=utime()
|
|
return (b is None) or (b==a)
|
|
|
|
def input_anr_warning():
|
|
log=sh("adb","-s",ADB,"logcat","-d")
|
|
return ("Input dispatching timed out" in log) or ("ANR in com.duowan.kiwi" in log)
|
|
|
|
def top_act():
|
|
out=sh("adb","-s",ADB,"shell","dumpsys","activity","activities")
|
|
for line in out.splitlines():
|
|
if "Hist #0" in line and PKG in line:
|
|
m=re.search(r"u0 ([\w.$]+) t\d+", line)
|
|
return m.group(1) if m else "?"
|
|
return None
|
|
|
|
def load_merged(s, delay=11000):
|
|
load_bypass(s, patch_guard_delay_ms=delay)
|
|
|
|
def load_original(s):
|
|
s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()).load()
|
|
s.create_script((RE/"evidence/scripts/mask_frida_maps_only.js").read_text()).load()
|
|
time.sleep(11)
|
|
s.create_script((RE/"evidence/scripts/patch_guard_block_termination.js").read_text()).load()
|
|
|
|
def check_once(recipe="merged", watch=45, guard_delay=11000):
|
|
sh("adb","-s",ADB,"shell","am","force-stop",PKG); time.sleep(4)
|
|
sh("adb","-s",ADB,"logcat","-c")
|
|
d=frida.get_device_manager().add_remote_device("127.0.0.1:31878")
|
|
pid0=d.spawn([PKG]); s=d.attach(pid0)
|
|
(load_merged if recipe=="merged" else load_original)(s, guard_delay) if recipe=="merged" else load_original(s)
|
|
d.resume(pid0)
|
|
L1_ok=True; trail=[]; die=None
|
|
t0=time.time()
|
|
while time.time()-t0<watch:
|
|
time.sleep(4)
|
|
p=pid_of()
|
|
if p is None: L1_ok=False; die=f"{time.time()-t0:.0f}s"; break
|
|
if p!=pid0: L1_ok=False; die=f"replaced@{time.time()-t0:.0f}s"; break
|
|
act=top_act()
|
|
if act and (not trail or trail[-1]!=act): trail.append(act)
|
|
log=sh("adb","-s",ADB,"logcat","-d")
|
|
L3_ok = not (("ANR in com.duowan.kiwi" in log) or ("FATAL EXCEPTION" in log and PKG in log))
|
|
entered = any("Splash" not in x for x in trail)
|
|
final = trail[-1] if trail else "(none)"
|
|
frozen = main_thread_frozen(pid_of()) if not die else None
|
|
warn = input_anr_warning()
|
|
# 层面2: 通过 splash + 主线程 utime 有增量 (首页没冻死) + 无 input ANR 前兆
|
|
L2_ok = entered and (frozen is False) and not warn
|
|
r={"L1_pid":L1_ok, "L2_ui":L2_ok, "L3_crash":L3_ok, "die":die, "final_act":final, "trail":trail, "main_frozen":frozen, "input_anr":warn}
|
|
r["PASS"]=L1_ok and L2_ok and L3_ok
|
|
try: s.detach()
|
|
except Exception: pass
|
|
return r
|
|
|
|
def main():
|
|
args=sys.argv[1:]
|
|
attempts=1; recipe="merged"; watch=45; delay=11000
|
|
i=0
|
|
while i<len(args):
|
|
if args[i]=="--recipe": recipe=args[i+1]; i+=2
|
|
elif args[i]=="--watch": watch=int(args[i+1]); i+=2
|
|
elif args[i]=="--delay": delay=int(args[i+1]); i+=2
|
|
else: attempts=int(args[i]); i+=1
|
|
results=[check_once(recipe, watch, delay) for _ in range(attempts)]
|
|
for n,r in enumerate(results,1):
|
|
print(f"attempt{n}: L1_pid={r['L1_pid']} L2_ui={r['L2_ui']} L3_crash={r['L3_crash']} die={r['die']} final={r['final_act']} frozen={r['main_frozen']} inputANR={r['input_anr']}")
|
|
print(f" trail: {' -> '.join(r['trail']) or '(none)'}")
|
|
total=all(r["PASS"] for r in results)
|
|
print(f"TOTAL: {'PASS ✅' if total else 'FAIL ❌'} ({recipe} x{attempts})")
|
|
sys.exit(0 if total else 1)
|
|
|
|
if __name__=="__main__": main()
|