feat(dfp): 🏆 dfpReport 加密完全攻破 — RC4+gzip, 12/12 密文解密+逐字节回验 (R35-R37)

This commit is contained in:
yml2213
2026-08-29 10:13:08 +08:00
parent 12fad07385
commit 4868a3b0e8
28 changed files with 4110 additions and 0 deletions
+34
View File
@@ -1565,3 +1565,37 @@ full: 7c da | 11 | 69 c9 f5 28 9c 4d 46 | 95 6f 21 | 10 bb 80
### 交付 ### 交付
- hook_final.js / hook_brute.js: 密钥+POST 同刻捕获体系 (可复用!) - hook_final.js / hook_brute.js: 密钥+POST 同刻捕获体系 (可复用!)
- 验证堆枚举法极大成功 — 后续抓任何 JVM 状态对象都可用此法! - 验证堆枚举法极大成功 — 后续抓任何 JVM 状态对象都可用此法!
## §11.66 R35-R37: 🏆🏆🏆 dfpReport 加密完全攻破 — RC4+gzip (2026-08-29)
### 破案三步 (离线配对 → 内存快照 → deflate 钩)
- R35 离线配对: Perseus 会话密钥 × 全部活体密文 XXTEA/TEA 全排列 = 全否 → 排除 XXTEA 路线!
- R35 内存快照 (hook_memshot.js): 全堆 rw- 扫描, 密文 5 处拷贝, 会话密钥 yHdK0y6F3GP9gbBo — 密钥≠会话密钥实锤
- R36 **libz deflate 钩 (hook_deflate.js)**: turingga.so 动态导入 deflate → 一击命中:
- deflate #9: in=7086B JSON → out=3625B **gzip (1f8b08!)** — XXTEA 假设全错的根源: 压缩是 gzip 不是 zlib-deflate
- **密文长度 == 明文长度 → 流密码, 不是分组密码!**
- bt: libhydeviceid.so!0x57e7c <- 0x58d38 <- 0x60e6c <- 0x5b1f4/0x69bc4 <- 0x5a938 <- 0x20e924 ← JNI
### 算法定论 (12/12 历史密文 100% 解密 + 3 轮逐字节回加密 IDENTICAL)
- 明文 = 指纹 JSON (~7-8KB: Athena + deviceinfo/Hebe_D1-D5 + ...) → **gzip** (zlib level9, 头 1f8b08 00 00000000 02 03)
- **C = RC4(key) keystream ⊕ gzip** (标准 KSA/PRGA, 无 drop-N, 逐字节长度保持)
- key = **32 字符 hex ASCII 串** (样例 865a4924a40897ac1fcfe6b4c2abc798), 每注册轮换, 非 md5 派生
- **magic 571882cf664bb39401ee = keystream[0:10] ⊕ gzip头[0:10]** — 根本不是魔数, 是密钥的影子!
- 头变体 7cda11/64bb/6cdb... = keystream ⊕ (gzip头尾+JSON开头) — 全部迷雾一次解开
- 密文尾部 10B = gzip 流后附加段 (decompressobj unused_data, 解码时忽略)
- 密钥捕获点: **libhydeviceid.so!0x60e6c onEnter x1 = 密钥串** (hook_hy.js, 一次注册命中 3 次)
### 45 函数零调用之谜 (R32-R34) 最终解释
- 加密在 **libhydeviceid.so** (turing SDK 静态链接进虎牙 wrapper 库), 非 turingmfa/turingga 的 JNI 方法族!
- 算法是 **RC4** 不是 XXTEA — mfa sub_24D58 / ga sub_1F1CC 的 XXTEA 属于 hdid 子系统, 与 dfpReport 无关
- 教训: bt ACCURATE 在 OLLVM/无帧指针下给假帧; libz deflate 这类"必经外部导出"才是可靠锚点
### 交付
- **tools/dfp_rc4.py**: 编解码器 (rc4_keystream/decrypt/encrypt/gzip_wrap), CLI `dec <bin> <key>`, 自测 3 轮密文逐字节还原
- tools/frida/hook_memshot.js + scripts/hook_memshot.py: R35 内存快照体系
- tools/frida/hook_deflate.js + scripts/hook_deflate.py: R36 deflate 钩 (只挂 libz.so! GL 驱动同名符号非 zlib ABI, hook 必崩; inflate 热路径禁挂 — 首屏卡死教训)
- tools/frida/hook_hy.js + scripts/hook_hy.py: R37 密钥捕获 (0x60e6c x1)
- evidence/dfp_live/r35_plain_gzip.bin / r35_wire_cipher.bin / r35_keystream.bin / r35_plain.json + dec_*.json (12 份明文)
### 端到端伪造路径 (已无阻塞)
JSON 构造 → gzip_wrap → RC4(捕获 key) → wire body → POST. 密钥每轮经 hook_hy.js 一挂即得.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""R36: hook libz deflate/inflate — 抓 XXTEA 输入明文 + 隐藏加密路径 backtrace."""
import json, subprocess, time
from pathlib import Path
import frida
REMOTE="127.0.0.1:31878"; ADB="5dd8c93f"; PACKAGE="com.duowan.kiwi"
RE=Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
HERE=Path(__file__).resolve().parent.parent
OUT=HERE/"evidence"/("deflate_"+time.strftime("%H%M%S")+".json")
def clear_turing():
for d in ("app_turingdfp","app_turingfd"):
subprocess.run(["adb","-s",ADB,"shell","su","-c",f"rm -rf /data/data/{PACKAGE}/{d}/*"],capture_output=True)
subprocess.run(["adb","-s",ADB,"shell","su","-c",f"find /data/data/{PACKAGE} -name 'resinfo*' -delete"],capture_output=True)
def main():
clear_turing()
d=frida.get_device_manager().add_remote_device(REMOTE)
subprocess.run(["adb","-s",ADB,"shell","am","force-stop",PACKAGE],capture_output=True)
time.sleep(1.5)
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
s=d.attach(pid)
try:
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load()
except Exception as e: print(f"[*] bypass err {e}",flush=True)
d.resume(pid)
result={}; got={"post":False,"out":False}
def on(m,_):
if m.get("type")=="error":
txt=str(m)
if "ClassNotFoundException" in txt: return
print(f"[JS] {txt[:160]}",flush=True); return
p=m.get("payload") or {}
ev=p.get("event")
if ev=="post": got["post"]=True; print(f"[+] POST {p['num']}B",flush=True)
elif ev=="deflate-out": got["out"]=True; print(f"[+] deflate-out #{p['i']} in={p['in_len']} out={p['total']}",flush=True)
elif ev=="deflate-found": print(f"[*] deflate export in {p['mod']}",flush=True)
elif ev=="inflate-in": print(f"[+] inflate-in {p['len']}B via {p['mod']}",flush=True)
result.setdefault(ev or "misc",[]).append(p)
sc=s.create_script((HERE/"tools/frida/hook_deflate.js").read_text())
sc.on("message",on); sc.load()
t0=time.time()
while time.time()-t0<150:
time.sleep(2)
if got["out"] and got["post"]: time.sleep(4); break
json.dump(result,open(OUT,"w"),indent=2)
print(f"[*] saved {OUT}",flush=True)
try: s.detach()
except Exception: pass
if __name__=="__main__": main()
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""R36: hook libz deflate/inflate — 抓 XXTEA 输入明文 + 隐藏加密路径 backtrace."""
import json, subprocess, time
from pathlib import Path
import frida
REMOTE="127.0.0.1:31878"; ADB="5dd8c93f"; PACKAGE="com.duowan.kiwi"
RE=Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
HERE=Path(__file__).resolve().parent.parent
OUT=HERE/"evidence"/("hy_"+time.strftime("%H%M%S")+".json")
def clear_turing():
for d in ("app_turingdfp","app_turingfd"):
subprocess.run(["adb","-s",ADB,"shell","su","-c",f"rm -rf /data/data/{PACKAGE}/{d}/*"],capture_output=True)
subprocess.run(["adb","-s",ADB,"shell","su","-c",f"find /data/data/{PACKAGE} -name 'resinfo*' -delete"],capture_output=True)
def main():
clear_turing()
d=frida.get_device_manager().add_remote_device(REMOTE)
subprocess.run(["adb","-s",ADB,"shell","am","force-stop",PACKAGE],capture_output=True)
time.sleep(1.5)
pid=d.spawn([PACKAGE]); print(f"[*] spawn pid={pid}",flush=True)
s=d.attach(pid)
try:
b=s.create_script((RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text()); b.load()
except Exception as e: print(f"[*] bypass err {e}",flush=True)
d.resume(pid)
result={}; got={"post":False,"out":False}
seen_fns=set()
def events_ok(p):
seen_fns.add(p.get('fn')); return True
def on(m,_):
if m.get("type")=="error":
txt=str(m)
if "ClassNotFoundException" in txt: return
print(f"[JS] {txt[:160]}",flush=True); return
p=m.get("payload") or {}
ev=p.get("event")
if ev=="post": got["post"]=True; print(f"[+] POST {p['num']}B",flush=True)
elif ev=="armed-hy": print("[*] hy chain armed",flush=True)
elif ev=="call":
if len(seen_fns)<99: pass
elif ev=="call" and events_ok(p): got["out"]=True
elif ev=="deflate-found": print(f"[*] deflate export in {p['mod']}",flush=True)
elif ev=="inflate-in": print(f"[+] inflate-in {p['len']}B via {p['mod']}",flush=True)
result.setdefault(ev or "misc",[]).append(p)
sc=s.create_script((HERE/"tools/frida/hook_hy.js").read_text())
sc.on("message",on); sc.load()
t0=time.time()
while time.time()-t0<150:
time.sleep(2)
if got["out"] and got["post"]: time.sleep(4); break
json.dump(result,open(OUT,"w"),indent=2)
print(f"[*] saved {OUT}",flush=True)
try: s.detach()
except Exception: pass
if __name__=="__main__": main()
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""R35: 清 turing 状态(保留登录) -> spawn -> bypass -> hook_memshot 内存快照.
产出 evidence/memshot_<HHMMSS>.json:
- dfp wire 全量 + backtrace
- 线上密文在进程内存中的全部拷贝 + ±2KB 邻域
- Perseus 会话密钥 + 静态密钥出现位置 + 16 字符候选密钥串
- zlib 流候选 (78 xx) 供主机 inflate 验证
"""
from __future__ import annotations
import json, subprocess, time
from pathlib import Path
import frida
REMOTE = "127.0.0.1:31878"
ADB = "5dd8c93f"
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
HERE = Path(__file__).resolve().parent.parent
OUT = HERE / "evidence" / ("memshot_" + time.strftime("%H%M%S") + ".json")
def clear_turing():
"""只清 turing 状态, 保留登录态 -> ~40s 内自动重注册上报."""
dirs = ["app_turingdfp", "app_turingfd"]
for d in dirs:
subprocess.run(["adb", "-s", ADB, "shell", "su", "-c",
f"rm -rf /data/data/{PACKAGE}/{d}/*"], capture_output=True)
# resinfo 清理 (配方 §11.55)
subprocess.run(["adb", "-s", ADB, "shell", "su", "-c",
f"find /data/data/{PACKAGE} -name 'resinfo*' -delete"], capture_output=True)
def main():
print("[*] clearing turing state (login kept)", flush=True)
clear_turing()
d = frida.get_device_manager().add_remote_device(REMOTE)
subprocess.run(["adb", "-s", ADB, "shell", "am", "force-stop", PACKAGE], capture_output=True)
time.sleep(1.5)
pid = d.spawn([PACKAGE])
print(f"[*] spawn pid={pid}", flush=True)
s = d.attach(pid)
try:
b = s.create_script((RE / "evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text())
b.load()
except Exception as e:
print(f"[*] bypass err {e}", flush=True)
d.resume(pid)
result: dict = {}
got = {"post": False, "done": False}
def on(m, _):
if m.get("type") == "error":
print(f"[JS] {str(m)[:200]}", flush=True)
return
p = m.get("payload") or {}
ev = p.get("event")
if ev in ("post", "scan-done"):
got["post" if ev == "post" else "done"] = True
if ev == "post":
print(f"[+] dfpReport POST {p['num']}B", flush=True)
elif ev == "ranges":
print(f"[*] scanning {p['n']} rw- ranges ({p['bytes']//1024//1024}MB)", flush=True)
elif ev == "cipher-copies":
print(f"[+] cipher copies in memory: {p['n']}", flush=True)
elif ev == "perseus":
print(f"[+] Perseus key: {p.get('key')}", flush=True)
elif ev == "key-cands":
print(f"[+] key candidates: {p['n']}", flush=True)
elif ev == "zcand":
pass
elif ev == "scan-done":
print(f"[*] scan complete (zlib cands={p['zcands']})", flush=True)
result.setdefault(ev or "misc", []).append(p)
sc = s.create_script((HERE / "tools/frida/hook_memshot.js").read_text())
sc.on("message", on)
sc.load()
t0 = time.time()
while time.time() - t0 < 120:
time.sleep(2)
if got["done"]:
break
if got["post"] and time.time() - t0 > 60:
break
json.dump(result, open(OUT, "w"), indent=2)
print(f"[*] saved {OUT}", flush=True)
try:
s.detach()
except Exception:
pass
if __name__ == "__main__":
main()
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""dfpReport 编解码器 — R37 攻坚定论 (2026-08-29)
算法 (R35-R37 三轮活体捕获 + 12/12 历史密文解密验证):
明文 JSON -> gzip (zlib deflate, gzip wrapper, 头 1f8b08 00 00000000 02 03)
-> C = RC4(key) keystream XOR gzip (逐字节, 长度保持, 标准 KSA/PRGA 无 drop)
-> wire body = 密文整段 (前 10B 即所谓 magic 571882cf664bb39401ee = keystream XOR gzip 头,
不是魔数! 密钥同则 magic 同)
-> 尾部 10B 为 gzip 流之后的附加段 (decompressobj unused_data)
密钥 = 32 字符 hex ASCII 串 (如 865a4924a40897ac1fcfe6b4c2abc798), 每注册轮换.
捕获点: libhydeviceid.so!0x60e6c onEnter x1 = 密钥串 (tools/frida/hook_hy.js).
XXTEA/静态密钥 f15e... 与 dfpReport 无关 (那是 hdid 子系统) — 45 函数零调用之谜即此.
"""
from __future__ import annotations
import sys, zlib
def rc4_keystream(key: bytes, n: int) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) & 0xFF
S[i], S[j] = S[j], S[i]
out = bytearray()
i = j = 0
for _ in range(n):
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
out.append(S[(S[i] + S[j]) & 0xFF])
return bytes(out)
def decrypt(body: bytes, key: bytes) -> bytes:
"""body = wire body (magic 10B + cipher) -> 指纹 JSON 明文."""
ks = rc4_keystream(key, len(body))
plain = bytes(a ^ b for a, b in zip(body, ks))
d = zlib.decompressobj(31) # gzip
json_bytes = d.decompress(plain, 1 << 20)
return json_bytes
def encrypt(json_bytes: bytes, key: bytes) -> bytes:
"""指纹 JSON -> wire body. 尾部 10B 附加段按全 0 处理 (服务端解 gzip 忽略)."""
gz = gzip_wrap(json_bytes)
ks = rc4_keystream(key, len(gz) + 10)
return bytes(a ^ b for a, b in zip(gz + b"\x00" * 10, ks))
def gzip_wrap(json_bytes: bytes) -> bytes:
co = zlib.compressobj(9, zlib.DEFLATED, 31) # gzip wrapper
gz = co.compress(json_bytes) + co.flush()
# 对齐 turing 原生 gzip 头: OS 字节 = 0x03 (Unix), Python 写 0x13
return gz[:9] + b"\x03" + gz[10:]
def _selftest():
MAGIC = bytes.fromhex("571882cf664bb39401ee")
KEY = b"865a4924a40897ac1fcfe6b4c2abc798"
body = open("evidence/dfp_live/dfp_body_4303.bin", "rb").read()
i = body.find(MAGIC)
wire = body[i:]
js = decrypt(wire, KEY)
assert js.startswith(b'{"Athena"'), js[:40]
print(f"decrypt OK: {len(js)}B JSON")
gz = gzip_wrap(js)
re_enc = encrypt(js, KEY)
n = len(gz)
print(f"re-encrypt: gzip {n}B + 10B tail vs wire {len(wire)}B, "
f"cipher part {'IDENTICAL' if re_enc[:n] == wire[:n] else 'DIFFERS'} "
f"(magic10 {'==' if re_enc[:10] == wire[:10] else '!='})")
if __name__ == "__main__":
if len(sys.argv) >= 3 and sys.argv[1] == "dec":
key = sys.argv[3].encode()
body = open(sys.argv[2], "rb").read()
MAGIC = bytes.fromhex("571882cf664bb39401ee")
i = body.find(MAGIC)
sys.stdout.buffer.write(decrypt(body[i:], key))
else:
_selftest()
+108
View File
@@ -0,0 +1,108 @@
// R36b: 只挂 libz.so 的 deflate/inflate — 抓 XXTEA 输入明文 + 隐藏路径 backtrace
// 教训: GL/EGL 驱动也有同名 deflate 导出但非 zlib ABI, hook 必崩 — 只认 libz.so
'use strict';
function emit(o){ try{ send(o); }catch(e){} }
function hexb(p,n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(b=>('0'+b.toString(16)).slice(-2)).join(''); }catch(e){ return ''; } }
function bt(ctx, depth){
var out = [];
try {
var fr = Thread.backtrace(ctx, Backtracer.ACCURATE).slice(0, depth).map(function(a){
var m = Process.findModuleByAddress(a);
return m ? m.name + '!0x' + a.sub(m.base).toString(16) : a.toString();
});
if (fr.length) return fr;
} catch(e){}
try {
out = Thread.backtrace(ctx, Backtracer.FUZZY).slice(0, depth).map(function(a){
var m = Process.findModuleByAddress(a);
return 'F:' + (m ? m.name + '!0x' + a.sub(m.base).toString(16) : a.toString());
});
} catch(e){}
return out;
}
// z_stream (LP64): next_in@0 avail_in@8 total_in@16 next_out@24 avail_out@32 total_out@40
var streams = {};
var nDumps = 0;
var armed = false;
function armDeflate(){
if (armed) return;
var libz = Process.findModuleByName('libz.so');
if (!libz) return;
var d = null, i = null;
try { d = libz.getExportByName('deflate'); } catch(e){}
try { i = libz.getExportByName('inflate'); } catch(e){}
if (d) {
armed = true;
emit({event:'armed-deflate', mod:'libz.so'});
Interceptor.attach(d, {
onEnter(args){
var strm = args[0]; var flush = args[1].toInt32();
try {
var key = strm.toString();
var st = streams[key];
if (!st){
st = streams[key] = {out_base: strm.add(24).readPointer(), bt0: null, n: 0};
var nin = strm.add(8).readU32();
if (nin > 0 && nin < 200000){
st.in_hex = hexb(strm.readPointer(), Math.min(nin, 65536));
st.in_len = nin;
}
}
if (!st.bt0 && st.n < 3) st.bt0 = bt(this.context, 24);
st.n++;
this.strm = strm; this.flush = flush;
} catch(e){}
},
onLeave(ret){
try {
var st = streams[this.strm.toString()];
if (!st) return;
var zret = ret.toInt32();
var totalOut = this.strm.add(40).readU64().toNumber();
if (this.flush === 4 && (zret === 1 || zret === 0)){
// 只转储 turing 调用 (bt 含 turing) 或大输入 (Lyra 帧 JSON) — 其余是 UI 资源压缩噪音
var btStr = (st.bt0 || []).join(',');
var isTuring = /turing/i.test(btStr);
if (!isTuring && (st.in_len || 0) < 300){ delete streams[this.strm.toString()]; return; }
var hex = hexb(st.out_base, Math.min(totalOut, 131072));
nDumps++;
emit({event:'deflate-out', i:nDumps, total: totalOut, ret: zret, turing: isTuring,
in_len: st.in_len || 0, in_hex: st.in_hex || '', out_hex: hex, bt: st.bt0});
delete streams[this.strm.toString()];
}
} catch(e){}
}
});
}
if (i) {
// inflate 不挂 — 图片解码热路径, 挂了首屏卡死 (R36 教训)
}
}
var poll = setInterval(function(){ armDeflate(); if (armed) clearInterval(poll); }, 150);
// SSL_write: 抓同轮 wire 密文
var done = false;
for (var mod of Process.enumerateModules()){
var addr = null;
try { addr = mod.getExportByName('SSL_write'); } catch(e){}
if (!addr) continue;
Interceptor.attach(addr, {
onEnter(args){
if (done) return;
try {
var num = args[2].toInt32();
if (num < 60 || num > 60000) return;
var u = new Uint8Array(args[1].readByteArray(num));
var s = '';
for (var i=0;i<u.length;i++) s += (u[i]>=32 && u[i]<127) ? String.fromCharCode(u[i]) : '.';
if (s.indexOf('dfpReport') < 0) return;
done = true;
var hex = Array.prototype.map.call(u, function(x){return ('0'+x.toString(16)).slice(-2)}).join('');
emit({event:'post', num:num, hex:hex});
} catch(e){}
}
});
}
emit({event:'script-loaded'});
+80
View File
@@ -0,0 +1,80 @@
// R37: 挂 libhydeviceid.so 加密调用链 — 抓加密函数的密钥/nonce 参数
// 已知链 (R36 deflate bt): 0x57e7c(gzip包装) <- 0x58d38 <- 0x60e6c <- 0x5b1f4 <- 0x5a938 <- 0x20e924 <- JNI
'use strict';
function emit(o){ try{ send(o); }catch(e){} }
function hexb(p,n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(b=>('0'+b.toString(16)).slice(-2)).join(''); }catch(e){ return ''; } }
var HY = null;
var events = 0;
var MAX = 400;
function dumpArgs(args, tag, ctx){
if (events >= MAX) return;
var row = {event:'call', fn:tag, args:[]};
for (var i=0;i<4;i++){
try {
var p = args[i];
var h = hexb(p, 48);
// 判定是否像指针指向数据
row.args.push({x:String(p), h: h});
} catch(e){ row.args.push({x:String(args[i]), h:''}); }
}
try {
row.bt = Thread.backtrace(ctx, Backtracer.ACCURATE).slice(0,12).map(function(a){
var m=Process.findModuleByAddress(a); return m? m.name+'!0x'+a.sub(m.base).toString(16) : a.toString();
});
} catch(e){}
events++;
emit(row);
}
function arm(){
if (HY) return true;
HY = Process.findModuleByName('libhydeviceid.so');
if (!HY) return false;
var offs = [0x57e7c, 0x58d38, 0x60e6c, 0x5b1f4, 0x5a938, 0x20e924];
for (var off of offs){
(function(off){
try {
Interceptor.attach(HY.base.add(off), {
onEnter(args){ this.tag='0x'+off.toString(16); this.on=true;
if (events < MAX) dumpArgs(args, this.tag+':e', this.context); },
onLeave(ret){
if (this.on && events < MAX){
events++;
emit({event:'ret', fn:this.tag, ret:String(ret), x0:String(this.context.x0), x1:String(this.context.x1)});
}
}
});
} catch(e){ emit({event:'hook-e', off:off, e:String(e).substring(0,120)}); }
})(off);
}
emit({event:'armed-hy'});
return true;
}
var poll = setInterval(function(){ if (arm()) clearInterval(poll); }, 150);
// SSL_write: 同轮 wire 密文
var done = false;
for (var mod of Process.enumerateModules()){
var addr = null;
try { addr = mod.getExportByName('SSL_write'); } catch(e){}
if (!addr) continue;
Interceptor.attach(addr, {
onEnter(args){
if (done) return;
try {
var num = args[2].toInt32();
if (num < 60 || num > 60000) return;
var u = new Uint8Array(args[1].readByteArray(num));
var s = '';
for (var i=0;i<u.length;i++) s += (u[i]>=32 && u[i]<127) ? String.fromCharCode(u[i]) : '.';
if (s.indexOf('dfpReport') < 0) return;
done = true;
var hex = Array.prototype.map.call(u, function(x){return ('0'+x.toString(16)).slice(-2)}).join('');
emit({event:'post', num:num, hex:hex});
} catch(e){}
}
});
}
emit({event:'script-loaded'});
+142
View File
@@ -0,0 +1,142 @@
// R35: 内存快照 @SSL_write — 定位密文缓冲区全部拷贝 + 邻域转储 + 会话密钥
// 目标: 在密文刚生成/刚发出的瞬间, 从堆内存里找到 (a) 明文 deflate 缓冲 (b) 16B 会话密钥
'use strict';
function emit(o){ try{ send(o); }catch(e){} }
function hexb(p,n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(b=>('0'+b.toString(16)).slice(-2)).join(''); }catch(e){ return ''; } }
var STATIC_KEY = 'f15e78d23033c2c4bac8a925677e863a';
var done = false;
function scanRanges(ranges, patternHex, maxHits){
var pat = '';
for (var i=0;i<patternHex.length;i+=2) pat += patternHex.substr(i,2)+' ';
var out = [];
for (var r=0; r<ranges.length && out.length<maxHits; r++){
var rg = ranges[r];
if (rg.size > 96*1024*1024) continue;
try {
var ms = Memory.scanSync(rg.base, rg.size, pat);
for (var m=0; m<ms.length && out.length<maxHits; m++) out.push({a: ms[m].address, mod: rg.file ? rg.file.path : String(rg.base), prot: rg.protection});
} catch(e){}
}
return out;
}
function enumAllRW(){
return Process.enumerateRanges({protection:'rw-', coalesce:false});
}
for (const mod of Process.enumerateModules()) {
let addr = null;
try { addr = mod.getExportByName('SSL_write'); } catch(e){}
if (!addr) continue;
Interceptor.attach(addr, {
onEnter(args){
if (done) return;
try {
var num = args[2].toInt32();
if (num < 60 || num > 60000) return;
var u = new Uint8Array(args[1].readByteArray(num));
var s = '';
for (var i=0;i<u.length;i++) s += (u[i]>=32 && u[i]<127) ? String.fromCharCode(u[i]) : '.';
if (s.indexOf('dfpReport') < 0) return;
done = true;
var hex = Array.prototype.map.call(u, function(x){return ('0'+x.toString(16)).slice(-2)}).join('');
var idx = hex.indexOf('571882cf664bb39401ee');
emit({event:'post', num:num, hex:hex, idx:idx});
if (idx < 0) return;
// 线上密文前缀 (magic 后 10B 起, 取 16B 作内存搜索指纹)
var CIPH = hex.substring(idx+20, idx+20+32);
emit({event:'cipher-prefix', ciph:CIPH});
var bt = Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0,30).map(function(a){ var m=Process.findModuleByAddress(a); return m? m.name+'!0x'+a.sub(m.base).toString(16) : a.toString(); }).join(' <- ');
emit({event:'bt', bt:bt});
setTimeout(function(){
// ---- 1) Java 堆: Perseus/Guava 会话密钥 ----
try {
Java.perform(function(){
try {
Java.enumerateClassLoaders({
onMatch: function(loader){
try {
Java.classFactory.loader = loader;
Java.choose('com.tencent.turingfd.sdk.ams.ga.Perseus', {
onMatch: function(inst){
try {
var g = inst.b.value;
emit({event:'perseus', key: g ? String(g.c.value) : null,
envLen: g && g.b && g.b.value ? g.b.value.length : -1,
d: g && g.d ? String(g.d.value) : null});
} catch(e){ emit({event:'perseus-e', e:String(e).substring(0,150)}); }
},
onComplete: function(){}
});
} catch(e){}
},
onComplete: function(){}
});
} catch(e){ emit({event:'j-e', e:String(e).substring(0,150)}); }
});
} catch(e){}
// ---- 2) native 堆扫描 ----
var ranges = enumAllRW();
emit({event:'ranges', n:ranges.length, bytes: ranges.reduce(function(a,r){return a+r.size;},0)});
// 2a. 密文拷贝定位
var hits = scanRanges(ranges, CIPH, 24);
emit({event:'cipher-copies', n:hits.length, addrs: hits.map(function(h){return String(h.a);})});
// 每处转储 ±2KB 邻域 (前 8 处全量, 其余只记地址)
for (var i=0;i<Math.min(hits.length,8);i++){
var a = hits[i].a;
var lo = a.sub(2048), hi = a.add(2048);
emit({event:'nbhd', i:i, addr:String(a), src:hits[i].mod, lo:String(lo), hex: hexb(lo, 4096)});
}
// 2b. 静态密钥出现位置
var sh = scanRanges(ranges, STATIC_KEY, 40);
emit({event:'static-key-hits', n:sh.length, addrs: sh.map(function(h){return String(h.a)+'@'+h.src;})});
// 2c. 密文邻域内 16 字符 [A-Za-z0-9] 候选密钥串 (±8KB of each cipher copy)
var cands = {}, cn=0;
for (var i=0;i<Math.min(hits.length,6) && cn<4000;i++){
try {
var buf = new Uint8Array(hits[i].a.sub(8192).readByteArray(16384));
var run = '', start=0;
for (var j=0;j<buf.length;j++){
var c=buf[j];
var ok = (c>=48&&c<=57)||(c>=65&&c<=90)||(c>=97&&c<=122);
if (ok){ if(!run) start=j; run+=String.fromCharCode(c); }
else {
if (run.length===16 && !(cands[run])) { cands[run]=1; cn++; }
run='';
}
}
if (run.length===16 && !cands[run]) { cands[run]=1; cn++; }
} catch(e){}
}
emit({event:'key-cands', n:cn, keys:Object.keys(cands)});
// 2d. 全堆 zlib 头候选 (78 01/5e/9c/da) — 限量转储 256B 供主机验证
var zs=0;
for (var r=0; r<ranges.length && zs<120; r++){
var rg=ranges[r];
if (rg.size>96*1024*1024) continue;
for (var pi=0; pi<4 && zs<120; pi++){
var zpat = ['78 01','78 5e','78 9c','78 da'][pi];
var ms=[];
try { ms = Memory.scanSync(rg.base, rg.size, zpat); } catch(e){ continue; }
for (var m=0; m<ms.length && zs<120; m++){
// 过滤: deflate 流应较长 (指纹 JSON ~1-3KB), 跳过落在密文拷贝邻域外的? 不跳, 交给主机验证
var h = hexb(ms[m].address, 192);
if (h) { emit({event:'zcand', pat:zpat, a:String(ms[m].address), hex:h}); zs++; }
}
}
}
emit({event:'scan-done', zcands:zs});
}, 200);
} catch(e){ emit({event:'e', e:String(e).substring(0,200)}); }
}
});
}
emit({event:'armed'});