docs: 绕过配方状态更正 (bypass_all 未验证, 以原版三脚本为准) + 三层健康检查工具
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# Frida 绕过配方 — 虎牙 com.duowan.kiwi (13.4.22 arm64)
|
||||
|
||||
> ⚠️ **当前可用配方 = RE 仓库三脚本两阶段流程** (hook_final_capture.py 同款, 实测唯一稳定):
|
||||
> `bypass_msaoaid_maps_art_callsite.js` + `mask_frida_maps_only.js` 挂起中加载 → resume → 11s → `patch_guard_block_termination.js`
|
||||
>
|
||||
> ⚠️ **`tools/frida/bypass_all.js` (合并版) 未通过验证 (R40)**: 三层健康检查显示卡加载页/延迟死亡,
|
||||
> 最小化测试 A1-only 也不通, 与原版的实质差异未定位 (emit 死锁已修但仍有问题)。**别用**。
|
||||
> `scripts/bypass_healthcheck.py` = 三层判定工具 (pid/界面轨迹/崩溃ANR+冻结探针), 可复用。
|
||||
|
||||
## 一、标准用法 (runner 模板)
|
||||
|
||||
```python
|
||||
import frida, time
|
||||
|
||||
d = frida.get_device_manager().add_remote_device("127.0.0.1:31878") # frida-server -l 0.0.0.0:31878
|
||||
pid = d.spawn(["com.duowan.kiwi"])
|
||||
s = d.attach(pid)
|
||||
from bypass_loader import load_bypass # scripts/ 在 sys.path (同目录运行即可)
|
||||
load_bypass(s) # ← 唯一的绕过步骤, 可传参
|
||||
d.resume(pid)
|
||||
|
||||
# 之后挂业务钩子 (挂业务 JS 的时机不再敏感 — patch_guard 已内置延迟)
|
||||
sc = s.create_script(business_js)
|
||||
sc.on("message", on_msg)
|
||||
sc.load()
|
||||
```
|
||||
|
||||
**参数** (经 `globalThis.BYPASS_OPTS` 注入):
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `patch_guard_delay_ms` | 3000 | B 层 (终止拦截) 延迟安装毫秒数。App 启动期被杀 → 调大到 5000-11000; 启动卡死/合法退出被吞 → 调小到 1500 |
|
||||
| `persist` | false | true 时写 `/data/user/0/<pkg>/cache/bypass-all-<pid>.jsonl` 调试日志 (每次掩盖/补丁事件) |
|
||||
|
||||
**设备侧前提**: root (magisk su) + `frida-server -l 0.0.0.0:31878` (必须 0.0.0.0, 否则 connection closed) + `adb forward tcp:31878 tcp:31878`。
|
||||
|
||||
## 二、三层各自防什么
|
||||
|
||||
| 层 | 生效时机 | 对抗点 |
|
||||
|---|---|---|
|
||||
| **A1 msaoaid 补丁** | 立即 (dlopen 双路触发) | `libmsaoaidsec.so`: 线程名探测 (`gmain`/`gum-js-loop`/`linjector` → `pool-worker`), fd 扫描 (`linjector` 掩写), frida 检出后的清理/退出分支 (`0x1c4a0` → 直接跳继续路径), ART 完整性周期 callsite (`0x1ceec` NOP) |
|
||||
| **A2 maps 内容掩盖** | 立即 | libc `open/open64/fopen/openat` 记录 maps fd → `read/pread64` 时把 `frida-agent`/`gum-js-loop`/`linjector` 掩写成无害串 (比 A1 更底层的兜底) |
|
||||
| **B 终止拦截** | 延迟 (默认 3s) | `kill/tgkill/exit/_exit/_Exit/abort/raise` 对自身进程一概吞掉 (turing 检出后 `_exit` 静默退出的最后防线) + msaoaid crash-guard `0x20ca8` 补丁 |
|
||||
|
||||
**为什么 B 层要延迟**: 终止拦截会无差别吞掉 App 自己的 `exit/abort`。启动初期 (0-2s) 有合法退出路径 (初始化失败重试等), 过早替换会卡死/异常。之前 11s 是保守值, 实测 3s 即可; **如果换 App 版本后启动期就被杀, 优先把 `patch_guard_delay_ms` 调回 11000 再排查**。
|
||||
|
||||
## 三、实战教训清单 (每条都付出过时间)
|
||||
|
||||
1. **绕过必须 spawn 挂起中加载**, attach 已启动进程 = 6/6 死 (登录态下更敏感)。
|
||||
2. **spawn 实例"安静退出"无 crash 日志** = msaoaid `_exit` 静默退, 说明绕过没生效或时序错 — 先检查是否 resume 前加载、frida-server 是否带 `-l 0.0.0.0`。
|
||||
3. **别 hook GL/EGL 驱动里的同名导出** (libllvm-glnext/eglSubDriver 也有 `deflate`, 非 zlib ABI, 读错内存直接崩) — hook 系统库符号前先确认模块名。
|
||||
4. **别 hook 热路径** (libz `inflate` = 图片解码): 首屏卡死、注册超时。要过滤: backtrace 含目标库才转储。
|
||||
5. **OLLVM/无帧指针下 `Backtracer.ACCURATE` 会给假帧** — 用 `Interceptor` 的 `this.returnAddress` (真实立即调用方) 逐级爬, 或以必经外部导出 (如 libz `deflate`) 为锚点反查。
|
||||
6. **sret 约定**: `md5(out&, in)` 的 x0 是输出槽, onEnter 读必空; 输入在 x1+。R34 曾因此误判"输入为空"。
|
||||
7. **std::string 判读**: `b0&1==0` → SSO (len=b0>>1, data=+1); 否则 len@+8, data@*(+16)。
|
||||
8. 业务 JS 里 **Java MessageDigest 钩子要在 8 分钟窗口类 runner 里限量** (证书解析会刷几千条)。
|
||||
|
||||
## 四、已知边界
|
||||
|
||||
- offset 表 (0x1c4a0/0x1ceec/0x20ca8 等) 对应 **13.4.22 arm64** 的 `libmsaoaidsec.so`; App 升级需重新定位 (以绕过事件日志 + IDA 对照)。
|
||||
- 模拟器上 A1 补丁因 EGL 竞争会崩 (原配方注释), 仅真机验证。
|
||||
- 需要"清 turing 状态触发重注册"时: `rm -rf /data/data/com.duowan.kiwi/{app_turingdfp,app_turingfd}/*` + 删 `resinfo*` (**保留登录态, 别 pm-clear**)。
|
||||
- 设备被风控限流 (登录超时) 时静置即可恢复; 与 frida 绕过无关。
|
||||
|
||||
## 五、历史配方存档
|
||||
|
||||
- 三脚本两阶段原版: RE 仓库 `evidence/scripts/{bypass_msaoaid_maps_art_callsite,mask_frida_maps_only,patch_guard_block_termination}.js`, 流程见 `scripts/hook_final_capture.py` (11s 稳定窗)。
|
||||
- `bypass_msaoaid_maps_skip_cleanup.js`: 旧单脚本, 匿名启动路径可用, **登录态启动路径不可靠** (R38 实测秒死) — 已被 bypass_all.js 取代, 留作对照。
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""bypass_all vs 原版三脚本 — 三项标准 + Activity 轨迹对比."""
|
||||
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]
|
||||
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 run(label, loader, attempts=2, watch=40):
|
||||
for a in range(1,attempts+1):
|
||||
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)
|
||||
loader(s)
|
||||
d.resume(pid0)
|
||||
trail=[]; die=None; t0=time.time()
|
||||
while time.time()-t0<watch:
|
||||
time.sleep(4)
|
||||
p=pid_of()
|
||||
if p is None: die=f"{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")
|
||||
anr = "ANR in com.duowan.kiwi" in log
|
||||
fatal = "FATAL EXCEPTION" in log and PKG in log
|
||||
entered = any("Splash" not in x for x in trail)
|
||||
tag = f"{label}#{a}"
|
||||
print(f"{tag}: die={die or 'no'} ANR={anr} FATAL={fatal} entered={entered}")
|
||||
print(f" trail: {' -> '.join(trail) if trail else '(none)'}")
|
||||
try: s.detach()
|
||||
except: pass
|
||||
|
||||
def load_merged(s):
|
||||
load_bypass(s, patch_guard_delay_ms=11000)
|
||||
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()
|
||||
|
||||
if __name__=="__main__":
|
||||
which=sys.argv[1] if len(sys.argv)>1 else "both"
|
||||
if which in ("merged","both"): run("merged", load_merged)
|
||||
if which in ("original","both"): run("original", load_original)
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""统一 bypass 加载器 — 所有 runner 共用.
|
||||
|
||||
用法:
|
||||
from bypass_loader import load_bypass
|
||||
s = d.attach(pid) # spawn 后、resume 前
|
||||
load_bypass(s) # 默认 patch_guard 3s
|
||||
load_bypass(s, patch_guard_delay_ms=5000, persist=True)
|
||||
d.resume(pid)
|
||||
|
||||
参数经 globalThis.BYPASS_OPTS 注入 hook (见 tools/frida/bypass_all.js 头注释).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent.parent
|
||||
BYPASS_SRC = (HERE / "tools/frida/bypass_all.js").read_text()
|
||||
|
||||
|
||||
def load_bypass(script_session, patch_guard_delay_ms: int = 3000, persist: bool = False):
|
||||
"""spawn 挂起态调用: 单脚本装载三层绕过 (maps 掩盖立即生效, 终止拦截延迟生效)."""
|
||||
src = (
|
||||
"globalThis.BYPASS_OPTS = "
|
||||
+ json.dumps({"patchGuardDelayMs": patch_guard_delay_ms, "persist": persist})
|
||||
+ ";\n" + BYPASS_SRC
|
||||
)
|
||||
sc = script_session.create_script(src)
|
||||
sc.load()
|
||||
return sc
|
||||
@@ -21,9 +21,8 @@ def main():
|
||||
time.sleep(1.5)
|
||||
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
|
||||
s=d.attach(pid)
|
||||
try:
|
||||
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load()
|
||||
except Exception as e: print(f"[*] bypass err {e}",flush=True)
|
||||
from bypass_loader import load_bypass
|
||||
load_bypass(s)
|
||||
d.resume(pid)
|
||||
result={}; got={"post":False,"out":False}
|
||||
def on(m,_):
|
||||
|
||||
+2
-3
@@ -21,9 +21,8 @@ def main():
|
||||
time.sleep(1.5)
|
||||
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
|
||||
s=d.attach(pid)
|
||||
try:
|
||||
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load()
|
||||
except Exception as e: print(f"[*] bypass err {e}",flush=True)
|
||||
from bypass_loader import load_bypass
|
||||
load_bypass(s)
|
||||
d.resume(pid)
|
||||
result={}; got={"post":False,"out":False}
|
||||
seen_fns=set()
|
||||
|
||||
@@ -21,9 +21,8 @@ def main():
|
||||
time.sleep(1.5)
|
||||
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
|
||||
s=d.attach(pid)
|
||||
try:
|
||||
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load()
|
||||
except Exception as e: print(f"[*] bypass err {e}",flush=True)
|
||||
from bypass_loader import load_bypass
|
||||
load_bypass(s)
|
||||
d.resume(pid)
|
||||
result={}; got={"post":False,"out":False}
|
||||
seen_fns=set()
|
||||
|
||||
@@ -21,9 +21,8 @@ def main():
|
||||
time.sleep(1.5)
|
||||
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
|
||||
s=d.attach(pid)
|
||||
try:
|
||||
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load()
|
||||
except Exception as e: print(f"[*] bypass err {e}",flush=True)
|
||||
from bypass_loader import load_bypass
|
||||
load_bypass(s)
|
||||
d.resume(pid)
|
||||
result={}; state={"post":False,"key":False}
|
||||
def on(m,_):
|
||||
|
||||
@@ -16,14 +16,10 @@ def main():
|
||||
pid=d.spawn([PACKAGE])
|
||||
print(f"[*] spawned {pid}",flush=True)
|
||||
s=d.attach(pid)
|
||||
# 双重防护: callsite bypass + maps 掩盖
|
||||
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()
|
||||
from bypass_loader import load_bypass
|
||||
load_bypass(s, patch_guard_delay_ms=3000)
|
||||
d.resume(pid)
|
||||
print("[*] resumed, 稳定 11s...",flush=True)
|
||||
time.sleep(11)
|
||||
s.create_script((RE/"evidence/scripts/patch_guard_block_termination.js").read_text()).load()
|
||||
print("[*] patch_guard on",flush=True)
|
||||
print("[*] resumed (bypass_all: A层立即 + B层3s patch_guard)",flush=True)
|
||||
|
||||
result={}; state={"udb":0}
|
||||
def on(m,_):
|
||||
|
||||
@@ -41,11 +41,8 @@ def main():
|
||||
pid = d.spawn([PACKAGE])
|
||||
print(f"[*] spawn pid={pid}", flush=True)
|
||||
s = d.attach(pid)
|
||||
try:
|
||||
b = s.create_script((RE / "evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text())
|
||||
b.load()
|
||||
except Exception as e:
|
||||
print(f"[*] bypass err {e}", flush=True)
|
||||
from bypass_loader import load_bypass
|
||||
load_bypass(s)
|
||||
d.resume(pid)
|
||||
|
||||
result: dict = {}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
'use strict';
|
||||
// ============================================================================
|
||||
// bypass_all.js — 虎牙 com.duowan.kiwi (13.4.22) 反 frida 绕过 · 一体化脚本
|
||||
// ============================================================================
|
||||
// 合并自 RE 仓库三脚本 (实测配方, R31-R39 全程验证):
|
||||
// 1. bypass_msaoaid_maps_art_callsite.js — libmsaoaidsec 线程名/fd 掩盖 + maps 清理分支跳转 + ART 完整性 callsite NOP
|
||||
// 2. mask_frida_maps_only.js — libc 层 /proc/self/maps 内容 'frida-agent' 掩写
|
||||
// 3. patch_guard_block_termination.js — kill/tgkill/exit/abort 自终止拦截 + msaoaid crash-guard 补丁
|
||||
//
|
||||
// 用法 (必须 spawn 挂起中、resume 之前加载!):
|
||||
// const opts = { patchGuardDelayMs: 3000 }; // B 层延迟, 传参可调
|
||||
// const src = 'globalThis.BYPASS_OPTS=' + JSON.stringify(opts) + ';\n' + bypassSrc;
|
||||
// s.create_script(src).load(); // 然后 d.resume(pid)
|
||||
//
|
||||
// 时序语义:
|
||||
// A 层 (立即): maps 线索掩盖 — 必须 resume 前就位, 否则启动检测线程先读到 frida 痕迹
|
||||
// B 层 (延迟): 终止拦截会无差别吞掉 exit/abort, 过早加载会卡死启动期合法退出
|
||||
// → 默认 3000ms 后安装; 若 App 秒死调小, 若启动期被杀调大
|
||||
// ============================================================================
|
||||
|
||||
var OPTS = { patchGuardDelayMs: 3000, persist: false };
|
||||
try { if (globalThis.BYPASS_OPTS) OPTS = Object.assign(OPTS, globalThis.BYPASS_OPTS); } catch (_) {}
|
||||
|
||||
function emit(o) {
|
||||
// 关键: 钩子上下文 (dlopen/拦截器 onEnter, 主线程启动期) 内直接 send() 会死锁主线程
|
||||
// (R40 ANR trace: main 阻塞在 frida-agent) — 一律入队, setImmediate 刷出
|
||||
try { pendingEmits.push(Object.assign({ src: 'bypass_all' }, o)); setImmediate(flushEmits); } catch (_) {}
|
||||
}
|
||||
var pendingEmits = [];
|
||||
function flushEmits() {
|
||||
while (pendingEmits.length) { try { send(pendingEmits.shift()); } catch (_) {} }
|
||||
}
|
||||
|
||||
// ---------- 持久化调试日志 (默认关) ----------
|
||||
var logFile = null;
|
||||
function persist(row) {
|
||||
if (!OPTS.persist) return;
|
||||
try {
|
||||
if (logFile === null) logFile = new File('/data/user/0/com.duowan.kiwi/cache/bypass-all-' + Process.id + '.jsonl', 'a');
|
||||
row.tid = Process.getCurrentThreadId();
|
||||
logFile.write(JSON.stringify(row) + '\n');
|
||||
logFile.flush();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function exportOf(name) {
|
||||
try { if (typeof Module.getGlobalExportByName === 'function') return Module.getGlobalExportByName(name); } catch (_) {}
|
||||
try { if (typeof Module.findGlobalExportByName === 'function') return Module.findGlobalExportByName(null, name); } catch (_) {}
|
||||
try { return Module.findExportByName(null, name); } catch (_) { return null; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// A1) libmsaoaidsec.so: 线程名/fd 掩盖 + predicate 分支跳转 + ART callsite NOP
|
||||
// (原 bypass_msaoaid_maps_art_callsite.js, offsets 对应 13.4.22 arm64)
|
||||
// ============================================================================
|
||||
var msaoaid = { installed: false };
|
||||
function readStr(pointer, length) { try { return pointer.readCString(length) || ''; } catch (_) { return ''; } }
|
||||
|
||||
function installMsaoaid(source) {
|
||||
if (msaoaid.installed) return;
|
||||
var module = Process.findModuleByName('libmsaoaidsec.so');
|
||||
if (module === null) return;
|
||||
msaoaid.installed = true;
|
||||
var branch = module.base.add(0x1c4a0);
|
||||
var continuePath = module.base.add(0x1c4b4);
|
||||
var artCallsite = module.base.add(0x1ceec);
|
||||
persist({ event: 'installed', source: source, base: module.base.toString() });
|
||||
emit({ event: 'A-msaoaid-patched', base: module.base.toString() });
|
||||
|
||||
// 线程名掩盖: /proc/<tid>/cmdline 读取处的 gmain/gum-js-loop/linjector → pool-worker
|
||||
[0x1c0e4, 0x1c0f4].forEach(function (offset) {
|
||||
Interceptor.attach(module.base.add(offset), {
|
||||
onEnter() {
|
||||
var buffer = this.context.sp.add(0x18);
|
||||
var value = readStr(buffer, 96);
|
||||
if (!value.startsWith('Name:\tgmain') && !value.startsWith('Name:\tgum-js-loop') && !value.startsWith('Name:\tlinjector')) return;
|
||||
buffer.writeUtf8String('Name:\tpool-worker');
|
||||
persist({ event: 'name-masked', offset: '0x' + offset.toString(16) });
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// fd 扫描掩盖: 'linjector' → 'xxxxxxxxx'
|
||||
Interceptor.attach(module.base.add(0x1c214), {
|
||||
onEnter() {
|
||||
var buffer = this.context.sp.add(0x288);
|
||||
var value = readStr(buffer, 512);
|
||||
var index = value.indexOf('linjector');
|
||||
if (index < 0) return;
|
||||
buffer.add(index).writeUtf8String('xxxxxxxxx');
|
||||
persist({ event: 'fd-masked' });
|
||||
},
|
||||
});
|
||||
|
||||
// predicate 分支: 检出 frida 线索后的清理分支 → 直接跳继续路径; ART 完整性周期 callsite → NOP
|
||||
try {
|
||||
Memory.patchCode(branch, 4, function (code) {
|
||||
var w = new Arm64Writer(code, { pc: branch });
|
||||
w.putBranchAddress(continuePath);
|
||||
w.flush();
|
||||
});
|
||||
Memory.patchCode(artCallsite, 4, function (code) {
|
||||
var w = new Arm64Writer(code, { pc: artCallsite });
|
||||
w.putNop();
|
||||
w.flush();
|
||||
});
|
||||
persist({ event: 'branches-patched' });
|
||||
} catch (error) {
|
||||
emit({ event: 'A-patch-error', error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
// msaoaid 可能在 spawn 时已加载, 也可能稍后 dlopen — 双路触发
|
||||
['dlopen', 'android_dlopen_ext'].forEach(function (name) {
|
||||
var address = exportOf(name);
|
||||
if (address !== null) Interceptor.attach(address, {
|
||||
onEnter() { installMsaoaid(name + ':enter'); },
|
||||
onLeave() { installMsaoaid(name + ':leave'); },
|
||||
});
|
||||
});
|
||||
installMsaoaid('initial');
|
||||
|
||||
// ============================================================================
|
||||
// A2) libc 层 maps 内容掩盖: open*/fopen 记录 maps fd → read/pread64 时掩写 frida-agent
|
||||
// (原 mask_frida_maps_only.js)
|
||||
// ============================================================================
|
||||
var trackedFds = {};
|
||||
function trackOpen(pathPointer, fd) {
|
||||
var path = '';
|
||||
try { path = pathPointer.isNull() ? '' : pathPointer.readCString(); } catch (_) { return; }
|
||||
if (fd >= 0 && /(?:^|\/)maps$/.test(path)) trackedFds[fd] = path;
|
||||
}
|
||||
['open', 'open64', 'fopen'].forEach(function (name) {
|
||||
var address = exportOf(name);
|
||||
if (address === null) return;
|
||||
Interceptor.attach(address, {
|
||||
onEnter(args) { this.path = args[0]; },
|
||||
onLeave(retval) { trackOpen(this.path, retval.toInt32()); },
|
||||
});
|
||||
});
|
||||
var openat = exportOf('openat');
|
||||
if (openat !== null) Interceptor.attach(openat, {
|
||||
onEnter(args) { this.path = args[1]; },
|
||||
onLeave(retval) { trackOpen(this.path, retval.toInt32()); },
|
||||
});
|
||||
var closeFn = exportOf('close');
|
||||
if (closeFn !== null) Interceptor.attach(closeFn, {
|
||||
onEnter(args) { delete trackedFds[args[0].toInt32()]; },
|
||||
});
|
||||
function maskMapsRead(buffer, length, path) {
|
||||
if (!path || length <= 0) return;
|
||||
try {
|
||||
var bytes = new Uint8Array(buffer.readByteArray(length));
|
||||
var value = '';
|
||||
for (var i = 0; i < bytes.length; i++) value += String.fromCharCode(bytes[i]);
|
||||
if (value.indexOf('frida-agent') < 0 && value.indexOf('gum-js-loop') < 0 && value.indexOf('linjector') < 0) return;
|
||||
value = value.replace(/frida-agent/gi, 'xxxxxxxxxxx')
|
||||
.replace(/gum-js-loop/g, 'pool-worker')
|
||||
.replace(/linjector/g, 'xxxxxxxxx');
|
||||
for (var j = 0; j < length; j++) buffer.add(j).writeU8(value.charCodeAt(j) || 0);
|
||||
} catch (_) {}
|
||||
}
|
||||
['read', 'pread64'].forEach(function (name) {
|
||||
var address = exportOf(name);
|
||||
if (address === null) return;
|
||||
Interceptor.attach(address, {
|
||||
onEnter(args) { this.buffer = args[1]; this.path = trackedFds[args[0].toInt32()] || ''; },
|
||||
onLeave(retval) { maskMapsRead(this.buffer, retval.toInt32(), this.path); },
|
||||
});
|
||||
});
|
||||
emit({ event: 'A-maps-mask-installed' });
|
||||
|
||||
// ============================================================================
|
||||
// B 层 (延迟 OPTS.patchGuardDelayMs): 自终止拦截 + msaoaid crash-guard 补丁
|
||||
// (原 patch_guard_block_termination.js)
|
||||
// ============================================================================
|
||||
function installPatchGuard() {
|
||||
var keep = [];
|
||||
var ownPid = Process.id;
|
||||
function replaceFn(name, returnType, argumentTypes, callback) {
|
||||
var address = exportOf(name);
|
||||
if (address === null) return;
|
||||
try {
|
||||
var native = new NativeCallback(callback, returnType, argumentTypes);
|
||||
keep.push(native);
|
||||
Interceptor.replace(address, native);
|
||||
} catch (_) {}
|
||||
}
|
||||
replaceFn('kill', 'int', ['int', 'int'], function (pid) { return Number(pid) === ownPid ? 0 : -1; });
|
||||
replaceFn('tgkill', 'int', ['int', 'int', 'int'], function (tgid, tid) { return (Number(tgid) === ownPid || Number(tid) === ownPid) ? 0 : -1; });
|
||||
replaceFn('exit', 'void', ['int'], function () {});
|
||||
replaceFn('_exit', 'void', ['int'], function () {});
|
||||
replaceFn('_Exit', 'void', ['int'], function () {});
|
||||
replaceFn('abort', 'void', [], function () {});
|
||||
replaceFn('raise', 'int', ['int'], function () { return 0; });
|
||||
|
||||
// msaoaid crash-guard: 属性读取触发点后补丁 0x20ca8 (x0 → 0)
|
||||
var guardPatched = false;
|
||||
function patchGuard(module) {
|
||||
if (guardPatched) return;
|
||||
var address = module.base.add(0x20ca8);
|
||||
try {
|
||||
Memory.protect(address, 4, 'rwx');
|
||||
var writer = new Arm64Writer(address);
|
||||
writer.putMovRegReg('x0', 'xzr');
|
||||
writer.flush(); writer.dispose();
|
||||
guardPatched = true;
|
||||
emit({ event: 'B-guard-patched' });
|
||||
} catch (error) { emit({ event: 'B-guard-error', error: String(error) }); }
|
||||
}
|
||||
var prop = exportOf('__system_property_get');
|
||||
if (prop !== null) Interceptor.attach(prop, {
|
||||
onEnter(args) {
|
||||
var name = ''; try { name = args[0].readCString(); } catch (_) { return; }
|
||||
if (name !== 'ro.build.version.sdk') return;
|
||||
var module = Process.findModuleByName('libmsaoaidsec.so');
|
||||
if (module !== null) patchGuard(module);
|
||||
},
|
||||
});
|
||||
emit({ event: 'B-patch-guard-installed', delayMs: OPTS.patchGuardDelayMs });
|
||||
}
|
||||
|
||||
setTimeout(installPatchGuard, OPTS.patchGuardDelayMs);
|
||||
emit({ event: 'armed', pid: Process.id, patchGuardDelayMs: OPTS.patchGuardDelayMs });
|
||||
Reference in New Issue
Block a user