feat(huya): cred来源定位 - HandlerResponseLoginPassport护照登录响应
- backtrace实锤: saveLoginData<-HandlerResponseLoginPassport<-JceInputStream - cred随护照登录响应一次性下发(BusBeansLoginData), 非独立消息 - HTTPS passwordLogin简化通道响应无cred(二次扫描确认) - 下一步: 对比App真实登录请求字段 / 长连接WSP复刻 / 混合采集兜底
This commit is contained in:
@@ -47,3 +47,29 @@ vtable 探测结论:slot2/3 是析构/通用槽(打中 MsgLoop::doMSG/sendMe
|
|||||||
2. 静态:`MsgRequestLgnCred::getClassName`@0x2a37d0 / 各消息 getClassName 取类名串,
|
2. 静态:`MsgRequestLgnCred::getClassName`@0x2a37d0 / 各消息 getClassName 取类名串,
|
||||||
在 rodata 找 类名→msgId 表(参考 MsgGetH5InfoEx=184549387 的命名规律)。
|
在 rodata 找 类名→msgId 表(参考 MsgGetH5InfoEx=184549387 的命名规律)。
|
||||||
3. 若走 B:用 tools/huya_wup_encoder.py 的信封 + OTP 加密链组 RequestLgnCred 发 wsapi 试探。
|
3. 若走 B:用 tools/huya_wup_encoder.py 的信封 + OTP 加密链组 RequestLgnCred 发 wsapi 试探。
|
||||||
|
|
||||||
|
## ★ 2026-08-25 晚 · 关键突破:cred 来源定位
|
||||||
|
|
||||||
|
增强 hook(bean 768B 全量 + backtrace)在用户重登时抓到决定性调用链:
|
||||||
|
|
||||||
|
```
|
||||||
|
BusinessCfg::saveLoginData ← _Z13saveLoginDataiR17BusBeansLoginData
|
||||||
|
← HandlerResponseLoginPassport ★
|
||||||
|
← udbjce::JceInputStream (wup/JCE 反序列化)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **cred 随"护照登录"响应一次性下发**(BusBeansLoginData 内),不是单独消息。
|
||||||
|
- 手机 App 的登录主通道是长连接上的 LoginPassport;我们的 HTTPS
|
||||||
|
`/open/hy/passwordLogin` 是简化通道,响应(1514B)只有 uid/name/mask/token,
|
||||||
|
无 cred(已二次扫描确认:无 `0a 80` 高熵段、无 114B 结构)。
|
||||||
|
- bean 内存可见字段样例: "hy_300023887"、uid串、"01******7524"掩码、
|
||||||
|
user_action JSON 片段 —— 与 BusBeansLoginData 字段对应。
|
||||||
|
- 证据: /tmp/credissue2.log + evidence/cred_issue_trace.json(第二次覆盖保存)
|
||||||
|
|
||||||
|
### 路线修正
|
||||||
|
|
||||||
|
- 路线 B 修正为「补全 HTTPS passwordLogin 的请求字段」:App 端若也存在
|
||||||
|
HTTPS 登录路径,其请求参数比我们的最小实现多 ⇒ 响应可能就含 cred。
|
||||||
|
下一步:静态对比 HandlerRequestLoginPassport 组包 vs huya_wup_encoder 差异;
|
||||||
|
或 mitmproxy 抓一次 App 真实密码登录流量对照。
|
||||||
|
- 若确认必须长连接 → 路线 C(WSP 客户端)或路线 A(混合采集)兜底。
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -42,18 +42,27 @@ Interceptor.attach(base.add(0x250038), {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// saveLoginData(string const&, BusBeansLoginData&, bool) 与 (BusBeansLoginData&, bool)
|
// saveLoginData 全量版: bean dump 768B + backtrace 10帧
|
||||||
Interceptor.attach(base.add(0x265fb0), {
|
Interceptor.attach(base.add(0x265fb0), {
|
||||||
onEnter: function(a){
|
onEnter: function(a){
|
||||||
send({type:'saveLD_str', str:rdStr(a[1]), bean_head:hx(a[2],128), ra:ra(this)});
|
send({type:'saveLD_str', str:rdStr(a[1]), bean_head:hx(a[2],768),
|
||||||
|
bt:Thread.backtrace(this.context, Backtracer.FUZZY).slice(0,10)
|
||||||
|
.map(function(x){return DebugSymbol.fromAddress(x).toString().slice(0,64);})});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Interceptor.attach(base.add(0x265a6c), {
|
Interceptor.attach(base.add(0x265a6c), {
|
||||||
onEnter: function(a){
|
onEnter: function(a){
|
||||||
send({type:'saveLD_bean', bean_head:hx(a[1],160), b:rdStr(a[1]), flag:a[2].toInt32(), ra:ra(this)});
|
send({type:'saveLD_bean', bean_head:hx(a[1],768), flag:a[2].toInt32(),
|
||||||
|
bt:Thread.backtrace(this.context, Backtracer.FUZZY).slice(0,10)
|
||||||
|
.map(function(x){return DebugSymbol.fromAddress(x).toString().slice(0,64);})});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// cred 打包/解包(网络侧明文必经)
|
||||||
|
Interceptor.attach(base.add(0x333170), {
|
||||||
|
onEnter: function(a){ send({type:'cred_unpack', in:rdStr(a[1]), ra:ra(this)}); }
|
||||||
|
});
|
||||||
|
|
||||||
// HandlerRequestLoginCred / HandlerResponseLoginCred vtable 探测:
|
// HandlerRequestLoginCred / HandlerResponseLoginCred vtable 探测:
|
||||||
// vtable+0x10 起为虚函数槽, 逐个attach前3个槽, 打印触发与backtrace首帧
|
// vtable+0x10 起为虚函数槽, 逐个attach前3个槽, 打印触发与backtrace首帧
|
||||||
function probeVtable(name, vtAddr){
|
function probeVtable(name, vtAddr){
|
||||||
@@ -105,10 +114,11 @@ def main():
|
|||||||
if t == 'vt_attached':
|
if t == 'vt_attached':
|
||||||
print(f"[vt] {p['name']} slot{p['slot']} @+{p['fp']}")
|
print(f"[vt] {p['name']} slot{p['slot']} @+{p['fp']}")
|
||||||
elif t == 'saveLD_str':
|
elif t == 'saveLD_str':
|
||||||
print(f"[saveLD_str] str={p['str']['t']}:{p['str']['len']}:{p['str']['v'][:80]}")
|
print(f"[saveLD_str] str={p['str']['t']}:{p['str']['len']}\n bean={p['bean_head']}\n bt={p['bt']}")
|
||||||
elif t == 'saveLD_bean':
|
elif t == 'saveLD_bean':
|
||||||
print(f"[saveLD_bean] flag={p['flag']} bean={p['bean_head'][:96]}")
|
print(f"[saveLD_bean] flag={p['flag']}\n bean={p['bean_head']}\n bt={p['bt']}")
|
||||||
print(f" asStr={p['b']}")
|
elif t == 'cred_unpack':
|
||||||
|
print(f"[cred_unpack] in={p['in']['t']}:{p['in']['len']}:{p['in']['v'][:160]} <- {p['ra']}")
|
||||||
elif t == 'bigaes':
|
elif t == 'bigaes':
|
||||||
print(f"[bigaes] len={p['x2']['len']} head={p['x2']['v'][:120]}")
|
print(f"[bigaes] len={p['x2']['len']} head={p['x2']['v'][:120]}")
|
||||||
elif t == 'vtable':
|
elif t == 'vtable':
|
||||||
|
|||||||
Reference in New Issue
Block a user