#!/usr/bin/env python3 """散弹枪 v2: 完整解码 dfpReport 管道参数 (指针数组 / (ptr,len) / std::string)。 挂载: topA_20e924, midA_5a938, cryptoA_5b1f4, copy_61098, pktA_7f744, pktB_92128 对每个 onEnter 的 x0..x4: 1) 直接缓冲探测 (JSON/MAGIC 前缀 -> full dump 6000B) 2) (ptr,len) 解释: 读 16B -> 若两个 u64 第二个 < 100000 且第一个可读 -> dump 3) 指针数组解释: 读 4x8B 指针, 对每个指针尝试 (2) 4) std::string 解释: [size u64][data u64][cap u64] 或 [data][size][cap] 所有事件带 t (ms since start)。SSL_write 抓 wire 对照。 """ 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") import time as _t OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_pipeline_" + _t.strftime("%H%M%S") + ".json") JS = r""" 'use strict'; Process.setExceptionHandler(function(d){ return true; }); send({type:'armed'}); var t0 = Date.now(); var JSON_HDR = [0x7b,0x22,0x61,0x70,0x70,0x49,0x64,0x22,0x3a,0x22,0x35,0x30,0x30,0x38]; var MAGIC_HDR = [0x57,0x18,0x82,0xcf,0x66,0x4b,0xb3,0x94,0x01,0xee]; function hexb(p, n){ try{ var arr = Array.from(new Uint8Array(p.readByteArray(n))); var s = ''; for (var i=0;i 0 && l0 < 20000 && p0 != p){ var b0 = hexb(p0, Math.min(Number(l0), 6000)); var kind = 'seg'; if (startsWith(p0, JSON_HDR)) kind = 'JSON'; else if (startsWith(p0, MAGIC_HDR)) kind = 'MAGIC'; res.kind = kind; res.ptrlen = {ptr: String(p0), len: Number(l0)}; res.seg = b0; return res; } // 3) pointer array (4 x 8B) var ptrs = []; for (var i=0;i<4;i++){ var q = p.add(i*8).readPointer(); if (q.isNull() || !isReadable(q)) break; ptrs.push(String(q)); } if (ptrs.length >= 2){ res.kind = 'ptrs'; res.ptrs = ptrs; // decode each pointer as (ptr,len) var segs = []; for (var j=0;j 0 && lj < 20000 && isReadable(q0)){ segs.push({ptr: String(q0), len: Number(lj), head: hexb(q0, 48), kind: startsWith(q0, JSON_HDR) ? 'JSON' : (startsWith(q0, MAGIC_HDR) ? 'MAGIC' : 'raw')}); } } res.segs = segs; return res; } // 4) raw res.kind = 'raw'; res.head = hexb(p, 32) || ''; return res; }catch(e){ return null; } } // deep decode: scan 1024B at p for plausible pointers, dump 9000B at each function deepDeref(p){ var out = {reg: 'deep', addr: String(p)}; var found = []; var seen = {}; try{ // also dump page-aligned base 12000B try{ var pg = p.and(ptr('0xfffffffffffff000')); if (isReadable(pg)){ found.push({kind: 'page', ptr: String(pg), full: hexb(pg, 12000)}); seen[String(pg)] = true; } }catch(e){} var base = p.sub(512); for (var i = 0; i < 1024; i += 8){ var v; try{ v = base.add(i).readU64(); }catch(e){ continue; } var cand = v.and(ptr('0x7fffffffffff')); var s = cand.toString(); if (cand == ptr('0')) continue; // heap-ish: 0x7[7-9ca].. or 0xb4.. or 0x77.. if (!(/^0x(7[78a-c9]|b4)[0-9a-f]{9,}$/.test(s))) continue; if (seen[s]) continue; if (!isReadable(cand)) continue; seen[s] = true; var entry = {off: i - 512, ptr: s}; var kind = 'raw'; if (startsWith(cand, JSON_HDR)) kind = 'JSON'; else if (startsWith(cand, MAGIC_HDR)) kind = 'MAGIC'; entry.kind = kind; if (kind === 'JSON' || kind === 'MAGIC'){ entry.full = hexb(cand, 9000); } else { entry.head = hexb(cand, 64) || ''; } found.push(entry); } }catch(e){} out.entries = found; return out; } function hookFn(off, name){ try{ var base = Process.getModuleByName('libhydeviceid.so').base; Interceptor.attach(base.add(off), { onEnter: function(a){ var ctx = this.context; var ev = {fn: name, phase: 'enter', t: Date.now() - t0}; ['x0','x1','x2','x3','x4'].forEach(function(r, i){ var pr = decodeArg(ctx[r], r); if (pr) ev[r] = pr; }); // cryptoA: deep deref x0 if (name === 'cryptoA_5b1f4'){ ev.deep = deepDeref(ctx.x0); } send({type:'pipe', ev: ev}); } }); send({type:'hooked', name: name}); }catch(e){ send({type:'hook_fail', name: name, e: String(e)}); } } var H = [ [0x20e924, 'topA_20e924'], [0x5a938, 'midA_5a938'], [0x5b1f4, 'cryptoA_5b1f4'], [0x61098, 'copy_61098'], [0x7f744, 'pktA_7f744'], [0x92128, 'pktB_92128'], ]; var hookedN = 0; function attachOne(base, off, name){ try{ Interceptor.attach(base.add(off), { onEnter: function(a){ var ctx = this.context; var ev = {fn: name, phase: 'enter', t: Date.now() - t0}; ['x0','x1','x2','x3','x4'].forEach(function(r, i){ var pr = decodeArg(ctx[r], r); if (pr) ev[r] = pr; }); // cryptoA: deep deref x0 if (name === 'cryptoA_5b1f4'){ ev.deep = deepDeref(ctx.x0); } // copy_61098 second call (x0=MAGIC): heap scan for JSON + dump 4600B if (name === 'copy_61098' && (ev.x0||{}).kind === 'MAGIC'){ var hits = []; try{ var pat = '7b 22 61 70 70 49 64 22 3a 22 35 30 30 38'; var ranges = Process.enumerateRanges('rw-'); for (var ri = 0; ri < ranges.length; ri++){ var rng = ranges[ri]; if (rng.size > 0x10000000) continue; var m = Memory.scanSync(rng.base, rng.size, pat); for (var mi = 0; mi < m.length; mi++){ var full = hexb(m[mi].address, 4600) || ''; var h = full.substring(0, 96); hits.push({addr: String(m[mi].address), head: h, full: full}); } } }catch(e){} send({type:'jsonhits', n: hits.length, hits: hits, t: Date.now() - t0}); } send({type:'pipe', ev: ev}); } }); hookedN++; return true; }catch(e){ return false; } } function tryHookAll(){ try{ var m = Process.getModuleByName('libhydeviceid.so'); for (var i=0;i 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, hex:hexb(a[1], 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=5): 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)[:300], 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 == "hooked_all": print(f"[*] hooked {p.get('n')} fns", flush=True) elif t == "ssl_hooked": print("[*] SSL_write hooked", flush=True) elif t == "pipe": ev = p.get('ev') or {} parts = [] for r in ['x0','x1','x2','x3','x4']: pr = ev.get(r) if not pr or not isinstance(pr, dict): continue k = pr.get('kind') if k == 'JSON': parts.append(f"{r}=JSON full") elif k == 'MAGIC': parts.append(f"{r}=MAGIC full") elif k == 'seg': parts.append(f"{r}=seg({pr.get('ptrlen',{}).get('len')})@{pr.get('ptrlen',{}).get('ptr')}") elif k == 'ptrs': parts.append(f"{r}=ptrs {pr.get('ptrs')} segs={[(s.get('kind'), s.get('len')) for s in pr.get('segs') or []]}") elif k == 'raw': parts.append(f"{r}=raw {pr.get('addr')} {pr.get('head','')[:16]}") print(f"[{ev.get('t')}ms] {ev.get('fn')}::{ev.get('phase')} " + " ".join(parts), flush=True) events.append({"type": "pipe", "ev": ev}) OUT.write_text(json.dumps(events)) elif t == "jsonhits": print(f"[{p.get('t')}ms] [jsonhits] n={p.get('n')}", flush=True) events.append({"type": "jsonhits", "t": p.get('t'), "hits": p.get('hits')}) OUT.write_text(json.dumps(events)) elif t == "wire": print(f"[{p.get('t')}ms] [wire] len={p.get('len')}", flush=True) events.append({"type": "wire", "len": p.get('len'), "t": p.get('t'), "hex": p.get('hex')}) 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("[*] 已 resume。等待 dfpReport", flush=True) try: while True: time.sleep(5) except KeyboardInterrupt: pass print(f"[*] 结束 -> {OUT}", flush=True) if __name__ == "__main__": main()