#!/usr/bin/env python3 """改写 libhydeviceid.so .data 中的 t1 指纹, 验证 dfpReport 响应变化。 t1 = 865a4924a40897ac1fcfe6b4c2cbb045 (3处静态内置, datadiv 解密)。 改为随机 32hex 后重新登录, 看 dfpReport 响应 t1 是否变化。 同时 hook SSL_write 抓新请求 body 对比魔数后密文是否变化。 """ from __future__ import annotations import json import random import string 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_t1_patch.json") # 新 t1 (随机 32hex) NEW_T1 = "".join(random.choice("0123456789abcdef") for _ in range(32)) print(f"[*] 新 t1 = {NEW_T1}") JS = r""" 'use strict'; Process.setExceptionHandler(function(d){ return true; }); send({type:'armed'}); var NEW_T1 = '%s'; var T1_OFFS = [0x3bb190, 0x3bb230, 0x3e0e50]; // 等待: so 加载后延时 800ms, 确保 datadiv 就地解密已完成 var tries = 0; var iv = setInterval(function(){ tries += 1; var m = null; try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){} if (m) { clearInterval(iv); setTimeout(function(){ patchAll(m); }, 800); } else if (tries > 100) { clearInterval(iv); send({type:'no_mod'}); } }, 50); function patchAll(m){ // 先验证当前明文, 若仍是密文说明 datadiv 未跑完, 再等 try{ var cur = m.base.add(0x3bb190).readUtf8String(32); if (cur.indexOf('865a') !== 0 && cur.indexOf('c1') !== 0) { // 还没解密好, 再等 setTimeout(function(){ patchAll(m); }, 400); return; } }catch(e){} T1_OFFS.forEach(function(off){ try{ var p = m.base.add(off); var cur = p.readUtf8String(32); send({type:'backup', off:off.toString(16), cur:cur}); Memory.protect(p, 64, 'rwx'); p.writeUtf8String(NEW_T1); var after = p.readUtf8String(32); send({type:'patched', off:off.toString(16), after:after, ok: after === NEW_T1}); }catch(e){ send({type:'patch_err', off:off.toString(16), e:String(e)}); } }); send({type:'patch_done'}); } // hook SSL_write 抓 dfpReport 请求 try{ var fns = []; var r = new ApiResolver('module'); fns = r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){ return x.address; }); fns.slice(0, 6).forEach(function(p){ Interceptor.attach(p, { 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; var arr = new Uint8Array(a[1].readByteArray(len)); var hex = ''; for (var j = 0; j < arr.length; j++) hex += ('0'+arr[j].toString(16)).slice(-2); send({type:'wire', len:len, hex:hex}); } }); }); }catch(e){ send({type:'ssl_err', e:String(e)}); } """ % NEW_T1 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 == "backup": print(f"[备份] 0x{p.get('off')}: {p.get('cur')}", flush=True) elif t == "patched": print(f"[改写] 0x{p.get('off')}: -> {p.get('after')} ok={p.get('ok')}", flush=True) elif t == "patch_done": print(f"[*] 改写完成 {p.get('patched')} 处", flush=True) events.append({"patched": True, "new_t1": NEW_T1}) OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1)) elif t == "wire": body = bytes.fromhex(p.get('hex')) try: hp = body.index(b'\r\n\r\n') + 4 except ValueError: hp = 0 b = body[hp:] j = b.find(b'\x57\x18\x82\xcf\x66\x4b\xb3\x94') print(f"[wire] len={p.get('len')} 魔数@{j}", flush=True) if j >= 0: print(f" 密文头: {b[j+10:j+30].hex(' ')}", flush=True) rec = {"type": "wire", "len": p.get('len'), "hex": p.get('hex')} events.append(rec) OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1)) 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(f"[*] 已 resume, t1 已改为 {NEW_T1}。请在手机 退出登录->重新登录", flush=True) try: while True: time.sleep(5) except KeyboardInterrupt: pass print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True) if __name__ == "__main__": main()