128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""dump libhydeviceid.so .data 段 (datadiv 就地解密区), 提取全部明文串。
|
|
|
|
datadiv 解码器把 .data 段 0x3c8xxx 区域的密文就地解密为明文。
|
|
dump 整个 .data(0x3bb000 ~ 0x3eac10), 提取可读字符串 + 搜魔数。
|
|
"""
|
|
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_datadiv_data.json")
|
|
|
|
JS = r"""
|
|
'use strict';
|
|
Process.setExceptionHandler(function(d){ return true; });
|
|
send({type:'armed'});
|
|
|
|
var DATA_RANGES = [
|
|
[0x36aec0, 0x6e75], // .rodata (含 datadiv 密文? 可能)
|
|
[0x3b2460, 0x74d8], // .data.rel.ro
|
|
[0x3bb000, 0x2fc08], // .data (datadiv 就地解密区)
|
|
];
|
|
|
|
function dumpData(m){
|
|
DATA_RANGES.forEach(function(r){
|
|
var off = r[0], size = r[1];
|
|
try{
|
|
var base = m.base.add(off);
|
|
var bytes = Array.from(new Uint8Array(base.readByteArray(size)));
|
|
// 提取可读字符串 (>=5 连续可打印)
|
|
var strs = [];
|
|
var cur = '';
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
var c = bytes[i];
|
|
if (c >= 32 && c < 127) cur += String.fromCharCode(c);
|
|
else {
|
|
if (cur.length >= 5) strs.push({s: cur, off: off + i - cur.length});
|
|
cur = '';
|
|
}
|
|
}
|
|
// 搜魔数
|
|
var hasMagic = false;
|
|
for (var i = 0; i + 5 < bytes.length; i++) {
|
|
if (bytes[i]==0x57 && bytes[i+1]==0x18 && bytes[i+2]==0x82 && bytes[i+3]==0xcf) { hasMagic = true; break; }
|
|
}
|
|
send({type:'data', sec:off.toString(16), size:size, strs:strs.length,
|
|
hasMagic: hasMagic,
|
|
sample: strs.slice(0, 60)});
|
|
}catch(e){
|
|
send({type:'data_err', sec:off.toString(16), e:String(e)});
|
|
}
|
|
});
|
|
}
|
|
|
|
var tries = 0;
|
|
var iv = setInterval(function(){
|
|
tries += 1;
|
|
var m = null;
|
|
try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){}
|
|
if (m) {
|
|
clearInterval(iv);
|
|
send({type:'mod', base:String(m.base), size:m.size});
|
|
dumpData(m);
|
|
} else if (tries > 100) { clearInterval(iv); send({type:'no_mod'}); }
|
|
}, 50);
|
|
"""
|
|
|
|
|
|
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 == "data":
|
|
print(f"[data] sec=0x{p.get('sec')} size={p.get('size')} "
|
|
f"strs={p.get('strs')} magic={p.get('hasMagic')}", flush=True)
|
|
for it in p.get('sample', [])[:40]:
|
|
print(f" 0x{it['off']:x}: {it['s']}", flush=True)
|
|
events.append({"section": p.get('sec'), "strings": p.get('sample')})
|
|
OUT.write_text(json.dumps(events, indent=1))
|
|
elif t == "data_err":
|
|
print(f"[data_err] {p.get('sec')}: {p.get('e')}", flush=True)
|
|
elif t == "mod":
|
|
print(f"[*] libhydeviceid @ {p.get('base')} size={p.get('size')}", flush=True)
|
|
elif t == "no_mod":
|
|
print("[*] 模块未加载", flush=True)
|
|
|
|
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("[*] 已 resume, dump 完成即 pkill", flush=True)
|
|
try:
|
|
while True:
|
|
time.sleep(5)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |