From a8aebfed4015c01d812f505dd406b7f2d2ad77b2 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 01:18:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20getkey=3D=E8=A3=B824B=E9=92=A5(?= =?UTF-8?q?=E5=A0=86=E8=8A=82=E7=82=B9=E5=8F=96=E8=AF=81=E6=8E=A8=E7=BF=BB?= =?UTF-8?q?=E6=B4=BE=E7=94=9F=E5=81=87=E8=AE=BE)=20+=20=E6=98=8E=E6=96=87?= =?UTF-8?q?=E6=97=8F=E7=88=86=E7=A0=B4=E9=9B=B6=E5=91=BD=E4=B8=AD=E8=90=BD?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unicorn2后端+全内存扫描: 0x127d* 堆节点含 ZMHAV/4VYc/xXED 裸钥 + 输入副本 - 下一步: OTP insert-pos 差分(内部副本) + hydeviceid_config 88B 解码路径 --- docs/HUYA_HDID_ALGORITHM_GEN.md | 12 ++ tools/unidbg/hydev/src/hydev/AesProbe.java | 131 +++++++++++++++++++- tools/unidbg/hydev/src/hydev/DiffProbe.java | 45 ++++++- 3 files changed, 182 insertions(+), 6 deletions(-) diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 55113da..9331783 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -726,3 +726,15 @@ UdbAESUtilC1(…) + encrypt ← 后段 - 16B 密文 (encode_aes 形态) — 朴素明文组合 (aid/mid/DEVID40/版本/配置) × 6 钥族 × 转置/b64/md5 = 零命中 - decode_aes 直解 (6 钥) = 32B 无明文 → **钥 = getkey(双 id) 派生材料** (非裸 24B) - 下步 (round10): 逆向 replaaica 得 decrypt → 或 DiffProbe 调 getkey 取派生钥 → 对拍 + +## §11.23 getkey 等于裸钥! 堆节点取证 + 明文族爆破零命中 (新目标 R1) + +### AESkeyMgr 堆节点取证 (unidbg 全内存扫) +- @0x127ddda0: `ZMHAV…24B` + len-0x18 = **getkey 返回的 = 裸 24B 钥串 (无派生!)** +- @0x127ea000: ZMHAV + 4VYcPdv… + xXEDWq… 相邻节点 = 钥表顺序装载 +- @0x127ddf00: ZMHAV + "abc" (输入副本同址 = OTP 的 insert 目标 = 内部副本!) +- 此前「派生钥」假设 → **推翻: 24B 钥直读直用** + +### appSign (ed0db8) 明文族爆破 (复刻引擎 × 8 钥 × 19 材料 × 8 形态 = 零命中) +- md5/sha1/sha256 摘要 16B、首尾 16B、双 md5、hex 编码等全灭 +- 结论: 明文 ≠ 简单材料哈希 — 可能是 config 解密产物 / 复合序列化 (下轮: OTP insert-pos 差分 + config 88B 解码路径) diff --git a/tools/unidbg/hydev/src/hydev/AesProbe.java b/tools/unidbg/hydev/src/hydev/AesProbe.java index ab07646..753119e 100644 --- a/tools/unidbg/hydev/src/hydev/AesProbe.java +++ b/tools/unidbg/hydev/src/hydev/AesProbe.java @@ -3,7 +3,7 @@ package hydev; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.arm.backend.BackendFactory; -import com.github.unidbg.arm.backend.DynarmicFactory; +import com.github.unidbg.arm.backend.Unicorn2Factory; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.DalvikModule; @@ -11,6 +11,7 @@ import com.github.unidbg.linux.android.dvm.StringObject; import com.github.unidbg.linux.android.dvm.VM; import com.github.unidbg.memory.Memory; import com.github.unidbg.pointer.UnidbgPointer; +import unicorn.Arm64Const; import java.io.File; import java.util.Arrays; @@ -37,7 +38,7 @@ public class AesProbe { private final Module module; AesProbe(String soPath) throws Exception { - BackendFactory backend = new DynarmicFactory(true); + BackendFactory backend = new Unicorn2Factory(true); emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build(); Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); @@ -149,7 +150,13 @@ public class AesProbe { UnidbgPointer.nativeValue(pb), UnidbgPointer.nativeValue(pc), UnidbgPointer.nativeValue(pd), 3L, 4L, UnidbgPointer.nativeValue(pout)); String out = readStdString(pout); + // 读回输入串 (native insert 后 = in+getkey钥!) + String pinAfter = readStdString(pa); System.out.println("[otp] a=" + a + " b=" + b + " -> hex=" + toHex(out)); + System.out.println("[otp-insert] a-after=" + pinAfter + " hex=" + toHex(pinAfter)); + System.out.println("[otp-insert] b-after=" + readStdString(pb) + " hex=" + toHex(readStdString(pb))); + System.out.println("[otp-insert] c-after=" + readStdString(pc) + " hex=" + toHex(readStdString(pc))); + System.out.println("[otp-insert] d-after=" + readStdString(pd) + " hex=" + toHex(readStdString(pd))); } catch (Throwable t) { System.out.println("[otp] err " + t); } @@ -172,6 +179,124 @@ public class AesProbe { return ""; } + void scanMemoryForKeys() { + byte[] needle = "ZMHAVPRaxJ3MtXDjduUnXAKQ".getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + long[] ranges = {0x0L, 0x1000000L, 0x40000000L, 0x80000000L, 0x100000000L, 0x300000000L, 0x70200000L}; + long STEP = 0x10000; + int found = 0; + byte[] buf = new byte[(int) STEP]; + for (long rb : ranges) { + long win = rb == 0x0L ? 0x1000000L : 0x20000000L; + for (long off = 0; off < win && found < 8; off += STEP) { + long addr = rb + off; + UnidbgPointer pp = UnidbgPointer.pointer(emulator, addr); + if (pp == null) continue; + try { pp.read(0, buf, 0, (int) STEP); } catch (Throwable t) { continue; } + for (int i = 0; i + needle.length <= buf.length; i++) { + boolean ok = true; + for (int j = 0; j < needle.length; j++) if (buf[i+j] != needle[j]) { ok = false; break; } + if (ok) { + System.out.println("[scan] KEY ZMHAV @ " + String.format("0x%x", addr + i)); + byte[] seg = new byte[80]; + try { UnidbgPointer.pointer(emulator, addr + i).read(0, seg, 0, 80); } catch (Throwable t) {} + StringBuilder hx = new StringBuilder(); + for (byte b : seg) hx.append(String.format("%02x", b & 0xff)); + System.out.println("[scan] ctx: " + hx); + found++; + } + } + } + } + System.out.println("[scan] done found=" + found); + } + + void hookGetkeyReturn() { + // OTP 内部 getkey 返回点 0x32fb80 (32fa24+0x15c): 读 x0/x1/x2 SSO + final boolean[] done = {false}; + com.github.unidbg.arm.backend.CodeHook hook = new com.github.unidbg.arm.backend.CodeHook() { + public void hook(com.github.unidbg.arm.backend.Backend backend, long address, int size, Object user) { + System.out.println("[hook] hit @" + String.format("0x%x", address)); + if (done[0]) return; done[0] = true; + try { + long x0 = backend.reg_read(Arm64Const.UC_ARM64_REG_X0).longValue(); + long x1 = backend.reg_read(Arm64Const.UC_ARM64_REG_X1).longValue(); + long x2 = backend.reg_read(Arm64Const.UC_ARM64_REG_X2).longValue(); + System.out.println("[hook-getkey] x0=" + Long.toHexString(x0) + " x1=" + Long.toHexString(x1) + " x2=" + Long.toHexString(x2)); + long flag = x0 & 1; + if (flag == 0) { + long len = (x0 >> 1) & 0x7f; + StringBuilder hx = new StringBuilder(); + for (int i = 0; i < len; i++) hx.append(String.format("%02x", (x0 >> (8 + i * 8)) & 0xff)); + System.out.println("[hook-getkey] SSO len=" + len + " hex=" + hx); + } else { + UnidbgPointer pp = UnidbgPointer.pointer(emulator, x2); + byte[] seg = new byte[32]; + if (pp != null) { pp.read(0, seg, 0, 32); + StringBuilder hx = new StringBuilder(); + for (byte b : seg) hx.append(String.format("%02x", b & 0xff)); + System.out.println("[hook-getkey] LONG size=" + x1 + " data=" + hx); } + else System.out.println("[hook-getkey] LONG null ptr"); + } + } catch (Throwable t) { System.out.println("[hook-getkey] err " + t); } + } + public void onAttach(com.github.unidbg.arm.backend.UnHook unHook) {} + public void detach() {} + }; + try { + emulator.getBackend().hook_add_new(hook, module.base + 0x32fb78L, module.base + 0x32fb88L, null); + System.out.println("[hook] installed at 0x32fb80"); + } catch (Throwable t) { System.out.println("[hook] install err " + t); } + // 触发一次 OTP (内部会走 getkey) + callOtp("abc", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); + } + + void scanBss() { + long base = module.base; + long[] range = {0x491000L, 0x491c00L}; + byte[] buf = new byte[(int) (range[1] - range[0])]; + try { + UnidbgPointer p0 = UnidbgPointer.pointer(emulator, base + range[0]); + if (p0 != null) p0.read(0, buf, 0, buf.length); else System.out.println("[bss] null ptr"); + } catch (Throwable t) { System.out.println("[bss] read err " + t); return; } + // 找 ASCII 串 (可打印, 长度>=8) + StringBuilder cur = new StringBuilder(); + int start = -1; + for (int i = 0; i < buf.length; i++) { + int b = buf[i] & 0xff; + if (b >= 0x20 && b < 0x7f) { if (start < 0) start = i; cur.append((char) b); } + else { + if (cur.length() >= 8) { + System.out.println("[bss] @" + String.format("0x%x", range[0] + start) + " = " + cur); + } + cur.setLength(0); start = -1; + } + } + if (cur.length() >= 8) System.out.println("[bss] @" + String.format("0x%x", range[0] + start) + " = " + cur); + // 特定 key 串的原始字节 + for (String k : new String[]{"ZMHAV", "HuyaUdb", "owNMia", "KFWAH"}) { + int idx = indexOfAscii(buf, k); + System.out.println("[bss] key-" + k + " @" + (idx >= 0 ? String.format("0x%x", range[0] + idx) : "not-found")); + if (idx >= 0) { + byte[] seg = new byte[64]; + UnidbgPointer pk = UnidbgPointer.pointer(emulator, base + range[0] + idx); + if (pk != null) pk.read(0, seg, 0, seg.length); + StringBuilder hx = new StringBuilder(); + for (byte x : seg) hx.append(String.format("%02x", x & 0xff)); + System.out.println("[bss] context: " + hx); + } + } + } + + static int indexOfAscii(byte[] buf, String needle) { + byte[] nb = needle.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + outer: + for (int i = 0; i + nb.length <= buf.length; i++) { + for (int j = 0; j < nb.length; j++) if (buf[i+j] != nb[j]) continue outer; + return i; + } + return -1; + } + void dumpKeyMgr() { UnidbgPointer self = emulator.getMemory().malloc(0x80, false).getPointer(); self.write(0, new byte[0x80], 0, 0x80); @@ -240,7 +365,7 @@ public class AesProbe { p.callOtp("1e8bdf7d4f7a01d3" + "13.4.22" + "1.0.80138" + "127.0.0.1" + "xiaomi", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); p.callOtp("5008" + "13.4.22" + "1.0.80138", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); p.callOtp("ed0db8334cadd236c00cadf7e11ab5a5", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); - p.emulator.close(); + p.scanMemoryForKeys(); p.emulator.close(); } } \ No newline at end of file diff --git a/tools/unidbg/hydev/src/hydev/DiffProbe.java b/tools/unidbg/hydev/src/hydev/DiffProbe.java index f2b4968..ae0d90f 100644 --- a/tools/unidbg/hydev/src/hydev/DiffProbe.java +++ b/tools/unidbg/hydev/src/hydev/DiffProbe.java @@ -13,6 +13,7 @@ import com.github.unidbg.pointer.UnidbgPointer; import java.io.File; import java.util.Arrays; +import unicorn.Arm64Const; import unicorn.UnicornConst; /** @@ -136,6 +137,41 @@ public class DiffProbe { probeGetkey(args.length > 0 ? args[0] : "so/libudbauthunify_merged.so"); } + static String bytesToHex(byte[] b) { + StringBuilder sb = new StringBuilder(); + for (byte x : b) sb.append(String.format("%02x", x & 0xff)); + return sb.toString(); + } + + static byte[] keyFromSso(long x0, long x1, long x2, DiffProbe p) throws Exception { + long flag = x0 & 1; + if (flag == 0) { + long len = (x0 >> 1) & 0x7f; + byte[] out = new byte[(int) len]; + long v = x0 >> 8; + for (int i = 0; i < (int) len; i++) { + int off = (i < 7) ? 8 + i * 8 : (i - 7) * 8; + int sh = (i < 7) ? 0 : 16; + out[i] = (byte) ((x0 >> (8 + i * 8)) & 0xff); + } + return out; + } else { + // long form: x1 = size, x2 = ptr + long size = x1, ptr = x2; + byte[] out = new byte[(int) size]; + UnidbgPointer pp = UnidbgPointer.pointer(p.emulator, ptr); + if (pp == null) return new byte[0]; + pp.read(0, out, 0, out.length); + return out; + } + } + + static String reconstruct(byte[] b) { + StringBuilder sb = new StringBuilder(); + for (byte x : b) { char c = (char) (x & 0xff); sb.append(c >= 32 && c < 127 ? c : '.'); } + return sb.toString(); + } + static void probeGetkey(String soPath) throws Exception { DiffProbe p = new DiffProbe(soPath); com.github.unidbg.memory.Memory mem = p.emulator.getMemory(); @@ -147,9 +183,12 @@ public class DiffProbe { for (int id1 = 0; id1 <= 4; id1++) { try { p.module.callFunction(p.emulator, 0x26871cL, UnidbgPointer.nativeValue(self), (long) id1, 0L); - long x0 = p.emulator.getBackend().reg_read(UnicornConst.UC_ARM64_REG_X0); - long x2 = p.emulator.getBackend().reg_read(UnicornConst.UC_ARM64_REG_X2); - System.out.println("[getkey] id1=" + id1 + " x0=" + Long.toHexString(x0) + " x2=" + Long.toHexString(x2)); + long x0 = p.emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0).longValue(); + long x1 = p.emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X1).longValue(); + long x2 = p.emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X2).longValue(); + System.out.println("[getkey] id1=" + id1 + " x0=" + Long.toHexString(x0) + " x1=" + Long.toHexString(x1) + " x2=" + Long.toHexString(x2)); + String kv = reconstruct(keyFromSso(x0, x1, x2, p)); + System.out.println("[getkey] id1=" + id1 + " KEY=" + kv + " hex=" + bytesToHex(kv.getBytes("ISO-8859-1"))); } catch (Throwable t) { System.out.println("[getkey] id1=" + id1 + " err " + t); } } p.emulator.close();