From dc109c5dba8324c9c2cadde9146330f45f666956 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 07:07:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20turing=20JNI=E8=A1=A8=E8=A7=A3?= =?UTF-8?q?=E5=87=BA+b87=5F@0x25c98=20(R21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - libturingmfa JNI 表: b87_(SparseArray,[B,Map,I)impl@0x25c98 + f87_ — 加密核心入口 - ELF 间隙洞察: LOAD-2 vaddr未4K对齐->unidbg映射崩因; mmap2补映射过导出 - TuringProbe.java: 装库+预注册类+gap补映射+直调框架(封送待办) - 路线: 直调b87_喂真实blob->SparseArray明文; ga侧JNI表同法 --- docs/HUYA_HDID_ALGORITHM_GEN.md | 27 +++ tools/frida/capture_20260829_065846.jsonl | 1 + tools/frida/capture_20260829_070028.jsonl | 1 + tools/frida/capture_20260829_070141.jsonl | 43 +++++ tools/frida/capture_20260829_070247.jsonl | 1 + tools/frida/hook_regnatives.js | 48 ++++++ tools/unidbg/hydev/src/hydev/TuringProbe.java | 163 ++++++++++++++++++ 7 files changed, 284 insertions(+) create mode 100644 tools/frida/capture_20260829_065846.jsonl create mode 100644 tools/frida/capture_20260829_070028.jsonl create mode 100644 tools/frida/capture_20260829_070141.jsonl create mode 100644 tools/frida/capture_20260829_070247.jsonl create mode 100644 tools/frida/hook_regnatives.js create mode 100644 tools/unidbg/hydev/src/hydev/TuringProbe.java diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 5db60b5..5bfe6ab 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -1194,3 +1194,30 @@ dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采 ### 工程交付 - 触发→捕获→存档 全自动化 (清状态+spawn+str_magic+杀) — 每次 ~40s - evidence/dfp_live/: 请求全文 ×5 + 采样集 + 组件 + fresh 状态文件 + +--- + +## §11.51 R21: turing native 静态突破 — JNI 表解出 + harness 装载 (2026-08-29) + +### 💡 重大进展: turing mfa JNI 原生方法表解出 (静态!) +- 从 libturingmfa.so 的 JNI_OnLoad (导出@0x25414) 反汇编 + JNI 方法名串交叉定位 +- **JNI 表 (data.rel.ro @0x45040)**: + - `b87_D2D99D56FECC73FD (Landroid/util/SparseArray;[BLjava/util/Map;I)Landroid/util/SparseArray;` **impl@0x25c98** + - `f87_D2D99D56FECC73FD (SparseArray;[BI)` (impl 邻近) — v87 是加密核心入口族! +- 方法名带版本哈希: D2D99D56FECC73FD = mfa v87 (ga 侧 2A742FA224BA8A2A 同类, 在 turingga.so) + +### ELF 布局洞察 (harness 装载崩因实锤) +- turing 库程序头正常 (LOAD-1 rx 0x0-0x43988, LOAD-2 rw @vaddr0x45648) — 但 **LOAD-2 vaddr 未4K对齐** (0x648) +- 真机按页映射覆盖 0x45000-0x45648 间隙; **unidbg 按精确边界 → 间隙未映射 → JNI_OnLoad 的 GOT 读 (0x45000+#dd0) 必崩** +- harness 补映射 (mmap2 gap) 后过导出调用, 再崩于 turing 共享指针原子操作 (x0 未映射 = 对象模型深度问题) + +### harness 状态 (TuringProbe.java) +- ✅ unidbg 装载 turing 双库 + 预注册 11 个 turing 类 (FindClass OK) +- ✅ gap 补映射 (mmap2@0x45000) +- ⏳ JNI_OnLoad 内 x0 原子崩溃 (turing 自研共享指针 vs harness 对象) +- ⏳ b87_@0x25c98 直调: DvmObject→jobject 封送待办 (addLocalObject int 句柄机制) + +### 下一步路线 (已提前到位) +1. **直调 b87_@0x25c98** (JNI 封送: vaList/int-handle) — 喂真实捕获 blob → 输出 SparseArray = 证据事件 = 明文! +2. JNI_OnLoad x0: 追踪 onLoad 内部分配链 (0x12b6c 原子函数的调用者) +3. ga 侧 JNI 表 (turingga.so 同法解剖) → a209202_ 族入口 diff --git a/tools/frida/capture_20260829_065846.jsonl b/tools/frida/capture_20260829_065846.jsonl new file mode 100644 index 0000000..8d2606f --- /dev/null +++ b/tools/frida/capture_20260829_065846.jsonl @@ -0,0 +1 @@ +{"event": "rn-err", "msg": "no RegisterNatives export"} diff --git a/tools/frida/capture_20260829_070028.jsonl b/tools/frida/capture_20260829_070028.jsonl new file mode 100644 index 0000000..a49c763 --- /dev/null +++ b/tools/frida/capture_20260829_070028.jsonl @@ -0,0 +1 @@ +{"event": "rn-err", "msg": "no RegisterNatives in libart"} diff --git a/tools/frida/capture_20260829_070141.jsonl b/tools/frida/capture_20260829_070141.jsonl new file mode 100644 index 0000000..366068a --- /dev/null +++ b/tools/frida/capture_20260829_070141.jsonl @@ -0,0 +1,43 @@ +{"event": "rn-hooked", "at": "0x72e1abccbc"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 12"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 3"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 18"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 12"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 3"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 18"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 7"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 13"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 12"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 12"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 8"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 11"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 8"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 7"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 11"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 8"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 19"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 7"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 15"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 17"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 22"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 20"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 13"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 18"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 13"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 14"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 24"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 10"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 20"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 18"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 11"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 17"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 11"} +{"event": "rn-err", "msg": "Error: can't decode byte 0x00 in position 19"} diff --git a/tools/frida/capture_20260829_070247.jsonl b/tools/frida/capture_20260829_070247.jsonl new file mode 100644 index 0000000..830d378 --- /dev/null +++ b/tools/frida/capture_20260829_070247.jsonl @@ -0,0 +1 @@ +{"event": "rn-hooked", "at": "0x72e1a37890"} diff --git a/tools/frida/hook_regnatives.js b/tools/frida/hook_regnatives.js new file mode 100644 index 0000000..bfe07b4 --- /dev/null +++ b/tools/frida/hook_regnatives.js @@ -0,0 +1,48 @@ +'use strict'; +function emit(row) { try { send(row); } catch (e) {} } +const seen = {}; +setTimeout(() => { + let addr = null; + try { + for (const m of Process.enumerateModules()) { + if (m.name !== 'libart.so') continue; + const syms = m.enumerateSymbols(); + for (const sm of syms) { + if (sm.name.indexOf('RegisterNatives') >= 0) { addr = sm.address; break; } + } + } + } catch (e) {} + if (addr === null) { emit({ event: 'rn-err', msg: 'no sym' }); return; } + emit({ event: 'rn-hooked', at: addr }); + Interceptor.attach(addr, { + onEnter(args) { + try { + const cls = args[1]; + const methods = args[2]; + const n = args[3].toInt32(); + if (n <= 0 || n > 500) return; + // 读 class 名 (jclass -> 从 vtable 附近读太复杂, 只读方法表) + const rows = []; + for (let i = 0; i < n; i++) { + const off = methods.add(i * 24); + const namePtr = off.readPointer(); + const sigPtr = off.add(8).readPointer(); + const fnPtr = off.add(16).readPointer(); + const mod = Process.findModuleByAddress(fnPtr); + const name = namePtr.readUtf8String(80) || '?'; + const sig = sigPtr.readUtf8String(80) || '?'; + rows.push({ + name: name.length > 60 ? name.slice(0, 60) : name, + sig: sig.length > 50 ? sig.slice(0, 50) : sig, + impl: (mod ? mod.name + '!' + fnPtr.sub(mod.base) : fnPtr.toString()), + }); + } + const key = rows.map(r => r.name + r.impl).join('|'); + if (!seen[key]) { + seen[key] = true; + emit({ event: 'rn-table', n, rows: rows.slice(0, 60) }); + } + } catch (e) { emit({ event: 'rn-err', msg: String(e).slice(0, 120) }); } + }, + }); +}, 1500); diff --git a/tools/unidbg/hydev/src/hydev/TuringProbe.java b/tools/unidbg/hydev/src/hydev/TuringProbe.java new file mode 100644 index 0000000..c61aec2 --- /dev/null +++ b/tools/unidbg/hydev/src/hydev/TuringProbe.java @@ -0,0 +1,163 @@ +package hydev; + +import com.github.unidbg.AndroidEmulator; +import com.github.unidbg.Emulator; +import com.github.unidbg.LibraryResolver; +import com.github.unidbg.linux.android.AndroidEmulatorBuilder; +import com.github.unidbg.linux.android.AndroidResolver; +import com.github.unidbg.linux.android.dvm.*; + +import java.io.File; + +/** + * TuringProbe: unidbg 装载 turing 双库 (libturingga/libturingmfa) + JNI_OnLoad, + * 列出注册的原生方法 -> 定位加密入口偏移。 + * + * 用法: java -cp $CP:out hydev.TuringProbe [ga|mfa|both] + */ +public class TuringProbe { + + private final AndroidEmulator emulator; + private final VM vm; + + TuringProbe() { + com.github.unidbg.arm.backend.BackendFactory backend = new com.github.unidbg.arm.backend.Unicorn2Factory(true); + emulator = AndroidEmulatorBuilder.for64Bit() + .setProcessName("com.duowan.kiwi") + .addBackendFactory(backend) + .build(); + vm = emulator.createDalvikVM(); + vm.setJni(new AbstractJni() {}); + vm.setVerbose(true); + if (!new File("/Users/yml/codes/douyu_login_py/evidence/dfp_live/turingga.so").exists()) { + System.out.println("[TuringProbe] missing libs in evidence/dfp_live/"); + } + } + + /** 预注册 turing 相关类 (JNI FindClass 需要) */ + void preResolveClasses() { + String[] cls = { + "com/tencent/turingfd/sdk/ams/ga/native", + "com/tencent/turingfd/sdk/ams/ga/TNiT$aa", + "com/tencent/turingfd/sdk/ams/ga/TNiT$if", + "com/tencent/turingfd/sdk/ams/ga/Seedless", + "com/tencent/turingfd/sdk/ams/ga/ITuringDID", + "com/tencent/turingfd/sdk/ams/ga/TuringSDK", + "com/tencent/turingfd/sdk/ams/ga/TNative$aa", + "com/tencent/turingfd/sdk/ams/ga/TNative$if", + "com/tencent/turingfd/sdk/ams/ga/Lemon", + "com/tencent/turingfd/sdk/ams/ga/Pineapple", + "android/util/SparseArray", + }; + for (String c : cls) { + try { + vm.resolveClass(c); + System.out.println("[TP] class ok " + c); + } catch (Throwable t) { + System.out.println("[TP] class '" + c + "' EXC " + t); + } + } + } + + void load(String which) { + String base = "/Users/yml/codes/douyu_login_py/evidence/dfp_live/"; + try { + 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"); + break; + } + case "mfa": { + DalvikModule dm = vm.loadLibrary(new File(base + "libturingmfa.so"), false); + com.github.unidbg.Module m = dm.getModule(); + long gapBase = m.base + 0x45000; + try { + // mmap2(addr, len, PROT_READ|WRITE|EXEC=7, MAP_PRIVATE|ANON|FIXED=0x32, -1, 0) + emulator.getMemory().mmap2(gapBase, 0x648, 7, 0x32, -1, 0); + // 清零 + com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, gapBase).setMemory(0, 0x648, (byte) 0); + System.out.println("[TP] gap mapped @" + Long.toHexString(gapBase)); + } catch (Throwable t2) { + System.out.println("[TP] gap map EXC " + t2); + } + try { + dm.callJNI_OnLoad(emulator); + System.out.println("[TP] libturingmfa JNI_OnLoad OK"); + } catch (Throwable t3) { + System.out.println("[TP] onLoad EXC: " + t3); + } + break; + } + default: { + load("ga"); + load("mfa"); + } + } + } catch (Throwable t) { + System.out.println("[TP] load " + which + " EXC: " + t); + try { + com.github.unidbg.arm.ARM emu = (com.github.unidbg.arm.ARM) emulator; + java.lang.reflect.Field f = emulator.getClass().getSuperclass().getDeclaredField("backend"); + f.setAccessible(true); + Object be = f.get(emulator); + java.lang.reflect.Method pc = be.getClass().getMethod("reg_read", int.class); + System.out.println("[TP] PC=" + Long.toHexString(((Number) pc.invoke(be, com.github.unidbg.arm.backend.Unicorn2Factory.class.getField("UC_ARM64_REG_PC").getLong(null))).longValue())); + } catch (Throwable t2) { + System.out.println("[TP] pc-dump fail " + t2); + } + t.printStackTrace(System.out); + } + } + + /** 直调 mfa 的 b87_ (impl@0x25c98) — JNI: (env, thiz, SparseArray, byte[], Map, int) -> SparseArray */ + void callEnc(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 t2) {} + byte[] blob = new java.io.FileInputStream(blobPath).readAllBytes(); + // SparseArray obj + DvmObject sa = vm.resolveClass("android/util/SparseArray").newObject(null); + DvmObject dba = vm.resolveClass("[B").newObject(blob); + DvmObject map = vm.resolveClass("java/util/HashMap").newObject(null); + // 直调 impl: module.base + 0x25c98; 参数: env, null, sa, dba, map, 1 + long fn = m.base + 0x25c98; + System.out.println("[TP] calling b87_ @ " + Long.toHexString(fn) + " blob=" + blob.length + "B"); + Number ret = m.callFunction(emulator, fn, vm.getJavaVM(), null, sa, dba, map, 1); + System.out.println("[TP] b87_ ret=" + ret); + } catch (Throwable t) { + System.out.println("[TP] callEnc EXC " + t); + t.printStackTrace(System.out); + } + } + + void listNatives() { + try { + java.lang.reflect.Method m = BaseVM.class.getDeclaredMethod("listNativeMethods"); + // 尝试不同的内部访问; 失败则走 vm 自省 + System.out.println("[TP] vm class methods: "); + for (java.lang.reflect.Method mm : vm.getClass().getDeclaredMethods()) { + if (mm.getName().toLowerCase().contains("native") || mm.getName().toLowerCase().contains("register")) + System.out.println(" " + mm.getName() + " " + java.util.Arrays.toString(mm.getParameterTypes())); + } + } catch (Throwable t) { + System.out.println("[TP] listNatives EXC " + t); + } + } + + public static void main(String[] args) { + TuringProbe p = new TuringProbe(); + String which = args.length > 0 ? args[0] : "both"; + p.preResolveClasses(); + if (which.equals("call")) { + p.callEnc(args.length > 1 ? args[1] : "/tmp/dfp_str2.bin"); + } else { + p.load(which); + p.listNatives(); + } + System.out.println("[TP] done"); + } +} \ No newline at end of file