feat(huya): 🎯 完整加密链 deflate+XTEA 闭环验证成功 (R27)
- sub_2E164=deflate+sub_25234=XTEA封套 (pad+4Blen+24D58加密) 源链闭环 - harness: Java-deflate+真25234 产出52B密文 - 关键坑: GOT RELRO只读页 mprotect后导入补丁才生效 (所有-1的真相!) - 捕获=[13B头][XTEA-wrap(key=设备派生)]; 密钥推导=下一步
This commit is contained in:
@@ -1369,3 +1369,33 @@ m209202(SparseArray,Context,Map)@0x21bdc n209202(SparseArray,[B,I,String,J)@0x43
|
|||||||
1. **用户导出 turingga.so 的 IDA decompile** (k209202 全家: 0x219d8/0x24334/0x19378/0x13eb8/0x1c640)
|
1. **用户导出 turingga.so 的 IDA decompile** (k209202 全家: 0x219d8/0x24334/0x19378/0x13eb8/0x1c640)
|
||||||
2. harness 继续: mfa 单例 34D68 全量 ctor (48BD8) + 各字段填充 → b87_ 对正确 resp 输入成功
|
2. harness 继续: mfa 单例 34D68 全量 ctor (48BD8) + 各字段填充 → b87_ 对正确 resp 输入成功
|
||||||
3. 或 frida 捕获一次 **服务器 dfpConfig 响应** (b87_ 的 resp 输入 = 密钥推导的旁路!)
|
3. 或 frida 捕获一次 **服务器 dfpConfig 响应** (b87_ 的 resp 输入 = 密钥推导的旁路!)
|
||||||
|
|
||||||
|
## §11.58 R27: 🎯 完整加密链验证 — deflate+XTEA 闭环出炉 (2026-08-29)
|
||||||
|
|
||||||
|
### 加密链全解 (mfa IDA 反编译闭环!)
|
||||||
|
- **sub_2E164 = 加密封装**: `deflate(a1, a2, &def, &dlen, 0xFFFFFFFF)` + `sub_25234(...)`
|
||||||
|
- **sub_25234 = XTEA 封套**:
|
||||||
|
```
|
||||||
|
v5 = len + 4*(len&3!=0) + 4 (对齐到4B + 长度字!)
|
||||||
|
calloc; memcpy; 尾字 = 明文长度!
|
||||||
|
sub_24D58(buf, +n, key@3DF88) (加密!)
|
||||||
|
```
|
||||||
|
- **解密对偶**: sub_252EC (XTEA-dec + 尾字长度校验) → sub_34A00 (inflate!)
|
||||||
|
- **链条**: 明文 → deflate(zlib) → [pad + 4B len] → XTEA-enc@3DF88 → 密文!
|
||||||
|
→ XTEA-dec → 尾字取长 → inflate → 明文 ✓
|
||||||
|
|
||||||
|
### harness 全链运行成功!
|
||||||
|
- Java-deflate 44B→47B (zlib) + **真 sub_25234 ret=1 密文 52B** (4012a3a8e4...)
|
||||||
|
- **GOT RELRO 坑找到: 导入槽在只读页!! — mprotect(0x45648,0x3440,7) 后补丁才真正写入**
|
||||||
|
(之前所有 -1/崩溃 = 未写成的 GOT 槽 br-0!)
|
||||||
|
- 修复顺序: load → gap-mmap → **mprotect RELRO→RWX** → patchImports → 调用!
|
||||||
|
- 52B 密文 = 48B (pad+XTEA 块) + 尾 4B 长度字段的所有权 = XTEA 加密涵盖
|
||||||
|
|
||||||
|
### 捕获密文格式确认
|
||||||
|
- **捕获 = [13B 请求头][XTEA-wrap(key=设备派生)]** — 13B 头 = 请求协议 (7c da/版本/nonce/uptime)
|
||||||
|
- 密钥 ≠ 静态 3DF88 (设备材料 mpdc/ga2/turing.dat 派生)
|
||||||
|
|
||||||
|
### 下一步 (密钥推导战)
|
||||||
|
1. 候选密钥批量解: mpdc-16B / ga2 / turing.dat / sha256(materials) / 13B-nonce
|
||||||
|
2. 13B 头语义解码 → nonce→key 关系 (R24 已证: 同 nonce 对 13B 头相同!)
|
||||||
|
3. ga k209202-16B 块 = 设备材料块加密器 (状态补全后 = 主密钥源!)
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ public class TuringProbe {
|
|||||||
/** 全量补 JUMP_SLOT 导入槽 (懒绑定未解析) */
|
/** 全量补 JUMP_SLOT 导入槽 (懒绑定未解析) */
|
||||||
void patchImports(com.github.unidbg.Module m) {
|
void patchImports(com.github.unidbg.Module m) {
|
||||||
int ok = 0, miss = 0;
|
int ok = 0, miss = 0;
|
||||||
|
try {
|
||||||
|
long relroStart = m.base + 0x45648;
|
||||||
|
emulator.getMemory().mprotect(relroStart, 0x3440, 7); // RELRO -> RWX
|
||||||
|
} catch (Throwable t) {}
|
||||||
|
try {
|
||||||
|
long bss = m.base + 0x48a88;
|
||||||
|
emulator.getMemory().mprotect(bss, 0x800, 7);
|
||||||
|
} catch (Throwable t) {}
|
||||||
for (int i = 0; i < IMPORT_SLOTS.length; i++) {
|
for (int i = 0; i < IMPORT_SLOTS.length; i++) {
|
||||||
String name = IMPORT_NAMES[i];
|
String name = IMPORT_NAMES[i];
|
||||||
try {
|
try {
|
||||||
@@ -392,6 +400,96 @@ public class TuringProbe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** etest2: 直调 sub_2E164 加密链 (deflate+XTEA) — (in, len, &out, &outlen, key@3DF88) */
|
||||||
|
void etest2(String blobPath) {
|
||||||
|
try {
|
||||||
|
DalvikModule dm = vm.loadLibrary(new File("/Users/yml/codes/douyu_login_py/evidence/dfp_live/libturingmfa.so"), false);
|
||||||
|
com.github.unidbg.Module m = dm.getModule();
|
||||||
|
long gapBase = m.base + 0x45000;
|
||||||
|
try { emulator.getMemory().mmap2(gapBase, 0x648, 7, 0x32, -1, 0); } catch (Throwable t) {}
|
||||||
|
byte[] blob = new java.io.FileInputStream(blobPath).readAllBytes();
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer inPtr = emulator.getMemory().mmap(blob.length + 32, 7);
|
||||||
|
for (int i=0;i<blob.length;i++) inPtr.setByte(i, blob[i]);
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer outSlot = emulator.getMemory().mmap(16, 7);
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer lenSlot = emulator.getMemory().mmap(16, 7);
|
||||||
|
long keyPtr = m.base + 0x3DF88;
|
||||||
|
System.out.println("[TP] etest2 ENC(deflate+XTEA) in=" + blob.length);
|
||||||
|
Number ret = m.callFunction(emulator, 0x2E164, inPtr.peer, blob.length, outSlot.peer, lenSlot.peer, keyPtr);
|
||||||
|
System.out.println("[TP] etest2 ret=" + ret);
|
||||||
|
long outAddr = outSlot.getLong(0); long outLen = lenSlot.getLong(0);
|
||||||
|
System.out.println("[TP] etest2 cipher=" + Long.toHexString(outAddr) + " len=" + outLen);
|
||||||
|
if (outAddr != 0 && outLen > 0 && outLen < 300000) {
|
||||||
|
byte[] c = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, outAddr).getByteArray(0, (int) outLen);
|
||||||
|
java.io.FileOutputStream fos = new java.io.FileOutputStream("/tmp/mfa_enc_out.bin"); fos.write(c); fos.close();
|
||||||
|
System.out.println("[TP] etest2 cipher head: " + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(c, Math.min(c.length, 32))));
|
||||||
|
// 解密对偶: sub_252EC (XTEA-dec + len) - 直接调用
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer cPtr = emulator.getMemory().mmap(c.length + 8, 7);
|
||||||
|
for (int i=0;i<c.length;i++) cPtr.setByte(i, c[i]);
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer doutSlot = emulator.getMemory().mmap(16, 7);
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer dlenSlot = emulator.getMemory().mmap(16, 7);
|
||||||
|
Number dr = m.callFunction(emulator, 0x252EC, cPtr.peer, c.length, doutSlot.peer, dlenSlot.peer, keyPtr);
|
||||||
|
long dAddr = doutSlot.getLong(0); long dLen = dlenSlot.getLong(0);
|
||||||
|
System.out.println("[TP] etest2 dec252EC ret=" + dr + " dout=" + dLen);
|
||||||
|
if (dAddr != 0 && dLen > 0 && dLen < 300000) {
|
||||||
|
byte[] d = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, dAddr).getByteArray(0, (int) dLen);
|
||||||
|
java.io.FileOutputStream fos2 = new java.io.FileOutputStream("/tmp/mfa_dec_out.bin"); fos2.write(d); fos2.close();
|
||||||
|
// inflate 对照 (sub_34A00 = java inflate 等价)
|
||||||
|
java.util.zip.Inflater inf = new java.util.zip.Inflater();
|
||||||
|
inf.setInput(d);
|
||||||
|
byte[] buf = new byte[300000];
|
||||||
|
int got = inf.inflate(buf);
|
||||||
|
System.out.println("[TP] etest2 inflate got=" + got + " match=" + java.util.Arrays.equals(java.util.Arrays.copyOf(buf, got), blob));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[TP] etest2 EXC " + t);
|
||||||
|
t.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** etest3: Java deflate -> 真 sub_25234 XTEA 封装 —— 验证捕获格式 */
|
||||||
|
void etest3(String blobPath) {
|
||||||
|
try {
|
||||||
|
DalvikModule dm = vm.loadLibrary(new File("/Users/yml/codes/douyu_login_py/evidence/dfp_live/libturingmfa.so"), false);
|
||||||
|
com.github.unidbg.Module m = dm.getModule();
|
||||||
|
long gapBase = m.base + 0x45000;
|
||||||
|
try { emulator.getMemory().mmap2(gapBase, 0x648, 7, 0x32, -1, 0); } catch (Throwable t) {}
|
||||||
|
patchImports(m);
|
||||||
|
byte[] blob = new java.io.FileInputStream(blobPath).readAllBytes();
|
||||||
|
// Java 标准 deflate (zlib!)
|
||||||
|
java.util.zip.Deflater def = new java.util.zip.Deflater(6, true); // raw? 试默认 zlib
|
||||||
|
def.setInput(blob); def.finish();
|
||||||
|
byte[] buf = new byte[blob.length * 2 + 64];
|
||||||
|
int dn = def.deflate(buf);
|
||||||
|
byte[] z = java.util.Arrays.copyOf(buf, dn);
|
||||||
|
System.out.println("[TP] etest3 deflate " + blob.length + "->" + dn + " head=" + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(z, Math.min(z.length, 12))));
|
||||||
|
// 真 sub_25234 (XTEA 封装!)
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer inPtr = emulator.getMemory().mmap(z.length + 32, 7);
|
||||||
|
for (int i=0;i<z.length;i++) inPtr.setByte(i, z[i]);
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer outSlot = emulator.getMemory().mmap(16, 7);
|
||||||
|
com.github.unidbg.pointer.UnidbgPointer lenSlot = emulator.getMemory().mmap(16, 7);
|
||||||
|
Number ret = m.callFunction(emulator, 0x25234, inPtr.peer, z.length, outSlot.peer, lenSlot.peer, m.base + 0x3DF88);
|
||||||
|
long outAddr = outSlot.getLong(0); long outLen = lenSlot.getLong(0);
|
||||||
|
System.out.println("[TP] etest3 XTEA-wrap ret=" + ret + " len=" + outLen);
|
||||||
|
// 验证 GOT 关键槽
|
||||||
|
for (long s : new long[]{0x45978, 0x45980, 0x45988, 0x45990, 0x45968, 0x45970}) {
|
||||||
|
long v = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, m.base + s).getLong(0);
|
||||||
|
System.out.println("[TP] GOT 0x" + Long.toHexString(s) + " = " + (v == 0 ? "ZERO!!" : Long.toHexString(v)));
|
||||||
|
}
|
||||||
|
if (outAddr != 0 && outLen > 0) {
|
||||||
|
byte[] c = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, outAddr).getByteArray(0, (int) outLen);
|
||||||
|
java.io.FileOutputStream fos = new java.io.FileOutputStream("/tmp/mfa_xt ea.bin".replace(" ",""));
|
||||||
|
fos.write(c); fos.close();
|
||||||
|
System.out.println("[TP] etest3 cipher head: " + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(c, Math.min(c.length, 40))));
|
||||||
|
byte[] captured = java.util.Arrays.copyOfRange(new java.io.FileInputStream("/tmp/cipher_only.bin").readAllBytes(), 0, Math.min(c.length, 40));
|
||||||
|
System.out.println("[TP] etest3 captured hd: " + java.util.HexFormat.of().formatHex(captured));
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[TP] etest3 EXC " + t);
|
||||||
|
t.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** gacall: ga 注册原生直调 — k209202([B)[B @0x219d8 */
|
/** gacall: ga 注册原生直调 — k209202([B)[B @0x219d8 */
|
||||||
void gacall(String blobPath) {
|
void gacall(String blobPath) {
|
||||||
try {
|
try {
|
||||||
@@ -470,6 +568,8 @@ public class TuringProbe {
|
|||||||
TuringProbe p = new TuringProbe();
|
TuringProbe p = new TuringProbe();
|
||||||
String which = args.length > 0 ? args[0] : "both";
|
String which = args.length > 0 ? args[0] : "both";
|
||||||
p.preResolveClasses();
|
p.preResolveClasses();
|
||||||
|
if (which.equals("etest3")) { p.etest3(args[1]); return; }
|
||||||
|
if (which.equals("etest2")) { p.etest2(args[1]); return; }
|
||||||
if (which.equals("gacall2")) { p.gacall2(args[1]); return; }
|
if (which.equals("gacall2")) { p.gacall2(args[1]); return; }
|
||||||
if (which.equals("gacall")) { p.gacall(args[1]); return; }
|
if (which.equals("gacall")) { p.gacall(args[1]); return; }
|
||||||
if (which.equals("etest")) { p.etest(args[1]); return; }
|
if (which.equals("etest")) { p.etest(args[1]); return; }
|
||||||
|
|||||||
Reference in New Issue
Block a user