From a5df6df8556c34709fd3ef7a82801f2aa5813d2b Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 07:42:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20=E2=9C=85=20XXTEA=E5=8F=98?= =?UTF-8?q?=E4=BD=93=E5=8A=A0=E5=AF=86=E4=BE=A7=E5=9B=9E=E7=8E=AF=E9=AA=8C?= =?UTF-8?q?=E8=AF=81+ga=E5=85=A8=E8=A1=A814=E6=96=B9=E6=B3=95=20(R25.5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sub_24D58 正参数=加密/负参数=解密, 44B回环100%还原, key@3DF88确认 - ga JNI表14方法全解 (j209202=取DID! k209202=传输加密入口@0x219d8) - 判断: 捕获密文=ga传输层 (非mfa内部序列化), 需ga IDA导出或运行时状态 --- docs/HUYA_HDID_ALGORITHM_GEN.md | 29 ++++ tools/unidbg/hydev/src/hydev/TuringProbe.java | 158 +++++++++++++++++- 2 files changed, 181 insertions(+), 6 deletions(-) diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 17f0cb0..4611218 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -1313,3 +1313,32 @@ dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采 - key 槽验证: 3DF88 结构内偏移 / 或 key 由 config 派生 - b87_ 全链 harness 直调 (喂真实捕获密文 → inflate → 明文!) - a87_@0x25680 = 加密侧 (sub_27DA4 链) — "纯代码铸造" 的核心 + +## §11.56 R25.5: 🎯 XXTEA 变体加密侧回环验证成功 — 3DF88 密钥确认 (2026-08-29) + +### harness 直调 sub_24D58 回环实验 +- `sub_24D58(buf, +n, key@3DF88)` = **加密** (a2 正参数分支) +- `sub_24D58(buf, -n, key@3DF88)` = **解密** (负参数分支) +- 44B 已知明文 → ENC → DEC → **100% 还原** (roundtrip match=True!) +- **key@0x3DF88 = 真密钥**: d2785ef1-c4c23330-25a9c8ba-3a867e67 (u32 LE) +- 三层后端对比: Unicorn2 直跑 XXTEA 无原子指令 ✓ (dynarmic 在 2E250 链 JIT 崩) + +### ga (turingga.so) JNI 表全解 — harness 注册后读表 @0x4d1a0 +14 方法: a209202(SparseArray,Context,Map,Map,I)@0x&1fcb8 b209202(SparseArray,[B,Map,I)@0x20110 +c209202(SparseArray,Context)@0x20574 d209202(SparseArray,Context,I)@0x20688 e209202(SparseArray,Context,Map,I)@0x20984 +f209202(SparseArray,[B,I)@0x20fc4 g209202(SparseArray,Context,Map,I)@0x211e8 h209202(...)@0x21468 +i209202(SparseArray,Context,Obj,Obj,Obj)@0x21624 **j209202()String@0x21948 (取DID!)** +**k209202([B)[B@0x219d8 (字节进出 = 传输加密!)** l209202(InvocationHandler,AtomicReference,ClassLoader)@0x42d50 +m209202(SparseArray,Context,Map)@0x21bdc n209202(SparseArray,[B,I,String,J)@0x43900 +- ga JNI_OnLoad @0x1faf0 (dynsym!) — 注册 = JNIEnv vtable-215 + 表@0x4d1a0 + 14计数 +- ga 传输加密入口 k209202@0x219d8: strncpy@plt + 内部链 (0x19378/0x13eb8×2/0x1c640×2/0x24334/0x19258) + +### 现状判断 +- **捕获密文 ≠ mfa 的 b87_ 内部序列化** (sub_2E250 全偏移网格拒绝 = key 或格式不匹配) +- **捕获密文 = ga 传输层加密** (k209202 家族) — ga 调用返回 -1 = SDK 全局状态未初始化 +- mfa 侧 a87_@0x25680 = 事件收集序列化 (sub_27DA4: timing-tags + 0x11-version-key + sub_2C76C 收集器) — 非传输加密 +- 捕获密文 10B 魔数 7cda... = 运行时合成 (无反编译常量) + +### 下一步 (两条关键路径) +1. **用户导出的 turingga.so IDA export** → k209202@0x219d8 反编译 → 传输加密链静态全解 (同 mfa 方法论) +2. harness 继续喂 ga 运行时状态 (0x48XXX 构建器等价物) → k209202 真调用 (同 b87_ -1 问题) diff --git a/tools/unidbg/hydev/src/hydev/TuringProbe.java b/tools/unidbg/hydev/src/hydev/TuringProbe.java index 4b64e4c..cc4f48d 100644 --- a/tools/unidbg/hydev/src/hydev/TuringProbe.java +++ b/tools/unidbg/hydev/src/hydev/TuringProbe.java @@ -54,7 +54,11 @@ public class TuringProbe { TuringProbe() { com.github.unidbg.arm.backend.BackendFactory backend; try { - backend = new com.github.unidbg.arm.backend.DynarmicFactory(false); + if (System.getenv().containsKey("TP_UNI2")) { + backend = new com.github.unidbg.arm.backend.Unicorn2Factory(true); + } else { + backend = new com.github.unidbg.arm.backend.DynarmicFactory(false); + } } catch (Throwable t) { backend = new com.github.unidbg.arm.backend.Unicorn2Factory(true); } emulator = AndroidEmulatorBuilder.for64Bit() .setProcessName("com.duowan.kiwi") @@ -113,8 +117,38 @@ public class TuringProbe { switch (which) { case "ga": { DalvikModule dm = vm.loadLibrary(new File(base + "turingga.so"), false); - dm.callJNI_OnLoad(emulator); - System.out.println("[TP] libturingga JNI_OnLoad OK"); + com.github.unidbg.Module gm = dm.getModule(); + // ga 的 GOT 页: LOAD2 vaddr 0x48720 (页0x48000), 补 0x48000-0x48720 间隙 + try { + long ggap = gm.base + 0x48000; + emulator.getMemory().mmap2(ggap, 0x720, 7, 0x32, -1, 0); + com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, ggap).setMemory(0, 0x720, (byte) 0); + System.out.println("[TP] ga gap mapped @" + Long.toHexString(ggap)); + } catch (Throwable t2) { System.out.println("[TP] ga gap EXC " + t2); } + // ga 的 JNI_OnLoad 日志桩区 (先试 0xca5c=log 调用) + try { + for (long stubOf : new long[]{0xca5c}) { + com.github.unidbg.pointer.UnidbgPointer lp = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, gm.base + stubOf); + lp.setInt(0, 0x52800000); + lp.setInt(4, 0xd65f03c0); + } + System.out.println("[TP] ga log stash ok"); + } catch (Throwable t3) { System.out.println("[TP] ga stash EXC " + t3); } + try { + dm.callJNI_OnLoad(emulator); + System.out.println("[TP] libturingga JNI_OnLoad OK"); + } catch (Throwable t4) { System.out.println("[TP] ga onLoad EXC: " + t4); regDump(); } + // 读注册表 @0x4d1a0 (14 项) + try { + for (int i = 0; i < 14; i++) { + com.github.unidbg.pointer.UnidbgPointer cell = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, gm.base + 0x4d1a0 + i * 24); + long nptr = cell.getLong(0), sptr = cell.getLong(8), fptr = cell.getLong(16); + String name = "?", sig = "?"; + if (nptr != 0) { try { name = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, nptr).getString(0); } catch (Throwable t) {} } + if (sptr != 0) { try { sig = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, sptr).getString(0); } catch (Throwable t) {} } + System.out.println("[TP] ga-m[" + i + "] " + name + " " + sig + " -> 0x" + Long.toHexString(fptr)); + } + } catch (Throwable t5) { System.out.println("[TP] ga table read EXC " + t5); } break; } case "mfa": { @@ -283,15 +317,24 @@ public class TuringProbe { 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(); + boolean enc = blobPath.startsWith("ENC:"); + if (enc) blobPath = blobPath.substring(4); + // 支持 offset 参数: blobPath 可含 ":off" + int off = 0; + if (blobPath.contains(":")) { + String[] sp = blobPath.split(":"); + blobPath = sp[0]; off = Integer.parseInt(sp[1]); + } + byte[] blobAll = new java.io.FileInputStream(blobPath).readAllBytes(); + byte[] blob = java.util.Arrays.copyOfRange(blobAll, off, blobAll.length); // 密文词数 int n = blob.length / 4; com.github.unidbg.pointer.UnidbgPointer vp = emulator.getMemory().mmap(blob.length + 32, 7); for (int i=0;i byteArg = vm.resolveClass("[B").newObject(blob); + System.out.println("[TP] gacall k209202 in=" + blob.length + "B"); + DvmObject ret = gaCls.callStaticJniMethodObject(emulator, "k209202_2A742FA224BA8A2A", byteArg); + if (ret != null && ret.getValue() instanceof byte[]) { + byte[] out = (byte[]) ret.getValue(); + System.out.println("[TP] gacall out=" + out.length + "B head=" + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(out, Math.min(out.length, 48)))); + java.io.FileOutputStream fos = new java.io.FileOutputStream("/tmp/ga_k_out.bin"); fos.write(out); fos.close(); + } else { + System.out.println("[TP] gacall ret=" + ret); + } + } catch (Throwable t) { + System.out.println("[TP] gacall EXC " + t); + t.printStackTrace(System.out); + } + } + + /** gacall2: 直调 ga k209202@0x219d8 (JNI C 参数 + 对象 hash) */ + void gacall2(String blobPath) { + 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 { + long ggap = gm.base + 0x48000; + emulator.getMemory().mmap2(ggap, 0x720, 7, 0x32, -1, 0); + com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, ggap).setMemory(0, 0x720, (byte) 0); + } catch (Throwable t) {} + try { dm.callJNI_OnLoad(emulator); } catch (Throwable t) {} + byte[] blob = new java.io.FileInputStream(blobPath).readAllBytes(); + DvmObject byteObj = vm.resolveClass("[B").newObject(blob); + long env = ((com.github.unidbg.pointer.UnidbgPointer) vm.getJNIEnv()).peer; + long fn = gm.base + 0x219d8; + System.out.println("[TP] gacall2 k209202@" + Long.toHexString(fn) + " in=" + blob.length); + Number ret = gm.callFunction(emulator, 0x219d8, env, 0, byteObj.hashCode()); + System.out.println("[TP] gacall2 ret-hash=" + ret); + try { + DvmObject out = vm.getObject(ret.intValue()); + Object v = out.getValue(); + if (v instanceof byte[]) { + byte[] outb = (byte[]) v; + System.out.println("[TP] gacall2 out=" + outb.length + "B head=" + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(outb, Math.min(outb.length, 48)))); + java.io.FileOutputStream fos = new java.io.FileOutputStream("/tmp/ga_k_out.bin"); fos.write(outb); fos.close(); + } else { + System.out.println("[TP] gacall2 out-val=" + v); + } + } catch (Throwable t) { System.out.println("[TP] gacall2 out-EXC " + t); } + } catch (Throwable t) { + System.out.println("[TP] gacall2 EXC " + t); + t.printStackTrace(System.out); + } + } + public static void main(String[] args) { TuringProbe p = new TuringProbe(); String which = args.length > 0 ? args[0] : "both"; p.preResolveClasses(); + 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; } if (which.equals("xtest")) { p.xtest(args[1], args.length > 2 ? args[2] : null); return; } if (which.equals("call")) { p.callEnc(args.length > 1 ? args[1] : "/tmp/dfp_str2.bin");