194 lines
6.6 KiB
Python
194 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""间接调用解析器 v3 (按需启用, 零常驻开销):
|
|
默认不挂任何桩 hook; copy_61098 轮次边界(x1=MAGIC)触发时动态 attach 72 个 br 桩,
|
|
3 秒后全部 detach。捕获 crypto 调用图谱定位 keystream 内核。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import frida
|
|
|
|
PACKAGE = "com.duowan.kiwi"
|
|
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
|
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/got_resolve.json")
|
|
|
|
JS = r"""
|
|
'use strict';
|
|
Process.setExceptionHandler(function(d){ return true; });
|
|
send({type:'armed'});
|
|
var t0 = Date.now();
|
|
var MAGIC_HDR = [0x57,0x18,0x82,0xcf,0x66,0x4b,0xb3,0x94,0x01,0xee];
|
|
var modBase = null;
|
|
var counts = new Map();
|
|
var roundN = 0;
|
|
var stubHandles = []; // Interceptor handles to detach
|
|
var stubsActive = false;
|
|
var activeSince = 0;
|
|
|
|
function hexb(p, n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(function(b){return ('0'+b.toString(16)).slice(-2)}).join(''); }catch(e){ return ''; } }
|
|
function startsWith(p, arr){
|
|
try{ var b = new Uint8Array(p.readByteArray(arr.length)); for (var i=0;i<arr.length;i++) if (b[i]!==arr[i]) return false; return true; }catch(e){ return false; }
|
|
}
|
|
|
|
function hookBrSite(addr){
|
|
try{
|
|
var h = Interceptor.attach(addr, {
|
|
onEnter: function(a){
|
|
var ctx = this.context;
|
|
var tgt = ctx.x17;
|
|
if (tgt.isNull()) return;
|
|
var off = tgt.sub(modBase).toInt32();
|
|
counts.set(off, (counts.get(off) || 0) + 1);
|
|
}
|
|
});
|
|
return h;
|
|
}catch(e){ return null; }
|
|
}
|
|
|
|
function enableStubs(){
|
|
if (stubsActive) return;
|
|
stubsActive = true;
|
|
activeSince = Date.now();
|
|
stubHandles = [];
|
|
for (var i = 0; i < 24; i++){
|
|
var h = hookBrSite(modBase.add(0x461c0 + i*0x10 + 0xc));
|
|
if (h) stubHandles.push(h);
|
|
}
|
|
for (var i = 0; i < 48; i++){
|
|
var h = hookBrSite(modBase.add(0x45aa0 + i*0x10 + 0xc));
|
|
if (h) stubHandles.push(h);
|
|
}
|
|
send({type:'stubs_on', n: stubHandles.length, t: Date.now()-t0});
|
|
// 3s 后自动摘除
|
|
setTimeout(function(){
|
|
if (!stubsActive) return;
|
|
for (var j = 0; j < stubHandles.length; j++){
|
|
try{ stubHandles[j].detach(); }catch(e){}
|
|
}
|
|
stubHandles = [];
|
|
stubsActive = false;
|
|
send({type:'stubs_off', t: Date.now()-t0});
|
|
}, 3000);
|
|
}
|
|
|
|
function tryHookRound(){
|
|
try{
|
|
var m = Process.getModuleByName('libhydeviceid.so');
|
|
modBase = m.base;
|
|
Interceptor.attach(m.base.add(0x61098), {
|
|
onEnter: function(a){
|
|
var ctx = this.context;
|
|
if (!startsWith(ctx.x1, MAGIC_HDR)) return;
|
|
roundN++;
|
|
// dump round data BEFORE enabling stubs (crypto already done)
|
|
var arr = [];
|
|
counts.forEach(function(v, k){ if (v > 2) arr.push([k, v]); });
|
|
send({type:'round', n: roundN, t: Date.now()-t0, counts: arr, activeMs: Date.now()-activeSince});
|
|
counts = new Map();
|
|
send({type:'magic', t: Date.now()-t0, full: hexb(ctx.x1, 4300)});
|
|
// 延迟到回调外再挂桩(frida 回调内 attach 会失败)
|
|
setTimeout(function(){ counts = new Map(); enableStubs(); }, 15);
|
|
}
|
|
});
|
|
send({type:'round_hooked'});
|
|
}catch(e){ setTimeout(tryHookRound, 500); }
|
|
}
|
|
setTimeout(tryHookRound, 400);
|
|
|
|
try{
|
|
var r = new ApiResolver('module');
|
|
r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){
|
|
Interceptor.attach(m.address, {
|
|
onEnter: function(a){
|
|
var len = a[2].toInt32();
|
|
if (len < 100 || len > 20000) return;
|
|
var head = '';
|
|
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
|
|
if (head.indexOf('dfpReport') < 0) return;
|
|
send({type:'wire', len:len, t: Date.now()-t0});
|
|
}
|
|
});
|
|
});
|
|
send({type:'ssl_hooked'});
|
|
}catch(e){ send({type:'ssl_err', e:String(e)}); }
|
|
"""
|
|
|
|
|
|
def load_with_retry(session, js, n=4):
|
|
last = None
|
|
for i in range(n):
|
|
try:
|
|
s = session.create_script(js)
|
|
s.load()
|
|
return s
|
|
except Exception as e:
|
|
last = e
|
|
print(f"[retry {i}] {e}", flush=True)
|
|
time.sleep(3)
|
|
raise last
|
|
|
|
|
|
def main():
|
|
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
|
|
pid = device.spawn([PACKAGE])
|
|
print(f"[*] spawned {PACKAGE} pid={pid}", flush=True)
|
|
session = device.attach(pid)
|
|
|
|
events = []
|
|
|
|
def on_message(message, data):
|
|
if message.get("type") == "error":
|
|
print("[JS-ERR]", str(message)[:200], flush=True)
|
|
return
|
|
if message.get("type") != "send":
|
|
return
|
|
p = message.get("payload") or {}
|
|
t = p.get("type")
|
|
if t == "armed":
|
|
print("[*] JS armed", flush=True)
|
|
elif t == "round_hooked":
|
|
print("[*] round boundary hooked", flush=True)
|
|
elif t == "stubs_on":
|
|
print(f"[t={p.get('t')}ms] stubs ON x{p.get('n')}", flush=True)
|
|
elif t == "stubs_off":
|
|
print(f"[t={p.get('t')}ms] stubs OFF", flush=True)
|
|
elif t == "round":
|
|
c = dict(p.get('counts') or [])
|
|
top = sorted(c.items(), key=lambda x: -x[1])[:15]
|
|
print(f"[t={p.get('t')}ms round{p.get('n')}] (stubs active {p.get('activeMs')}ms) calls={sum(c.values())} distinct={len(c)}", flush=True)
|
|
for off, cnt in top:
|
|
print(f" off=0x{off:x} x{cnt}", flush=True)
|
|
events.append({"type": "round", **p})
|
|
OUT.write_text(json.dumps(events))
|
|
elif t == "magic":
|
|
events.append({"type": "magic", "t": p.get('t'), "full": p.get('full')})
|
|
OUT.write_text(json.dumps(events))
|
|
elif t == "wire":
|
|
print(f"[t={p.get('t')}ms] [wire] len={p.get('len')}", flush=True)
|
|
events.append({"type": "wire", **p})
|
|
OUT.write_text(json.dumps(events))
|
|
|
|
# 严格按约定顺序: spawn -> bypass x2 -> resume + sleep 11 -> patch_guard -> 最后主 hook JS
|
|
print("[*] loading bypass", flush=True)
|
|
load_with_retry(session, (RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text())
|
|
load_with_retry(session, (RE / "evidence/scripts/mask_frida_maps_only.js").read_text())
|
|
device.resume(pid)
|
|
time.sleep(11)
|
|
load_with_retry(session, (RE / "evidence/scripts/patch_guard_block_termination.js").read_text())
|
|
print("[*] loading 主 hook JS (最后)", flush=True)
|
|
script = load_with_retry(session, JS)
|
|
script.on("message", on_message)
|
|
print("[*] all loaded. waiting dfpReport", flush=True)
|
|
try:
|
|
while True:
|
|
time.sleep(5)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
print(f"[*] done -> {OUT}", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |