From 6bde9beec2b3efd71dcfc102bae59dcf99b437d2 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 02:10:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20getSDID=E6=96=B0=E5=80=BC?= =?UTF-8?q?=E6=97=8F(36B=20base64=E5=B8=B8=E9=87=8F=E8=AF=81=E4=B9=A6)=20+?= =?UTF-8?q?=20A=E8=B7=AF=E5=BE=84=E5=AE=9E=E9=AA=8C=E5=AE=9A=E7=95=8C(APK?= =?UTF-8?q?=E5=8F=AF=E8=BD=BDJava=E4=B8=8D=E5=8F=AF=E8=B0=83)=20+=20LoginF?= =?UTF-8?q?low=E6=A1=A5=E6=8E=A5harness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/HUYA_HDID_ALGORITHM_GEN.md | 16 ++ tools/unidbg/hydev/src/hydev/AesProbe.java | 12 ++ tools/unidbg/hydev/src/hydev/HyDeviceId.java | 5 + tools/unidbg/hydev/src/hydev/LoginFlow.java | 161 +++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 tools/unidbg/hydev/src/hydev/LoginFlow.java diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 355f17a..204422f 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -780,3 +780,19 @@ UdbAESUtilC1(…) + encrypt ← 后段 - 0x3ba2f4: hdid/ok/adb_ (调试) - OTP mid 长度前缀族 (JCE varint/1/2/4B BE/LE + 消息名前置) = 零命中 → 组合含更深的 cred_packls 字段流 - 下轮: 0x3ba484-xref (hdid: 日志的调用者 = 32hex 装配函数) + cred_packls 静态串流 + +## §11.29 A路径实测边界 + getSDID 新值族发现 (R6) +### A路径 (unidbg 跑全 Java 登录流) = 实验性定界 +- APK 直载成功 (createDalvikVM(apk) ✓), 双库加载 ✓, HuyaAuth 类可解析 ✓ +- **纯 Java 方法不可调** (callStaticJniMethod 只走 JNI 符号; unidbg 无 dex 字节码解释器) +- 若要跑 Java 侧 → ProxyClassFactory 主机 JVM 方案 (dex→class 编译登录链 = 重型) + +### getSDID 新值族! +- getSDID@0x20ede8 = `*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T` (=36B base64) +- **输入不透明性: 换指纹 GUID/MID 全变, SDID 恒定** = 静态钥材料计算的 native 证书 +- SDID 36B: 16B 块 × encode_aes(8钥)/decode_aes(6钥)/md5/sha1/hmac = 全零命中; 帧字段×SDID 签名族 = 零 +- 与 ed0db8 关系未定 (golden 帧 = 08-24 态; harness SDID 恒定同值 ⇒ 若关系存在则可验!) + +### OTP 消息名差分 +hypasswordLogin/MsgLoginReq/LogLoginReq → mid 各异, tail 恒定 8b38f1 → appSign ≠ OTP 任何段 +### 下轮: setSafeDeviceId 直调后 getHDID 状态变化 + 0x64670 "hdid: " 装配函数直调 diff --git a/tools/unidbg/hydev/src/hydev/AesProbe.java b/tools/unidbg/hydev/src/hydev/AesProbe.java index 47f3c4e..7e5164a 100644 --- a/tools/unidbg/hydev/src/hydev/AesProbe.java +++ b/tools/unidbg/hydev/src/hydev/AesProbe.java @@ -403,6 +403,18 @@ 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"); + // 登录消息名 OTP 对拍 (mid 16B 与 ed0db8 对照) + p.callOtp("hypasswordLogin", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); + p.callOtp("hypasswordLogin", "owNMiaCgcHmqoTr3iRamFuHj"); + p.callOtp("MsgLoginReq", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); + p.callOtp("LogLoginReq", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); + // SDID 块直解 (36B 证书的两个16B块 × 全部钥) + p.callDecodeAes("859acf6fad86b24ac479361c2de5132f", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); + p.callDecodeAes("859acf6fad86b24ac479361c2de5132f", "owNMiaCgcHmqoTr3iRamFuHj"); + p.callDecodeAes("859acf6fad86b24ac479361c2de5132f", "HuyaUdb1928374650qwertyuiop"); + p.callDecodeAes("57c9d4e47c932df1f6a5d0ab4b3a2022", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); + p.callDecodeAes("57c9d4e47c932df1f6a5d0ab4b3a2022", "owNMiaCgcHmqoTr3iRamFuHj"); + p.callDecodeAes("57c9d4e47c932df1f6a5d0ab4b3a2022", "HuyaUdb1928374650qwertyuiop"); p.dumpRegion("0x127ddbe0", 0x127ddf00L, 0x420); p.dumpRegion("0x127ddf00", 0x127ddf00L, 0x200); p.emulator.close(); diff --git a/tools/unidbg/hydev/src/hydev/HyDeviceId.java b/tools/unidbg/hydev/src/hydev/HyDeviceId.java index 528e6e5..e29479b 100644 --- a/tools/unidbg/hydev/src/hydev/HyDeviceId.java +++ b/tools/unidbg/hydev/src/hydev/HyDeviceId.java @@ -147,6 +147,10 @@ public class HyDeviceId extends AbstractJni { else if (i == 101 || i == 102) v = System.getProperty("hydev.android_id", "1e8bdf7d4f7a01d3"); else if (i == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df"; else if (i == 104 || i == 105) v = ""; + else if (i == 99 || i == 103) v = "7c5387e0539c023c31c4ff0e807e7256117385ee"; + else if (i == 5) v = "{\"install\":\"2024-08-24 10:00:00\"}"; + else if (i == 107) v = "1e8bdf7d4f7a01d3"; // qimei16 + else if (i == 108) v = "0a7dfaa882938a6ab502511452142c571e8bdf7d"; // qimei36 猜测 else if (i == 1) v = "5008"; else if (i == 2) v = "13.4.22"; else if (i == 3) v = "1.14.68"; @@ -350,6 +354,7 @@ public class HyDeviceId extends AbstractJni { String guid = h.callString(0x20f8c8L, "getGUID"); String cdid = h.callString(0x20eb08L, "getCDID"); String hdid = h.callString(0x20f0c8L, "getHDID"); + String sdid = h.callString(0x20ede8L, "getSDID"); String mid = h.callString(0x20fba8L, "getMID"); // 32hex 探针: 再调一轮以观察缓存 (若 init 后 getGUID 重取 = 缓存) h.emulator.close(); diff --git a/tools/unidbg/hydev/src/hydev/LoginFlow.java b/tools/unidbg/hydev/src/hydev/LoginFlow.java new file mode 100644 index 0000000..b41e3c6 --- /dev/null +++ b/tools/unidbg/hydev/src/hydev/LoginFlow.java @@ -0,0 +1,161 @@ +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.Unicorn2Factory; +import com.github.unidbg.linux.android.AndroidEmulatorBuilder; +import com.github.unidbg.linux.android.AndroidResolver; +import com.github.unidbg.linux.android.dvm.AbstractJni; +import com.github.unidbg.linux.android.dvm.BaseVM; +import com.github.unidbg.linux.android.dvm.DalvikModule; +import com.github.unidbg.linux.android.dvm.DvmClass; +import com.github.unidbg.linux.android.dvm.DvmObject; +import com.github.unidbg.linux.android.dvm.VM; +import com.github.unidbg.memory.Memory; +import com.github.unidbg.memory.MemoryBlock; +import com.github.unidbg.pointer.UnidbgPointer; + +import java.io.File; + +/** + * A路径: APK 全 dex 加载 + 双 native 库 + 驱动 HuyaAuth 登录流 + * 目标: 让 native 真执行 5008 帧编码(含 appSign 签发), 捕获每帧的 32hex + */ +public class LoginFlow extends AbstractJni { + + private final AndroidEmulator emulator; + private final VM vm; + private final Module hydev; + private final Module udb; + + LoginFlow(String apkPath, String hydevSo, String udbSo) throws Exception { + BackendFactory backend = new Unicorn2Factory(true); + emulator = AndroidEmulatorBuilder.for64Bit() + .setProcessName("com.duowan.kiwi") + .addBackendFactory(backend) + .build(); + Memory memory = emulator.getMemory(); + memory.setLibraryResolver(new AndroidResolver(23)); + vm = emulator.createDalvikVM(new File(apkPath)); + vm.setJni(this); + vm.setVerbose(false); + + DalvikModule dm1 = vm.loadLibrary(new File(hydevSo), false); + dm1.callJNI_OnLoad(emulator); + hydev = dm1.getModule(); + System.out.println("[*] hydeviceid base=0x" + Long.toHexString(hydev.base)); + + DalvikModule dm2 = vm.loadLibrary(new File(udbSo), false); + dm2.callJNI_OnLoad(emulator); + udb = dm2.getModule(); + System.out.println("[*] udbauthunify base=0x" + Long.toHexString(udb.base)); + } + + // ==================== JNI 桩 (保守: 记录 + 回 null/默认) ==================== + + private static String strArg(Object va, int idx) { + // 兼容: DvmObject 数组或 VaList — 用反射最省事? 不, 用通用 VaList 兼容写法 + return null; + } + + @Override + public DvmObject callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:SObj] " + dvmClass.getClassName() + "->" + signature); + try { + return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList); + } catch (Throwable t) { + // 常见可空兜底 + if (signature.endsWith("Ljava/lang/String;") || signature.endsWith("Landroid/content/Context;") + || signature.endsWith("Ljava/lang/Object;")) { + return null; + } + throw new RuntimeException(t); + } + } + + @Override + public DvmObject callObjectMethodV(BaseVM vm, DvmObject dvmObject, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:Obj] " + dvmObject.getObjectType() + "->" + signature); + try { + return super.callObjectMethodV(vm, dvmObject, signature, vaList); + } catch (Throwable t) { + if (signature.endsWith("Ljava/lang/String;") || signature.endsWith("Landroid/content/Context;")) return null; + throw new RuntimeException(t); + } + } + + @Override + public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:SInt] " + dvmClass.getClassName() + "->" + signature); + return super.callStaticIntMethodV(vm, dvmClass, signature, vaList); + } + + @Override + public int callIntMethodV(BaseVM vm, DvmObject dvmObject, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:Int] " + dvmObject.getObjectType() + "->" + signature); + return super.callIntMethodV(vm, dvmObject, signature, vaList); + } + + @Override + public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:SBool] " + dvmClass.getClassName() + "->" + signature); + return super.callStaticBooleanMethodV(vm, dvmClass, signature, vaList); + } + + @Override + public boolean callBooleanMethodV(BaseVM vm, DvmObject dvmObject, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:Bool] " + dvmObject.getObjectType() + "->" + signature); + return super.callBooleanMethodV(vm, dvmObject, signature, vaList); + } + + @Override + public long callStaticLongMethodV(BaseVM vm, DvmClass dvmClass, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + return super.callStaticLongMethodV(vm, dvmClass, signature, vaList); + } + + @Override + public DvmObject getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) { + System.out.println("[JNI:SField] " + dvmClass.getClassName() + " " + signature); + try { + return super.getStaticObjectField(vm, dvmClass, signature); + } catch (Throwable t) { + return null; + } + } + + @Override + public void callVoidMethodV(BaseVM vm, DvmObject dvmObject, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:Void] " + dvmObject.getObjectType() + "->" + signature); + super.callVoidMethodV(vm, dvmObject, signature, vaList); + } + + @Override + public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, com.github.unidbg.linux.android.dvm.VaList vaList) { + System.out.println("[JNI:SVoid] " + dvmClass.getClassName() + "->" + signature); + super.callStaticVoidMethodV(vm, dvmClass, signature, vaList); + } + + @Override + public DvmObject getObjectField(BaseVM vm, DvmObject dvmObject, String signature) { + System.out.println("[JNI:Field] " + dvmObject.getObjectType() + " " + signature); + try { + return super.getObjectField(vm, dvmObject, signature); + } catch (Throwable t) { + return null; + } + } + + public static void main(String[] args) throws Exception { + String apk = "/Users/yml/codes/douyu_login_py/apks/huya-13.4.22-115315-live.apk"; + String hydevSo = "/Users/yml/codes/douyu_login_py/tools/unidbg/hydev/so/libhydeviceid_merged_r2.so"; + String udbSo = "/Users/yml/codes/douyu_login_py/tools/unidbg/hydev/so/libudbauthunify_merged.so"; + LoginFlow lf = new LoginFlow(apk, hydevSo, udbSo); + System.out.println("=== 解析 HuyaAuth ==="); + DvmClass ha = lf.vm.resolveClass("com/huyaudbunify/HuyaAuth"); + System.out.println("HuyaAuth resolved: " + ha); + ha.callStaticJniMethod(lf.emulator, "getInstance()Lcom/huyaudbunify/HuyaAuth;"); + System.out.println("getInstance OK"); + lf.emulator.close(); + } +} \ No newline at end of file