#!/usr/bin/env python3 """按已验证成功的时序采集证书生成全链路数据。 时序(不可变): spawn挂起 -> bypass单独装载 -> resume -> 稳定10s -> patch_guard -> 业务hooks 然后提示用户 手动退出登录 -> 重新登录。 """ 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") JS = r""" 'use strict'; var base = Process.getModuleByName("libudbauthunify.so").base; send({type:'armed', base:String(base)}); function b64ify(ptr, n) { var arr = new Uint8Array(ptr.readByteArray(n)); var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var c = ""; for (var i=0;i>2]+B64[((b0&3)<<4)|(b1>>4)]+(i+1>6)]:"=")+(i+2>1,v:p.add(1).readUtf8String(b0>>1)}; var len=parseInt(p.add(8).readU64().toString()); if(len>16384) return {t:"err",v:"too long"}; return {t:"b64",v:b64ify(p.add(16).readPointer(),len)}; }catch(e){return {t:"err",v:String(e)};} } function hexof(p,n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(function(x){return ('0'+x.toString(16)).slice(-2);}).join(''); } catch(e){ return 'ERR'; } } Interceptor.attach(base.add(0x32e71c), { onEnter: function(a){ this.inp = rdStrObj(a[0]); this.outp = a[1]; }, onLeave: function(){ send({type:'md5', inp:this.inp, out:rdStrObj(this.outp)}); } }); Interceptor.attach(base.add(0x32fa24), { onEnter: function(a){ this.a = {s1:rdStrObj(a[0]), b1:a[1].toInt32(), b2:a[2].toInt32(), s2:rdStrObj(a[3]), s3:rdStrObj(a[4]), s4:rdStrObj(a[5]), b3:a[6].toInt32(), nonce:a[7].toString()}; this.outp = this.context.sp.readPointer(); }, onLeave: function(){ send({type:'otp', args:this.a, out:rdStrObj(this.outp)}); } }); // sret 约定: x0=返回串, x1=this, x2=in, x3=key Interceptor.attach(base.add(0x250038), { onEnter: function(a){ this.retp = a[0]; this.inp = rdStrObj(a[2]); this.key = rdStrObj(a[3]); this.thishead = hexof(a[1],48); }, onLeave: function(){ send({type:'aes', inp:this.inp, key:this.key, out:rdStrObj(this.retp), this_head:this.thishead}); } }); Interceptor.attach(base.add(0x38dab0), { onEnter: function(a){ this.ret = a[0]; }, onLeave: function(){ send({type:'createWup', result:rdStrObj(this.ret)}); } }); Java.perform(function(){ var n = 0; function go(){ try { var seed = Java.use('com.duowan.kiwi.base.login.udb.HuyaLoginProxy'); var F = Java.ClassFactory.get(seed.class.getClassLoader()); var inst = F.use('com.hysdkproxy.LoginProxy').getInstance(); var q = inst.getQUrlData(1199666914671, "", ""); send({type:'qurl_done', len: q?q.length:0}); } catch(e){ n+=1; if(n%8===0) send({type:'retry', n:n}); setTimeout(go, 5000); } } setTimeout(go, 20000); }); """ def main(): d = frida.get_device_manager().add_remote_device('127.0.0.1:31877') pid = d.spawn(['com.duowan.kiwi']) print(f'spawned {pid}') s = d.attach(pid) # 双重防护: callsite bypass + maps 掩盖 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() d.resume(pid) print('resumed') time.sleep(11) s.create_script((RE / "evidence/scripts/patch_guard_block_termination.js").read_text()).load() print('patch_guard on') events = [] def on_msg(m, _): if m.get('type') != 'send': if m.get('type') == 'error': print('ERR:', str(m)[:160]) return p = m.get('payload') or {} t = p.get('type') if t != 'retry': events.append(p) if t == 'armed': print(f'[armed] base={p["base"]}') elif t == 'otp': a = p['args'] print(f"[otp] uid={str(a['s1'].get('v'))[:18]} b={a['b1']},{a['b2']},{a['b3']}") elif t == 'md5': print(f"[md5] in={str(p['inp'].get('v',''))[:36]} out={str(p['out'].get('v',''))[:40]}") elif t == 'aes': k = p.get('key',{}) print(f"[aes] key[{k.get('t')}]={str(k.get('v',''))[:44]}") print(f" in={str(p.get('inp',{}).get('v',''))[:90]}") print(f" out={str(p.get('out',{}).get('v',''))[:60]}") elif t == 'createWup': print(f"[createWup] len={len(str(p['result'].get('v','')))}") elif t == 'qurl_done': print(f"[qurl_done] {p.get('len')}") sc = s.create_script(JS) sc.on('message', on_msg) sc.load() print('\n>>> 请现在在手机上: 手动退出登录 -> 重新登录 <<<\n') deadline = time.time() + 260 while time.time() < deadline: time.sleep(2) if any(e.get('type') == 'qurl_done' for e in events): time.sleep(6) break Path('/tmp/final_capture.json').write_text(json.dumps(events, ensure_ascii=False)) types = {} for e in events: types[e.get('type')] = types.get(e.get('type'), 0) + 1 print('saved:', types) if __name__ == '__main__': import subprocess for attempt in range(4): print(f'===== 尝试 #{attempt+1} =====') try: main() break except frida.InvalidOperationError as e: print(f'session detached: {e}') subprocess.run(['adb','shell','am','force-stop','com.duowan.kiwi']) time.sleep(3) except Exception as e: print(f'err: {e}') time.sleep(3)