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
@@ -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");
}
}