feat(huya): turing密文流式模式判定 + 全双工捕获 (R18)

- 三样本差分: 仅5稳定字节(7cda+69c9f5头), 流式CTR+nonce非ECB
- SSL_write+SSL_read 全双工: req4198B+resp108B 捕获, java侧拼body回溯实锤
- 采样集: golden+f1+f2 三份密文; dfp_live 存档
- turing专项起点: 密钥材料mpdc/81B配置/ga2 齐备
This commit is contained in:
yml2213
2026-08-29 06:24:53 +08:00
parent b06a768eb2
commit 021cd636c8
6 changed files with 1277 additions and 25 deletions
+22
View File
@@ -1121,3 +1121,25 @@ dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采
- 解析 WUP 完整结构 + 抓 SSL_read 响应
- 破 getDfpConfig 81B 配置 (含加密钥?) -> 破 dfpReport 密文
- 或运行时 trace 密文装配点 (SSL_magic 命中时刻的调用者)
---
## §11.48 R18: turing 密文流式模式判定 + 全双工捕获 (2026-08-29)
### 三样本差分 (同设备 08-24/06-14/06-25)
- 密文长 3531~3931B; **全三样本仅 5 稳定字节**: [7c da][XX][69 c9 f5] (位置0-2 + 3-5)
- 其余全运行时变 -> **非确定性ECB, 是带 nonce/头部的流式(CTR)加密** — 5B稳定段=头部/标志
- 密码结构: [10B魔数 571882cf...][5B头][nonce/数据...][密文流][尾部 14B]
- 尾部特征: 400c0b8c980ca80c (两次 fresh 均有, 疑包尾/校验)
### 全双工捕获 (SSL_write+SSL_read, hook_ssl_magic.js)
- 请求 4198B (POST wsapi.huya.com, okhttp) + 响应帧 108B (826a...hyudbwebuif resp)
- 证据: evidence/dfp_live/dfpReport_post_full_0625.bin
- SSL_write 回溯 = ART/Java 直呼 (JAVA 侧拼 body, 非 turing 直接写 TLS)
- 采样集: golden(3908B) + f1(3531B) + f2(3931B) 三份密文齐备
### turing-cipher 专项起点 (会话内到此为止)
1. 密文=流式+nonce头; 明文=采集数据(可能gzip先压 — SSL_read 见 1f8b0800 gzip 流)
2. 密钥材料候选: mpdc_32hex / getDfpConfig 81B配置 / ga2 / turing.dat
3. 下一步: (a) frida 追 turing 内部 encrypt 调用 (JNI 层), (b) 静态 OLLVM 破 ga/mfa,
(c) 若只求实用: 重放已捕获密文即可 (同设备身份)
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
File diff suppressed because one or more lines are too long
+49 -25
View File
@@ -1,8 +1,8 @@
'use strict';
function emit(row) { try { send(row); } catch (e) {} }
function btOk() {
function btOk(ctx) {
try {
return Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0, 10).map(a => {
return Thread.backtrace(ctx, Backtracer.ACCURATE).slice(0, 14).map(a => {
const m = Process.findModuleByAddress(a);
return m ? m.name + '!' + a.sub(m.base) : a.toString();
}).join(' <- ');
@@ -11,29 +11,53 @@ function btOk() {
const PREF = [0x57, 0x18, 0x82, 0xcf];
let fired = false;
for (const mod of Process.enumerateModules()) {
// ---- SSL_write: 扫描明文请求缓冲中的魔数 -> 全量 dump + 回溯 ----
let addr = null;
try { addr = mod.getExportByName('SSL_write'); } catch (e) {}
if (addr === null) continue;
Interceptor.attach(addr, {
onEnter(args) {
if (fired) return;
const buf = args[1];
const num = args[2].toInt32();
if (num < 40) return;
try {
const b = buf.readByteArray(1 << 20 > num ? num : num); // 尽量读
const u = new Uint8Array(b);
for (let i = 0; i + 4 < u.length; i++) {
if (u[i] === PREF[0] && u[i+1] === PREF[1] && u[i+2] === PREF[2] && u[i+3] === PREF[3]) {
fired = true;
const n = Math.min(num, 12000);
const hex = Array.from((new Uint8Array(buf.readByteArray(n)))).map(x => x.toString(16).padStart(2, '0')).join('');
emit({ event: 'ssl-magic', module: mod.name, num, off: i, full: hex, bt: btOk() });
return;
if (addr !== null) {
Interceptor.attach(addr, {
onEnter(args) {
const ctx = this.context;
if (fired) return;
const buf = args[1];
const num = args[2].toInt32();
if (num < 40) return;
try {
const u = new Uint8Array(buf.readByteArray(Math.min(num, 20000)));
for (let i = 0; i + 4 < u.length; i++) {
if (u[i] === PREF[0] && u[i+1] === PREF[1] && u[i+2] === PREF[2] && u[i+3] === PREF[3]) {
fired = true;
const hex = Array.from(u).map(x => x.toString(16).padStart(2, '0')).join('');
emit({ event: 'ssl-magic', module: mod.name, num, off: i, full: hex, bt: btOk(ctx) });
return;
}
}
}
} catch (e) {}
},
});
emit({ event: 'ssl-hook', module: mod.name });
}
} catch (e) {}
},
});
emit({ event: 'ssl-hook', module: mod.name, fn: 'write' });
}
// ---- SSL_read: 捕获响应 (含 tRsp/t1/sdid 特征的才发) ----
let rd = null;
try { rd = mod.getExportByName('SSL_read'); } catch (e) {}
if (rd !== null) {
Interceptor.attach(rd, {
onEnter(args) {
this._rdBuf = args[1];
this._rdNum = args[2].toInt32();
this._rdMod = mod.name;
},
onLeave(retval) {
const n = retval.toInt32();
if (n <= 40 || n > 20000) return;
try {
const hex = Array.from(new Uint8Array(this._rdBuf.readByteArray(n))).map(x => x.toString(16).padStart(2, '0')).join('');
if (fired || hex.indexOf('74727370') >= 0 || hex.indexOf('dfp') >= 0) {
emit({ event: 'ssl-readcap', module: this._rdMod, num: n, full: hex, bt: btOk(this.context) });
}
} catch (e) {}
},
});
emit({ event: 'ssl-hook', module: mod.name, fn: 'read' });
}
}