diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index cd9ba14..754943d 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -1446,3 +1446,37 @@ full: 7c da | 11 | 69 c9 f5 28 9c 4d 46 | 95 6f 21 | 10 bb 80 1. sub_1BAD8/1AA64 (编码上下文!) — 请求密钥路径 2. b209202 a6=1 harness 全调用 (喂捕获密文 → 解码输出!) 3. 或: 版本字节 01/11 的密钥变体 + +## §11.61 R30: 💎💎 JAVA 侧全逆 — 请求 XXTEA + 会话密钥架构 + Lyra TLV 格式 (2026-08-29) + +### Cprivate.java = 请求加密器 (jadx 直读!) +```java +// b(data, key) = 加密 (XXTEA!): +// a(key) = key.length > 16 ? MD5(key) : key ← 密钥变换! +// words; v[last] = data.length ← 尾字=明文长度! +// XXTEA: sum -= 0x9E3779B9; e = (sum>>>2)&3; k[(i&3)^e]! ← 与 native sub_24D58 同构! +// a(data, key) = 解密 (140行!): 逆循环 + 长度校验 (i11<=i3<<2!) +``` +- **KEY 变换确认: >16B → MD5; 16B 内 → 原样** + +### 密钥架构 (Perseus.java + Guava.java!) +- **密钥 = SecureRandom 16 字符 [A-Za-z0-9] (62^16!)** — 内存字符串 guava.c! +- RSA-encrypt(hex(key), 内嵌公钥 MIIBI...) → 帧1 上送服务器 (guava.b 信封!) +- "EP_TuringMM" 标识串 + Guava{seq, envelope, KEY-STRING, sessionId, expire} +- 请求密文 key = guava.c 的 UTF-8 字节 (16B 不触发 MD5!) + +### Lyra/Lynx TLV 格式 (请求结构!) +- 帧头 = (type<<4)|tag; type==15 → 扩展 tag 下一字节! +- type: 0=1B 1=2B 2=4B 3=8B 4=4B 5=8B 6=lenB+str 7=4Blen+bytes 8/9=集合 10=map 11=END +- 请求 = 帧10(配置) + 帧11(CIPHER 字段2!) — 捕获密文 = Cprivate.b(序列化事件, session-key!) +- 13B 头 decode 进行中 (first: '7c' = type7-tagC bytes!) + +### 结论 +- **捕获密文 = Java Cprivate.a(data, 16字符会话密钥) 可解 — 密钥在 app 内存 (guava.c)!** +- 静态/哈希密钥全错的根因: 每次会话随机 16 字符! +- 纯代码铸造 = 伪造会话: RSA 信封自己签不了 → 但可 frida 内存取 key! + +### 下一步 (R31) +1. frida 抓 guava.c (16字符) — Java 层单点 hook (Perseus.a 本地串 / Guava 实例) +2. 用真 key 跑 Cprivate.a(捕获密文) = 明文事件! +3. Lynx 全解码器 (13B 头语义 + 帧结构) diff --git a/evidence/dfp_live/turingga.so.id0 b/evidence/dfp_live/turingga.so.id0 index 7346e6a..e29cf31 100644 Binary files a/evidence/dfp_live/turingga.so.id0 and b/evidence/dfp_live/turingga.so.id0 differ diff --git a/evidence/dfp_live/turingga.so.id1 b/evidence/dfp_live/turingga.so.id1 index cc97b32..1892e89 100644 Binary files a/evidence/dfp_live/turingga.so.id1 and b/evidence/dfp_live/turingga.so.id1 differ diff --git a/evidence/dfp_live/turingga.so.id2 b/evidence/dfp_live/turingga.so.id2 index f7eb68b..c96a0e5 100644 Binary files a/evidence/dfp_live/turingga.so.id2 and b/evidence/dfp_live/turingga.so.id2 differ diff --git a/evidence/dfp_live/turingga.so.nam b/evidence/dfp_live/turingga.so.nam index 5d8fbf9..47641d9 100644 Binary files a/evidence/dfp_live/turingga.so.nam and b/evidence/dfp_live/turingga.so.nam differ diff --git a/evidence/dfp_live/turingga.so.til b/evidence/dfp_live/turingga.so.til index c988582..b561c3d 100644 Binary files a/evidence/dfp_live/turingga.so.til and b/evidence/dfp_live/turingga.so.til differ diff --git a/tools/frida/_b2_run.sh b/tools/frida/_b2_run.sh new file mode 100755 index 0000000..707262f --- /dev/null +++ b/tools/frida/_b2_run.sh @@ -0,0 +1,3 @@ +export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" +cd /Users/yml/codes/douyu_login_py/tools/frida +CAP_HOOK=hook_b209202_args.js CAP_HOOK_DELAY=8 CAP_WINDOW=150 CAP_MAX_ATTEMPTS=3 CAP_OUT_DIR=/tmp/b2cap python3 run_capture.py 2>&1 | tail -40 diff --git a/tools/frida/hook_b209202_args.js b/tools/frida/hook_b209202_args.js new file mode 100644 index 0000000..27884e5 --- /dev/null +++ b/tools/frida/hook_b209202_args.js @@ -0,0 +1,50 @@ +// hook ga 的 b209202@0x20110 + k209202@0x219d8 — 抓 JNI 实参 (moderate - 只有 2 个 attach!) +function pad(h, n){ h=h||'0'; return h.length>=n?h:('0'.repeat(n-h.length)+h); } +setTimeout(function(){ + var mod = Process.findModuleByName('libturingga.so'); + if (!mod) { console.log('[B2] no ga module'); return; } + var base = mod.base; + var hooks = [ + {name:'b209202', off:0x20110, nargs:7}, + {name:'k209202', off:0x219d8, nargs:4}, + ]; + hooks.forEach(function(h){ + var addr = base.add(h.off); + try { + Interceptor.attach(addr, { + onEnter: function(args){ + this.t = Date.now(); + var regs = 'a0=' + pad(args[0].toString(16)) + ' a1=' + pad(args[1].toString(16)); + for (var i=h.nargs-2; i ' + pad(ret.toString(16)) + ' dt=' + (Date.now()-this.t) + 'ms'); + } + }); + console.log('[B2] hooked ' + h.name + ' @' + h.off.toString(16)); + } catch(e) { console.log('[B2] hook ' + h.name + ' FAIL ' + e); } + }); + + // 监听 DFPService capture (同 hook_str_magic 的过滤) + var ssl = Process.findModuleByName('libssl.so'); + if (ssl) { + var f = Module.findExportByName('libssl.so', 'SSL_write'); + if (f) { + Interceptor.attach(f, { + onEnter: function(args){ + try { + var len = args[2].toInt32(); + if (len < 50 || len > 60000) return; + var b = args[1].readByteArray(Math.min(len, 300)); + var s = Array.from(new Uint8Array(b)).map(function(x){return ('0'+x.toString(16)).slice(-2)}).join(''); + if (s.indexOf('7cda') >= 0 || s.indexOf('dfpReport') >= 0 || s.indexOf('hyudbwebuif') >= 0) { + console.log('[B2] SSL_write len=' + len); + } + } catch(e){} + } + }); + console.log('[B2] ssl-write hook ok'); + } + } +}, 1000); diff --git a/tools/frida/hook_cprivate_b.js b/tools/frida/hook_cprivate_b.js new file mode 100644 index 0000000..ba268e0 --- /dev/null +++ b/tools/frida/hook_cprivate_b.js @@ -0,0 +1,22 @@ +// 轻量 Java hook: Cprivate.b(byte[] data, byte[] key) — 抓明文+密钥 (单点 hook, 不扫内存!) +setTimeout(function(){ + try { + Java.perform(function(){ + try { + var Cpr = Java.use('com.tencent.turingfd.sdk.ams.ga.Cprivate'); + var Melon = Java.use('com.tencent.turingfd.sdk.ams.ga.Melon'); + Cpr.b.overload('[B','[B').implementation = function(a, b){ + try { + var plainHex = Melon.a(a); // a() = byte[] -> hex? (Melon.a(byte[]) = hex!) + var keyHex = Melon.a(b); + console.log('[CPR] Cprivate.b data-len=' + a.length + ' key-len=' + b.length); + console.log('[CPR] key=' + keyHex); + console.log('[CPR] data-head=' + (plainHex ? plainHex.substring(0, 160) : 'null')); + } catch(e) { console.log('[CPR] dump-exc ' + e); } + return this.b(a, b); + }; + console.log('[CPR] hooked Cprivate.b'); + } catch(e2) { console.log('[CPR] no Cprivate: ' + e2); } + }); + } catch(e) { console.log('[CPR] perm-exc ' + e); } +}, 1500); diff --git a/tools/unidbg/hydev/src/hydev/TuringProbe.java b/tools/unidbg/hydev/src/hydev/TuringProbe.java index 6fa28a2..dace369 100644 --- a/tools/unidbg/hydev/src/hydev/TuringProbe.java +++ b/tools/unidbg/hydev/src/hydev/TuringProbe.java @@ -351,6 +351,88 @@ public class TuringProbe { } } + /** kchain: 拆分 k209202 链 — 19378对象 + 24334状态 + Java-deflate + 1F918 XTEA(max=16B输入) */ + void kchain(String keyPath) { + try { + DalvikModule dm = vm.loadLibrary(new File("/Users/yml/codes/douyu_login_py/evidence/dfp_live/turingga.so"), false); + com.github.unidbg.Module gm = dm.getModule(); + try { emulator.getMemory().mmap2(gm.base + 0x48000, 0x720, 7, 0x32, -1, 0); } catch (Throwable t) {} + patchGaImports(gm); + try { dm.callJNI_OnLoad(emulator); } catch (Throwable t) {} + byte[] key = new java.io.FileInputStream(keyPath).readAllBytes(); + // 1) sub_19378 对象 + Number obj = gm.callFunction(emulator, 0x19378); + System.out.println("[TP] kchain obj=" + obj); + // 2) sub_24334(obj, &ptr, &len) + com.github.unidbg.pointer.UnidbgPointer outSlot = emulator.getMemory().mmap(16, 7); + com.github.unidbg.pointer.UnidbgPointer lenSlot = emulator.getMemory().mmap(16, 7); + Number r2 = gm.callFunction(emulator, 0x24334, obj.longValue(), outSlot.peer, lenSlot.peer); + long statePtr = outSlot.getLong(0); long stateLen = lenSlot.getLong(0); + System.out.println("[TP] kchain 24334 ret=" + r2 + " state=" + stateLen); + if (statePtr != 0 && stateLen > 0 && stateLen < 100000) { + byte[] state = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, statePtr).getByteArray(0, (int) stateLen); + System.out.println("[TP] kchain state head=" + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(state, Math.min(state.length, 32)))); + // 3) Java deflate (raw ~ sub_2E904) + java.util.zip.Deflater def2 = new java.util.zip.Deflater(6, true); + def2.setInput(state); def2.finish(); + byte[] zbuf = new byte[state.length * 2 + 64]; + int zn = def2.deflate(zbuf); + byte[] z = java.util.Arrays.copyOf(zbuf, zn); + System.out.println("[TP] kchain deflate " + stateLen + "->" + zn); + // 4) 真 sub_1F918 XTEA-wrap (key = 16B 输入!) + com.github.unidbg.pointer.UnidbgPointer inPtr = emulator.getMemory().mmap(z.length + 32, 7); + for (int i=0;i sa = vm.resolveClass("android/util/SparseArray").newObject(null); + DvmObject dba = vm.resolveClass("[B").newObject(blob); + DvmObject map = vm.resolveClass("java/util/HashMap").newObject(null); + long env = ((com.github.unidbg.pointer.UnidbgPointer) vm.getJNIEnv()).peer; + int hv = Integer.parseInt(System.getenv().getOrDefault("TP_INT", "0")); + int hsa = vm.addLocalObject(sa); + int hba = vm.addLocalObject(dba); + int hmap = vm.addLocalObject(map); + System.out.println("[TP] gacallb b209202@0x20110 in=" + blob.length + " a6=" + hv); + Number ret = gm.callFunction(emulator, 0x20110, env, 0, hsa, hba, hmap, hv); + System.out.println("[TP] gacallb ret=" + ret); + } catch (Throwable t) { + System.out.println("[TP] gacallb EXC " + t); + t.printStackTrace(System.out); + } + } + /** gaxtest: 直调 ga sub_1F1CC (XTEA) — (buf, ±n, key@5788) */ void gaxtest(String blobPath) { try { @@ -635,6 +717,8 @@ public class TuringProbe { TuringProbe p = new TuringProbe(); String which = args.length > 0 ? args[0] : "both"; p.preResolveClasses(); + if (which.equals("kchain")) { p.kchain(args[1]); return; } + if (which.equals("gacallb")) { p.gacallb(args[1]); return; } if (which.equals("gaxtest")) { p.gaxtest(args[1]); return; } if (which.equals("etest3")) { p.etest3(args[1]); return; } if (which.equals("etest2")) { p.etest2(args[1]); return; }