diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 1aabe73..17f0cb0 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -1285,3 +1285,31 @@ dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采 - **纯样本分析无法破** (无密钥派生链); 密钥源 = getDfpConfig 81B 服务端下发镜钥 + 设备材料 - 破解必须回到加密内核: A (harness onLoad 空槽冲关) / B (ga JNI_OnLoad 注册表反汇编) - 已获价值: 报告时刻可判定 (头 byte6-7 ≈ uptime-ms), 版本/重注册模式可区分 + +--- + +## §11.55 R25: 💥 加密器识别 — turing mfa XXTEA 变体 + harness 直调运行 (2026-08-29) + +### 关键突破 (IDA 导出 + unidbg 双线合流) +1. **JNI 表全解 (11 方法)**: a87_@0x25680 b87_@0x25c98 c87_@0x262a8 d87_@0x26418 e87_@0x26768 + f87_@0x26edc g87_@0x271d0 h87_@0x274c0 i87_@0x276d0 j87_@0x27a78 k87_@0x27b74 + (+ onServiceConnected@0x37324) — 类 TNative$aa (com/tencent/turingface/sdk/mfa) +2. **b87_ 语义**: JNI 分发壳 (a6==1 → sub_2E4A4!) — **sub_2E4A4 = 解包链**: + sub_2E250 { sub_252EC [拷贝 + **sub_24D58 = XXTEA变体解密** + 尾4B长度校验] + sub_34A00 [inflate!] } + -> "resp" 存 SparseArray — **输入 = [XOR/XXTEA密文][4B len] → inflate → 明文!!** +3. **sub_24D58 反编译 = XXTEA 变体**: 负长度参数 + 0x9E3779B9(2B16!) + 掩码混淆运算 + (constant: 6CC17688/933E8977/D3115A55/5613AC09/A9EC53F6/B13795CC/4EC86A33/73067D6A/8CF98295) + key索引 = (sum>>2&3 变体) — 自定义变体非标准XXTEA +4. **harness 直调 sub_24D58 成功**: (v-ptr, -n, key@3DF88) — n=977/976/978 全量运行无崩! + 输出非zlib — key槽或输入格式待定 + +### harness 进展 +- 导入补丁 115/121 解析 (ASensor 族缺 libandroid) +- 0x48A90/0x48A98 构建器全局按语义手工构建 (rc@0 len@8 data@16!) +- 日志链桩 (11890/11928/3e80/3f70 = mov-w0-#0;ret) +- Unicorn2 = ldxr 原子缺陷; Dynarmic = 过 init 到注册段 (JNI-Env 回调 abort); 三后端均可跑纯函数 + +### 下一步 (解密侧续攻) +- key 槽验证: 3DF88 结构内偏移 / 或 key 由 config 派生 +- b87_ 全链 harness 直调 (喂真实捕获密文 → inflate → 明文!) +- a87_@0x25680 = 加密侧 (sub_27DA4 链) — "纯代码铸造" 的核心 diff --git a/tools/unidbg/hydev/src/hydev/TuringProbe.java b/tools/unidbg/hydev/src/hydev/TuringProbe.java index ed3b6ee..4b64e4c 100644 --- a/tools/unidbg/hydev/src/hydev/TuringProbe.java +++ b/tools/unidbg/hydev/src/hydev/TuringProbe.java @@ -19,13 +19,61 @@ public class TuringProbe { private final AndroidEmulator emulator; private final VM vm; + static final long[] IMPORT_SLOTS = {0x45908, 0x45910, 0x45918, 0x45920, 0x45928, 0x45930, 0x45938, 0x45940, 0x45948, 0x45950, 0x45958, 0x45960, 0x45968, 0x45970, 0x45978, 0x45980, 0x45988, 0x45990, 0x45998, 0x459a0, 0x459a8, 0x459b0, 0x459b8, 0x459c0, 0x459c8, 0x459d0, 0x459d8, 0x459e0, 0x459e8, 0x459f0, 0x459f8, 0x45a00, 0x45a08, 0x45a10, 0x45a18, 0x45a20, 0x45a28, 0x45a30, 0x45a38, 0x45a40, 0x45a48, 0x45a50, 0x45a58, 0x45a60, 0x45a68, 0x45a70, 0x45a78, 0x45a80, 0x45a88, 0x45a90, 0x45a98, 0x45aa0, 0x45aa8, 0x45ab0, 0x45ab8, 0x45ac0, 0x45ac8, 0x45ad0, 0x45ad8, 0x45ae0, 0x45ae8, 0x45af0, 0x45af8, 0x45b00, 0x45b08, 0x45b10, 0x45b18, 0x45b20, 0x45b28, 0x45b30, 0x45b38, 0x45b40, 0x45b48, 0x45b50, 0x45b58, 0x45b60, 0x45b68, 0x45b70, 0x45b78, 0x45b80, 0x45b88, 0x45b90, 0x45b98, 0x45ba0, 0x45ba8, 0x45bb0, 0x45bb8, 0x45bc0, 0x45bc8, 0x45bd0, 0x45bd8, 0x45be0, 0x45be8, 0x45bf0, 0x45bf8, 0x45c00, 0x45c08, 0x45c10, 0x45c18, 0x45c20, 0x45c28, 0x45c30, 0x45c38, 0x45c40, 0x45c48, 0x45c50, 0x45c58, 0x45c60, 0x45c68, 0x45c70, 0x45c78, 0x45c80, 0x45c88, 0x45c90, 0x45c98, 0x45ca0, 0x45ca8, 0x45cb0, 0x45cb8, 0x45cc0, 0x45cc8}; + static final String[] IMPORT_NAMES = {"popen", "deflateEnd", "pthread_mutex_destroy", "pthread_create", "strtoull", "realloc", "open", "pthread_key_create", "inflate", "pthread_attr_setdetachstate", "__cxa_finalize", "ftell", "regcomp", "mkdir", "calloc", "fread", "strncmp", "read", "ASensor_getVendor", "strtoul", "fstat", "pthread_mutex_init", "vsnprintf", "closedir", "strlcpy", "lstat", "qsort", "__cxa_guard_acquire", "__errno", "srand", "ASensor_getName", "snprintf", "_ZdlPv", "dl_iterate_phdr", "deflateInit_", "sscanf", "pthread_mutexattr_settype", "deflate", "write", "getenv", "strcasecmp", "strrchr", "access", "time", "rand", "regfree", "memcmp", "fclose", "mincore", "rewind", "__stack_chk_fail", "fgets", "_Znwm", "pthread_attr_init", "gettimeofday", "feof", "pthread_mutexattr_init", "__cxa_guard_release", "strtok_r", "ioctl", "pthread_setspecific", "strncpy", "opendir", "inet_ntop", "recv", "__android_log_print", "unlink", "inflateEnd", "pthread_attr_destroy", "strchr", "tolower", "fseek", "strcmp", "flock", "_Znam", "pipe2", "pthread_mutexattr_destroy", "syscall", "regexec", "sprintf", "real_syscall", "send", "pthread_mutex_lock", "mmap", "pthread_getspecific", "inflateInit_", "memmove", "if_indextoname", "strsep", "ASensor_getType", "ASensorManager_getInstance", "getuid", "readlink", "strstr", "getpagesize", "strdup", "usleep", "readdir", "fdopen", "strlen", "close", "remove", "stat", "ASensorManager_getSensorList", "rename", "munmap", "ASensor_getResolution", "malloc", "memcpy", "statfs", "memset", "fopen", "socket", "pclose", "strtol", "free", "strcpy", "__system_property_get", "pthread_mutex_unlock", "__cxa_atexit", "_ZdaPv"}; + + /** 全量补 JUMP_SLOT 导入槽 (懒绑定未解析) */ + void patchImports(com.github.unidbg.Module m) { + int ok = 0, miss = 0; + for (int i = 0; i < IMPORT_SLOTS.length; i++) { + String name = IMPORT_NAMES[i]; + try { + com.github.unidbg.pointer.UnidbgPointer slot = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, m.base + IMPORT_SLOTS[i]); + if (slot.getLong(0) != 0) continue; // already resolved + com.github.unidbg.Symbol sym = null; + for (com.github.unidbg.Module md : emulator.getMemory().getLoadedModules()) { + try { sym = md.findSymbolByName(name, false); } catch (Throwable t) {} + if (sym != null && sym.getAddress() != 0) break; + } + // __system_property_get 在 libc 里是核心符号 + if (sym == null && name.equals("__system_property_get")) { + try { + com.github.unidbg.Module libc = emulator.getMemory().findModule("libc.so"); + if (libc != null) sym = libc.findSymbolByName("__system_property_get", false); + } catch (Throwable t) {} + } + if (sym != null && sym.getAddress() != 0) { + slot.setPointer(0, com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, sym.getAddress())); + ok++; + } else miss++; + } catch (Throwable t) { miss++; } + } + System.out.println("[TP] imports patched ok=" + ok + " miss=" + miss); + } TuringProbe() { - com.github.unidbg.arm.backend.BackendFactory backend = new com.github.unidbg.arm.backend.Unicorn2Factory(true); + com.github.unidbg.arm.backend.BackendFactory backend; + try { + 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") .addBackendFactory(backend) .build(); + emulator.getMemory().setLibraryResolver(new AndroidResolver(23)); + // 系统属性钩子 (turing 可能读取) + try { + com.github.unidbg.linux.android.SystemPropertyHook propHook = new com.github.unidbg.linux.android.SystemPropertyHook(emulator); + java.util.Map props = new java.util.LinkedHashMap<>(); + String[][] propArr = { + {"ro.build.version.sdk","30"}, {"ro.hardware","qcom"}, {"ro.product.board","thyme"}, + {"ro.product.brand","Xiaomi"}, {"ro.product.device","thyme"}, {"ro.product.manufacturer","Xiaomi"}, + {"ro.product.model","M2102J2SC"}, {"ro.product.name","thyme"}, {"ro.build.type","user"}, + }; + for (String[] kv : propArr) props.put(kv[0], kv[1]); + propHook.setPropertyProvider(pname -> props.getOrDefault(pname, "")); + emulator.getMemory().addHookListener(propHook); + } catch (Throwable t) { System.out.println("[TP] prophook EXC " + t); } vm = emulator.createDalvikVM(); vm.setJni(new AbstractJni() {}); vm.setVerbose(true); @@ -82,16 +130,30 @@ public class TuringProbe { } catch (Throwable t2) { System.out.println("[TP] gap map EXC " + t2); } - // 全局槽基础补丁: base+0x48a90 读取为 NULL -> 塞一个 8B dummy 对象 (引用计数目标) + // 全局字符串构建器补丁: 0x48A90=alloc基址(rc@0 len@8 data@16), 0x48A98=data 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)); + com.github.unidbg.pointer.UnidbgPointer alloc = emulator.getMemory().mmap(24 + 64, 7); + alloc.setMemory(0, 24 + 64, (byte) 0); + alloc.setLong(0, 1L); // rc = 1 + alloc.setLong(8, 0L); // len = 0 + com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, m.base + 0x48a90).setPointer(0, alloc); + com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, m.base + 0x48a98).setPointer(0, com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, alloc.peer + 16)); + System.out.println("[TP] builder alloc=" + Long.toHexString(alloc.peer)); } catch (Throwable t4) { - System.out.println("[TP] slot patch EXC " + t4); + System.out.println("[TP] builder patch EXC " + t4); } + // 若有旧 dummy 会覆盖 builder, 故只保留 patchImports + try { patchImports(m); } catch (Throwable t4) { System.out.println("[TP] imports EXC " + t4); } + // 桩掉日志链 sub_11890/11928 (mov w0,#0; ret) — 注册才是主线 + try { + for (long stubOf : new long[]{0x11890, 0x11928, 0x3e80, 0x3f70}) { + com.github.unidbg.pointer.UnidbgPointer lp = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, m.base + stubOf); + lp.setInt(0, 0x52800000); // mov w0, #0 + lp.setInt(4, 0xd65f03c0); // ret + } + com.github.unidbg.pointer.UnidbgPointer chk = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, m.base + 0x11890); + System.out.println("[TP] log stubs @" + String.format("%08x", chk.getInt(0)) + "/" + String.format("%08x", chk.getInt(4))); + } catch (Throwable t5) { System.out.println("[TP] log stub EXC " + t5); } try { dm.callJNI_OnLoad(emulator); System.out.println("[TP] libturingmfa JNI_OnLoad OK"); @@ -155,11 +217,20 @@ public class TuringProbe { } // 尝试读 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); + long x19 = be.reg_read(unicorn.Arm64Const.UC_ARM64_REG_X19).longValue(); + com.github.unidbg.pointer.UnidbgPointer pp = com.github.unidbg.pointer.UnidbgPointer.pointer(emulator, x19); 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); } + System.out.println("[TP] *X19 = " + java.util.HexFormat.of().formatHex(b)); + } catch (Throwable t4) { System.out.println("[TP] *X19 unreadable " + t4); } + // 打印 0x123d8000 附近的映射区域 + try { + for (com.github.unidbg.Module md : emulator.getMemory().getLoadedModules()) { + for (com.github.unidbg.memory.MemRegion r : md.getRegions()) { + if (r.begin > 0x123d9000 || r.end < 0x123d7000) continue; + System.out.println("[TP] region " + Long.toHexString(r.begin) + "-" + Long.toHexString(r.end)); + } + } + } catch (Throwable t6) { System.out.println("[TP] regions EXC " + t6); } } catch (Throwable t2) { System.out.println("[TP] regDump fail " + t2); } } @@ -205,10 +276,42 @@ public class TuringProbe { } } + /** xtest: 直调 sub_24D58 (XXTEA变体) 解样本密文 @ blob 文件 */ + void xtest(String blobPath, String outPath) { + 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(); + // 密文词数 + int n = blob.length / 4; + com.github.unidbg.pointer.UnidbgPointer vp = emulator.getMemory().mmap(blob.length + 32, 7); + for (int i=0;i 0 ? args[0] : "both"; p.preResolveClasses(); + 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"); } else {