Files
live-hub-py/tools/frida/run_capture.py
T
yml2213 08fcf5c34c feat(huya): 💎💎 R31 活体重抓3密文+Java全逆+native零调用铁证
- 配方: frida-server -l 0.0.0.0 + 清状态(保留登录) + str_magic → 40s命中
- 3新POST (4384/4267/4261B, cipher@70) 存 evidence/dfp_live/
- Java Cprivate XXTEA + Perseus 会话密钥 + Lyra TLV 全解
- 报告加密 = 第三路径 (JNI族+XTEA核心+Java三重钩子全0调用!)
- cprivate.py 解密器就绪 (key一到即解)
2026-08-29 08:53:31 +08:00

175 lines
6.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""虎牙 OTP / setDeviceInfo 定向捕获运行器 (自愈多轮版)。
稳定性设计 (2026-08-29 第二轮):
- 两阶段注入: spawn 挂起只注入 bypass_msaoaid_maps_art_callsite.js (真机唯一稳定配方)
-> resume -> 等 app 启动后 (+6s) 再注入 OTP/设备信息钩子 (避开 msaoaid 启动扫描竞争)
- 自愈: 每轮 app 被杀/窗口结束自动 force-stop 重 spawn, 最多 N 轮, 事件汇总到同一 JSONL
- 单轮窗口 150s (真机稳定绿区 ~90s+, 加上两阶段补偿)
用法:
python3 run_capture.py
CAP_MAX_ATTEMPTS=12 CAP_WINDOW=160 python3 run_capture.py
"""
import json
import os
import subprocess
import sys
import time
import threading
from datetime import datetime
HERE = os.path.dirname(os.path.abspath(__file__))
OUT_DIR = os.environ.get("CAP_OUT_DIR", HERE)
PKG = "com.duowan.kiwi"
SERVER_PATH = "/data/local/tmp/fs152"
SERVER_PORT = "31878"
BYpass_RE = "/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/scripts/bypass_msaoaid_maps_art_callsite.js"
HOOK_LOCAL = os.environ.get("CAP_HOOK")
MAX_ATTEMPTS = int(os.environ.get("CAP_MAX_ATTEMPTS", "8"))
WINDOW_SEC = int(os.environ.get("CAP_WINDOW", "150"))
HOOK_DELAY = float(os.environ.get("CAP_HOOK_DELAY", "6"))
_count = {"events": 0}
def adb(*args):
return subprocess.run(["adb"] + list(args), capture_output=True, text=True)
def clear_turing_state():
"""清 turing 状态 -> 自动重注册触发报告 (文档配方 §11.49)."""
try:
subprocess.run(["adb","shell","am","force-stop","com.duowan.kiwi"], capture_output=True, text=True)
time.sleep(1)
for path in ["app_turingdfp","app_turingfd","resinfo","app_turingdmp","cocos","turing_files"]:
r = subprocess.run(["adb","shell","run-as","com.duowan.kiwi","rm","-rf",f"/data/data/com.duowan.kiwi/{path}"], capture_output=True, text=True)
if r.returncode == 0:
print(f"[runner] cleared {path}", flush=True)
print("[runner] turing state cleared -> expect auto re-register report", flush=True)
except Exception as e:
print(f"[runner] clear-EXC {e}", flush=True)
def run_once(fout, out_path):
"""单次 spawn 捕获一轮; 返回该轮事件数 (0 = 该轮无捕获)."""
import frida
# 1) frida-server 存活
adb("shell", "su", "-c",
f"if ! pgrep -f {SERVER_PATH}; then nohup {SERVER_PATH} -l 127.0.0.1:{SERVER_PORT} >/data/local/tmp/fs152.log 2>&1 & fi")
time.sleep(1.5)
dev = None
for i in range(6):
try:
dev = frida.get_device_manager().add_remote_device(f"127.0.0.1:{SERVER_PORT}")
n = len(dev.enumerate_processes())
print(f"[runner] frida-server OK ({n} procs)", flush=True)
break
except Exception as e:
print(f"[runner] wait frida-server [{i}] {str(e)[:80]}", flush=True)
time.sleep(2.0)
if dev is None:
print("[runner] FATAL: frida-server unreachable", flush=True)
return 0
adb("shell", "am", "force-stop", PKG)
time.sleep(1.0)
try:
clear_turing_state()
pid = dev.spawn([PKG])
print(f"[runner] spawned pid={pid}", flush=True)
except Exception as e:
print(f"[runner] spawn failed: {e}", flush=True)
return 0
session = dev.attach(pid)
def on_message(msg, data):
if msg.get("type") == "send":
payload = msg.get("payload")
if payload is not None:
line = json.dumps(payload, ensure_ascii=False)
fout.write(line + "\n")
fout.flush()
_count["events"] += 1
ev = payload.get("event", "")
if ev in ("crypto-otp", "setdi", "gethdid", "hook-missing", "hook-installed"):
print(f"[cap] {line[:400]}", flush=True)
elif msg.get("type") == "error":
print(f"[cap-err] {msg.get('stack', msg)[:300]}", flush=True)
elif msg.get("type") == "device":
print(f"[cap-dev] {msg.get('payload')}", flush=True)
# 阶段1: 仅注入 bypass
try:
with open(BYpass_RE, "r", encoding="utf-8") as f:
sc = session.create_script(f.read())
sc.on("message", on_message)
sc.load()
print("[runner] loaded bypass (phase-1)", flush=True)
except Exception as e:
print(f"[runner] bypass load failed: {e}", flush=True)
session.detach()
return 0
dev.resume(pid)
print("[runner] RESUMED (phase-1). 等待 app 启动...", flush=True)
# 阶段2: 延迟注入 otp 钩子
def inject_phase2():
time.sleep(HOOK_DELAY)
try:
with open(HOOK_LOCAL, "r", encoding="utf-8") as f:
sc2 = session.create_script(f.read())
sc2.on("message", on_message)
sc2.load()
print("[runner] loaded otp-hook (phase-2)", flush=True)
except Exception as e:
print(f"[runner] phase-2 inject failed: {e[:200]}", flush=True)
threading.Thread(target=inject_phase2, daemon=True).start()
# 窗口等待 + 进程死亡检测 (自愈: 死了立刻重试)
start = time.time()
while time.time() - start < WINDOW_SEC:
time.sleep(1.0)
out = adb("shell", "su", "-c", f"kill -0 {pid} 2>/dev/null && echo alive || echo dead")
if "dead" in out.stdout:
print(f"[runner] process {pid} died at {int(time.time()-start)}s", flush=True)
break
try:
session.detach()
except Exception:
pass
adb("shell", "am", "force-stop", PKG)
print(f"[runner] round done: {out_path}", flush=True)
return _count["events"]
def main():
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
out_path = os.path.join(OUT_DIR, f"capture_{ts}.jsonl")
fout = open(out_path, "w", encoding="utf-8")
print(f"[runner] output -> {out_path} attempts<={MAX_ATTEMPTS} window={WINDOW_SEC}s", flush=True)
attempts = 0
while attempts < MAX_ATTEMPTS:
attempts += 1
before = _count["events"]
try:
run_once(fout, out_path)
except Exception as e:
print(f"[runner] round {attempts} exception: {str(e)[:150]}", flush=True)
if _count["events"] > before:
print(f"[runner] attempts={attempts} events={_count['events']} — stopping (capture got data)", flush=True)
break
time.sleep(3)
fout.close()
print(f"[runner] FINAL attempts={attempts} events={_count['events']} -> {out_path}", flush=True)
if __name__ == "__main__":
main()