dfpReport: 确定加密为逐字节XOR(动态keystream)+同步pair捕获+IDA导出线索, 记录破解进度

This commit is contained in:
yml2213
2026-08-26 14:17:58 +08:00
parent acb0a737eb
commit 4eec01568f
38 changed files with 4942 additions and 3 deletions
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""改写 libhydeviceid.so .data 中的 t1 指纹, 验证 dfpReport 响应变化。
t1 = 865a4924a40897ac1fcfe6b4c2cbb045 (3处静态内置, datadiv 解密)。
改为随机 32hex 后重新登录, 看 dfpReport 响应 t1 是否变化。
同时 hook SSL_write 抓新请求 body 对比魔数后密文是否变化。
"""
from __future__ import annotations
import json
import random
import string
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_t1_patch.json")
# 新 t1 (随机 32hex)
NEW_T1 = "".join(random.choice("0123456789abcdef") for _ in range(32))
print(f"[*] 新 t1 = {NEW_T1}")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var NEW_T1 = '%s';
var T1_OFFS = [0x3bb190, 0x3bb230, 0x3e0e50];
// 等待: so 加载后延时 800ms, 确保 datadiv 就地解密已完成
var tries = 0;
var iv = setInterval(function(){
tries += 1;
var m = null;
try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){}
if (m) {
clearInterval(iv);
setTimeout(function(){
patchAll(m);
}, 800);
} else if (tries > 100) { clearInterval(iv); send({type:'no_mod'}); }
}, 50);
function patchAll(m){
// 先验证当前明文, 若仍是密文说明 datadiv 未跑完, 再等
try{
var cur = m.base.add(0x3bb190).readUtf8String(32);
if (cur.indexOf('865a') !== 0 && cur.indexOf('c1') !== 0) {
// 还没解密好, 再等
setTimeout(function(){ patchAll(m); }, 400);
return;
}
}catch(e){}
T1_OFFS.forEach(function(off){
try{
var p = m.base.add(off);
var cur = p.readUtf8String(32);
send({type:'backup', off:off.toString(16), cur:cur});
Memory.protect(p, 64, 'rwx');
p.writeUtf8String(NEW_T1);
var after = p.readUtf8String(32);
send({type:'patched', off:off.toString(16), after:after, ok: after === NEW_T1});
}catch(e){ send({type:'patch_err', off:off.toString(16), e:String(e)}); }
});
send({type:'patch_done'});
}
// hook SSL_write 抓 dfpReport 请求
try{
var fns = [];
var r = new ApiResolver('module');
fns = r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){ return x.address; });
fns.slice(0, 6).forEach(function(p){
Interceptor.attach(p, {
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;
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', len:len, hex:hex});
}
});
});
}catch(e){ send({type:'ssl_err', e:String(e)}); }
""" % NEW_T1
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "backup":
print(f"[备份] 0x{p.get('off')}: {p.get('cur')}", flush=True)
elif t == "patched":
print(f"[改写] 0x{p.get('off')}: -> {p.get('after')} ok={p.get('ok')}", flush=True)
elif t == "patch_done":
print(f"[*] 改写完成 {p.get('patched')}", flush=True)
events.append({"patched": True, "new_t1": NEW_T1})
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
elif t == "wire":
body = bytes.fromhex(p.get('hex'))
try: hp = body.index(b'\r\n\r\n') + 4
except ValueError: hp = 0
b = body[hp:]
j = b.find(b'\x57\x18\x82\xcf\x66\x4b\xb3\x94')
print(f"[wire] len={p.get('len')} 魔数@{j}", flush=True)
if j >= 0:
print(f" 密文头: {b[j+10:j+30].hex(' ')}", flush=True)
rec = {"type": "wire", "len": p.get('len'), "hex": p.get('hex')}
events.append(rec)
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 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(f"[*] 已 resume, t1 已改为 {NEW_T1}。请在手机 退出登录->重新登录", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()