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
+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'});