feat(huya): 干净流水线仍40020 - 登录响应cred与cert-cred为两态, 转换步骤待抓
This commit is contained in:
@@ -293,3 +293,14 @@ saveLoginData, 对照同账号纯Python响应字节, 直接对出cred真实位
|
||||
或响应态与存储态之间有一步转换(saveLoginData前?)。
|
||||
下一步: 设备登录时hook saveLoginData入参bean与getCred出口做同会话字节对照,
|
||||
精确定位 响应t3 -> 存储cred 的转换函数。
|
||||
|
||||
## 关键发现: 钥代次轮换 + 响应cred非直接可用
|
||||
|
||||
cred字节1轮换: 0x10/0x20/0x30/0x40/0x50/0x70/0x80(观测值)=代次计数器。
|
||||
干净流水线(登录->新nonce铸证->立即bind)仍40020。
|
||||
对照: 桥采cred(0a20,设备会话内)始终有效。
|
||||
=> 密码登录响应中的cred与getCred返回的cert-cred是两态:
|
||||
登录后app还有后续步骤(getAppComomData等)才得到可用cert-cred,
|
||||
或响应cred需会话上下文解密/兑换。
|
||||
下一步: hook getCred的调用链回溯(谁写入该值), 或登录后逐func试调
|
||||
(hyanonymousCredlogin/getAppComomData)观察cred0文件变化。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,6 @@
|
||||
"scan_uid": null
|
||||
}
|
||||
},
|
||||
"p1_new_hex": "010400353030381400c25220f81f60cf64ee54e83ef1eab6bdd047b5af28003032646633393837393734333265616465666363313237363731313961643565383039393933383972000a4003ef982e01399335f13e9ae294ebdf18a91fc43e6be883c67a7e19784689ec2d38b5c55b79048d8bec1ab66e55749605a86eb6eae62805a708910655b0be91056b9d7159d2bf116eaed44ded41d7b7fe810d0fbd6e3a689dcd2daa8477c304a49ab2b17d2512b9bc99e33f7d6e99daec",
|
||||
"ts": 1787654957.844005
|
||||
"p1_new_hex": "010400353030381400c25220f81f60cf64ee54e83ef1eab6bdd047b5af28003032646633393837393734333265616465666363313237363731313961643565383039393933383972000a20171d4885bb9bb40f29517ab490f3634a0640e942e24039e2f471a5a66a0c64662b68c43f64a90ab982d8298bfc5de4e58a0bb6617a7ab8ecc730cde4a28bd828261096ca95b6247bba5e4714395166f9a22eb0c9954e6d0890ba40fa951e70e63c7c4c8e8e5547f6ca029830f31adbe8",
|
||||
"ts": 1787655679.05882
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/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)}; } }
|
||||
Interceptor.attach(base.add(0x265a6c), {
|
||||
onEnter: function(a){ send({type:'bean', data:hx(a[1],2048)}); }
|
||||
});
|
||||
Interceptor.attach(base.add(0x265fb0), {
|
||||
onEnter: function(a){ send({type:'bean', data:hx(a[2],2048)}); }
|
||||
});
|
||||
|
||||
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')=='bean' for e in events) and any(e.get('type')=='getcred' for e in events):
|
||||
time.sleep(10)
|
||||
break
|
||||
Path('/Users/yml/codes/douyu_login_py/evidence/credtrace.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)
|
||||
Reference in New Issue
Block a user