feat(huya): harness onLoad迭代修复 - 全局槽补丁过引用计数关 (R22)

- 0x48a90槽NULL崩溃 -> dummy对象补丁 -> 推进到vtable空跳
- 根因: turing类定义缺失(需ProxyClassFactory或C结构对象模型)
- TuringProbe.java: gap+类+槽三级补丁可复现
This commit is contained in:
yml2213
2026-08-29 07:13:22 +08:00
parent 47bc38d638
commit c85693e790
2 changed files with 77 additions and 6 deletions
+19
View File
@@ -1227,3 +1227,22 @@ dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采
- 三种输入 (全POST/cipher-only/magic+cipher) × int{0,1} = 全部确定性 **ret=-1** - 三种输入 (全POST/cipher-only/magic+cipher) × int{0,1} = 全部确定性 **ret=-1**
- 判定: jobject 封送通; -1 = turing 全局初始化态缺失 (config/dat 未载入) 或 SparseArray 内容格式不符 - 判定: jobject 封送通; -1 = turing 全局初始化态缺失 (config/dat 未载入) 或 SparseArray 内容格式不符
- 下一步: (a) onLoad 初始化链修复后直调 (x0 原子问题先解), (b) 或喂 SparseArray 事件内容探格式 - 下一步: (a) onLoad 初始化链修复后直调 (x0 原子问题先解), (b) 或喂 SparseArray 事件内容探格式
---
## §11.52 R22: harness onLoad 迭代修复进展 (2026-08-29)
### 崩因链逐级突破 (每补一个槽位推进一段)
1. **0x48a90 全局槽 NULL → 引用计数崩溃** (0x12b6c ldxr x0):
- onLoad 的 init 函数 0x11690 先读该槽 (adrp 0x48000 + ldr #0xa90) 再 refcount
- 真机上该槽在 init 链前序已赋值; harness 中静默为空
- **补丁: 塞 8B dummy 对象指针 → 过此关!**
2. 新崩溃: BR-x8 空跳 (X8=0x0, PC=0x0), X0=X1=0x1203ced5 (rodata 字符串)
- 变体函数指针表空槽 / C++ 虚表未建 / PLT 未解析
3. 根因深化: turing 类定义 (字段/方法) 缺失 — 预注册的空类无法支撑 JNI 静态字段链
- unidbg VM 无 dex 加载 (仅 setDvmClassFactory/ProxyClassFactory 宿主代理)
### 状态判定
- onLoad 全仿真 = 类定义工程 (ProxyClassFactory) 或连续空槽 wack-a-mole — 小时级
- 替代: 跳过 onLoad, 直接按 JNI 表构造 b87_@0x25c98 的 C 结构对象模型直调
- 进展固化: gap补映射 + 预注册类 + 全局槽补丁 = 已进 TuringProbe.java 可复现
+58 -6
View File
@@ -82,11 +82,22 @@ public class TuringProbe {
} catch (Throwable t2) { } catch (Throwable t2) {
System.out.println("[TP] gap map EXC " + t2); System.out.println("[TP] gap map EXC " + t2);
} }
// 全局槽基础补丁: base+0x48a90 读取为 NULL -> 塞一个 8B dummy 对象 (引用计数目标)
try {
long slot = m.base + 0x48a90;
com.github.unidbg.pointer.UnidbgPointer dummyPtr = emulator.getMemory().mmap(8, 7);
dummyPtr.setMemory(0, 8, (byte) 0);
com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, slot).setPointer(0, dummyPtr);
System.out.println("[TP] global slot 0x48a90 <- " + Long.toHexString(dummyPtr.peer));
} catch (Throwable t4) {
System.out.println("[TP] slot patch EXC " + t4);
}
try { try {
dm.callJNI_OnLoad(emulator); dm.callJNI_OnLoad(emulator);
System.out.println("[TP] libturingmfa JNI_OnLoad OK"); System.out.println("[TP] libturingmfa JNI_OnLoad OK");
} catch (Throwable t3) { } catch (Throwable t3) {
System.out.println("[TP] onLoad EXC: " + t3); System.out.println("[TP] onLoad EXC: " + t3);
regDump();
} }
break; break;
} }
@@ -98,12 +109,20 @@ public class TuringProbe {
} catch (Throwable t) { } catch (Throwable t) {
System.out.println("[TP] load " + which + " EXC: " + t); System.out.println("[TP] load " + which + " EXC: " + t);
try { try {
com.github.unidbg.arm.ARM emu = (com.github.unidbg.arm.ARM) emulator; com.github.unidbg.arm.backend.Backend be = emulator.getBackend();
java.lang.reflect.Field f = emulator.getClass().getSuperclass().getDeclaredField("backend"); int[] regs = {unicorn.Arm64Const.UC_ARM64_REG_PC,
f.setAccessible(true); unicorn.Arm64Const.UC_ARM64_REG_X0,
Object be = f.get(emulator); unicorn.Arm64Const.UC_ARM64_REG_X1,
java.lang.reflect.Method pc = be.getClass().getMethod("reg_read", int.class); unicorn.Arm64Const.UC_ARM64_REG_X8,
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())); unicorn.Arm64Const.UC_ARM64_REG_X19,
unicorn.Arm64Const.UC_ARM64_REG_LR,
unicorn.Arm64Const.UC_ARM64_REG_SP};
String[] names = {"PC","X0","X1","X8","X19","LR","SP"};
for (int k=0;k<regs.length;k++) {
long v = be.reg_read(regs[k]).longValue();
System.out.println("[TP] " + names[k] + "=" + Long.toHexString(v)
+ (v>=0x12000000L ? " (mod-rel " + Long.toHexString(v - 0x12012000L) + ")" : ""));
}
} catch (Throwable t2) { } catch (Throwable t2) {
System.out.println("[TP] pc-dump fail " + t2); System.out.println("[TP] pc-dump fail " + t2);
} }
@@ -111,6 +130,39 @@ public class TuringProbe {
} }
} }
void regDump() {
try {
com.github.unidbg.arm.backend.Backend be = emulator.getBackend();
// 反汇编调用者区域 (LR-0x40 .. LR+0x60)
try {
long lr = be.reg_read(unicorn.Arm64Const.UC_ARM64_REG_LR).longValue();
StringBuilder sb = new StringBuilder();
capstone.api.Instruction[] ins = emulator.disassemble(lr - 0x40, 0xa8, 0);
for (capstone.api.Instruction i : ins) sb.append(String.format(" 0x%x: %s %s%n", i.getAddress(), i.getMnemonic(), i.getOpStr() == null ? "" : i.getOpStr()));
String dasm = sb.toString();
System.out.println("[TP] dasm@caller:\n" + dasm);
} catch (Throwable t5) { System.out.println("[TP] dasm fail " + t5); }
int[] regs = {unicorn.Arm64Const.UC_ARM64_REG_PC, unicorn.Arm64Const.UC_ARM64_REG_X0,
unicorn.Arm64Const.UC_ARM64_REG_X1, unicorn.Arm64Const.UC_ARM64_REG_X2,
unicorn.Arm64Const.UC_ARM64_REG_X8, unicorn.Arm64Const.UC_ARM64_REG_X19,
unicorn.Arm64Const.UC_ARM64_REG_X20, unicorn.Arm64Const.UC_ARM64_REG_LR,
unicorn.Arm64Const.UC_ARM64_REG_SP};
String[] names = {"PC","X0","X1","X2","X8","X19","X20","LR","SP"};
for (int k=0;k<regs.length;k++) {
long v = be.reg_read(regs[k]).longValue();
String rel = (v>=0x12012000L && v<0x12018000L) ? " (rel 0x" + Long.toHexString(v - 0x12012000L) + ")" : "";
System.out.println("[TP] " + names[k] + "=0x" + Long.toHexString(v) + rel);
}
// 尝试读 x0 处内存
try {
long x0 = be.reg_read(unicorn.Arm64Const.UC_ARM64_REG_X0).longValue();
com.github.unidbg.pointer.UnidbgPointer pp = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, x0);
byte[] b = pp.getByteArray(0, 32);
System.out.println("[TP] *X0 = " + java.util.HexFormat.of().formatHex(b));
} catch (Throwable t4) { System.out.println("[TP] *X0 unreadable " + t4); }
} catch (Throwable t2) { System.out.println("[TP] regDump fail " + t2); }
}
/** 直调 mfa 的 b87_ (impl@0x25c98) — JNI: (env, thiz, SparseArray, byte[], Map, int) -> SparseArray */ /** 直调 mfa 的 b87_ (impl@0x25c98) — JNI: (env, thiz, SparseArray, byte[], Map, int) -> SparseArray */
void callEnc(String blobPath) { void callEnc(String blobPath) {
try { try {