#!/usr/bin/env python3 """dump 设备运行时 libhydeviceid.so 的 datadiv 解密区(就地解密=明文)。 datadiv 解码器在 JNI_OnLoad 时把 0x3c8xxx 数据段密文就地解密为明文。 直接 dump 该区域 + 扫描全进程可读字符串(android- 等), 命中即持久化。 """ from __future__ import annotations import json import time from pathlib import Path import frida RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_runtime_strings.json") JS = r""" 'use strict'; Process.setExceptionHandler(function(d){ return true; }); send({type:'armed'}); function hex(p, n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); } catch(e){ return 'ERR'; } } // 1. dump libhydeviceid.so 数据段 (datadiv 就地解密区 0x3c8000 ~ 0x3ca000) function dumpModuleData(){ var m = null; try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){ return; } [0x3c8000, 0x3ca000, 0x3cc000, 0x3d0000, 0x3e0000].forEach(function(off){ try{ var base = m.base.add(off); var buf = base.readByteArray(0x2000); var bytes = Array.from(new Uint8Array(buf)); // 提取可读字符串 var strs = []; var cur = ''; for (var i = 0; i < bytes.length; i++) { var c = bytes[i]; if (c >= 32 && c < 127) { cur += String.fromCharCode(c); } else { if (cur.length >= 5) strs.push(cur); cur = ''; } } if (strs.length) { send({type:'datastr', off:off.toString(16), n:strs.length, strs: strs.slice(0, 40)}); } // 搜魔数 var hexs = bytes.map(function(x){return ('0'+x.toString(16)).slice(-2);}).join(''); if (hexs.indexOf('571882cf') >= 0) send({type:'magic_in_data', off:off.toString(16)}); }catch(e){} }); } // 2. 全进程扫描明文设备数据 (android- / UA / JSON) function scanPlain(){ try{ Process.enumerateRanges('r--').forEach(function(r){ if (r.size > 1024*1024*128) return; try{ // 找 "android-" 出现且周围有可读文本 var hits = Memory.scanSync(r.base, r.size, '61 6e 64 72 6f 69 64 2d'); hits.slice(0, 30).forEach(function(x){ var ctx = ''; try{ ctx = x.address.sub(64).readUtf8String(); }catch(e){} send({type:'plain', addr:String(x.address), ctx: (ctx || '').slice(0, 200)}); }); }catch(e){} }); }catch(e){} send({type:'scan_done'}); } dumpModuleData(); setTimeout(function(){ scanPlain(); }, 2000); """ def main(): device = frida.get_device_manager().add_remote_device("127.0.0.1:31877") pid = device.spawn(["com.duowan.kiwi"]) print(f"[*] spawned 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 == "datastr": print(f"[datastr] off=0x{p.get('off')} {p.get('n')} 条:", flush=True) for s in p.get('strs', []): print(f" {s}", flush=True) events.append(p) OUT.write_text(json.dumps(events, indent=1)) elif t == "magic_in_data": print(f"[MAGIC-IN-DATA] off=0x{p.get('off')}", flush=True) elif t == "plain": print(f"[plain] @{p.get('addr')}: {p.get('ctx')!r}", flush=True) events.append(p) elif t == "scan_done": print("[*] scan done", flush=True) script = session.create_script(JS) script.on("message", on_message) script.load() print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True) session.create_script( (RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text() ).load() session.create_script( (RE / "evidence/scripts/mask_frida_maps_only.js").read_text() ).load() device.resume(pid) time.sleep(11) session.create_script( (RE / "evidence/scripts/patch_guard_block_termination.js").read_text() ).load() print("[*] 已 resume, dump 完成即 pkill", flush=True) try: while True: time.sleep(5) except KeyboardInterrupt: pass print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True) if __name__ == "__main__": main()