feat(huya): XXTEA变体加密侧回环验证+ga全表14方法 (R25.5)

- sub_24D58 正参数=加密/负参数=解密, 44B回环100%还原, key@3DF88确认
- ga JNI表14方法全解 (j209202=取DID! k209202=传输加密入口@0x219d8)
- 判断: 捕获密文=ga传输层 (非mfa内部序列化), 需ga IDA导出或运行时状态
This commit is contained in:
yml2213
2026-08-29 07:42:56 +08:00
parent 44948277d4
commit a5df6df855
2 changed files with 181 additions and 6 deletions
+29
View File
@@ -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 问题)
+152 -6
View File
@@ -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<blob.length;i++) vp.setByte(i, blob[i]);
// key @ 3DF88 (LOAD-1 identity fileoff==vaddr)
long keyPtr = m.base + 0x3DF88;
System.out.println("[TP] xtest calling sub_24D58 n=" + n + " key@" + Long.toHexString(keyPtr));
Number ret = m.callFunction(emulator, 0x24D58, vp.peer, -n, keyPtr);
System.out.println("[TP] xtest " + (enc ? "ENC" : "DEC") + " sub_24D58 n=" + n + " key@" + Long.toHexString(keyPtr));
Number ret = m.callFunction(emulator, 0x24D58, vp.peer, enc ? n : -n, keyPtr);
System.out.println("[TP] xtest ret=" + ret);
byte[] out = vp.getByteArray(0, Math.min(blob.length, 64));
System.out.println("[TP] xtest out head: " + java.util.HexFormat.of().formatHex(out));
@@ -307,10 +350,113 @@ public class TuringProbe {
}
}
/** etest: 直调 sub_2E250 完整封套解析 (XXTEA + len 提取) */
void etest(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] etest sub_2E250 in=" + blob.length + "B key@" + Long.toHexString(keyPtr));
Number ret = m.callFunction(emulator, 0x2E250, inPtr.peer, blob.length, outSlot.peer, lenSlot.peer, keyPtr);
System.out.println("[TP] etest ret=" + ret);
long outAddr = outSlot.getLong(0);
long outLen = lenSlot.getLong(0);
System.out.println("[TP] etest out=" + Long.toHexString(outAddr) + " len=" + outLen);
if (outAddr != 0 && outLen > 0 && outLen < 200000) {
byte[] out = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, outAddr).getByteArray(0, (int) outLen);
System.out.println("[TP] etest plain head: " + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(out, Math.min(out.length, 64))));
// 尝试 inflate (sub_34A00 等同)
java.util.zip.Inflater inf = new java.util.zip.Inflater();
inf.setInput(out);
byte[] buf = new byte[200000];
int got = inf.inflate(buf);
System.out.println("[TP] etest inflate got=" + got + " head=" + java.util.HexFormat.of().formatHex(java.util.Arrays.copyOf(buf, Math.min(got, 48))));
}
} catch (Throwable t) {
System.out.println("[TP] etest EXC " + t);
t.printStackTrace(System.out);
}
}
/** gacall: ga 注册原生直调 — k209202([B)[B @0x219d8 */
void gacall(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) {}
// 注册 (onLoad 部分成功)
try { dm.callJNI_OnLoad(emulator); } catch (Throwable t) { System.out.println("[TP] ga onLoad EXC " + t); }
byte[] blob = new java.io.FileInputStream(blobPath).readAllBytes();
DvmClass gaCls = vm.resolveClass("com/tencent/turingfd/sdk/ams/ga/TNative$aa");
DvmObject<?> 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");