- 定位完整调用链: HYUDBMSDKCommon桥接 -> MsgGetH5InfoEx(184549387) -> HandlerGetH5InfoEx::onHandler@0x38d548 -> getOtp@0x2689f4 -> hyudb_otp_encrypt@0x32fa24 -> createWupRequestData@0x38dab0 - 实测参数表: otp(uid串,2,轮换idx,'5008',SESS_HEX32,hyCred114B,4,nonce) - 密钥字符串确认: 'HuyaUdb1928374650qwertyuiop' (encrypt x3 参数) - Java桥直呼触发: LoginProxy.getQUrlData/getH5InfoEx/getCred 可编程调用 - 稳定时序固化: spawn挂起->bypass->resume->10s->patch_guard->hooks (attach已运行进程必被检测闪退) - 发现App登录态JSON含现成web cookie.biztoken(344B)
170 lines
5.8 KiB
Python
170 lines
5.8 KiB
Python
#!/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<arr.length;i+=3){
|
|
var b0=arr[i],b1=i+1<arr.length?arr[i+1]:0,b2=i+2<arr.length?arr[i+2]:0;
|
|
c+=B64[b0>>2]+B64[((b0&3)<<4)|(b1>>4)]+(i+1<arr.length?B64[((b1&15)<<2)|(b2>>6)]:"=")+(i+2<arr.length?B64[b2&63]:"=");
|
|
}
|
|
return c;
|
|
}
|
|
function rdStrObj(p){
|
|
try{
|
|
var b0=p.readU8();
|
|
if((b0&1)===0) return {t:"s",len:b0>>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)
|