From 0ade681881cefe1c209d43b7f2585d9f0749a6c9 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 25 Aug 2026 17:16:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20Java=E6=A1=A5=E4=B8=BB=E5=8A=A8?= =?UTF-8?q?=E8=B0=83getCred=E6=88=90=E5=8A=9F=20-=20=E6=B4=BB=E4=BD=93hyCr?= =?UTF-8?q?ed(152b64/114B)=E5=88=B0=E6=89=8B=E5=B9=B6=E9=93=B8=E5=87=BA?= =?UTF-8?q?=E6=96=B0=E8=AF=81=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- evidence/forged_with_live_cred.json | 1 + evidence/getcred.json | 1 + evidence/hycred_live.json | 1 + scripts/hook_getcred.py | 112 ++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 evidence/forged_with_live_cred.json create mode 100644 evidence/getcred.json create mode 100644 evidence/hycred_live.json create mode 100644 scripts/hook_getcred.py diff --git a/evidence/forged_with_live_cred.json b/evidence/forged_with_live_cred.json new file mode 100644 index 0000000..5c6ad0c --- /dev/null +++ b/evidence/forged_with_live_cred.json @@ -0,0 +1 @@ +{"p1": "010400353030381400c25220f81f60cf64ee54e83ef1eab6bdd047b5af28003032646633393837393734333265616465666363313237363731313961643565383039393933383972000a20171d4885bb9bb40f29517ab490f3634a0640e942e24039e2f471a5a66a0c64662b68c43f64a90ab982d8298bfc5de4e58a0bb6617a7ab8ecc730cde4a28bd828261096ca95b6247bba5e4714395166f9a22eb0c9954e6d0890ba40fa951e70e63c7c4c8e8e5547f6ca029830f31adbe8", "cert_hex": "0c201aeb9aa1d4c9e6fc6d5628b1cb936a3b0c360b3914ed9b94f2cf4808490594202cbaa481870772fe2a4df9a94fdb8a0e41312514470c77cbfa586558f87309bea9c2abc3b6948072d9c345292866c8517e60cc7cd93f2c2bb82f8d8a255da88418e86fd21309d9306024a8006b99540c58054f823c7d8924b56f841c8dbb10d093ddf859b21625ba8bb1a34ab8b03d27b5a3ed92a67750464225ba8b9be3cd5c359369ef783b7f4487614e65bc04a159b4e75bd74c62f9cb9755d765ab007748"} \ No newline at end of file diff --git a/evidence/getcred.json b/evidence/getcred.json new file mode 100644 index 0000000..cb93687 --- /dev/null +++ b/evidence/getcred.json @@ -0,0 +1 @@ +[{"type": "armed"}, {"type": "getcred", "uid": "0x11751b8656f", "out": {"len": 152, "v": "43694158485569467535753044796c526572535138324e4b426b447051754a414f654c306361576d6167786b5a69746f7844396b7151713567746770692f7864354f574b43375a68656e7134374d63777a6553696939676f4a684357797057324a487536586b63554f56466d2b614975734d6d56546d30496b4c70412b705565634f59386645794f6a6c564839736f436d44447a4774766f"}}, {"type": "java_cred", "cls": "com.huyaudbunify.HuyaAuth", "r": "com.huyaudbunify.bean.ResGetCred@9bed1cb"}] \ No newline at end of file diff --git a/evidence/hycred_live.json b/evidence/hycred_live.json new file mode 100644 index 0000000..51a93a6 --- /dev/null +++ b/evidence/hycred_live.json @@ -0,0 +1 @@ +{"uid": 1199666914671, "hyCred_b64": "CiAXHUiFu5u0DylRerSQ82NKBkDpQuJAOeL0caWmagxkZitoxD9kqQq5gtgpi/xd5OWKC7Zhenq47McwzeSii9goJhCWypW2JHu6XkcUOVFm+aIusMmVTm0IkLpA+pUecOY8fEyOjlVH9soCmDDzGtvo", "bin_hex": "0a20171d4885bb9bb40f29517ab490f3634a0640e942e24039e2f471a5a66a0c64662b68c43f64a90ab982d8298bfc5de4e58a0bb6617a7ab8ecc730cde4a28bd828261096ca95b6247bba5e4714395166f9a22eb0c9954e6d0890ba40fa951e70e63c7c4c8e8e5547f6ca029830f31adbe8"} \ No newline at end of file diff --git a/scripts/hook_getcred.py b/scripts/hook_getcred.py new file mode 100644 index 0000000..8f2c14d --- /dev/null +++ b/scripts/hook_getcred.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""抓 hyCred 签发链: 挂 saveLoginData(落盘) + LoginCred handler(vtable探测)。 + +用法: 脚本跑起来后, 在手机上 手动退出登录 -> 重新登录 一次。 +捕获: 登录态JSON明文(含cred字段) / saveLoginData 入参 / LoginCred handler 触发。 +""" +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") + +JS = r""" +var base = Process.getModuleByName('libudbauthunify.so').base; +send({type:'armed'}); +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; return {len:l,v:l?hx(p.add(1),Math.min(l,256)):''}; } + var len=parseInt(p.add(8).readU64().toString()); + if(len>4096) return {len:len,v:''}; + return {len:len,v:hx(p.add(16).readPointer(),len)}; +}catch(e){ return {len:-1,v:String(e)}; } } +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 done=false; + ['com.huyaudbunify.HuyaAuth','com.hysdkproxy.HuyaAuth','com.huyaudbunify.sdk.HuyaAuth'].forEach(function(cn){ + if(done) return; + try{ + var C=F.use(cn); + var inst = C.getInstance ? C.getInstance() : null; + var r = inst ? inst.getCred(1199666914671) : C.getCred(1199666914671); + send({type:'java_cred', cls:cn, r:String(r).slice(0,400)}); + done=true; + }catch(e2){} + }); + if(!done) throw new Error('no HuyaAuth'); + }catch(e){ n+=1; if(n%6===0) send({type:'retry',n:n}); setTimeout(go,5000); } + } + setTimeout(go, 15000); +}); +Interceptor.attach(base.add(0x265524), { + onEnter: function(a){ this.uid=a[1].toString(); this.out=a[2]; }, + onLeave: function(){ send({type:'getcred', uid:this.uid, out:rdStr(this.out)}); } +}); +""" + + +def main(): + d = frida.get_device_manager().add_remote_device('127.0.0.1:31877') + pid = d.spawn(['com.duowan.kiwi']) + 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() + print('armed. >>> 请在手机上退出登录并重新登录 <<<') + + sc = s.create_script(JS) + events = [] + 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') + events.append(p) + if t == 'vt_attached': + print(f"[vt] {p['name']} slot{p['slot']} @+{p['fp']}") + elif t == 'saveLD_str': + print(f"[saveLD_str] str={p['str']['t']}:{p['str']['len']}\n bean={p['bean_head']}\n bt={p['bt']}") + elif t == 'saveLD_bean': + print(f"[saveLD_bean] flag={p['flag']}\n bean={p['bean_head']}\n bt={p['bt']}") + elif t == 'cred_unpack': + print(f"[cred_unpack] in={p['in']['t']}:{p['in']['len']}:{p['in']['v'][:160]} <- {p['ra']}") + elif t == 'bigaes': + print(f"[bigaes] len={p['x2']['len']} head={p['x2']['v'][:120]}") + elif t == 'vtable': + print(f"[VT!] {p['name']}#{p['slot']} a1={p['a1']} bt={p['bt']}") + sc.on('message', on_msg) + sc.load() + deadline = time.time() + 300 + while time.time() < deadline: + time.sleep(3) + if any(e.get('type') in ('java_cred','getcred') for e in events): + time.sleep(10) + break + Path('/Users/yml/codes/douyu_login_py/evidence/getcred.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): + try: + main() + break + except frida.InvalidOperationError as e: + print('detached:', e) + subprocess.run(['adb', 'shell', 'am', 'force-stop', 'com.duowan.kiwi']) + time.sleep(3)