feat(huya): 纯Python hypasswordLogin包服务器接受(HTTP200) - 传输层全通!
This commit is contained in:
@@ -34,3 +34,8 @@ data/web.db
|
||||
.tmp_reverse/
|
||||
apks/
|
||||
evidence/qr_login_success.json
|
||||
evidence/final_capture.json
|
||||
evidence/final_wupdata.bin
|
||||
evidence/frida_cred_dump.json
|
||||
|
||||
work/
|
||||
@@ -195,3 +195,10 @@ _Z16createWupPackageP...PKci @0x274ea4 (0x610B):
|
||||
func硬编码"wupudbrequest_v0"; payload=x1。
|
||||
=> 对应TAF RequestPacket{iVer,cPktType,iReqId,servant,func,sBuffer,...}
|
||||
下步: 找PLT调用点看运行时实参(payload格式/id值), 复刻帧->Python直连wup.huya.com:443。
|
||||
|
||||
## ★★★ 纯Python护照登录打通!
|
||||
|
||||
build_password_login_wup(前会话复刻包) POST https://wup.huya.com
|
||||
body=原始TAF字节(非b64!), CT=application/multipart-formdata, Dalvik UA
|
||||
→ HTTP200 588B结构化TAF响应(huyaudbwebui/hypasswordLogin回显)!
|
||||
假密码测试通过=帧格式实锤。下步: 解响应取rc/cred字段,真密码登录取cred->铸证->cookie。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
[{"type": "armed"}, {"type": "wupbuild", "id": "0x218de4", "payload": "hylogout", "payload_head": "68796c6f676f7574007eca770000000070b1cc770000006f65b8511701000000363461356264630000796c6f676f7574007eca770000000070b1cc7700000031000000000000002b0000000000000040335f85780000b4904a8bca770000006f"}, {"type": "wupbuild", "id": "0x218de6", "payload": "hyanonymousCredlogin", "payload_head": "6879616e6f6e796d6f7573437265646c6f67696e00000000000000000000000002000000000000000079616e6f6e796d6f7573437265646c6f67696e00000031000000000000002b00000000000000b01126bc770000b408a68aca7700000000"}, {"type": "wupbuild", "id": "0x1", "payload": "getAppComomData", "payload_head": "676574417070436f6d6f6d44617461006261645f7765616b5f70747200772b6265004a00510064006c6f772d6c696e6500710074776f00766572746963616c2d7461620074656d706c61746520706172616d65746572206f626a65637420666f"}, {"type": "wupbuild", "id": "0x218df1", "payload": "report", "payload_head": "7265706f727400000000000000000000000000000000000000000000000000004cd76ba75c28be00000000000000000000000000000000000000000000000031000000000000002b000000000000000043864d770000b401eb8dbe7700000000"}, {"type": "wupbuild", "id": "0x218df3", "payload": "hypasswordLogin", "payload_head": "687970617373776f72644c6f67696e0070b1cc770000000000000000000000000200000000000000007970617373776f72644c6f67696e0070b1cc7700000031000000000000002b0000000000000010dfba47770000b438a88aca7700000018"}, {"type": "wupbuild", "id": "0x1", "payload": "getAppComomData", "payload_head": "676574417070436f6d6f6d44617461006261645f7765616b5f70747200772b6265004a00510064006c6f772d6c696e6500710074776f00766572746963616c2d7461620074656d706c61746520706172616d65746572206f626a65637420666f"}, {"type": "wupbuild", "id": "0x218df6", "payload": "", "payload_head": "00501c78000000aceb68ca770000000600000000000000000000000000000000000000000000000000501c78000000aceb68ca77000000060000000000000031000000000000002b000000000000006064411e750000b4d8cd8aca7700000000"}, {"type": "wupbuild", "id": "0x218df7", "payload": "report", "payload_head": "7265706f727400000000000000000000000000000000000000000000000000004cd76ba75c28be00000000000000000000000000000000000000000000000031000000000000002b00000000000000d0de6219750000b401eb8dbe7700000000"}]
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/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'});
|
||||
var hooked=0;
|
||||
var fns=[];
|
||||
try{ fns=DebugSymbol.findFunctionsNamed('SSL_write'); }catch(e){}
|
||||
if(!fns.length){ try{ var r=new ApiResolver('module'); fns=r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){return x.address;}); }catch(e2){} }
|
||||
fns.slice(0,6).forEach(function(p,i){
|
||||
Interceptor.attach(p,{ onEnter:function(a){
|
||||
var len=a[2].toInt32(); if(len<40||len>20000) return;
|
||||
var head=''; try{ head=a[1].readCString(Math.min(len,400)); }catch(e){ return; }
|
||||
if(head.indexOf('wupudbrequest')<0 && head.indexOf('huyaudbwebui')<0) return;
|
||||
var arr=new Uint8Array(a[1].readByteArray(len));
|
||||
var hex=''; for(var j=0;j<arr.length;j++) hex+=('0'+arr[j].toString(16)).slice(-2);
|
||||
send({type:'wire', idx:i, len:len, head:head.slice(0,300), hex:hex});
|
||||
}});
|
||||
hooked++;
|
||||
send({type:'hooked', idx:i, at:String(p)});
|
||||
});
|
||||
if(hooked===0) send({type:'nowrite'});"""
|
||||
|
||||
|
||||
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') == 'saveLD_bean' for e in events):
|
||||
time.sleep(10)
|
||||
break
|
||||
Path('/Users/yml/codes/douyu_login_py/evidence/sslwrite.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)
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/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 rdC(p){ try{ var t=p.readCString(); return t&&t.length<20000?t.slice(0,3000):''; }catch(e){ return ''; } }
|
||||
Interceptor.attach(base.add(0x274ea4), {
|
||||
onEnter: function(a){
|
||||
var pl=rdC(a[1]);
|
||||
send({type:'wupbuild', id:String(a[2]), payload:pl, payload_head:hx(a[1],96)});
|
||||
}
|
||||
});"""
|
||||
|
||||
|
||||
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)
|
||||
Path('/Users/yml/codes/douyu_login_py/evidence/wupbuild.json').write_text(json.dumps(events))
|
||||
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','wupbuild') for e in events):
|
||||
time.sleep(10)
|
||||
break
|
||||
Path('/Users/yml/codes/douyu_login_py/evidence/wupbuild.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)
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""虎牙长连接(wup.huya.com:443)纯 Python 客户端骨架。
|
||||
|
||||
协议: TAF/mobileuif 长连接; 请求包=标准 TAF RequestPacket JCE 结构
|
||||
{iVer0,cPktType1,iReqId2,sServant3,sFunc4,sBuffer5,iTimeout6,context7,status8}
|
||||
createWupPackage@0x274ea4 实证: ver=3, servant=huyaudbwebui,
|
||||
func=wupudbrequest_v0, sBuffer=JSON 载荷, id=arg(疑似 msgId 0x1001)。
|
||||
|
||||
用法: python3 huya_longconn.py probe # 测连通性+观察服务器主动下发
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import socket
|
||||
import ssl
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
HOST, PORT = "wup.huya.com", 443
|
||||
|
||||
|
||||
# ---------- 极简 JCE(TAF) 编码器: 只覆盖本协议需要的类型 ----------
|
||||
def jce_zero(tag: int) -> bytes:
|
||||
return bytes([(tag << 4) | 0]) # ZERO_TAG
|
||||
|
||||
|
||||
def jce_int(tag: int, v: int) -> bytes:
|
||||
if v == 0:
|
||||
return jce_zero(tag)
|
||||
if -128 <= v <= 127:
|
||||
return bytes([(tag << 4) | 1]) + struct.pack(">b", v)
|
||||
if -32768 <= v <= 32767:
|
||||
return bytes([(tag << 4) | 2]) + struct.pack(">h", v)
|
||||
if -(1 << 31) <= v <= (1 << 31) - 1:
|
||||
return bytes([(tag << 4) | 3]) + struct.pack(">i", v)
|
||||
return bytes([(tag << 4) | 4]) + struct.pack(">q", v)
|
||||
|
||||
|
||||
def jce_str(tag: int, s: str | bytes) -> bytes:
|
||||
b = s.encode() if isinstance(s, str) else s
|
||||
n = len(b)
|
||||
head = bytes([(tag << 4) | 6])
|
||||
if n <= 255:
|
||||
return head + bytes([n]) + b
|
||||
return head + b"\xff" + struct.pack(">I", n) + b # LONG_LEN
|
||||
|
||||
|
||||
def jce_bytes(tag: int, b: bytes) -> bytes:
|
||||
"""SimpleList(vector<byte>): head byte + size(int) + data。"""
|
||||
return bytes([(tag << 4) | 13, 0]) + struct.pack(">i", len(b)) + b
|
||||
|
||||
|
||||
def jce_map_str(tag: int, d: dict[str, str]) -> bytes:
|
||||
out = bytes([(tag << 4) | 8]) + struct.pack(">i", len(d))
|
||||
for k, v in d.items():
|
||||
out += jce_str(0, k) + jce_str(1, v)
|
||||
return out
|
||||
|
||||
|
||||
def taf_request_packet(req_id: int, servant: str, func: str, buffer: bytes,
|
||||
timeout_ms: int = 3000,
|
||||
context: dict | None = None,
|
||||
status: dict | None = None) -> bytes:
|
||||
body = (jce_int(0, 3) # iVersion=3
|
||||
+ jce_int(1, 0) # cPacketType
|
||||
+ jce_int(2, req_id) # iRequestId
|
||||
+ jce_str(3, servant)
|
||||
+ jce_str(4, func)
|
||||
+ jce_bytes(5, buffer)
|
||||
+ jce_int(6, timeout_ms)
|
||||
+ jce_map_str(7, context or {})
|
||||
+ jce_map_str(8, status or {}))
|
||||
return struct.pack(">I", len(body) + 4) + body
|
||||
|
||||
|
||||
# ---------- 连接层 ----------
|
||||
def connect_tls(timeout: float = 10.0) -> ssl.SSLSocket:
|
||||
raw = socket.create_connection((HOST, PORT), timeout=timeout)
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
return ctx.wrap_socket(raw, server_hostname=HOST)
|
||||
|
||||
|
||||
def probe(recv_seconds: float = 6.0):
|
||||
print(f"connect {HOST}:{PORT} ...")
|
||||
s = connect_tls()
|
||||
print("TLS ok:", s.version(), s.getpeercert(binary_form=False) is not None)
|
||||
s.settimeout(recv_seconds)
|
||||
try:
|
||||
data = s.recv(4096)
|
||||
print(f"server push on connect: {len(data)}B {data[:64].hex()}")
|
||||
except socket.timeout:
|
||||
print("no push within", recv_seconds, "s")
|
||||
s.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
(probe,) or None
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "probe":
|
||||
probe()
|
||||
Reference in New Issue
Block a user