Files
live-hub-py/scripts/hook_dfp_pipeline.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

259 lines
8.5 KiB
Python

#!/usr/bin/env python3
"""散弹枪 v2: 完整解码 dfpReport 管道参数 (指针数组 / (ptr,len) / std::string)。
挂载: topA_20e924, midA_5a938, cryptoA_5b1f4, copy_61098, pktA_7f744, pktB_92128
对每个 onEnter 的 x0..x4:
1) 直接缓冲探测 (JSON/MAGIC 前缀 -> full dump 6000B)
2) (ptr,len) 解释: 读 16B -> 若两个 u64 第二个 < 100000 且第一个可读 -> dump
3) 指针数组解释: 读 4x8B 指针, 对每个指针尝试 (2)
4) std::string 解释: [size u64][data u64][cap u64] 或 [data][size][cap]
所有事件带 t (ms since start)。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_pipeline_v2.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var t0 = Date.now();
var JSON_HDR = [0x7b,0x22,0x61,0x70,0x70,0x49,0x64,0x22,0x3a,0x22,0x35,0x30,0x30,0x38];
var MAGIC_HDR = [0x57,0x18,0x82,0xcf,0x66,0x4b,0xb3,0x94,0x01,0xee];
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 startsWith(p, arr){
try{
var b = new Uint8Array(p.readByteArray(arr.length));
for (var i=0;i<arr.length;i++) if (b[i]!==arr[i]) return false;
return true;
}catch(e){ return false; }
}
function isReadable(p){
try{ p.readU8(); return true; }catch(e){ return false; }
}
function u64(p){
try{ return p.readU64().toString(10); }catch(e){ return null; }
}
function decodeArg(p, tag){
var res = {reg: tag};
try{
if (!p || p.isNull()) return null;
res.addr = String(p);
// 1) direct buffer
if (startsWith(p, JSON_HDR)){
res.kind = 'JSON'; res.full = hexb(p, 6000); return res;
}
if (startsWith(p, MAGIC_HDR)){
res.kind = 'MAGIC';
var pre = '';
try{ pre = hexb(p.sub(1536), 1536); }catch(e){}
res.pre = pre;
res.full = hexb(p, 9000);
return res;
}
// 2) (ptr,len) pair: [ptr][len]
var p0 = p.readPointer(); var l0 = p.add(8).readU64();
if (!p0.isNull() && isReadable(p0) && l0 > 0 && l0 < 20000 && p0 != p){
var b0 = hexb(p0, Math.min(Number(l0), 6000));
var kind = 'seg';
if (startsWith(p0, JSON_HDR)) kind = 'JSON';
else if (startsWith(p0, MAGIC_HDR)) kind = 'MAGIC';
res.kind = kind;
res.ptrlen = {ptr: String(p0), len: Number(l0)};
res.seg = b0;
return res;
}
// 3) pointer array (4 x 8B)
var ptrs = [];
for (var i=0;i<4;i++){
var q = p.add(i*8).readPointer();
if (q.isNull() || !isReadable(q)) break;
ptrs.push(String(q));
}
if (ptrs.length >= 2){
res.kind = 'ptrs'; res.ptrs = ptrs;
// decode each pointer as (ptr,len)
var segs = [];
for (var j=0;j<ptrs.length;j++){
var qj = ptr(j);
var q0 = qj.readPointer();
var lj = qj.add(8).readU64();
if (!q0.isNull() && lj > 0 && lj < 20000 && isReadable(q0)){
segs.push({ptr: String(q0), len: Number(lj), head: hexb(q0, 48),
kind: startsWith(q0, JSON_HDR) ? 'JSON' : (startsWith(q0, MAGIC_HDR) ? 'MAGIC' : 'raw')});
}
}
res.segs = segs;
return res;
}
// 4) raw
res.kind = 'raw'; res.head = hexb(p, 32) || '';
return res;
}catch(e){ return null; }
}
function hookFn(off, name){
try{
var base = Process.getModuleByName('libhydeviceid.so').base;
Interceptor.attach(base.add(off), {
onEnter: function(a){
var ctx = this.context;
var ev = {fn: name, phase: 'enter', t: Date.now() - t0};
['x0','x1','x2','x3','x4'].forEach(function(r, i){
var pr = decodeArg(ctx[r], r);
if (pr) ev[r] = pr;
});
send({type:'pipe', ev: ev});
}
});
send({type:'hooked', name: name});
}catch(e){ send({type:'hook_fail', name: name, e: String(e)}); }
}
var H = [
[0x20e924, 'topA_20e924'],
[0x5a938, 'midA_5a938'],
[0x5b1f4, 'cryptoA_5b1f4'],
[0x61098, 'copy_61098'],
[0x7f744, 'pktA_7f744'],
[0x92128, 'pktB_92128'],
];
var hookedN = 0;
function attachOne(base, off, name){
try{
Interceptor.attach(base.add(off), {
onEnter: function(a){
var ctx = this.context;
var ev = {fn: name, phase: 'enter', t: Date.now() - t0};
['x0','x1','x2','x3','x4'].forEach(function(r, i){
var pr = decodeArg(ctx[r], r);
if (pr) ev[r] = pr;
});
send({type:'pipe', ev: ev});
}
});
hookedN++;
return true;
}catch(e){ return false; }
}
function tryHookAll(){
try{
var m = Process.getModuleByName('libhydeviceid.so');
for (var i=0;i<H.length;i++){
if (attachOne(m.base, H[i][0], H[i][1])){}
}
send({type:'hooked_all', n: hookedN});
}catch(e){ setTimeout(tryHookAll, 800); }
}
setTimeout(tryHookAll, 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()-t0});
}
});
});
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_all":
print(f"[*] hooked {p.get('n')} fns", flush=True)
elif t == "ssl_hooked":
print("[*] SSL_write hooked", flush=True)
elif t == "pipe":
ev = p.get('ev') or {}
parts = []
for r in ['x0','x1','x2','x3','x4']:
pr = ev.get(r)
if not pr or not isinstance(pr, dict):
continue
k = pr.get('kind')
if k == 'JSON':
parts.append(f"{r}=JSON full")
elif k == 'MAGIC':
parts.append(f"{r}=MAGIC full")
elif k == 'seg':
parts.append(f"{r}=seg({pr.get('ptrlen',{}).get('len')})@{pr.get('ptrlen',{}).get('ptr')}")
elif k == 'ptrs':
parts.append(f"{r}=ptrs {pr.get('ptrs')} segs={[(s.get('kind'), s.get('len')) for s in pr.get('segs') or []]}")
elif k == 'raw':
parts.append(f"{r}=raw {pr.get('addr')} {pr.get('head','')[:16]}")
print(f"[{ev.get('t')}ms] {ev.get('fn')}::{ev.get('phase')} " + " ".join(parts), flush=True)
events.append({"type": "pipe", "ev": ev})
OUT.write_text(json.dumps(events))
elif t == "wire":
print(f"[{p.get('t')}ms] [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", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束 -> {OUT}", flush=True)
if __name__ == "__main__":
main()