docs: 绕过配方状态更正 (bypass_all 未验证, 以原版三脚本为准) + 三层健康检查工具

This commit is contained in:
yml2213
2026-08-29 11:44:54 +08:00
parent 5fa1f6d082
commit 1a6711c180
11 changed files with 504 additions and 24 deletions
+68
View File
@@ -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 取代, 留作对照。
+60
View File
@@ -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)
+109
View File
@@ -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()
+30
View File
@@ -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
+2 -3
View File
@@ -21,9 +21,8 @@ def main():
time.sleep(1.5) time.sleep(1.5)
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True) pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
s=d.attach(pid) s=d.attach(pid)
try: from bypass_loader import load_bypass
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load() load_bypass(s)
except Exception as e: print(f"[*] bypass err {e}",flush=True)
d.resume(pid) d.resume(pid)
result={}; got={"post":False,"out":False} result={}; got={"post":False,"out":False}
def on(m,_): def on(m,_):
+2 -3
View File
@@ -21,9 +21,8 @@ def main():
time.sleep(1.5) time.sleep(1.5)
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True) pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
s=d.attach(pid) s=d.attach(pid)
try: from bypass_loader import load_bypass
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load() load_bypass(s)
except Exception as e: print(f"[*] bypass err {e}",flush=True)
d.resume(pid) d.resume(pid)
result={}; got={"post":False,"out":False} result={}; got={"post":False,"out":False}
seen_fns=set() seen_fns=set()
+2 -3
View File
@@ -21,9 +21,8 @@ def main():
time.sleep(1.5) time.sleep(1.5)
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True) pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
s=d.attach(pid) s=d.attach(pid)
try: from bypass_loader import load_bypass
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load() load_bypass(s)
except Exception as e: print(f"[*] bypass err {e}",flush=True)
d.resume(pid) d.resume(pid)
result={}; got={"post":False,"out":False} result={}; got={"post":False,"out":False}
seen_fns=set() seen_fns=set()
+2 -3
View File
@@ -21,9 +21,8 @@ def main():
time.sleep(1.5) time.sleep(1.5)
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True) pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
s=d.attach(pid) s=d.attach(pid)
try: from bypass_loader import load_bypass
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load() load_bypass(s)
except Exception as e: print(f"[*] bypass err {e}",flush=True)
d.resume(pid) d.resume(pid)
result={}; state={"post":False,"key":False} result={}; state={"post":False,"key":False}
def on(m,_): def on(m,_):
+3 -7
View File
@@ -16,14 +16,10 @@ def main():
pid=d.spawn([PACKAGE]) pid=d.spawn([PACKAGE])
print(f"[*] spawned {pid}",flush=True) print(f"[*] spawned {pid}",flush=True)
s=d.attach(pid) s=d.attach(pid)
# 双重防护: callsite bypass + maps 掩盖 from bypass_loader import load_bypass
s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()).load() load_bypass(s, patch_guard_delay_ms=3000)
s.create_script((RE/"evidence/scripts/mask_frida_maps_only.js").read_text()).load()
d.resume(pid) d.resume(pid)
print("[*] resumed, 稳定 11s...",flush=True) print("[*] resumed (bypass_all: A层立即 + B层3s patch_guard)",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)
result={}; state={"udb":0} result={}; state={"udb":0}
def on(m,_): def on(m,_):
+2 -5
View File
@@ -41,11 +41,8 @@ def main():
pid = d.spawn([PACKAGE]) pid = d.spawn([PACKAGE])
print(f"[*] spawn pid={pid}", flush=True) print(f"[*] spawn pid={pid}", flush=True)
s = d.attach(pid) s = d.attach(pid)
try: from bypass_loader import load_bypass
b = s.create_script((RE / "evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()) load_bypass(s)
b.load()
except Exception as e:
print(f"[*] bypass err {e}", flush=True)
d.resume(pid) d.resume(pid)
result: dict = {} result: dict = {}
+224
View File
@@ -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 });