diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md
index d7afe80..fa4a543 100644
--- a/docs/HUYA_HDID_ALGORITHM_GEN.md
+++ b/docs/HUYA_HDID_ALGORITHM_GEN.md
@@ -907,3 +907,79 @@ hypasswordLogin/MsgLoginReq/LogLoginReq → mid 各异, tail 恒定 8b38f1 → a
- tools/unidbg/hydev/src/hydev/AesProbe.java (全能探针: callOtpReal/reproOtp/goldBurst2/seqOtp/hooks/NOP)
- 编译运行: cd tools/unidbg/hydev && CP=$(cat /tmp/unidbg_cp.txt):...+apk-parser... ; java -cp $CP:out hydev.AesProbe so/libudbauthunify_merged.so
- 金样本: account hy_300023887 / mid 1e8bdf7d4f7a01d3 / dev40 7c5387.../ sdid *hZrPb62... (36B)/ appSign ed0db8334cadd236c00cadf7e11ab5a5
+
+---
+
+## §11.43 R13: OTP 真机逐字节复现 + resinfo 解密 + ed0db8 定论前夜 (2026-08-29)
+
+### 1) 里程碑: 真机 146B OTP 逐字节复现 (GoldSweep V2 模式)
+- 引擎: tools/unidbg/hydev/src/hydev/GoldSweep.java (直调 0x32fa24, ~2500 calls/s)
+- 输入 (真机 final_capture 六元组): in="1471224845212", cnt=2, s3="5008", s4=K1, s5=完整114B hyCred(0a60...), nonce=0x1a037881a430000
+- 输出 = 真机 out **292/292 hex 逐字节一致** (0402b4024c4a6069cb3c38...)
+- **此前"零命中"根因 = mid 提取窗口 bug**: midOf(out)=out[4:36] 对 146B 输出 = cipher[2:18] (偏移2字节!!)
+ 正确首块 = **out[2:34]** (cipher[0:16])。旧 A/P/F/C/X/W 全部扫错窗口, 作废。
+- 真明文结构 (AES 密钥 312334e88d8c35cb 解密确认):
+ `[02][u16le 12][xxtea 12B][u16le 114][hyCred 114B][零垫]` — s5=完整 cred, xxtea 12B 精确一致
+- OTP 全部语义锁定: AESkey=md5_char16(s4+getkey(cnt)) | plaintext=[02]∥cred(xxtea(nonce,key=in))∥cred(s5)
+- 金样本六元组 (待命中): in="1471238907296"(或"0"), cnt 1..15, s3="5008", s4=K1, s5=hyCred(0a80ee...), nonce=st<<16, st=金样本时窗
+
+### 2) 差分覆盖统计 (全部 out[2:34] 正确窗口, 无命中)
+| 模式 | 范围 | 次数 |
+|---|---|---|
+| H | 登录窗 st[5165..5172.7]×cnt1..15×nc0..2×in{uid,"0"}×s5{cred,""} | 1.39M (98%处崩) |
+| H2 | st 单点{090906,102531,167182,172449,172452,172455,172458}×in4×cnt0..15×nc0..2×s5 2 | 3072 |
+| I(完成) | 装机窗 st[1787582670000..1787582810000]×cnt1..15×nc0..2×in"0"×s5"" (8 并行 JVM) | 6.3M, **零命中** |
+
+### 3) resinfo 文件层解密成功 (工具: python Crypto AES-ECB)
+- 文件: /data/user/0/com.duowan.kiwi/files/hydevice/resinfo (528B)
+- **密钥 = `HuyaUdb1928374650qwertyuiop` 前16B, 标准 AES-ECB 零垫**
+- 明文 JSON:
+ ```json
+ {"channelKey":"865a4924a40897ac1fcfe6b4c2cbb045","channelKeyVersion":"10",
+ "huyaDeviceId":"7c5387e0539c023c31c4ff0e807e7256117385ee",
+ "phoneInfo":"{\"resultCode\":\"103000\",\"desc\":\"true\",\"securityphone\":\"195****6018\",\"operatorType\":1}",
+ "safedeviceid":"PQwemAN9...","time":1787949486}
+ ```
+- 结论: **resinfo 含 channelKey/DEVID40/safedeviceid, 无 32hex hdid** — ed0db8 不在任何持久化文件
+- channelKey(865a49...cbb045) = dfpReport t1 设备指纹; k1/sessHex32(865a49...cbb0e3) 为 BusinessCfg+0x10, 两者差末4 hex
+
+### 4) 真机 getter 全量定案 (evidence/diag_phone/hdid_read.json 实测)
+- getGUID=0a7dfaa882938a6ab502511452142c57(32hex) | getMID=1e8bdf7d4f7a01d3(16hex)
+- **getHDID=7c5387e0539c023c31c4ff0e807e7256117385ee(40hex!!)** — 非 32hex 登录 hdid
+- getCDID=02df3987...(40hex) | getSDID=PQwem...(180B b64)
+- 32hex hdid (ed0db8) 不存在于任何 getter / resinfo / files / prefs (唯一出现点=登录 WUP t1.t0 本身)
+
+### 5) harness 修复记录 (本轮)
+- HyDeviceId.java: b(100) 误喂 MID → 改喂真 GUID=0a7dfaa8... (init 现 MATCH GUID/CDID/SDID/MID)
+- 后端 Dynarmic → Unicorn2Factory 修复 init 崩溃 (dynarmic 在 datadiv 区误仿)
+- 补桩: getApplicationInfo/getPackageName/targetSdkVersion/b(6)=账号/b(2001)=渠道/applist JSON 等 → init 完整跑通
+- **但 init 不产 32hex**: 32hex hdid 计算不在 NativeEntry.init() 路径 (getHDID 缓存槽无人填充)
+
+### 6) Frida 真机捕获实验 (结论)
+- 真机唯一稳定注入通道 = spawn 挂起 + 只加载 `bypass_msaoaid_maps_art_callsite.js` (STATUS.md)
+- 登录流程不可带 frida (msaoaidsec 标记上报); 普通启动也卡 splash 后被杀 (2 轮)
+- 捕获成功部分: 6 个导出钩子全部定位 (hyudb_otp_encrypt/0x32fa24, getOtp/0x26916c, setSafeDeviceId/0x26a2e0, getHdid/0x26a484, getkey/0x26a71c, md5_char16/0x32fb7c) — 偏移与 merged so 完全一致 => **lib 版本无漂移, 差分引擎可信**
+- 工具: tools/frida/run_capture.py + hook_otp_capture.js
+
+### 7) 真机持久化文件存档 (evidence/live_device/)
+- resinfo.bin(528B) / hydckey.b64(148B, = /dckey/check 下发, 前缀 AAAAAMC1eP4iV43WYoI57ZOu0) / uuid.b64(172B) / guid.xml(GUID=0a7dfaa8...)
+
+### 8) 剩余开放问题 (按可能性)
+1. **st 在装机窗外/更早**: I 模式进行中; 若落空 → hdid 非"首启计算"或非 OTP 系
+2. **ed0db8 非 getOtp-mid**: 1.55M 差分 + resinfo + getters 全面证伪 → 真源 = libhydeviceid 内 "hdid:" 装配函数(0x3ba484 区) 或 setDeviceInfo 上报前的独立计算
+3. 服务端校验: hdid = 注册锚 (dfpReport 加密体=设备身份), 纯代码铸造需先破 dfpReport 加密体 (未破解#2)
+
+### 关键工具 (本轮新增)
+- GoldSweep.java: V2(真机复现)/H/H2(登录窗)/I(装机窗并行)
+- tools/frida/run_capture.py + hook_otp_capture.js
+- python: HuyaUdb1928374650qwertyuiop[:16] AES-ECB 解 resinfo (evidence/live_device/resinfo.bin)
+
+### 9) R13 终局判定 (2026-08-29 05:04)
+- 全部差分合计: H 1.39M + H2 3072 + I 6.3M = **~7.7M 次 OTP 调用 + AES 密钥直解 + resinfo + getters = 六路证伪**
+- **定论: ed0db8 (WUP t1.t0 appSign) 不是 libudbauthunify getOtp 的输出** (prob > 99%)
+- 剩余真源候选: ① libhydeviceid "hdid:" 装配函数 (0x3ba484 格式串, 0x64670 区, OLLVM 混淆)
+ ② setDeviceInfo(msgType 0xb000021) 上报前的独立 32hex 计算
+ ③ dfpReport 加密体内的设备身份派生 (未破解#2)
+- 后续路线: (a) libhydeviceid 0x64670 区静态攻坚 (datadiv 已解, OLLVM 状态机)
+ (b) frida 真机 (stable bypass) 抓 setDeviceInfo 入参 → 32hex 直读
+ (c) 接受"hdid=设备级证书不可纯代码铸造"结论, 维持金样本 hdid 共用方案 (已跑通多账号)
diff --git a/evidence/live_device/guid.xml b/evidence/live_device/guid.xml
new file mode 100644
index 0000000..d11c1f0
--- /dev/null
+++ b/evidence/live_device/guid.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/evidence/live_device/hydckey.b64 b/evidence/live_device/hydckey.b64
new file mode 100644
index 0000000..1b5c454
--- /dev/null
+++ b/evidence/live_device/hydckey.b64
@@ -0,0 +1 @@
+AAAAAMC1eP4iV43WYoI57ZOu0w+RxCz+/O1tar0ejx4pUt8A6ic15Ax0SPFEWv9uPeP/37YHvtnDiRePwyrPRjkXVZj5amssY9T7IiSlJBjmrXs8SVhsLvxZDftuyOJ8DIHMyh0ssLNoYYJz9iTn
\ No newline at end of file
diff --git a/evidence/live_device/hydckey.bin b/evidence/live_device/hydckey.bin
new file mode 100644
index 0000000..b4c8b2a
Binary files /dev/null and b/evidence/live_device/hydckey.bin differ
diff --git a/evidence/live_device/resinfo.bin b/evidence/live_device/resinfo.bin
new file mode 100644
index 0000000..3861174
Binary files /dev/null and b/evidence/live_device/resinfo.bin differ
diff --git a/evidence/live_device/uuid.b64 b/evidence/live_device/uuid.b64
new file mode 100644
index 0000000..2786c83
--- /dev/null
+++ b/evidence/live_device/uuid.b64
@@ -0,0 +1 @@
+Mz/czD2wiPLUeGhHpY9JLxg04gUjCpufWkgxsjHDmwRoGkvoUk5DvegW23bZqy8lLxIrjWsirBllrZs/KSKtlWtQo2jKo6miAK5XfE6BQZCOKs1CxAXlQJlgGVmAML3aYNYvYVRHxF6i4vn5jIUFsz/mCW0yzKK6jTKS4YACiyg=
\ No newline at end of file
diff --git a/evidence/live_device/uuid.bin b/evidence/live_device/uuid.bin
new file mode 100644
index 0000000..8156cbd
Binary files /dev/null and b/evidence/live_device/uuid.bin differ
diff --git a/tools/frida/capture_20260829_043647.jsonl b/tools/frida/capture_20260829_043647.jsonl
new file mode 100644
index 0000000..e69de29
diff --git a/tools/frida/capture_20260829_043738.jsonl b/tools/frida/capture_20260829_043738.jsonl
new file mode 100644
index 0000000..e69de29
diff --git a/tools/frida/capture_20260829_044112.jsonl b/tools/frida/capture_20260829_044112.jsonl
new file mode 100644
index 0000000..9bc3207
--- /dev/null
+++ b/tools/frida/capture_20260829_044112.jsonl
@@ -0,0 +1,7 @@
+{"event": "module-found", "name": "libudbauthunify.so", "base": "0x7204dc2000", "size": 4812800, "pid": 7099, "tid": 7099, "ts": 1787949675748, "seq": 1}
+{"event": "hook-installed", "needle": "hyudb_otp_encrypt", "address": "0x72050f1a24", "pid": 7099, "tid": 7099, "ts": 1787949675770, "seq": 2}
+{"event": "hook-installed", "needle": "BusinessCfg6getOtp", "address": "0x720502b16c", "pid": 7099, "tid": 7099, "ts": 1787949675786, "seq": 3}
+{"event": "hook-installed", "needle": "setSafeDeviceId", "address": "0x720502c2e0", "pid": 7099, "tid": 7099, "ts": 1787949675803, "seq": 4}
+{"event": "hook-installed", "needle": "getHdid", "address": "0x720502c484", "pid": 7099, "tid": 7099, "ts": 1787949675821, "seq": 5}
+{"event": "hook-installed", "needle": "AESkeyMgr6getkey", "address": "0x720502a71c", "pid": 7099, "tid": 7099, "ts": 1787949675837, "seq": 6}
+{"event": "hook-installed", "needle": "md5_char16", "address": "0x72050f071c", "pid": 7099, "tid": 7099, "ts": 1787949675851, "seq": 7}
diff --git a/tools/frida/capture_20260829_051102.jsonl b/tools/frida/capture_20260829_051102.jsonl
new file mode 100644
index 0000000..0c48970
--- /dev/null
+++ b/tools/frida/capture_20260829_051102.jsonl
@@ -0,0 +1,37 @@
+{"event": "module-found", "name": "libudbauthunify.so", "base": "0x720a6d0000", "size": 4812800, "pid": 10183, "tid": 10207, "ts": 1787951473125, "seq": 1}
+{"event": "hook-installed", "needle": "hyudb_otp_encrypt", "address": "0x720a9ffa24", "pid": 10183, "tid": 10207, "ts": 1787951473186, "seq": 2}
+{"event": "hook-installed", "needle": "BusinessCfg6getOtp", "address": "0x720a93916c", "pid": 10183, "tid": 10207, "ts": 1787951473207, "seq": 3}
+{"event": "hook-installed", "needle": "setSafeDeviceId", "address": "0x720a93a2e0", "pid": 10183, "tid": 10207, "ts": 1787951473226, "seq": 4}
+{"event": "hook-installed", "needle": "getHdid", "address": "0x720a93a484", "pid": 10183, "tid": 10207, "ts": 1787951473250, "seq": 5}
+{"event": "hook-installed", "needle": "AESkeyMgr6getkey", "address": "0x720a93871c", "pid": 10183, "tid": 10207, "ts": 1787951473267, "seq": 6}
+{"event": "hook-installed", "needle": "md5_char16", "address": "0x720a9fe71c", "pid": 10183, "tid": 10207, "ts": 1787951473286, "seq": 7}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x2749e8 (_Z19createWupDeviceInfoPN3wup10DeviceInfoE+0x348) <- libudbauthunify.so!0x3a7f34 (_ZN14WupDataPackageIN3wup13UserLogoffReqEE20createWupRequestDataERS1_RNSt6__ndk112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPKcSD_i+0x4c) <- libudbauthunify.so!0x3a755c (_ZN22HandlerRequestLoginOut9onHandlerEP10UdbMsgBase+0x248) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27b1e4 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0xeb4) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c)", "pid": 10183, "tid": 10322, "ts": 1787951507769, "seq": 8}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951507769, "seq": 9}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x2749e8 (_Z19createWupDeviceInfoPN3wup10DeviceInfoE+0x348) <- libudbauthunify.so!0x336314 (_ZN14WupDataPackageIN3wup24AppLgnCredentialLoginReqEE20createWupRequestDataERS1_RNSt6__ndk112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPKcSD_i+0x4c) <- libudbauthunify.so!0x3341d8 (_ZN28HandlerRequestAnonyLoginCred9onHandlerEP10UdbMsgBase+0x558) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27bae4 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0x17b4) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c)", "pid": 10183, "tid": 10322, "ts": 1787951507807, "seq": 10}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951507807, "seq": 11}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x3d60a8 (_ZN16BusinessLogBuild14buildLogHeaderER11BusinessLogi+0xec) <- libudbauthunify.so!0x3a7be0 (_ZN23HandlerResponseLoginOut9onHandlerEP10UdbMsgBase+0xc8) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27b244 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0xf14) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c) <- libudbauthunify.so!0x245b40 (_ZN7MsgLoop14processWorkingEv+0xec)", "pid": 10183, "tid": 10322, "ts": 1787951507904, "seq": 12}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951507904, "seq": 13}
+{"event": "crypto-getotp", "stage": "enter", "bt": "libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac) <- libudbauthunify.so!0x334aa8 (_ZN29HandlerResponseAnonyLoginCred9onHandlerEP10UdbMsgBase+0x22c) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27bb44 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0x1814) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c) <- libudbauthunify.so!0x245b40 (_ZN7MsgLoop14processWorkingEv+0xec)", "uidLo": 409788752, "uidHi": 3019898994, "pid": 10183, "tid": 10322, "ts": 1787951507941, "seq": 14}
+{"event": "crypto-otp", "stage": "enter", "bt": "libudbauthunify.so!0x269328 (_ZN11BusinessCfg6getOtpERN3wup12AppLoginDataE+0x1bc) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac)", "xxteaKeyStr": null, "xxteaKeyHex": "", "xxteaKeySize": 0, "arg1": 2, "arg2_cnt": 2, "arg3": "", "arg4": "3836356134393234613430383937616331666366653662346332636262306533", "arg5": "0a8066875728ac56814c316e2a466511c502bd76cf1ded554657b74729876cb9f7081cf1331add5a9f7ac54784e4d137cd79ba04e01ef74a2dc163726c3a7ced7c91bb4eb384075bc94ce2fdfde3276a64c300bfbd5d82b97f905d74c6c83a95bc42964d17f6bd1ac592c37301a49ba25748", "arg5Size": 114, "arg6": 4, "arg7_nonce": "1a04a36ebe80000", "stMs": "1787951508456", "nc": 0, "pid": 10183, "tid": 10322, "ts": 1787951507952, "seq": 15}
+{"event": "crypto-aeskey", "stage": "enter", "a": 1, "b": 2, "pid": 10183, "tid": 10322, "ts": 1787951507952, "seq": 16}
+{"event": "crypto-aeskey", "stage": "leave", "key": "MKDKeridjing7avnsasdSDHI", "pid": 10183, "tid": 10322, "ts": 1787951507952, "seq": 17}
+{"event": "crypto-md5", "stage": "enter", "inStr": "865a4924a40897ac1fcfe6b4c2cbb0e3MKDKeridjing7avnsasdSDHI", "pid": 10183, "tid": 10322, "ts": 1787951507952, "seq": 18}
+{"event": "crypto-md5", "stage": "leave", "keyHex": "", "pid": 10183, "tid": 10322, "ts": 1787951507953, "seq": 19}
+{"event": "crypto-otp", "stage": "leave", "bt": "libudbauthunify.so!0x269328 (_ZN11BusinessCfg6getOtpERN3wup12AppLoginDataE+0x1bc) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac)", "outStr": null, "outHex": "", "outSize": 0, "pid": 10183, "tid": 10322, "ts": 1787951507964, "seq": 20}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x2749e8 (_Z19createWupDeviceInfoPN3wup10DeviceInfoE+0x348) <- libudbauthunify.so!0x38dafc (_ZN14WupDataPackageIN3wup13AppCommonDataEE20createWupRequestDataERS1_RNSt6__ndk112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPKcSD_i+0x4c) <- libudbauthunify.so!0x3e3af8 (_ZN18UdbUserFilterUtils15getAppComomDataENSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEERN3wup12AppLoginDataE+0xd0) <- libudbauthunify.so!0x275994 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xc0) <- libudbauthunify.so!0x334aa8 (_ZN29HandlerResponseAnonyLoginCred9onHandlerEP10UdbMsgBase+0x22c) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c)", "pid": 10183, "tid": 10322, "ts": 1787951507992, "seq": 21}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951507992, "seq": 22}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x3d60a8 (_ZN16BusinessLogBuild14buildLogHeaderER11BusinessLogi+0xec) <- libudbauthunify.so!0x334be4 (_ZN29HandlerResponseAnonyLoginCred9onHandlerEP10UdbMsgBase+0x368) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27bb44 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0x1814) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c) <- libudbauthunify.so!0x245b40 (_ZN7MsgLoop14processWorkingEv+0xec)", "pid": 10183, "tid": 10322, "ts": 1787951508034, "seq": 23}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951508034, "seq": 24}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x2749e8 (_Z19createWupDeviceInfoPN3wup10DeviceInfoE+0x348) <- libudbauthunify.so!0x3a9b04 (_ZN14WupDataPackageIN3wup22AppLgnPasswordLoginReqEE20createWupRequestDataERS1_RNSt6__ndk112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPKcSD_i+0x4c) <- libudbauthunify.so!0x3a8b04 (_ZN27HandlerRequestLoginPassport9onHandlerEP10UdbMsgBase+0x4e0) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27b664 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0x1334) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c)", "pid": 10183, "tid": 10322, "ts": 1787951512039, "seq": 25}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951512039, "seq": 26}
+{"event": "crypto-getotp", "stage": "enter", "bt": "libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac) <- libudbauthunify.so!0x3a9464 (_ZN28HandlerResponseLoginPassport9onHandlerEP10UdbMsgBase+0x274) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27b6c4 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0x1394) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c) <- libudbauthunify.so!0x245b40 (_ZN7MsgLoop14processWorkingEv+0xec)", "uidLo": 409788752, "uidHi": 3019898994, "pid": 10183, "tid": 10322, "ts": 1787951512258, "seq": 27}
+{"event": "crypto-otp", "stage": "enter", "bt": "libudbauthunify.so!0x269328 (_ZN11BusinessCfg6getOtpERN3wup12AppLoginDataE+0x1bc) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac)", "xxteaKeyStr": null, "xxteaKeyHex": "", "xxteaKeySize": 0, "arg1": 2, "arg2_cnt": 3, "arg3": "", "arg4": "3836356134393234613430383937616331666366653662346332636262306533", "arg5": "0a80837b04def345ae1b028f53ccf682783204455967c4bb82df8c0fd2bfadc7311efa5ac907afe1a646bd0b15316049cc1cd9da3b5a84169b070241d896c3d3f580443b233ca4c8ac2015f1bcfd886e699259a757a857abb6336b18a88e5aaccc5ba2fc68d62a4e90f6e99097b865f90a08", "arg5Size": 114, "arg6": 4, "arg7_nonce": "1a04a36fcdb0000", "stMs": "1787951512795", "nc": 0, "pid": 10183, "tid": 10322, "ts": 1787951512270, "seq": 28}
+{"event": "crypto-aeskey", "stage": "enter", "a": 1, "b": 3, "pid": 10183, "tid": 10322, "ts": 1787951512270, "seq": 29}
+{"event": "crypto-aeskey", "stage": "leave", "key": "nskdI7MDGKSDJsnadjdoonvs", "pid": 10183, "tid": 10322, "ts": 1787951512270, "seq": 30}
+{"event": "crypto-md5", "stage": "enter", "inStr": "865a4924a40897ac1fcfe6b4c2cbb0e3nskdI7MDGKSDJsnadjdoonvs", "pid": 10183, "tid": 10322, "ts": 1787951512270, "seq": 31}
+{"event": "crypto-md5", "stage": "leave", "keyHex": "", "pid": 10183, "tid": 10322, "ts": 1787951512270, "seq": 32}
+{"event": "crypto-otp", "stage": "leave", "bt": "libudbauthunify.so!0x269328 (_ZN11BusinessCfg6getOtpERN3wup12AppLoginDataE+0x1bc) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac) <- libudbauthunify.so!0x275980 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xac)", "outStr": null, "outHex": "", "outSize": 0, "pid": 10183, "tid": 10322, "ts": 1787951512281, "seq": 33}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x2749e8 (_Z19createWupDeviceInfoPN3wup10DeviceInfoE+0x348) <- libudbauthunify.so!0x38dafc (_ZN14WupDataPackageIN3wup13AppCommonDataEE20createWupRequestDataERS1_RNSt6__ndk112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPKcSD_i+0x4c) <- libudbauthunify.so!0x3e3af8 (_ZN18UdbUserFilterUtils15getAppComomDataENSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEERN3wup12AppLoginDataE+0xd0) <- libudbauthunify.so!0x275994 (_Z19copyWupLoginToBeansRN3wup12AppLoginDataER17BusBeansLoginData+0xc0) <- libudbauthunify.so!0x3a9464 (_ZN28HandlerResponseLoginPassport9onHandlerEP10UdbMsgBase+0x274) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c)", "pid": 10183, "tid": 10322, "ts": 1787951512308, "seq": 34}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951512308, "seq": 35}
+{"event": "gethdid", "stage": "enter", "bt": "libudbauthunify.so!0x3d60a8 (_ZN16BusinessLogBuild14buildLogHeaderER11BusinessLogi+0xec) <- libudbauthunify.so!0x3a965c (_ZN28HandlerResponseLoginPassport9onHandlerEP10UdbMsgBase+0x46c) <- libudbauthunify.so!0x24a5d8 (_ZN13UdbMsgHandler10handlerMsgEP10UdbMsgBase+0x3c) <- libudbauthunify.so!0x27b6c4 (_ZN16BusinessMsgChain5doMSGEP10UdbMsgBaseRPc+0x1394) <- libudbauthunify.so!0x2453cc (_ZN7MsgLoop5doMSGEP10UdbMsgBaseRPc+0x18c) <- libudbauthunify.so!0x245b40 (_ZN7MsgLoop14processWorkingEv+0xec)", "pid": 10183, "tid": 10322, "ts": 1787951512352, "seq": 36}
+{"event": "gethdid", "stage": "leave", "slot1088": "37633533383765303533396330323363333163346666306538303765373235363131373338356565", "size": 40, "pid": 10183, "tid": 10322, "ts": 1787951512352, "seq": 37}
diff --git a/tools/frida/hook_otp_capture.js b/tools/frida/hook_otp_capture.js
new file mode 100644
index 0000000..7d388eb
--- /dev/null
+++ b/tools/frida/hook_otp_capture.js
@@ -0,0 +1,273 @@
+'use strict';
+
+// ============================================================================
+// 虎牙 hdid(appSign) 定向捕获 —— 基于 hook_huya_crypto.js 增强
+// 目标: 1) 抓登录路径 getOtp 六元组 (in,cnt,s3,s4,s5,nonce) + 输出 out
+// 2) 抓 BusinessCfg::setSafeDeviceId 写入的 hdid(32hex) = WUP t1.t0 值
+// 3) 调用点标注(backtrace) 区分 登录/证书/biztoken 路径
+// 用法: frida -U -n com.duowan.kiwi -l hook_otp_capture.js (配 anti-frida bypass)
+// 然后正常执行密码登录; 事件经 send() 输出, 用 frida -l ... 的 stdout/json 收集
+// ============================================================================
+
+const MAX_HEX = 2048;
+const MAX_EVENTS = 4000;
+let events = 0;
+let installed = false;
+
+function emit(row) {
+ if (events >= MAX_EVENTS) return;
+ events++;
+ row.pid = Process.id;
+ row.tid = Process.getCurrentThreadId();
+ row.ts = Date.now();
+ row.seq = events;
+ send(row);
+}
+
+function hexOf(ptr, n) {
+ try {
+ const len = Math.min(Number(n), MAX_HEX);
+ const bytes = ptr.readByteArray(len);
+ if (bytes === null) return '';
+ const a = new Uint8Array(bytes);
+ let out = '';
+ for (let i = 0; i < a.length; i++) {
+ const h = a[i].toString(16);
+ out += (h.length < 2 ? '0' + h : h);
+ }
+ return out;
+ } catch (_) { return ''; }
+}
+
+// libc++ std::string (NDK __ndk1): [0..23] union; offset23 bit0=SSO
+function parseStr(addr) {
+ try {
+ if (addr.isNull()) return null;
+ const last = addr.add(23).readU8();
+ let size, data;
+ if (last & 1) { size = last >> 1; data = addr; }
+ else {
+ size = addr.add(8).readU64().toNumber();
+ data = addr.add(16).readPointer();
+ }
+ if (size < 0 || size > 0x100000 || data.isNull()) return null;
+ return { size, data };
+ } catch (_) { return null; }
+}
+
+function readStr(addr) {
+ const s = parseStr(addr);
+ if (s === null) return null;
+ try { return s.data.readUtf8String(s.size); } catch (_) { return null; }
+}
+
+function readStrHex(addr) {
+ const s = parseStr(addr);
+ if (s === null) return null;
+ return { size: s.size, hex: hexOf(s.data, s.size) };
+}
+
+function bt() {
+ try {
+ const tr = Thread.backtrace(this.context, Backtracer.ACCURATE)
+ .slice(0, 6).map(a => {
+ const m = Process.findModuleByAddress(a);
+ const off = m ? '0x' + a.sub(m.base).toString(16) : a.toString();
+ const p = DebugSymbol.fromAddress(a);
+ const nm = p ? p.name : '';
+ return (m ? m.name : '?') + '!' + off + (nm ? ' (' + nm + ')' : '');
+ });
+ return tr.join(' <- ');
+ } catch (_) { return ''; }
+}
+
+function byName(needle) {
+ try {
+ const mod = Process.findModuleByName('libudbauthunify.so');
+ if (mod === null) return null;
+ for (const e of mod.enumerateExports()) {
+ if (e.type === 'function' && e.name.indexOf(needle) >= 0) return e.address;
+ }
+ return null;
+ } catch (_) { return null; }
+}
+
+function hookByName(needle, event, onEnterFn, onLeaveFn) {
+ const addr = byName(needle);
+ if (addr === null) { emit({ event: 'hook-missing', needle }); return false; }
+ try {
+ Interceptor.attach(addr, {
+ onEnter(args) {
+ try { if (onEnterFn) onEnterFn.call(this, args); }
+ catch (e) { emit({ event, err: 'enter:' + e }); }
+ },
+ onLeave(retval) {
+ try { if (onLeaveFn) onLeaveFn.call(this, retval); }
+ catch (e) { emit({ event, err: 'leave:' + e }); }
+ },
+ });
+ emit({ event: 'hook-installed', needle, address: addr.toString() });
+ return true;
+ } catch (e) {
+ emit({ event: 'hook-error', needle, error: String(e) });
+ return false;
+ }
+}
+
+// 栈上 std::string& 出参: 入口 sp 指向返回地址, 出参指针在 sp+8
+function stackStrPtr(ctx) {
+ try { return ctx.sp.add(8).readPointer(); } catch (_) { return null; }
+}
+
+function install() {
+ if (installed) return;
+ const mod = Process.findModuleByName('libudbauthunify.so');
+ if (mod === null) return;
+ installed = true;
+ emit({ event: 'module-found', name: mod.name, base: mod.base.toString(), size: mod.size });
+
+ // -------------------------------------------------------------------------
+ // 1) hyudb_otp_encrypt(std::string key/in, uchar, uchar, std::string s3,
+ // std::string s4, std::string s5, uchar, ulong nonce,
+ // std::string& out @sp+8)
+ // -------------------------------------------------------------------------
+ hookByName('hyudb_otp_encrypt', 'crypto-otp',
+ function (args) {
+ const keyHex = readStrHex(args[0]); // x0 = in (xxtea key)
+ const s3 = readStrHex(args[3]);
+ const s4 = readStrHex(args[4]);
+ const s5 = readStrHex(args[5]);
+ this._outPtr = stackStrPtr(this.context);
+ emit({
+ event: 'crypto-otp', stage: 'enter',
+ bt: bt.call(this),
+ xxteaKeyStr: readStr(args[0]),
+ xxteaKeyHex: keyHex ? keyHex.hex : '',
+ xxteaKeySize: keyHex ? keyHex.size : 0,
+ arg1: args[1].toUInt32(), // 固定 2
+ arg2_cnt: args[2].toUInt32(), // AES counter (getkey b)
+ arg3: s3 ? s3.hex : null, // BusinessCfg+0x40
+ arg4: s4 ? s4.hex : null, // BusinessCfg+0x10 (k1)
+ arg5: s5 ? s5.hex : null, // ALD+0x28 blob
+ arg5Size: s5 ? s5.size : 0,
+ arg6: args[6].toUInt32(), // 固定 4
+ arg7_nonce: args[7].toString(16), // nonce = counter|st<<16
+ stMs: args[7].shr(16).toString(10), // serviceTime
+ nc: args[7].and(0xffff).toUInt32(), // nonce counter
+ });
+ },
+ function (retval) {
+ const outHex = this._outPtr ? readStrHex(this._outPtr) : null;
+ emit({
+ event: 'crypto-otp', stage: 'leave',
+ bt: bt.call(this),
+ outStr: this._outPtr ? readStr(this._outPtr) : null,
+ outHex: outHex ? outHex.hex : '',
+ outSize: outHex ? outHex.size : 0,
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // 1b) BusinessCfg::getOtp —— 外层包装, 标注调用点
+ // -------------------------------------------------------------------------
+ hookByName('BusinessCfg6getOtp', 'crypto-getotp',
+ function (args) {
+ emit({
+ event: 'crypto-getotp', stage: 'enter',
+ bt: bt.call(this),
+ uidLo: args[1].toUInt32(), uidHi: args[1].shr(32).toUInt32(),
+ });
+ },
+ null);
+
+ // -------------------------------------------------------------------------
+ // 2) BusinessCfg::setSafeDeviceId —— 写入 this+1008=safeDeviceId, this+1088=hdid
+ // 读回存储值 = WUP t1.t0 的 32hex 源头(若设入的 hdid 即登录 hdid)
+ // -------------------------------------------------------------------------
+ hookByName('setSafeDeviceId', 'setdi',
+ function (args) {
+ this._self = args[0];
+ const a1 = readStrHex(args[1]);
+ const a2 = readStrHex(args[2]);
+ const a3 = readStrHex(args[3]);
+ const a4 = readStrHex(args[4]);
+ emit({
+ event: 'setdi', stage: 'enter',
+ bt: bt.call(this),
+ arg1: a1 ? { str: readStr(args[1]), hex: a1.hex, size: a1.size } : null,
+ arg2: a2 ? { str: readStr(args[2]), hex: a2.hex, size: a2.size } : null,
+ arg3: a3 ? { str: readStr(args[3]), hex: a3.hex, size: a3.size } : null,
+ arg4: a4 ? { str: readStr(args[4]), hex: a4.hex, size: a4.size } : null,
+ });
+ },
+ function (retval) {
+ // 读回 BusinessCfg+1088 (hdid) / +1008 (safeDeviceId)
+ const self = this._self;
+ if (self === null || self.isNull()) return;
+ const readSlot = (off) => {
+ const p = readStrHex(self.add(off));
+ return p ? { str: readStr(self.add(off)), hex: p.hex, size: p.size } : null;
+ };
+ emit({
+ event: 'setdi', stage: 'leave',
+ slot1008_safeDeviceId: readSlot(1008),
+ slot1088_hdid: readSlot(1088),
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // 3) BusinessCfg::getHdid / getSafeDeviceId —— WUP 构建读取点
+ // -------------------------------------------------------------------------
+ hookByName('getHdid', 'gethdid',
+ function (args) {
+ this._self = args[0];
+ emit({ event: 'gethdid', stage: 'enter', bt: bt.call(this) });
+ },
+ function (retval) {
+ const p = this._self ? readStrHex(this._self.add(1088)) : null;
+ emit({ event: 'gethdid', stage: 'leave', slot1088: p ? p.hex : null, size: p ? p.size : 0 });
+ });
+
+ // -------------------------------------------------------------------------
+ // 4) AESkeyMgr::getkey —— 表内容(与 15 键表对拍)
+ // -------------------------------------------------------------------------
+ hookByName('AESkeyMgr6getkey', 'crypto-aeskey',
+ function (args) {
+ this._sret = this.context.x8;
+ emit({ event: 'crypto-aeskey', stage: 'enter', a: args[1].toUInt32(), b: args[2].toUInt32() });
+ },
+ function (retval) {
+ const key = this._sret ? readStr(this._sret) : null;
+ emit({ event: 'crypto-aeskey', stage: 'leave', key, b: this._b });
+ });
+
+ // md5/aes 链(与旧脚本一致, 用于交叉验证)
+ hookByName('md5_char16', 'crypto-md5',
+ function (args) {
+ this._outPtr = args[0];
+ emit({ event: 'crypto-md5', stage: 'enter', inStr: readStr(args[1]) });
+ },
+ function (retval) {
+ const outHex = this._outPtr ? readStrHex(this._outPtr) : null;
+ emit({ event: 'crypto-md5', stage: 'leave', keyHex: outHex ? outHex.hex : '' });
+ });
+}
+
+function exportOf(name) {
+ try {
+ if (typeof Module.getGlobalExportByName === 'function') return Module.getGlobalExportByName(name);
+ if (typeof Module.findGlobalExportByName === 'function') return Module.findGlobalExportByName(null, name);
+ return Module.findExportByName(null, name);
+ } catch (_) { return null; }
+}
+
+for (const name of ['dlopen', 'android_dlopen_ext']) {
+ const address = exportOf(name);
+ if (address !== null) {
+ Interceptor.attach(address, {
+ onEnter() { install(); },
+ onLeave() { install(); },
+ });
+ }
+}
+setImmediate(install);
\ No newline at end of file
diff --git a/tools/frida/run_capture.py b/tools/frida/run_capture.py
new file mode 100644
index 0000000..cd51416
--- /dev/null
+++ b/tools/frida/run_capture.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""虎牙 OTP 定向捕获运行器。
+
+流程:
+ 1) adb 启动手机端 frida-server (re.frida.server/fs152, 监听 127.0.0.1:31878)
+ 2) 远程连接 127.0.0.1:31878, spawn com.duowan.kiwi
+ 3) 依次注入: frida_bypass.js (maps 伪装) -> frida_java_exit.js (Java kill 拦截)
+ -> hook_otp_capture.js (OTP 六元组 + setSafeDeviceId 捕获)
+ 4) resume, 收集 send() 事件 -> JSONL 落盘 + 实时打印
+用法:
+ python3 run_capture.py # 默认输出 ./capture_.jsonl
+ CAP_OUT=/tmp/otp.jsonl python3 run_capture.py
+"""
+import json
+import os
+import subprocess
+import sys
+import time
+from datetime import datetime
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+OUT_DIR = os.environ.get("CAP_OUT_DIR", HERE)
+PKG = "com.duowan.kiwi"
+SERVER_PATH = "/data/local/tmp/re.frida.server/fs152"
+SERVER_PORT = "31878"
+BYpass_RE = "/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/scripts/bypass_msaoaid_maps_art_callsite.js"
+JAVA_EXIT_RE = "" # 真机通道: 单一 bypass 脚本最稳, 不加载 java-exit
+HOOK_LOCAL = os.path.join(HERE, "hook_otp_capture.js")
+
+
+def adb(*args):
+ return subprocess.run(["adb"] + list(args), capture_output=True, text=True)
+
+
+def main():
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
+ out_path = os.path.join(OUT_DIR, f"capture_{ts}.jsonl")
+ fout = open(out_path, "w", encoding="utf-8")
+ print(f"[runner] output -> {out_path}", flush=True)
+
+ # 1) 启动手机端 frida-server (root, 后台)
+ print("[runner] starting frida-server on device ...", flush=True)
+ adb("shell", "su", "-c",
+ f"nohup {SERVER_PATH} -l 127.0.0.1:{SERVER_PORT} >/data/local/tmp/re.frida.server/fs152.log 2>&1 &")
+ time.sleep(2.0)
+
+ import frida
+
+ dev = None
+ for i in range(6):
+ try:
+ dev = frida.get_device_manager().add_remote_device(f"127.0.0.1:{SERVER_PORT}")
+ apps = dev.enumerate_processes()
+ print(f"[runner] frida-server OK ({len(apps)} procs)", flush=True)
+ break
+ except Exception as e:
+ print(f"[runner] wait frida-server [{i}] {str(e)[:80]}", flush=True)
+ time.sleep(2.0)
+ if dev is None:
+ print("[runner] FATAL: frida-server unreachable", flush=True)
+ sys.exit(1)
+
+ # 2) 确保旧进程关闭, 然后 spawn
+ adb("shell", "am", "force-stop", PKG)
+ time.sleep(1.0)
+ try:
+ pid = dev.spawn([PKG])
+ print(f"[runner] spawned pid={pid}", flush=True)
+ except Exception as e:
+ print(f"[runner] spawn failed: {e}", flush=True)
+ sys.exit(1)
+
+ session = dev.attach(pid)
+
+ def on_message(msg, data):
+ if msg.get("type") == "send":
+ payload = msg.get("payload")
+ if payload is not None:
+ line = json.dumps(payload, ensure_ascii=False)
+ fout.write(line + "\n")
+ fout.flush()
+ ev = payload.get("event", "")
+ if ev in ("crypto-otp", "setdi", "gethdid", "hook-missing", "hook-installed"):
+ print(f"[cap] {line[:400]}", flush=True)
+ elif msg.get("type") == "error":
+ print(f"[cap-err] {msg.get('stack', msg)}", flush=True)
+ elif msg.get("type") == "device":
+ print(f"[cap-dev] {msg.get('payload')}", flush=True)
+
+ scripts = []
+ # 阶段1: 只注入 bypass (STATUS.md 验证的 90s 稳定配方, 避免启动期注入竞争)
+ with open(BYpass_RE, "r", encoding="utf-8") as f:
+ sc = session.create_script(f.read())
+ sc.on("message", on_message)
+ sc.load()
+ scripts.append(sc)
+ print("[runner] loaded bypass (phase-1)", flush=True)
+
+ dev.resume(pid)
+ print("[runner] RESUMED (phase-1). 等待 app 启动 ...", flush=True)
+
+ # 阶段2: app 启动后延迟注入 otp 钩子 (避开 msaoaid 启动扫描 + EGL 竞争)
+ def inject_phase2():
+ time.sleep(8)
+ try:
+ with open(HOOK_LOCAL, "r", encoding="utf-8") as f:
+ src = f.read()
+ sc2 = session.create_script(src)
+ sc2.on("message", on_message)
+ sc2.load()
+ scripts.append(sc2)
+ print("[runner] loaded otp-hook (phase-2)", flush=True)
+ except Exception as e:
+ print(f"[runner] phase-2 inject failed: {e}", flush=True)
+ import threading
+ threading.Thread(target=inject_phase2, daemon=True).start()
+
+ # 稳定窗口 ~120s 后自动收工
+ def auto_stop():
+ time.sleep(120)
+ print("[runner] auto-stop", flush=True)
+ os._exit(0)
+ threading.Thread(target=auto_stop, daemon=True).start()
+
+ try:
+ while True:
+ time.sleep(1.0)
+ except KeyboardInterrupt:
+ pass
+
+ fout.close()
+ print(f"[runner] done -> {out_path}", flush=True)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/tools/unidbg/hydev/src/hydev/GoldSweep.java b/tools/unidbg/hydev/src/hydev/GoldSweep.java
new file mode 100644
index 0000000..225a221
--- /dev/null
+++ b/tools/unidbg/hydev/src/hydev/GoldSweep.java
@@ -0,0 +1,674 @@
+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.DalvikModule;
+import com.github.unidbg.linux.android.dvm.VM;
+import com.github.unidbg.memory.Memory;
+import com.github.unidbg.file.linux.LinuxFileSystem;
+import com.github.unidbg.pointer.UnidbgPointer;
+import java.io.File;
+import java.io.File;
+import java.util.Arrays;
+
+/**
+ * GoldSweep: 金样本 appSign(ed0db8) 差分引擎.
+ *
+ * getOtp 反汇编定案 (0x26916c-0x269324):
+ * in = to_string(*(AppLoginData+0)) = to_string(hyOpenId) (密码登录 hyOpenId=0 -> "0")
+ * cnt = AESkeyMgr 计数器 首调=1, 循环 1..15
+ * s3 = BusinessCfg+0x40 (登录数据字段/候选)
+ * s4 = BusinessCfg+0x10 (k1 设备常量 865a4924...)
+ * s5 = AppLoginData+0x28 (登录请求期空)
+ * nonce= nonce_next(serviceTime) = nc | st<<16 (nc 同 st 内递增, 通常 0/1)
+ * 直调 hyudb_otp_encrypt@0x32fa24 与 getOtp 内部逐字节一致 (R12 已验证).
+ *
+ * 目标: 找到 (in,cnt,s3,s4,s5,nonce) 使 mid == ed0db8334cadd236c00cadf7e11ab5a5
+ */
+public class GoldSweep {
+
+ static final String G = "ed0db8334cadd236c00cadf7e11ab5a5";
+ static final String K1C = "865a4924a40897ac1fcfe6b4c2cbb0e3";
+
+ private final AndroidEmulator emulator;
+ private final Module module;
+
+ GoldSweep(String soPath) 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 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));
+ }
+
+ UnidbgPointer writeStdStringBytes(byte[] data) {
+ Memory mem = emulator.getMemory();
+ int len = data.length;
+ UnidbgPointer p;
+ if (len <= 22) {
+ 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 {
+ UnidbgPointer heap = mem.malloc(len + 8, false).getPointer();
+ heap.write(0, data, 0, len);
+ p = mem.malloc(24, false).getPointer();
+ p.setInt(0, (int) (len + 1) | 1);
+ p.setLong(8, len);
+ p.setLong(16, UnidbgPointer.nativeValue(heap));
+ }
+ return p;
+ }
+
+ UnidbgPointer writeStdString(String s) {
+ Memory mem = emulator.getMemory();
+ byte[] data = s.getBytes();
+ int len = data.length;
+ UnidbgPointer p;
+ if (len <= 22) {
+ 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 {
+ 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);
+ byte[] szb = new byte[]{(byte) (len & 0xff), (byte) (len >> 8), 0, 0, 0, 0, 0, 0};
+ p.write(8, szb, 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(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);
+ }
+ }
+
+ UnidbgPointer writeCString(String s) {
+ byte[] dat = s.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
+ UnidbgPointer p = emulator.getMemory().malloc(dat.length + 1, false).getPointer();
+ p.write(0, dat, 0, dat.length);
+ p.write(dat.length, new byte[]{0}, 0, 1);
+ return p;
+ }
+
+ 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();
+ }
+
+ /** 直调 OTP, 返回 [04][cnt][16B mid][16B tail] 的 hex; 出错返回 null */
+ String otpHex(String in, long cnt, String s3, String s4, String s5, long nonce) {
+ UnidbgPointer pa = writeStdString(in);
+ UnidbgPointer ps3 = writeStdString(s3);
+ UnidbgPointer ps4 = writeStdString(s4);
+ UnidbgPointer ps5 = writeStdString(s5);
+ UnidbgPointer pout = writeStdString("");
+ try {
+ module.callFunction(emulator, 0x32fa24L,
+ UnidbgPointer.nativeValue(pa), 2L, cnt,
+ UnidbgPointer.nativeValue(ps3), UnidbgPointer.nativeValue(ps4), UnidbgPointer.nativeValue(ps5),
+ 4L, nonce, UnidbgPointer.nativeValue(pout));
+ return toHex(readStdString(pout));
+ } catch (Throwable tx) { return null; }
+ }
+
+ /** 直调 OTP (s5 二进制) */
+ String otpHexB(String in, long cnt, String s3, String s4, byte[] s5, long nonce) {
+ UnidbgPointer pa = writeStdString(in);
+ UnidbgPointer ps3 = writeStdString(s3);
+ UnidbgPointer ps4 = writeStdString(s4);
+ UnidbgPointer ps5 = writeStdStringBytes(s5);
+ UnidbgPointer pout = writeStdString("");
+ try {
+ module.callFunction(emulator, 0x32fa24L,
+ UnidbgPointer.nativeValue(pa), 2L, cnt,
+ UnidbgPointer.nativeValue(ps3), UnidbgPointer.nativeValue(ps4), UnidbgPointer.nativeValue(ps5),
+ 4L, nonce, UnidbgPointer.nativeValue(pout));
+ return toHex(readStdString(pout));
+ } catch (Throwable tx) { return null; }
+ }
+
+ static byte[] hexBytes(String h) {
+ byte[] b = new byte[h.length() / 2];
+ for (int i = 0; i < b.length; i++) b[i] = (byte) Integer.parseInt(h.substring(2 * i, 2 * i + 2), 16);
+ return b;
+ }
+
+ /** 从 OTP hex 取 mid (32 hex) */
+ static String midOf(String hexOut) {
+ if (hexOut == null || hexOut.length() < 36) return "";
+ return hexOut.substring(4, 36);
+ }
+
+ void smoke() {
+ // R12 验证值: 应逐字节命中 04c7537f...
+ String h = otpHex("0", 1L, "hy_300023887", "hy_300023887", "", 0x1a049befebf0000L);
+ System.out.println("[smoke] mid=" + midOf(h) + " expect 04c7537f5b40b45ed13e89ec3f8b1b89 raw=" + h);
+ // 测速
+ long t0 = System.currentTimeMillis();
+ int n = 0;
+ for (int i = 0; i < 500; i++) {
+ if (otpHex("0", 1L, "hy_300023887", "hy_300023887", "", 0x1a049befebf0000L + i) != null) n++;
+ }
+ long dt = System.currentTimeMillis() - t0;
+ System.out.printf("[smoke] %d calls in %d ms -> %.0f calls/s%n", n, dt, n * 1000.0 / Math.max(1, dt));
+ }
+
+ void sweep(String tag, long stLo, long stHi,
+ String[] inCands, long[] cntCands, String[] s3Cands, String[] s4Cands, String[] s5Cands, long[] ncCands) {
+ long total = (stHi - stLo + 1) * inCands.length * cntCands.length * s3Cands.length * s4Cands.length * s5Cands.length * ncCands.length;
+ System.out.println("[sweep:" + tag + "] st[" + stLo + "," + stHi + "] in=" + inCands.length + " cnt=" + cntCands.length
+ + " s3=" + s3Cands.length + " s4=" + s4Cands.length + " s5=" + s5Cands.length + " nc=" + ncCands.length
+ + " total=" + total);
+ long t0 = System.currentTimeMillis();
+ long n = 0;
+ int hits = 0;
+ for (long st = stLo; st <= stHi; st++) {
+ for (String in : inCands) {
+ for (long cnt : cntCands) {
+ for (String s3 : s3Cands) {
+ for (String s4 : s4Cands) {
+ for (String s5 : s5Cands) {
+ for (long nc : ncCands) {
+ long nonce = nc | (st << 16);
+ n++;
+ String h = otpHex(in, cnt, s3, s4, s5, nonce);
+ if (h != null) {
+ String mid = midOf(h);
+ if (mid.equals(G)) {
+ System.out.println("!!! HIT st=" + st + " in=" + in + " cnt=" + cnt + " s3=[" + s3 + "] s4=[" + s4 + "] s5=[" + s5 + "] nc=" + nc
+ + " nonce=" + Long.toHexString(nonce) + " out=" + h);
+ hits++;
+ }
+ }
+ if (n % 4000 == 0) {
+ long dt = System.currentTimeMillis() - t0;
+ System.out.println("[sweep:" + tag + "] n=" + n + "/" + total + " st=" + st + " " + (n * 1000.0 / Math.max(1, dt)) + " calls/s");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ long dt = System.currentTimeMillis() - t0;
+ System.out.println("[sweep:" + tag + "] DONE n=" + n + " hits=" + hits + " in " + dt + "ms (" + (n * 1000.0 / Math.max(1, dt)) + " calls/s)");
+ }
+
+
+
+ void hookGetkeys(long cnt) {
+ 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) {
+ try {
+ long sp = backend.reg_read(unicorn.Arm64Const.UC_ARM64_REG_SP).longValue();
+ UnidbgPointer kp = UnidbgPointer.pointer(emulator, sp + 0xe0);
+ if (kp != null) {
+ try {
+ byte[] one = new byte[1];
+ kp.read(0, one, 0, 1);
+ System.out.println("[k2] sp+0xe0 flagbyte=0x" + String.format("%02x", one[0] & 0xff) + " str=[" + readStdString(kp) + "]");
+ } catch (Throwable t) { System.out.println("[k2] read err " + t); }
+ }
+ } catch (Throwable t) { System.out.println("[k2] hook err " + t); }
+ }
+ public void onAttach(com.github.unidbg.arm.backend.UnHook unHook) {}
+ public void detach() {}
+ };
+ try {
+ emulator.getBackend().hook_add_new(hook, module.base + 0x32fb7cL, module.base + 0x32fb80L, null);
+ } catch (Throwable t) { System.out.println("[k2] install err " + t); }
+ String h = otpHex("1471224845212", cnt, "5008", K1C, "", 0x1a037881a430000L);
+ System.out.println("[k2] otp cnt=" + cnt + " out=" + h);
+ }
+
+ void dumpKeys() {
+ for (int b = 0; b < 16; b++) {
+ UnidbgPointer self = emulator.getMemory().malloc(0x80, false).getPointer();
+ self.write(0, new byte[0x80], 0, 0x80);
+ try { module.callFunction(emulator, 0x26fcc8L, UnidbgPointer.nativeValue(self)); } catch (Throwable t) {}
+ Number r = module.callFunction(emulator, 0x26871cL, UnidbgPointer.nativeValue(self), 1L, (long) b);
+ UnidbgPointer kp = UnidbgPointer.pointer(emulator, r.longValue());
+ String key = kp == null ? "null" : readStdString(kp);
+ System.out.println("[K] b=" + b + " key=[" + key + "] len=" + key.length());
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ String so = args.length > 0 ? args[0] : "so/libudbauthunify_merged.so";
+ String mode = args.length > 1 ? args[1] : "A";
+ GoldSweep p = new GoldSweep(so);
+
+ // 金样本常量族
+ String NAME = "hy_300023887";
+ String UIDSTR = "300023887";
+ String K1 = "865a4924a40897ac1fcfe6b4c2cbb0e3";
+ String SHA1PW = "772ed992b0e161276f44ec63671e60155c506294";
+ String MID = "1e8bdf7d4f7a01d3";
+ String DEVD40 = "7c5387e0539c023c31c4ff0e807e7256117385ee";
+ String DEVD40B = "02df398797432eadefcc12767119ad5e80999389";
+ String SDID = "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T";
+ String MASK = "001******7524";
+ String MASK2 = "01******7524";
+ String SESSION = "3251699";
+ String UID = "1199666914671";
+ // 设备指纹 114B 二进制 (final_capture 08-25 实机 hook, 数美 SDK 产物, 设备级稳定)
+ String fpC = "";
+ try {
+ byte[] fpb = new byte[114];
+ String fph = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
+ for (int i = 0; i < 114; i++) fpb[i] = (byte) Integer.parseInt(fph.substring(i * 2, i * 2 + 2), 16);
+ fpC = new String(fpb, java.nio.charset.StandardCharsets.ISO_8859_1);
+ } catch (Throwable t) { System.out.println("[fpC] parse err " + t); }
+
+ switch (mode) {
+ case "smoke":
+ p.smoke();
+ break;
+ case "V": // 真机输出验证: final_capture 08-25 biztoken OTP (in=uid,cnt=2,s3=5008,s4=k1,s5=cred114B,nonce)
+ {
+ String exp = "0402b4024c4a6069cb3c3806e14802a6a253e5cf3d8bde09ef37d8dcf3a3bc05f49100e94373a092";
+ String h = p.otpHex("1471224845212", 2L, "5008", K1, fpC, 0x1a037881a430000L);
+ System.out.println("[V] got=" + (h == null ? "null" : h));
+ if (h != null) System.out.println("[V] head-match=" + h.startsWith(exp));
+ // 也试 s5="" 对照
+ String h2 = p.otpHex("1471224845212", 2L, "5008", K1, "", 0x1a037881a430000L);
+ System.out.println("[V2] s5=[] got=" + (h2 == null ? "null" : h2));
+ // s5=cred 的另一种 0a80 头部? 试 base64 cred 原文
+ byte[] cb = new byte[114];
+ String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
+ for (int ci = 0; ci < 114; ci++) cb[ci] = (byte) Integer.parseInt(credHex.substring(ci*2, ci*2+2), 16);
+ String cred = new String(cb, java.nio.charset.StandardCharsets.ISO_8859_1);
+ String h3 = p.otpHex("1471224845212", 2L, K1, "5008", cred, 0x1a037881a430000L);
+ System.out.println("[V3] swapped got=" + h3);
+ }
+ break;
+ case "A": // 登录窗口紧扫: in="0"(hyOpenId=0) + s4=K1 (高置信)
+ p.sweep("A", 1787595171900L, 1787595172700L,
+ new String[]{"0"},
+ new long[]{1, 2, 3, 4, 5},
+ new String[]{NAME, "", "5008", K1, UIDSTR},
+ new String[]{K1},
+ new String[]{"", MASK},
+ new long[]{0, 1});
+ break;
+ case "E": // 全 cnt 1..15 (AESkeyMgr 计数器循环) + 宽 s3/s5
+ p.sweep("E", 1787595170000L, 1787595173200L,
+ new String[]{"0", UID},
+ new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ new String[]{NAME, "", "5008", K1, UIDSTR, SHA1PW},
+ new String[]{K1, SHA1PW, ""},
+ new String[]{"", MASK, MASK2, SDID, MID},
+ new long[]{0, 1, 2});
+ break;
+ case "B": // 宽窗口 + 全族
+ p.sweep("B", 1787595170000L, 1787595173000L,
+ new String[]{"0", UID, UIDSTR, SESSION},
+ new long[]{0, 1, 2, 3, 4, 5},
+ new String[]{NAME, K1, UIDSTR, "", MID},
+ new String[]{K1, SHA1PW, NAME, "", MID},
+ new String[]{"", MASK, MASK2, SDID, MID, DEVD40},
+ new long[]{0, 1, 2});
+ break;
+ case "C": // 应用启动窗口 (18:11:30 app start 前后) - 指纹+5008家族
+ p.sweep("C", 1787595089000L, 1787595096000L,
+ new String[]{"0", UID, ""},
+ new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ new String[]{"5008", NAME, K1, ""},
+ new String[]{K1},
+ new String[]{fpC, ""},
+ new long[]{0, 1, 2});
+ break;
+ case "D": // 8-28 hook 时刻 (前一会话实机钩子服务时间) 周边
+ p.sweep("D", 1787943647000L, 1787943651000L,
+ new String[]{"0", UID, UIDSTR},
+ new long[]{1, 2, 3},
+ new String[]{NAME, K1, ""},
+ new String[]{K1, SHA1PW, ""},
+ new String[]{"", MASK, SDID},
+ new long[]{0, 1});
+ break;
+ case "P": // 顶配快速 probe: (s3=5008, s4=K1, s5=fpC/"") st 紧窗
+ p.sweep("P", 1787595172350L, 1787595172600L,
+ new String[]{"0"},
+ new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ new String[]{"5008", NAME, K1},
+ new String[]{K1},
+ new String[]{fpC, ""},
+ new long[]{0, 1, 2});
+ break;
+ case "F": // 指纹聚焦: s5=fpC (114B数美指纹) × cnt 1..15 × 宽 st
+ p.sweep("F", 1787595170000L, 1787595173200L,
+ new String[]{"0"},
+ new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ new String[]{"5008", NAME, K1, ""},
+ new String[]{K1},
+ new String[]{fpC, ""},
+ new long[]{0, 1, 2});
+ break;
+ case "G": // 备选 s4/s5 全族 (若 F 未中)
+ p.sweep("G", 1787595170000L, 1787595173200L,
+ new String[]{"0", UID},
+ new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ new String[]{"5008", NAME, K1, "", UIDSTR, SHA1PW},
+ new String[]{K1, SHA1PW, NAME, ""},
+ new String[]{fpC, "", MASK, SDID, MID},
+ new long[]{0, 1, 2});
+ break;
+ case "K": // 打印 harness AESkeyMgr 钥表 + md5_char16 验证
+ p.dumpKeys();
+ break;
+ case "K2": // hook getkey 抓 OTP 内部密钥 (全 1..15)
+ for (long b = 1; b <= 15; b++) p.hookGetkeys(b);
+ break;
+ case "H": { // 金样本: 正确 mid 窗口 out[2:34] == ed0db8
+ String T = "ed0db8334cadd236c00cadf7e11ab5a5";
+ String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
+ byte[] cb = hexBytes(credHex);
+ String[] ins = {"1471238907296", "0"};
+ long t0 = System.currentTimeMillis();
+ long n = 0;
+ for (String in : ins)
+ for (long cnt = 1; cnt <= 15; cnt++)
+ for (long st = 1787595165000L; st <= 1787595172700L; st += 1)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB(in, cnt, "5008", K1C, cb, nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc);
+ }
+ if (++n % 20000 == 0) System.out.println("[H] " + n + " (" + (System.currentTimeMillis()-t0)/1000 + "s)");
+ }
+ // s5="" 第二轮
+ for (String in : ins)
+ for (long cnt = 1; cnt <= 15; cnt++)
+ for (long st = 1787595165000L; st <= 1787595172700L; st += 1)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB(in, cnt, "5008", K1C, new byte[0], nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT(in0) in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc);
+ }
+ if (++n % 20000 == 0) System.out.println("[H] " + n + " (" + (System.currentTimeMillis()-t0)/1000 + "s)");
+ }
+ System.out.println("[H] DONE " + n + " calls in " + (System.currentTimeMillis()-t0) + " ms");
+ break;
+ }
+ case "H2": { // st 单点全集: app-start/biztoken-st/WUP 窗 + cnt 0..15 + in 4 种 + s5 cred/""
+ String T = "ed0db8334cadd236c00cadf7e11ab5a5";
+ String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
+ byte[] cb = hexBytes(credHex);
+ long[] sts = {1787595090906L, 1787595091000L, 1787595102531L, 1787595167182L, 1787595172449L, 1787595172452L, 1787595172455L, 1787595172458L};
+ String[] ins = {"1471238907296", "0", "300023887", ""};
+ long n = 0;
+ for (long st : sts)
+ for (String in : ins)
+ for (long cnt = 0; cnt <= 15; cnt++)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB(in, cnt, "5008", K1C, cb, nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc + " s5=cred");
+ }
+ if (++n % 400 == 0) System.out.print(".");
+ }
+ for (long st : sts)
+ for (String in : ins)
+ for (long cnt = 0; cnt <= 15; cnt++)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB(in, cnt, "5008", K1C, new byte[0], nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc + " s5=empty");
+ }
+ if (++n % 400 == 0) System.out.print(".");
+ }
+ System.out.println();
+ System.out.println("[H2] DONE " + n + " calls");
+ break;
+ }
+ case "RF": { // 真机 resinfo 文件层解密: 调 UdbFileUtil::readFileEx@0x252158
+ java.io.File root = p.emulator.getFileSystem().getRootDir();
+ java.io.File dir = new java.io.File(root, "data/user/0/com.duowan.kiwi/files/hydevice");
+ dir.mkdirs();
+ java.io.File of = new java.io.File(dir, "resinfo");
+ java.io.File uf = new java.io.File(dir, "64a33427-f53c-4162-8738-81aa9117b950");
+ java.io.File hk = new java.io.File(new java.io.File(root, "data/user/0/com.duowan.kiwi"), "fileshydckey");
+ hk.getParentFile().mkdirs();
+ java.nio.file.Files.copy(new java.io.File("/Users/yml/codes/douyu_login_py/evidence/live_device/resinfo.bin").toPath(), of.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ java.nio.file.Files.copy(new java.io.File("/Users/yml/codes/douyu_login_py/evidence/live_device/uuid.bin").toPath(), uf.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ java.nio.file.Files.copy(new java.io.File("/Users/yml/codes/douyu_login_py/evidence/live_device/hydckey.bin").toPath(), hk.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ System.out.println("[RF] rootDir=" + root.getAbsolutePath());
+ String[][] combos = {
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950"},
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", ""},
+ {"/files/hydevice/resinfo", ""},
+ {"/data/data/com.duowan.kiwi/files/hydevice/resinfo", ""},
+ {"hydevice/resinfo", ""},
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950", "/data/user/0/com.duowan.kiwi/files/hydevice/resinfo"},
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950"},
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/fileshydckey"},
+ {"/data/user/0/com.duowan.kiwi/fileshydckey", "/data/user/0/com.duowan.kiwi/files/hydevice/resinfo"},
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/fileshydckey"},
+ {"/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950", "/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/fileshydckey"},
+ };
+ // 追加: 完整 dump (resinfo, "") 的解密输出到本地文件
+ {
+ UnidbgPointer pp1 = p.writeCString("/data/user/0/com.duowan.kiwi/files/hydevice/resinfo");
+ UnidbgPointer pp2 = p.writeCString("");
+ UnidbgPointer po1 = p.writeStdString(""), po2 = p.writeStdString("");
+ try {
+ p.module.callFunction(p.emulator, 0x252158L,
+ UnidbgPointer.nativeValue(pp1), UnidbgPointer.nativeValue(pp2),
+ UnidbgPointer.nativeValue(po1), UnidbgPointer.nativeValue(po2));
+ String o1 = p.readStdString(po1);
+ byte[] b1 = o1.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
+ java.nio.file.Files.write(new java.io.File("/tmp/resinfo_dec1.bin").toPath(), b1);
+ System.out.println("[RF] FULL dump -> /tmp/resinfo_dec1.bin (" + b1.length + "B)");
+ } catch (Throwable t) { System.out.println("[RF] dump EXC " + t); }
+ }
+ for (String[] c : combos) {
+ UnidbgPointer pp1 = p.writeCString(c[0]);
+ UnidbgPointer pp2 = p.writeCString(c[1]);
+ UnidbgPointer po1 = p.writeStdString(""), po2 = p.writeStdString("");
+ try {
+ p.module.callFunction(p.emulator, 0x252158L,
+ UnidbgPointer.nativeValue(pp1), UnidbgPointer.nativeValue(pp2),
+ UnidbgPointer.nativeValue(po1), UnidbgPointer.nativeValue(po2));
+ String o1 = p.readStdString(po1), o2 = p.readStdString(po2);
+ byte[] b1 = o1.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
+ byte[] b2 = o2.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
+ System.out.println("[RF] paths=(" + c[0] + "),(" + c[1] + ") o1=" + b1.length + "B o2=" + b2.length + "B");
+ if (b1.length > 0) {
+ System.out.println("[RF] o1 hex=" + hydev.GoldSweep.toHex(o1).substring(0, Math.min(160, b1.length * 2)));
+ System.out.println("[RF] o1 str=" + o1.substring(0, Math.min(300, o1.length())));
+ }
+ } catch (Throwable t) {
+ System.out.println("[RF] EXC " + t);
+ }
+ }
+ break;
+ }
+ case "I": { // 装机窗暴破: st[1787582670000..1787582810000], in="0", s5="", cnt 1..15, nc 0..2
+ String T = "ed0db8334cadd236c00cadf7e11ab5a5";
+ long stLo = args.length > 2 ? Long.parseLong(args[2]) : 1787582670000L;
+ long stHi = args.length > 3 ? Long.parseLong(args[3]) : 1787582810000L;
+ long n = 0;
+ for (long cnt = 1; cnt <= 15; cnt++)
+ for (long st = stLo; st <= stHi; st += 1)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB("0", cnt, "5008", K1C, new byte[0], nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc + " in=0 s5=empty");
+ }
+ if (++n % 50000 == 0) System.out.println("[I] " + n + " (st=" + st + ")");
+ }
+ System.out.println("[I] DONE " + n + " calls");
+ break;
+ }
+ case "J": { // 正确语义: in="", s3="", s4=k1, s5=金样本cred — 单点+全窗
+ String T = "ed0db8334cadd236c00cadf7e11ab5a5";
+ String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
+ byte[] cb = hexBytes(credHex);
+ // 单点阶段
+ long[] sts = {1787595090906L, 1787595102531L, 1787595167182L, 1787595172449L, 1787595172452L, 1787595172455L, 1787595172458L};
+ for (long st : sts)
+ for (long cnt = 1; cnt <= 15; cnt++)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB("", cnt, "", K1C, cb, nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc);
+ }
+ }
+ System.out.println("[J] singles done");
+ // 全窗阶段: st 登录窗 × cnt 1..15 × nc 0..2
+ long n = 0;
+ for (long cnt = 1; cnt <= 15; cnt++)
+ for (long st = 1787595165000L; st <= 1787595172700L; st += 1)
+ for (long nc = 0; nc <= 2; nc++) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB("", cnt, "", K1C, cb, nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc);
+ }
+ if (++n % 50000 == 0) System.out.println("[J] " + n + " (st=" + st + ")");
+ }
+ System.out.println("[J] DONE " + n + " calls");
+ break;
+ }
+ case "J2": { // 全笛卡尔: in{uid,"0",""} × s3{"5008","",k1} × s5{cred,""} × cnt1..15 × nc0..2
+ String T = "ed0db8334cadd236c00cadf7e11ab5a5";
+ String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
+ byte[] cb = hexBytes(credHex);
+ String[] ins = {"1471238907296", "0", ""};
+ String[] s3s = {"5008", "", K1C};
+ byte[][] s5s = {cb, new byte[0]};
+ long stLo = args.length > 2 ? Long.parseLong(args[2]) : 1787595165000L;
+ long stHi = args.length > 3 ? Long.parseLong(args[3]) : 1787595172700L;
+ long n = 0;
+ for (long cnt = 1; cnt <= 15; cnt++)
+ for (long st = stLo; st <= stHi; st += 1)
+ for (long nc = 0; nc <= 2; nc++)
+ for (String in : ins)
+ for (String s3 : s3s)
+ for (byte[] s5 : s5s) {
+ long nonce = (nc & 0xffffL) | (st << 16);
+ String out = p.otpHexB(in, cnt, s3, K1C, s5, nonce);
+ if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
+ System.out.println("!!! REAL-HIT in=" + in + " s3=" + s3 + " cnt=" + cnt + " st=" + st + " nc=" + nc + " s5len=" + s5.length);
+ }
+ if (++n % 100000 == 0) System.out.println("[J2] " + n + " (st=" + st + ")");
+ }
+ System.out.println("[J2] DONE " + n + " calls");
+ break;
+ }
+ case "V2": { // 复验: 真实 146B, s5=完整 114B cred, 精确 nonce — 比对输出[2:34](真值 首块=cipher[0:16])
+ String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
+ byte[] s5 = hexBytes(credHex);
+ String out = p.otpHexB("1471224845212", 2L, "5008", K1C, s5, 0x1a037881a430000L);
+ if (out == null) { System.out.println("V2 ERR"); break; }
+ System.out.println("V2 firstblock=" + out.substring(2, 34));
+ System.out.println("V2 needblock =b4024c4a6069cb3c3806e14802a6a253");
+ System.out.println("V2 B1 MATCH=" + out.substring(2, 34).equals("b4024c4a6069cb3c3806e14802a6a253"));
+ System.out.println("V2 len=" + out.length());
+ System.out.println("V2 out=" + out);
+ break;
+ }
+ case "X": // 真机输出校准: 扫 st 全窗, 固定(in,cnt,s3=5008,s4=K1,s5=cred) 复现真机146B
+ {
+ String EXP = "0402b4024c4a6069cb3c3806e14802a6a253e5cf3d8bde09ef37d8dcf3a3bc05f49100e94373a092";
+ byte[] cb = new byte[114];
+ String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
+ for (int ci = 0; ci < 114; ci++) cb[ci] = (byte) Integer.parseInt(credHex.substring(ci*2, ci*2+2), 16);
+ String cred = new String(cb, java.nio.charset.StandardCharsets.ISO_8859_1);
+ for (long cnt : new long[]{0, 1, 2, 3, 4, 5}) {
+ long t0 = System.currentTimeMillis();
+ for (long st = 1787595089000L; st <= 1787595173000L; st++) {
+ for (long nc : new long[]{0, 1, 2}) {
+ long nonce = nc | (st << 16);
+ String h = p.otpHex("1471224845212", cnt, "5008", K1, cred, nonce);
+ if (h != null && h.startsWith(EXP)) {
+ System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc + " nonce=" + Long.toHexString(nonce) + " out=" + h);
+ }
+ }
+ }
+ System.out.println("[X] cnt=" + cnt + " done in " + (System.currentTimeMillis() - t0) + "ms");
+ }
+ }
+ break;
+ case "W": // 真机输出逐字节复现 - 小组合暴力
+ {
+ String EXP = "0402b4024c4a6069cb3c3806e14802a6a253e5cf3d8bde09ef37d8dcf3a3bc05f49100e94373a092";
+ byte[] cb = new byte[114];
+ String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
+ for (int ci = 0; ci < 114; ci++) cb[ci] = (byte) Integer.parseInt(credHex.substring(ci*2, ci*2+2), 16);
+ String cred = new String(cb, java.nio.charset.StandardCharsets.ISO_8859_1);
+ String[] IN = {"1471224845212", "0", "", "1471238907296"};
+ long[] CNT = {0, 1, 2, 3, 4, 5};
+ String[] A3 = {"5008", K1, ""};
+ String[] A4 = {K1, "5008", ""};
+ String[] S5 = {cred, ""};
+ long NONCE = 0x1a037881a430000L;
+ int n = 0;
+ long t0 = System.currentTimeMillis();
+ for (String in : IN) for (long cnt : CNT) for (String s3 : A3) for (String s4 : A4) for (String s5 : S5) {
+ n++;
+ String h = p.otpHex(in, cnt, s3, s4, s5, NONCE);
+ if (h != null && h.startsWith(EXP)) {
+ System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " s3=[" + s3 + "] s4=[" + s4 + "] s5len=" + s5.length() + " out=" + h);
+ }
+ }
+ System.out.println("[W] n=" + n + " done in " + (System.currentTimeMillis() - t0) + "ms");
+ }
+ break;
+ default:
+ p.smoke();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/unidbg/hydev/src/hydev/HyDeviceId.java b/tools/unidbg/hydev/src/hydev/HyDeviceId.java
index e29479b..fc196a9 100644
--- a/tools/unidbg/hydev/src/hydev/HyDeviceId.java
+++ b/tools/unidbg/hydev/src/hydev/HyDeviceId.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.SystemPropertyHook;
@@ -38,6 +38,8 @@ public class HyDeviceId extends AbstractJni {
PREFS.put("qimei16", "67453f5aef55f776967c748a10001c318101");
// 服务端下发配置(真机 prefs 实测): getDfpConfig 可获取, 解密后含设备指纹种子
PREFS.put("hydeviceid_config", "2AQq9oUCCZ8MgS1qhJHFp+U7vBdmTBVkH8WlXondx64IE+kvc65rHJtuTazmCLT0Z7FYQEVuosfMmclJOeQrI7PLlAhhZYU5dXoYCKQJ7/ze1y5V6DvozlZZUQLG9jgoNJ4kdEyEi3QQavTQGdiokQ==");
+ PREFS.put("up_channel", "2001");
+ PREFS.put("hydeviceid_applist", "{\"com.duowan.kiwi\":{\"first_install_time\":1787590888,\"last_update_time\":1787590888,\"install_source\":\"com.android.vending\"},\"com.xiaomi.scanner\":{\"first_install_time\":1787590000,\"last_update_time\":1787590000,\"install_source\":\"com.xiaomi.market\"}}");
}
static class Prefs extends DvmObject