From 0ef95a5c0ec3930e1020f9c761b7f43d57d06ba5 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 07:48:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20=F0=9F=8E=AF=20=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E9=93=BE=20deflate+XTEA=20=E9=97=AD=E7=8E=AF?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E6=88=90=E5=8A=9F=20(R27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sub_2E164=deflate+sub_25234=XTEA封套 (pad+4Blen+24D58加密) 源链闭环 - harness: Java-deflate+真25234 产出52B密文 - 关键坑: GOT RELRO只读页 mprotect后导入补丁才生效 (所有-1的真相!) - 捕获=[13B头][XTEA-wrap(key=设备派生)]; 密钥推导=下一步 --- docs/HUYA_HDID_ALGORITHM_GEN.md | 30 ++++++ tools/unidbg/hydev/src/hydev/TuringProbe.java | 100 ++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 588ffff..c6ded87 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -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) 2. harness 继续: mfa 单例 34D68 全量 ctor (48BD8) + 各字段填充 → 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 块 = 设备材料块加密器 (状态补全后 = 主密钥源!) diff --git a/tools/unidbg/hydev/src/hydev/TuringProbe.java b/tools/unidbg/hydev/src/hydev/TuringProbe.java index 190f6bd..8721d02 100644 --- a/tools/unidbg/hydev/src/hydev/TuringProbe.java +++ b/tools/unidbg/hydev/src/hydev/TuringProbe.java @@ -25,6 +25,14 @@ public class TuringProbe { /** 全量补 JUMP_SLOT 导入槽 (懒绑定未解析) */ void patchImports(com.github.unidbg.Module m) { 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++) { String name = IMPORT_NAMES[i]; 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 真 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 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 */ void gacall(String blobPath) { try { @@ -470,6 +568,8 @@ public class TuringProbe { TuringProbe p = new TuringProbe(); String which = args.length > 0 ? args[0] : "both"; 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("gacall")) { p.gacall(args[1]); return; } if (which.equals("etest")) { p.etest(args[1]); return; }