- 定位完整调用链: HYUDBMSDKCommon桥接 -> MsgGetH5InfoEx(184549387) -> HandlerGetH5InfoEx::onHandler@0x38d548 -> getOtp@0x2689f4 -> hyudb_otp_encrypt@0x32fa24 -> createWupRequestData@0x38dab0 - 实测参数表: otp(uid串,2,轮换idx,'5008',SESS_HEX32,hyCred114B,4,nonce) - 密钥字符串确认: 'HuyaUdb1928374650qwertyuiop' (encrypt x3 参数) - Java桥直呼触发: LoginProxy.getQUrlData/getH5InfoEx/getCred 可编程调用 - 稳定时序固化: spawn挂起->bypass->resume->10s->patch_guard->hooks (attach已运行进程必被检测闪退) - 发现App登录态JSON含现成web cookie.biztoken(344B)
173 lines
5.8 KiB
Python
173 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""一次性抓取: App 存储的 cred + Common/QUrl 两种 wupData 完整输出。"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import frida
|
|
|
|
RE_DIR = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
|
|
|
JS = r"""
|
|
'use strict';
|
|
|
|
function b64ify(ptr, n) {
|
|
var bytes = ptr.readByteArray(n);
|
|
var arr = new Uint8Array(bytes);
|
|
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
var chars = "";
|
|
for (var i = 0; i < arr.length; i += 3) {
|
|
var b0 = arr[i], b1 = i+1 < arr.length ? arr[i+1] : 0, b2 = i+2 < arr.length ? arr[i+2] : 0;
|
|
chars += B64[b0>>2] + B64[((b0&3)<<4)|(b1>>4)] +
|
|
(i+1 < arr.length ? B64[((b1&15)<<2)|(b2>>6)] : "=") +
|
|
(i+2 < arr.length ? B64[b2&63] : "=");
|
|
}
|
|
return chars;
|
|
}
|
|
|
|
// otp 加密链 hook(二进制安全)
|
|
var base = Process.getModuleByName("libudbauthunify.so").base;
|
|
Interceptor.attach(base.add(0x32fa24), {
|
|
onEnter: function (args) {
|
|
function rd(p) {
|
|
try {
|
|
var b0 = p.readU8();
|
|
if ((b0 & 1) === 0) return {t:"s", v:p.add(1).readUtf8String(b0>>1)};
|
|
var len = parseInt(p.add(8).readU64().toString());
|
|
return {t:"b64", v:b64ify(p.add(16).readPointer(), Math.min(len,4096))};
|
|
} catch(e) { return {t:"err", v:String(e)}; }
|
|
}
|
|
this.s1 = rd(args[0]); this.b2 = args[2].toInt32();
|
|
this.s2 = rd(args[3]); this.s3 = rd(args[4]); this.s4 = rd(args[5]);
|
|
this.nonce = args[7].toString();
|
|
this.outPtr = this.context.sp.readPointer();
|
|
},
|
|
onLeave: function () {
|
|
function rdo(p) {
|
|
try {
|
|
var b0 = p.readU8();
|
|
if ((b0 & 1) === 0) return {t:"b64", v:b64ify(p.add(1), b0>>1)};
|
|
var len = parseInt(p.add(8).readU64().toString());
|
|
return {t:"b64", v:b64ify(p.add(16).readPointer(), Math.min(len,4096))};
|
|
} catch(e){ return {t:"err", v:String(e)}; }
|
|
}
|
|
send({type:"otp", s1:this.s1, b2:this.b2, s2:this.s2, s3:this.s3,
|
|
s4:this.s4, nonce:this.nonce, out:rdo(this.outPtr)});
|
|
}
|
|
});
|
|
|
|
function trigger() {
|
|
Java.perform(function () {
|
|
var attempt = 0;
|
|
var timer = null;
|
|
function once() {
|
|
attempt += 1;
|
|
var done = false;
|
|
Java.enumerateClassLoaders({
|
|
onMatch: function (loader) {
|
|
if (done) return;
|
|
try {
|
|
var f = Java.ClassFactory.get(loader);
|
|
var LP = f.use('com.hysdkproxy.LoginProxy');
|
|
var inst = LP.getInstance();
|
|
var HAS = f.use('com.huyaudbunify.HuyaAccountSaveUtils').getInstance();
|
|
var uid = HAS.getUid();
|
|
send({type:'uid', uid: String(uid)});
|
|
var HA = f.use('com.huyaudbunify.HuyaAuth').getInstance();
|
|
var cred = HA.getCred(uid);
|
|
if (cred !== null) {
|
|
send({type:'cred',
|
|
hyCred: cred.getHyCred() ? cred.getHyCred() : null,
|
|
yyCred: cred.getYyCred() ? cred.getYyCred() : null});
|
|
} else {
|
|
send({type:'cred', hyCred:null, yyCred:null});
|
|
}
|
|
var common = inst.getH5InfoEx();
|
|
send({type:'wup_common', len: common?common.length:0, data: common});
|
|
var qurl = inst.getQUrlData(uid, "", "");
|
|
send({type:'wup_qurl', len: qurl?qurl.length:0, data: qurl});
|
|
done = true;
|
|
} catch (e) { /* 下一个 loader */ }
|
|
},
|
|
onComplete: function () {
|
|
if (!done) {
|
|
if (attempt % 4 === 0) send({type:'retry', n: attempt});
|
|
timer = setTimeout(once, 3000);
|
|
} else {
|
|
send({type:'all_done', attempts: attempt});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
once();
|
|
});
|
|
}
|
|
|
|
trigger();
|
|
"""
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--duration", type=float, default=40.0)
|
|
ap.add_argument("--stabilize", type=float, default=10.0)
|
|
ap.add_argument("--out", default="/tmp/full_dump.json")
|
|
ap.add_argument("--package", default="com.duowan.kiwi")
|
|
ap.add_argument("--port", default="127.0.0.1:31877")
|
|
args = ap.parse_args()
|
|
|
|
bypass_src = (RE_DIR / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
|
|
patch_guard = (RE_DIR / "evidence/scripts/patch_guard_block_termination.js").read_text()
|
|
|
|
device = frida.get_device_manager().add_remote_device(args.port)
|
|
pid = device.spawn([args.package])
|
|
print(f"spawned pid={pid}")
|
|
session = device.attach(pid)
|
|
session.create_script(bypass_src).load()
|
|
device.resume(pid)
|
|
time.sleep(args.stabilize)
|
|
session.create_script(patch_guard).load()
|
|
print("guards loaded")
|
|
|
|
collected = {}
|
|
|
|
def on_message(message, _data):
|
|
if message.get("type") != "send":
|
|
if message.get("type") == "error":
|
|
print("ERR:", str(message)[:200])
|
|
return
|
|
p = message.get("payload") or {}
|
|
t = p.get("type")
|
|
if t in ("wup_common", "wup_qurl"):
|
|
collected[t] = p.get("data")
|
|
print(f"{t}: len={p.get('len')}")
|
|
elif t == "cred":
|
|
collected["cred"] = p
|
|
hc, yc = p.get("hyCred"), p.get("yyCred")
|
|
print(f"cred: hyCred={str(hc)[:60]}... ({len(hc) if hc else 0}) "
|
|
f"yyCred={str(yc)[:60]}... ({len(yc) if yc else 0})")
|
|
elif t == "uid":
|
|
collected["uid"] = p.get("uid")
|
|
print(f"uid={p.get('uid')}")
|
|
elif t == "loader_err":
|
|
print("loader:", p.get("err"))
|
|
else:
|
|
print(json.dumps(p, ensure_ascii=False)[:120])
|
|
|
|
script = session.create_script(JS)
|
|
script.on("message", on_message)
|
|
script.load()
|
|
time.sleep(args.duration - args.stabilize)
|
|
|
|
out = Path(args.out)
|
|
out.write_text(json.dumps(collected, ensure_ascii=False))
|
|
print(f"\nsaved: {out} keys={list(collected.keys())}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|