Files
live-hub-py/scripts/hook_dfp_input.py
T
yml2213 da2cf5a61d feat(huya): 整合 App 协议登录获取 Cookie 全链路并与 Web 登录解耦
- core/huya: 新增 app_login, wup_encoder, nonce_forge, cert_forge, envelope_forge, device_profile, udb_aes
- core/huya/__init__.py: 导出 login_huya_app_password 与 HuyaAppPasswordLogin
- web/backend: 新增 /accounts/app-password-login 与 /accounts/app-password-login/selected 路由及 Schema,与原 Web 密码登录独立分开
- web/frontend: 增加 App 登录 API 与前端界面“App 登录选中”操作,弹窗结果明确区分
- tests: 新增 test_huya_app_login.py 单元测试覆盖全链路
2026-08-26 15:51:30 +08:00

211 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""hook libhydeviceid.so!0x91ce8: t2 [魔数+密文] 拷贝点 -> 抓完整加密输入。
0x91ce8 是 t2 组装中的 memcpy 调用点 (src 已含 魔数+密文, size~3867-3986)。
onEnter:
1) 从 x0/x1/x2/sp 定位 [魔数+密文] 输出缓冲, dump 4096B
2) 全内存扫 {"appId":"5008 找加密输入 (JSON+二进制) 缓冲, dump 4096B
3) SSL_write 抓 wire (密文基准)
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_input_capture.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var MAGIC_STR = '57 18 82 cf 66 4b b3 94 01 ee';
var JSON_STR = '7b 22 61 70 70 49 64 22 3a 22 35 30 30 38'; // {"appId":"5008
function hexb(p, n){
try{
var arr = Array.from(new Uint8Array(p.readByteArray(n)));
var s = '';
for (var i=0;i<arr.length;i++) s += ('0'+arr[i].toString(16)).slice(-2);
return s;
}catch(e){ return null; }
}
function startsWithHex(p, hexstr){
try{
var want = hexstr.split(' ').map(function(x){return parseInt(x,16);});
var b = new Uint8Array(p.readByteArray(want.length));
for (var i=0;i<want.length;i++) if (b[i]!==want[i]) return false;
return true;
}catch(e){ return false; }
}
function scanPattern(pattern, cap){
var hits = [];
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*256) return;
if (hits.length >= cap) return;
try{
var m = Memory.scanSync(r.base, r.size, pattern);
m.slice(0, cap - hits.length).forEach(function(x){
hits.push({addr: String(x.address),
mod: (function(){ var mm=Process.findModuleByAddress(x.address); return mm? mm.name : 'anon'; })()});
});
}catch(e){}
});
}catch(e){}
return hits;
}
function dumpAround(addr, pre, post){
try{
var p = addr.sub(pre);
var d = hexb(p, pre + post);
return {base: String(p), pre: pre, post: post, hex: d};
}catch(e){ return null; }
}
// 找到含魔数的缓冲 (扫描参数指向的内存)
function findMagicBuf(ctx){
var cands = [];
[ctx.x0, ctx.x1, ctx.x2, ctx.x3, ctx.x4].forEach(function(p, i){
try{
if (p.isNull()) return;
if (startsWithHex(p, MAGIC_STR)) cands.push({reg: 'x'+i, addr: String(p)});
// 也看看指针对应的数据是否在 sp 附近 (std::string 结构: ptr 在偏移8/16)
var q = p.readPointer();
if (!q.isNull() && startsWithHex(q, MAGIC_STR)) cands.push({reg: 'x'+i+'_ptr', addr: String(q)});
}catch(e){}
});
// 扫最近的匿名内存找 [魔数+密文] (限 512MB 内, 快)
if (!cands.length){
try{
var m = Memory.scanSync(ctx.sp, 64*1024*1024, MAGIC_STR);
m.slice(0, 3).forEach(function(x){ cands.push({reg: 'sp_scan', addr: String(x.address)}); });
}catch(e){}
}
return cands;
}
var nCalls = 0;
var hooked91 = false;
function tryHook91(){
if (hooked91) return;
try{
var base = Process.getModuleByName('libhydeviceid.so').base;
Interceptor.attach(base.add(0x91ce8), {
onEnter: function(a){
nCalls++;
var ctx = this.context;
var ev = {type:'t2copy', n:nCalls, x0:String(ctx.x0), x1:String(ctx.x1),
x2:String(ctx.x2), x3:String(ctx.x3), x4:String(ctx.x4)};
var mbs = findMagicBuf(ctx);
ev.magicbufs = mbs;
for (var i=0;i<mbs.length && i<2;i++){
var d = dumpAround(ptr(mbs[i].addr), 0, 4096);
ev['magicdump'+i] = d;
}
// 全内存扫明文 JSON (加密输入)
var jh = scanPattern(JSON_STR, 6);
ev.jsonhits = jh;
for (var j=0;j<jh.length && j<4;j++){
var d = dumpAround(ptr(jh[j].addr), 0, 4096);
ev['jsondump'+j] = d;
}
send({type:'t2copy', ev: ev});
}
});
hooked91 = true;
send({type:'hooked_91ce8', base:String(base)});
}catch(e){
setTimeout(tryHook91, 1000);
}
}
setTimeout(tryHook91, 500);
try{
var r = new ApiResolver('module');
r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){
Interceptor.attach(m.address, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 100 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('dfpReport') < 0) return;
send({type:'wire', len:len, hex:hexb(a[1], len), t:Date.now()});
}
});
});
send({type:'ssl_hooked'});
}catch(e){ send({type:'ssl_err', e:String(e)}); }
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:300], flush=True)
return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "armed":
print("[*] JS armed", flush=True)
elif t == "hooked_91ce8":
print(f"[*] hooked 0x91ce8 @ {p.get('base')}", flush=True)
elif t == "ssl_hooked":
print("[*] SSL_write hooked", flush=True)
elif t == "hook_err":
print("[hook_err]", p.get('e'), flush=True)
elif t == "t2copy":
ev = p.get('ev') or {}
print(f"[t2copy] n={ev.get('n')} x0={ev.get('x0')} x1={ev.get('x1')} x2={ev.get('x2')}", flush=True)
for mb in (ev.get('magicbufs') or [])[:3]:
print(f" magicbuf reg={mb.get('reg')} @{mb.get('addr')}", flush=True)
for jh in (ev.get('jsonhits') or [])[:6]:
print(f" jsonhit @{jh.get('addr')} mod={jh.get('mod')}", flush=True)
events.append({"type": "t2copy", "ev": ev})
OUT.write_text(json.dumps(events))
elif t == "wire":
print(f"[wire] len={p.get('len')}", flush=True)
events.append({"type": "wire", "len": p.get('len'), "t": p.get('t'), "hex": p.get('hex')})
OUT.write_text(json.dumps(events))
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载, 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume。等待自动 dfpReport (0x91ce8 命中)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束 -> {OUT}", flush=True)
if __name__ == "__main__":
main()