180 lines
6.1 KiB
Python
180 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""attach 到已运行的虎牙App进程, hook dfpReport 加密链 + datadiv 解码器。
|
|
|
|
不 spawn,直接 attach 到用户正在操作的实例, 避免挂错进程。
|
|
用法: 脚本跑起来后, 在手机上 退出登录 -> 重新登录 一次。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import frida
|
|
|
|
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
|
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_attach_capture.json")
|
|
|
|
JS = r"""
|
|
'use strict';
|
|
Process.setExceptionHandler(function(d){ return true; });
|
|
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 rdStr(p){
|
|
try{
|
|
var b0=p.readU8();
|
|
if((b0&1)===0){ var l=b0>>1; if(l>524288) return {t:'s?',len:l,v:''};
|
|
return {t:'s',len:l,v:l?hx(p.add(1),Math.min(l,16384)):''}; }
|
|
var len=parseInt(p.add(8).readU64().toString());
|
|
if(len>1048576) return {t:'l?',len:len,v:''};
|
|
return {t:'l',len:len,v:hx(p.add(16).readPointer(),Math.min(len,16384))};
|
|
}catch(e){ return {t:'err',len:-1,v:String(e)}; }
|
|
}
|
|
function ra(ctx){
|
|
try{ return DebugSymbol.fromAddress(ctx.returnAddress).toString().slice(0,90);}catch(e){return '?';}
|
|
}
|
|
|
|
var KEYWORDS = ['dfp', '5718', '82cf', 'b394', 'collect', 'report'];
|
|
|
|
// 1) datadiv 解码器 (libhydeviceid + libudbauthunify)
|
|
var ddHooked = 0;
|
|
['libhydeviceid.so', 'libudbauthunify.so'].forEach(function(modName){
|
|
var m = null;
|
|
try{ m = Process.getModuleByName(modName); }catch(e){ return; }
|
|
m.enumerateExports().forEach(function(exp){
|
|
if (exp.name.indexOf('datadiv') < 0) return;
|
|
try{
|
|
Interceptor.attach(exp.address, {
|
|
onLeave: function(ret){
|
|
var s = '';
|
|
try{ s = ret.readUtf8String(); }catch(e){}
|
|
if (!s || s.length < 3 || s.length > 2000) return;
|
|
var low = s.toLowerCase();
|
|
var hit = false;
|
|
KEYWORDS.forEach(function(k){ if (low.indexOf(k) >= 0) hit = true; });
|
|
if (hit) send({type:'dd', mod:modName, fn:exp.name, str:s.slice(0,300)});
|
|
}
|
|
});
|
|
ddHooked += 1;
|
|
}catch(e){}
|
|
});
|
|
});
|
|
|
|
// 2) xxtea (hyudb_crypt_util + hyudbxxt)
|
|
var base = Process.getModuleByName('libudbauthunify.so').base;
|
|
try{
|
|
Interceptor.attach(base.add(0x32e83c), {
|
|
onEnter: function(a){
|
|
send({type:'xxtea', r:ra(this), in:rdStr(a[1]), key:rdStr(a[2])});
|
|
}
|
|
});
|
|
}catch(e){}
|
|
|
|
// 3) UdbAESUtil::encrypt (in len 大 -> dfp)
|
|
try{
|
|
Interceptor.attach(base.add(0x250038), {
|
|
onEnter: function(a){
|
|
var inp = rdStr(a[0]);
|
|
if (inp.len > 500) {
|
|
send({type:'aes_in', r:ra(this), in:inp});
|
|
}
|
|
},
|
|
onLeave: function(ret){
|
|
var out = rdStr(this.context.x0);
|
|
if (out.len > 500) send({type:'aes_out', out:out});
|
|
}
|
|
});
|
|
}catch(e){}
|
|
|
|
// 4) enpack_header / enpack_body (打包)
|
|
try{
|
|
Interceptor.attach(base.add(0x32ff44), {
|
|
onEnter: function(a){ this.h=a[0].toInt32()&0xff; this.r=ra(this); },
|
|
onLeave: function(){ send({type:'enpack_hdr', h:this.h, r:this.r, out:rdStr(this.context.x2)}); }
|
|
});
|
|
}catch(e){}
|
|
try{
|
|
Interceptor.attach(base.add(0x3300c8), {
|
|
onEnter: function(a){
|
|
this.h=a[0].toInt32()&0xff; this.r=ra(this);
|
|
send({type:'enpack_body_in', h:this.h, r:this.r,
|
|
s1:rdStr(a[1]), s2:rdStr(a[2]), s3:rdStr(a[3]), s4:rdStr(a[4])});
|
|
},
|
|
onLeave: function(){ send({type:'enpack_body_out', h:this.h, out:rdStr(this.context.x5)}); }
|
|
});
|
|
}catch(e){}
|
|
|
|
send({type:'dd_hooked', n:ddHooked});
|
|
"""
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--pid", type=int, default=7296)
|
|
args = parser.parse_args()
|
|
|
|
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
|
|
session = device.attach(args.pid)
|
|
print(f"[*] attached pid={args.pid}")
|
|
# attach 模式不需要 msaoaid bypass (进程已过检测点), 但保险起见加载 patch_guard
|
|
try:
|
|
session.create_script(
|
|
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
|
|
).load()
|
|
except Exception as e:
|
|
print("[warn] patch_guard:", str(e)[:80])
|
|
|
|
events = []
|
|
|
|
def on_message(message, data):
|
|
if message.get("type") == "error":
|
|
print("[JS-ERR]", str(message)[:160]); return
|
|
if message.get("type") != "send":
|
|
return
|
|
p = message.get("payload") or {}
|
|
t = p.get("type")
|
|
if t == "dd":
|
|
print(f"[dd] {p.get('mod')} {p.get('fn')}: {p.get('str')!r}")
|
|
elif t == "xxtea":
|
|
inp = p.get("in", {}); key = p.get("key", {})
|
|
print(f"[xxtea] in.len={inp.get('len')} key={key.get('v','')[:40]} r={p.get('r','')[:50]}")
|
|
elif t == "aes_in":
|
|
inp = p.get("in", {})
|
|
print(f"[aes_in] len={inp.get('len')} head={inp.get('v','')[:64]} r={p.get('r','')[:50]}")
|
|
elif t == "aes_out":
|
|
out = p.get("out", {})
|
|
print(f"[aes_out] len={out.get('len')} head={out.get('v','')[:64]}")
|
|
elif t == "enpack_hdr":
|
|
print(f"[enpack_hdr] h={p.get('h')} out.len={p.get('out',{}).get('len','?')} r={p.get('r','')[:40]}")
|
|
elif t == "enpack_body_in":
|
|
print(f"[enpack_body_in] h={p.get('h')} s1={p.get('s1',{}).get('v','')[:48]} r={p.get('r','')[:40]}")
|
|
elif t == "enpack_body_out":
|
|
print(f"[enpack_body_out] h={p.get('h')} out.len={p.get('out',{}).get('len','?')}")
|
|
elif t == "armed":
|
|
print("[*] armed")
|
|
elif t == "dd_hooked":
|
|
print(f"[*] datadiv hooked: {p.get('n')}")
|
|
events.append(p)
|
|
|
|
script = session.create_script(JS)
|
|
script.on("message", on_message)
|
|
script.load()
|
|
print("[*] hooks 装好, 请在手机上 退出登录 -> 重新登录 (180s)")
|
|
deadline = time.time() + 180
|
|
try:
|
|
while time.time() < deadline:
|
|
time.sleep(5)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
|
|
print(f"[*] 捕获 {len(events)} 条 -> {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|