feat(huya): turing JNI表解出+b87_@0x25c98 (R21)

- 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表同法
This commit is contained in:
yml2213
2026-08-29 07:07:18 +08:00
parent 39c516b71b
commit dc109c5dba
7 changed files with 284 additions and 0 deletions
+27
View File
@@ -1194,3 +1194,30 @@ dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采
### 工程交付 ### 工程交付
- 触发→捕获→存档 全自动化 (清状态+spawn+str_magic+杀) — 每次 ~40s - 触发→捕获→存档 全自动化 (清状态+spawn+str_magic+杀) — 每次 ~40s
- evidence/dfp_live/: 请求全文 ×5 + 采样集 + 组件 + fresh 状态文件 - 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_ 族入口
@@ -0,0 +1 @@
{"event": "rn-err", "msg": "no RegisterNatives export"}
@@ -0,0 +1 @@
{"event": "rn-err", "msg": "no RegisterNatives in libart"}
+43
View File
@@ -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"}
@@ -0,0 +1 @@
{"event": "rn-hooked", "at": "0x72e1a37890"}
+48
View File
@@ -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);
@@ -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");
}
}