feat(huya): OTP真机逐字节复现 + resinfo解密 + 登录路径六元组live捕获 (R13)
- GoldSweep: V2(真机146B复现292/292hex一致)/H/H2/I/J/J2差分模式 修复mid提取窗口bug(out[4:36]→out[2:34], 旧扫描作废) - 实机捕获(tools/frida两阶段注入): 登录路径OTP六元组 in="" s3="" s4=k1 s5=cred nonce=st<<16 + getHdid=DEVID40实锤 - resinfo解密: 密钥HuyaUdb1928374650qwertyuiop[:16] AES-ECB, 含channelKey/huyaDeviceId(40hex)/safedeviceid - HyDeviceId: b(100)真GUID修复 + Unicorn2后端 + init补桩全通 - docs §11.43: R13全量记录(差分统计/resinfo/getter定案/frida结论) - evidence/live_device: 真机持久化文件存档
This commit is contained in:
@@ -907,3 +907,79 @@ hypasswordLogin/MsgLoginReq/LogLoginReq → mid 各异, tail 恒定 8b38f1 → a
|
|||||||
- tools/unidbg/hydev/src/hydev/AesProbe.java (全能探针: callOtpReal/reproOtp/goldBurst2/seqOtp/hooks/NOP)
|
- 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
|
- 编译运行: 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
|
- 金样本: 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 共用方案 (已跑通多账号)
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
|
||||||
|
<map>
|
||||||
|
<string name="GUID">0a7dfaa882938a6ab502511452142c57</string>
|
||||||
|
<string name="HYSIGNAL_DEVICE_ID_KEY">5b434c778490889697170e225029f56aff19ca47</string>
|
||||||
|
</map>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
AAAAAMC1eP4iV43WYoI57ZOu0w+RxCz+/O1tar0ejx4pUt8A6ic15Ax0SPFEWv9uPeP/37YHvtnDiRePwyrPRjkXVZj5amssY9T7IiSlJBjmrXs8SVhsLvxZDftuyOJ8DIHMyh0ssLNoYYJz9iTn
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
Mz/czD2wiPLUeGhHpY9JLxg04gUjCpufWkgxsjHDmwRoGkvoUk5DvegW23bZqy8lLxIrjWsirBllrZs/KSKtlWtQo2jKo6miAK5XfE6BQZCOKs1CxAXlQJlgGVmAML3aYNYvYVRHxF6i4vn5jIUFsz/mCW0yzKK6jTKS4YACiyg=
|
||||||
Binary file not shown.
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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);
|
||||||
@@ -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_<ts>.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()
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package hydev;
|
|||||||
import com.github.unidbg.AndroidEmulator;
|
import com.github.unidbg.AndroidEmulator;
|
||||||
import com.github.unidbg.Module;
|
import com.github.unidbg.Module;
|
||||||
import com.github.unidbg.arm.backend.BackendFactory;
|
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.AndroidEmulatorBuilder;
|
||||||
import com.github.unidbg.linux.android.AndroidResolver;
|
import com.github.unidbg.linux.android.AndroidResolver;
|
||||||
import com.github.unidbg.linux.android.SystemPropertyHook;
|
import com.github.unidbg.linux.android.SystemPropertyHook;
|
||||||
@@ -38,6 +38,8 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
PREFS.put("qimei16", "67453f5aef55f776967c748a10001c318101");
|
PREFS.put("qimei16", "67453f5aef55f776967c748a10001c318101");
|
||||||
// 服务端下发配置(真机 prefs 实测): getDfpConfig 可获取, 解密后含设备指纹种子
|
// 服务端下发配置(真机 prefs 实测): getDfpConfig 可获取, 解密后含设备指纹种子
|
||||||
PREFS.put("hydeviceid_config", "2AQq9oUCCZ8MgS1qhJHFp+U7vBdmTBVkH8WlXondx64IE+kvc65rHJtuTazmCLT0Z7FYQEVuosfMmclJOeQrI7PLlAhhZYU5dXoYCKQJ7/ze1y5V6DvozlZZUQLG9jgoNJ4kdEyEi3QQavTQGdiokQ==");
|
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<Map<String, String>> {
|
static class Prefs extends DvmObject<Map<String, String>> {
|
||||||
@@ -65,7 +67,7 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
private int unhandledBudget = 80;
|
private int unhandledBudget = 80;
|
||||||
|
|
||||||
HyDeviceId(String soPath) throws Exception {
|
HyDeviceId(String soPath) throws Exception {
|
||||||
BackendFactory backend = new DynarmicFactory(true);
|
BackendFactory backend = new Unicorn2Factory(true);
|
||||||
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build();
|
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build();
|
||||||
Memory memory = emulator.getMemory();
|
Memory memory = emulator.getMemory();
|
||||||
memory.setLibraryResolver(new AndroidResolver(23));
|
memory.setLibraryResolver(new AndroidResolver(23));
|
||||||
@@ -136,6 +138,10 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
return vm.resolveClass("android/app/Application",
|
return vm.resolveClass("android/app/Application",
|
||||||
vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(signature);
|
vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(signature);
|
||||||
}
|
}
|
||||||
|
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->h()Ljava/lang/String;")) {
|
||||||
|
System.out.println("[JNI] NativeBridge.h() -> (null)");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->b(I)Ljava/lang/String;")) {
|
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->b(I)Ljava/lang/String;")) {
|
||||||
System.out.println("[JNI] NativeBridge.b FALLBACK-STUB (native not registered?)");
|
System.out.println("[JNI] NativeBridge.b FALLBACK-STUB (native not registered?)");
|
||||||
int i = vaList.getIntArg(0);
|
int i = vaList.getIntArg(0);
|
||||||
@@ -143,18 +149,23 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
// b()-语义来自 jadx (NativeBridge.b -> fj3.c): 1=APPID(5008) 2=versionName 3=SDK版本(1.14.68)
|
// b()-语义来自 jadx (NativeBridge.b -> fj3.c): 1=APPID(5008) 2=versionName 3=SDK版本(1.14.68)
|
||||||
// 102=l5h.a=ANDROID_ID 104=l5h.f 105=pnc.f 106=l5h.i 107=qimei16 108=qimei36
|
// 102=l5h.a=ANDROID_ID 104=l5h.f 105=pnc.f 106=l5h.i 107=qimei16 108=qimei36
|
||||||
String v = null;
|
String v = null;
|
||||||
if (i == 100) v = System.getProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
if (i == 100) v = System.getProperty("hydev.guid", "0a7dfaa882938a6ab502511452142c57");
|
||||||
else if (i == 101 || i == 102) v = System.getProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
else if (i == 101 || i == 102) v = System.getProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
||||||
else if (i == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df";
|
else if (i == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df";
|
||||||
else if (i == 104 || i == 105) v = "";
|
else if (i == 104 || i == 105) v = "";
|
||||||
else if (i == 99 || i == 103) v = "7c5387e0539c023c31c4ff0e807e7256117385ee";
|
else if (i == 99 || i == 103) v = "7c5387e0539c023c31c4ff0e807e7256117385ee";
|
||||||
else if (i == 5) v = "{\"install\":\"2024-08-24 10:00:00\"}";
|
else if (i == 5) v = "{\"install\":\"2024-08-24 10:00:00\"}";
|
||||||
else if (i == 107) v = "1e8bdf7d4f7a01d3"; // qimei16
|
else if (i == 107) v = "1e8bdf7d4f7a01d3"; // qimei16
|
||||||
else if (i == 108) v = "0a7dfaa882938a6ab502511452142c571e8bdf7d"; // qimei36 猜测
|
else if (i == 108) v = System.getProperty("hydev.qimei36", "67453f5aef55f776967c748a10001c318101"); // qimei36 猜测
|
||||||
else if (i == 1) v = "5008";
|
else if (i == 1) v = "5008";
|
||||||
else if (i == 2) v = "13.4.22";
|
else if (i == 2) v = "13.4.22";
|
||||||
else if (i == 3) v = "1.14.68";
|
else if (i == 3) v = "1.14.68";
|
||||||
else if (i == 107 || i == 108) v = "67453f5aef55f776967c748a10001c318101";
|
else if (i == 107) v = "1e8bdf7d4f7a01d3";
|
||||||
|
else if (i == 108) v = "0a7dfaa882938a6ab502511452142c571e8bdf7d";
|
||||||
|
else if (i == 6) v = System.getProperty("hydev.b6", "hy_300023887");
|
||||||
|
else if (i == 2001) v = System.getProperty("hydev.b2001", "2001");
|
||||||
|
else if (i == 2002) v = System.getProperty("hydev.b2002", "300023887");
|
||||||
|
else if (i == 2003) v = System.getProperty("hydev.b2003", "1199666914671");
|
||||||
System.out.println("[JNI] NativeBridge.b(" + i + ") -> " + v);
|
System.out.println("[JNI] NativeBridge.b(" + i + ") -> " + v);
|
||||||
return v == null ? null : new StringObject(vm, v);
|
return v == null ? null : new StringObject(vm, v);
|
||||||
}
|
}
|
||||||
@@ -236,6 +247,24 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
case "android/content/Context->getFilesDir()Ljava/io/File;": {
|
case "android/content/Context->getFilesDir()Ljava/io/File;": {
|
||||||
return new FileObj(vm, "/data/data/com.duowan.kiwi/files");
|
return new FileObj(vm, "/data/data/com.duowan.kiwi/files");
|
||||||
}
|
}
|
||||||
|
case "android/app/Application->getPackageName()Ljava/lang/String;":
|
||||||
|
case "android/content/Context->getPackageName()Ljava/lang/String;": {
|
||||||
|
System.out.println("[JNI] getPackageName() -> com.duowan.kiwi");
|
||||||
|
return new StringObject(vm, "com.duowan.kiwi");
|
||||||
|
}
|
||||||
|
case "android/app/Application->getApplicationInfo()Landroid/content/pm/ApplicationInfo;":
|
||||||
|
case "android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;": {
|
||||||
|
System.out.println("[JNI] getApplicationInfo() -> new ApplicationInfo");
|
||||||
|
return vm.resolveClass("android/content/pm/ApplicationInfo").newObject(signature);
|
||||||
|
}
|
||||||
|
case "android/content/Context->getPackageManager()Landroid/content/pm/PackageManager;": {
|
||||||
|
System.out.println("[JNI] getPackageManager() -> new PackageManager");
|
||||||
|
return vm.resolveClass("android/content/pm/PackageManager").newObject(signature);
|
||||||
|
}
|
||||||
|
case "android/content/pm/PackageManager->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;": {
|
||||||
|
System.out.println("[JNI] getPackageInfo() -> new PackageInfo");
|
||||||
|
return vm.resolveClass("android/content/pm/PackageInfo").newObject(signature);
|
||||||
|
}
|
||||||
case "java/io/File->getAbsolutePath()Ljava/lang/String;":
|
case "java/io/File->getAbsolutePath()Ljava/lang/String;":
|
||||||
case "java/io/File->getPath()Ljava/lang/String;":
|
case "java/io/File->getPath()Ljava/lang/String;":
|
||||||
case "java/io/File->toString()Ljava/lang/String;": {
|
case "java/io/File->toString()Ljava/lang/String;": {
|
||||||
@@ -274,8 +303,41 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
|
||||||
|
System.out.println("[JNI-getField] " + signature);
|
||||||
|
if (signature.contains("ApplicationInfo") || signature.contains("PackageInfo")) {
|
||||||
|
String field = signature.substring(signature.lastIndexOf("->") + 2);
|
||||||
|
if (field.startsWith("packageName")) return new StringObject(vm, "com.duowan.kiwi");
|
||||||
|
if (field.startsWith("versionName")) return new StringObject(vm, "13.4.22");
|
||||||
|
if (field.startsWith("versionCode")) return new StringObject(vm, "115315");
|
||||||
|
if (field.startsWith("label")) return new StringObject(vm, "虎牙直播");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return super.getObjectField(vm, dvmObject, signature);
|
||||||
|
} catch (Exception e) {
|
||||||
|
noteUnhandled(signature);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
|
||||||
|
if (signature.contains("targetSdkVersion")) { System.out.println("[JNI-intfield] targetSdkVersion->30"); return 30; }
|
||||||
|
if (signature.contains("versionCode")) { System.out.println("[JNI-intfield] versionCode->115315"); return 115315; }
|
||||||
|
if (signature.contains("minSdkVersion")) { System.out.println("[JNI-intfield] minSdkVersion->30"); return 30; }
|
||||||
|
try {
|
||||||
|
return super.getIntField(vm, dvmObject, signature);
|
||||||
|
} catch (Exception e) {
|
||||||
|
noteUnhandled(signature + " -> 0");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
|
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
|
||||||
|
if (signature.equals("android/content/pm/PackageInfo->versionCode:I")) { System.out.println("[JNI-int] versionCode->115315"); return 115315; }
|
||||||
|
if (signature.equals("android/content/ApplicationInfo->versionCode:I")) return 115315;
|
||||||
try {
|
try {
|
||||||
return super.callIntMethodV(vm, dvmObject, signature, vaList);
|
return super.callIntMethodV(vm, dvmObject, signature, vaList);
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
@@ -348,7 +410,9 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
String so = args.length > 0 ? args[0] : "so/libhydeviceid_merged.so";
|
String so = args.length > 0 ? args[0] : "so/libhydeviceid_merged.so";
|
||||||
System.setProperty("hydev.android_id", args.length > 1 ? args[1] : "1e8bdf7d4f7a01d3");
|
System.setProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
||||||
|
System.setProperty("hydev.guid", "0a7dfaa882938a6ab502511452142c57");
|
||||||
|
if (args.length > 2) System.setProperty("hydev.guid", args[2]);
|
||||||
HyDeviceId h = new HyDeviceId(so);
|
HyDeviceId h = new HyDeviceId(so);
|
||||||
h.callInit();
|
h.callInit();
|
||||||
String guid = h.callString(0x20f8c8L, "getGUID");
|
String guid = h.callString(0x20f8c8L, "getGUID");
|
||||||
@@ -356,7 +420,6 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
String hdid = h.callString(0x20f0c8L, "getHDID");
|
String hdid = h.callString(0x20f0c8L, "getHDID");
|
||||||
String sdid = h.callString(0x20ede8L, "getSDID");
|
String sdid = h.callString(0x20ede8L, "getSDID");
|
||||||
String mid = h.callString(0x20fba8L, "getMID");
|
String mid = h.callString(0x20fba8L, "getMID");
|
||||||
// 32hex 探针: 再调一轮以观察缓存 (若 init 后 getGUID 重取 = 缓存)
|
|
||||||
h.emulator.close();
|
h.emulator.close();
|
||||||
System.out.println("=== GOLDEN COMPARE ===");
|
System.out.println("=== GOLDEN COMPARE ===");
|
||||||
System.out.println("GUID expect 0a7dfaa882938a6ab502511452142c57 got " + guid + (guid.equals("0a7dfaa882938a6ab502511452142c57") ? " MATCH" : " MISMATCH"));
|
System.out.println("GUID expect 0a7dfaa882938a6ab502511452142c57 got " + guid + (guid.equals("0a7dfaa882938a6ab502511452142c57") ? " MATCH" : " MISMATCH"));
|
||||||
|
|||||||
Reference in New Issue
Block a user