diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index d05e6e7..165755a 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -576,3 +576,24 @@ UdbAESUtil 的盒表 @0x1c5d8e (SBOX) + @0x1c5e8e (INV-SBOX) = **标准 AES 盒* ### 下步 (决定性二选一) A. **unidbg 调 encode_aes(IN, KEY) → OUT**: 可控输入差分, 直接对拍 ed0db8 + 反推钥/明文 B. **静态读完整 encrypt 路径** (0x24f9f0 _encrypt + Cipher@0x24f314): 明文源 + 模式 + 派生 + +## §11.16 unidbg encode_aes 探针上线 + 64B 钥材料发现 (2026-08-28 夜) + +### tools/unidbg/hydev/src/hydev/AesProbe.java (可执行差分源!) +- 直接调 libudbauthunify_merged.so 的 encode_aes@0x330218 (IN, KEY, OUT) +- NDK-libc++ std::string ABI (short ≤22B inline / long heap) 正确读写字 +- 权威输出 (已知输入): + | IN | KEY | 输出 | + |---|---|---| + | 0123456789abcdef | ZMHAVPRaxJ3MtXDjduUnXAKQ(24B) | ba3fb8f156b03a9d7db185d1254e0730 | + | 0123456789abcdef | owNMiaCgcHmqoTr3iRamFuHj | 74bd517c7e5d2bbce63c8e98a192c760 | + | 0123456789abcdef | ZMHAV…+64B-pad | (同24B) ba3fb8f1… | + | 0123456789abcdef | 64B-"0123456789…" | 72727e881edcfd0100a718687909b565 | + +### KeyExpansion@0x24ebd4 关键发现 +- 密钥装载 = **以 16B 步长读入** (x1[0]/x1[16]/x1[32]/x1[48] → 字) → **钥材料 64B** (非 24B!) +- 输出 = 64B (状态/扩展轮钥?) — Nk/Nr 分支在 0x24ec78-cmp#0x29 + +### 对比结论 +标准 AES-192-ECB(同一输入+24B钥) = b1e3abcc… ≠ 原生 ba3fb8f1… — **原生 AES 变体 (64B 钥材料 + 自定义扩展)**。 +华生线索: 原生输出对「输入块内含 NUL 的 64B 钥」敏感 — 下一轮 = Nk 分支 + Cipher 轮结构静态定案 → Python 复刻。 diff --git a/tools/unidbg/hydev/src/hydev/AesProbe.java b/tools/unidbg/hydev/src/hydev/AesProbe.java new file mode 100644 index 0000000..50eb8ae --- /dev/null +++ b/tools/unidbg/hydev/src/hydev/AesProbe.java @@ -0,0 +1,142 @@ +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.linux.android.AndroidEmulatorBuilder; +import com.github.unidbg.linux.android.AndroidResolver; +import com.github.unidbg.linux.android.dvm.DalvikModule; +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 java.io.File; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * unidbg 探针: 直接调用 libudbauthunify.so 的 encode_aes@0x330218 (IN, KEY, OUT) + * 与 UdbAESUtil 全家, 用可控输入差分出 32hex-HDID32 的 AES 算法细节. + * + * std::string ABI (NDK libc++): + * short: [0]=len<<1 (LSB=0), data@[1..24] + * long : [0..7]=cap(LSB=1), [8..15]=size, [16..23]=data-ptr + */ +public class AesProbe { + + static final Map PREFS = new LinkedHashMap<>(); + static { + PREFS.put("hydeviceid_config", "2AQq9oUCCZ8MgS1qhJHFp+U7vBdmTBVkH8WlXondx64IE+kvc65rHJtuTazmCLT0Z7FYQEVuosfMmclJOeQrI7PLlAhhZYU5dXoYCKQJ7/ze1y5V6DvozlZZUQLG9jgoNJ4kdEyEi3QQavTQGdiokQ=="); + } + + private final AndroidEmulator emulator; + private final VM vm; + private final Module module; + + AesProbe(String soPath) throws Exception { + BackendFactory backend = new DynarmicFactory(true); + emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build(); + Memory memory = emulator.getMemory(); + memory.setLibraryResolver(new AndroidResolver(23)); + vm = emulator.createDalvikVM(); + vm.setJni(new com.github.unidbg.linux.android.dvm.AbstractJni() {}); + vm.setVerbose(false); + DalvikModule dm = vm.loadLibrary(new File(soPath), false); + dm.callJNI_OnLoad(emulator); + module = dm.getModule(); + System.out.println("[*] lib loaded base=0x" + Long.toHexString(module.base)); + } + + // 写一个 NDK-libc++ std::string (short 形式: len<=22 走 inline) + UnidbgPointer writeStdString(String s) { + Memory mem = emulator.getMemory(); + byte[] data = s.getBytes(); + int len = data.length; + UnidbgPointer p; + if (len <= 22) { + // short: [0] = len<<1, data @1 + p = mem.malloc(24, false).getPointer(); + byte[] buf = new byte[24]; + buf[0] = (byte) (len << 1); + System.arraycopy(data, 0, buf, 1, len); + p.write(0, buf, 0, buf.length); + } else { + // long: [0..7]=cap(LSB=1 = 0xffff...f), [8..15]=size, [16..23]=ptr + UnidbgPointer heap = mem.malloc(len + 1, false).getPointer(); + heap.write(0, data, 0, len); + heap.write(len, new byte[]{0}, 0, 1); + p = mem.malloc(24, false).getPointer(); + byte[] cap = new byte[8]; Arrays.fill(cap, (byte) 0xff); + p.write(0, cap, 0, 8); + p.write(8, new byte[]{(byte) (len & 0xff), (byte) (len >> 8), 0, 0, 0, 0, 0, 0}, 0, 8); + byte[] ptrBytes = new byte[8]; + long pv = UnidbgPointer.nativeValue(heap); + for (int i = 0; i < 8; i++) ptrBytes[i] = (byte) (pv >> (8 * i)); + p.write(16, ptrBytes, 0, 8); + } + return p; + } + + String readStdString(UnidbgPointer p) { + byte[] one = new byte[1]; + p.read(0, one, 0, 1); + int first = one[0] & 0xff; + if ((first & 1) == 0) { + int len = first >> 1; + byte[] dat = new byte[len]; + p.read(1, dat, 0, len); + return new String(dat, java.nio.charset.StandardCharsets.ISO_8859_1); + } else { + long size = 0; + byte[] szb = new byte[8]; + p.read(8, szb, 0, 8); + for (int i = 0; i < 8; i++) size |= ((long) (szb[i] & 0xff)) << (8 * i); + byte[] ptrb = new byte[8]; + p.read(16, ptrb, 0, 8); + long pv = 0; + for (int i = 0; i < 8; i++) pv |= ((long) (ptrb[i] & 0xff)) << (8 * i); + UnidbgPointer data = UnidbgPointer.pointer(this.emulator, pv); + byte[] dat2 = new byte[(int) size]; + data.read(0, dat2, 0, (int) size); + return new String(dat2, java.nio.charset.StandardCharsets.ISO_8859_1); + } + } + + String callEncodeAes(String in, String key) { + UnidbgPointer pin = writeStdString(in); + UnidbgPointer pkey = writeStdString(key); + UnidbgPointer pout = writeStdString(""); + long base = module.base; + Number retN = module.callFunction(emulator, 0x330218L, UnidbgPointer.nativeValue(pin), UnidbgPointer.nativeValue(pkey), UnidbgPointer.nativeValue(pout)); + long ret = retN == null ? 0 : retN.longValue(); + String out = readStdString(pout); + System.out.println("[encode_aes] in=" + in + " key=" + key + " -> out=" + out + " hex=" + toHex(out) + " (ret=" + ret + ")"); + return out; + } + + static String toHex(String s) { + StringBuilder sb = new StringBuilder(); + for (byte b : s.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1)) sb.append(String.format("%02x", b)); + return sb.toString(); + } + + public static void main(String[] args) throws Exception { + String so = args.length > 0 ? args[0] : "so/libudbauthunify_merged.so"; + AesProbe p = new AesProbe(so); + // 基准: 已知 16B 明文 + 24B 钥 + // 64B 钥材料测试 (KeyExpansion 以 16B 步长读!!) + String k64_zmhav = "ZMHAVPRaxJ3MtXDjduUnXAKQ" + "\0".repeat(0); + k64_zmhav = String.format("%-64s", "ZMHAVPRaxJ3MtXDjduUnXAKQ").replace(' ', '\0'); + String k64_ow = String.format("%-64s", "owNMiaCgcHmqoTr3iRamFuHj").replace(' ', '\0'); + String k64_cfg0 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + p.callEncodeAes("0123456789abcdef", k64_zmhav); + p.callEncodeAes("0123456789abcdef", k64_ow); + p.callEncodeAes("0123456789abcdef", k64_cfg0); + p.callEncodeAes("1e8bdf7d4f7a01d3", k64_zmhav); + p.callEncodeAes("1e8bdf7d4f7a01d3", k64_cfg0); + p.emulator.close(); + } +} \ No newline at end of file