feat(huya): 铸证活体验证通过 - 换cred重铸证书bind成功
- 实验C: 原nonce+原指纹+旧轮换hyCred -> 全新密文证书 -> bind stage=2 uid=1199666914671 + 344B biztoken - 证明: 服务端校验nonce合法性但不校验时效; 身份完全由cert内cred决定 - gen_biz_token trace: enum=0x78, 入参(uid串,sessHex32), xxtea明文16B=[t][t<<16]结构, enpack_body组装P1顺序实锤 - => 拿到任意账号有效hyCred(114B)即可铸其证书
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""解剖 gen_biz_token: 抓 BIZTOKEN 枚举/入参/xxtea 明文与密钥/enpack_body 字段。
|
||||
|
||||
目的: 搞清 P1 中 20B nonce 字段的生成规则(服务端校验它 -> 重铸被 40020 拒)。
|
||||
时序(不可变): force-stop -> spawn挂起 -> 双bypass -> resume -> 10s -> patch_guard -> hooks
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import frida
|
||||
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
UID = 1199666914671
|
||||
|
||||
JS = r"""
|
||||
'use strict';
|
||||
Process.setExceptionHandler(function(d){
|
||||
send({type:'segv', info:{type:d.type, addr:String(d.address)}});
|
||||
return true;
|
||||
});
|
||||
var base = Process.getModuleByName('libudbauthunify.so').base;
|
||||
send({type:'armed', base:String(base)});
|
||||
function hx(p,n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }catch(e){ return 'ERR'; } }
|
||||
function rdStr(p){
|
||||
try{
|
||||
var b0=p.readU8();
|
||||
if((b0&1)===0){ var l=b0>>1; if(l>65536) return {t:'s?',len:l,v:''};
|
||||
return {t:'s',len:l,v:l?hx(p.add(1),l):''}; }
|
||||
var len=parseInt(p.add(8).readU64().toString());
|
||||
if(len>262144) return {t:'l?',len:len,v:''};
|
||||
return {t:'l',len:len,v:hx(p.add(16).readPointer(),Math.min(len,4096))};
|
||||
}catch(e){ return {t:'err',len:-1,v:String(e)}; }
|
||||
}
|
||||
function ra(ctx){ try{ return DebugSymbol.fromAddress(ctx.returnAddress).toString().slice(0,80);}catch(e){return '?';} }
|
||||
|
||||
// gen_biz_token(BIZTOKEN w0, const string& x1, const string& x2, string& x3)
|
||||
Interceptor.attach(base.add(0x3307e4), {
|
||||
onEnter: function(a){
|
||||
this.enum=a[0].toInt32()&0xff;
|
||||
this.s1=rdStr(a[1]); this.s2=rdStr(a[2]);
|
||||
this.r=ra(this);
|
||||
},
|
||||
onLeave: function(){ send({type:'genbiz', enum:this.enum, r:this.r,
|
||||
s1:this.s1, s2:this.s2}); }
|
||||
});
|
||||
|
||||
// hyudb_crypt_util::xxtea_encrypt(out&, const in&, key*) void
|
||||
Interceptor.attach(base.add(0x32e83c), {
|
||||
onEnter: function(a){
|
||||
send({type:'xxtea', r:ra(this),
|
||||
out_len:rdStr(a[0]).len, inp:rdStr(a[1]), key:rdStr(a[2])});
|
||||
}
|
||||
});
|
||||
|
||||
// enpack_header(uchar w0, NEWKEY x1, string& x2)
|
||||
Interceptor.attach(base.add(0x32ff44), {
|
||||
onEnter: function(a){ send({type:'hdr', h:a[0].toInt32()&0xff,
|
||||
newkey:rdStr(a[1]), r:ra(this)}); }
|
||||
});
|
||||
|
||||
// enpack_body(uchar x0, str* x1(byval), const str& x2, s3, s4, s5)
|
||||
Interceptor.attach(base.add(0x3300c8), {
|
||||
onEnter: function(a){ send({type:'body', h:a[0].toInt32()&0xff,
|
||||
p1:rdStr(a[1]), p2:rdStr(a[2]), p3:rdStr(a[3]), p4:rdStr(a[4]), r:ra(this)}); }
|
||||
});
|
||||
|
||||
// encode_aes(P1, key24, out) —— 最终明文+密钥对
|
||||
Interceptor.attach(base.add(0x330218), {
|
||||
onEnter: function(a){ this.p0=rdStr(a[0]); this.p1=rdStr(a[1]); },
|
||||
onLeave: function(){ send({type:'encode_aes', p0:this.p0, p1:this.p1}); }
|
||||
});
|
||||
|
||||
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();
|
||||
inst.getQUrlData(__UID__, "", "");
|
||||
send({type:'qurl_done'});
|
||||
}catch(e){ n+=1; if(n%6===0) send({type:'retry',n:n}); setTimeout(go,5000); }
|
||||
}
|
||||
setTimeout(go, 20000);
|
||||
});
|
||||
""".replace('__UID__', str(UID))
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
time.sleep(11)
|
||||
s.create_script((RE / "evidence/scripts/patch_guard_block_termination.js").read_text()).load()
|
||||
|
||||
sc = s.create_script(JS)
|
||||
events, armed = [], []
|
||||
def on_msg(m, _):
|
||||
if m.get('type') == 'error':
|
||||
print('JS ERR:', str(m)[:200]); return
|
||||
if m.get('type') != 'send':
|
||||
return
|
||||
p = m.get('payload') or {}
|
||||
t = p.get('type')
|
||||
if t not in ('retry',):
|
||||
events.append(p)
|
||||
if t == 'armed':
|
||||
armed.append(1)
|
||||
elif t == 'genbiz':
|
||||
print(f"[genbiz] enum={p['enum']} s1={p['s1']['t']}:{p['s1']['len']}={p['s1']['v'][:64]} "
|
||||
f"s2={p['s2']['t']}:{p['s2']['len']}={p['s2']['v'][:64]} <- {p['r'][:48]}")
|
||||
elif t == 'xxtea':
|
||||
print(f"[xxtea] in={p['inp']['t']}:{p['inp']['len']}:{p['inp']['v'][:64]} "
|
||||
f"key={p['key']['t']}:{p['key']['len']}:{p['key']['v'][:40]}")
|
||||
elif t == 'hdr':
|
||||
print(f"[enpack_header] h={p['h']:#04x} newkey={p['newkey']}")
|
||||
elif t == 'body':
|
||||
print(f"[enpack_body] h={p['h']:#04x} p1={p['p1']['t']}:{p['p1']['len']}:{p['p1']['v'][:96]} "
|
||||
f"p2={p['p2']['t']}:{p['p2']['len']} p3={p['p3']['t']}:{p['p3']['len']}:{p['p3']['v'][:64]} "
|
||||
f"p4={p['p4']['t']}:{p['p4']['len']}:{p['p4']['v'][:64]}")
|
||||
elif t == 'encode_aes':
|
||||
print(f"[encode_aes] P1={p['p0']['t']}:{p['p0']['len']} head={p['p0']['v'][:96]} "
|
||||
f"key={p['p1']['t']}:{p['p1']['len']}:{p['p1']['v']}")
|
||||
sc.on('message', on_msg)
|
||||
sc.load()
|
||||
assert armed
|
||||
deadline = time.time() + 90
|
||||
while time.time() < deadline:
|
||||
time.sleep(2)
|
||||
if any(e.get('type') == 'qurl_done' for e in events):
|
||||
time.sleep(8)
|
||||
break
|
||||
Path('/Users/yml/codes/douyu_login_py/evidence/genbiz_trace.json').write_text(json.dumps(events))
|
||||
from collections import Counter
|
||||
print('saved:', Counter(e.get('type') for e in events))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
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)
|
||||
Reference in New Issue
Block a user