From 128a57cbf3b964ab03516bd1555acda74e0e627a Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 05:15:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(huya):=20OTP=E7=9C=9F=E6=9C=BA=E9=80=90?= =?UTF-8?q?=E5=AD=97=E8=8A=82=E5=A4=8D=E7=8E=B0=20+=20resinfo=E8=A7=A3?= =?UTF-8?q?=E5=AF=86=20+=20=E7=99=BB=E5=BD=95=E8=B7=AF=E5=BE=84=E5=85=AD?= =?UTF-8?q?=E5=85=83=E7=BB=84live=E6=8D=95=E8=8E=B7=20(R13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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: 真机持久化文件存档 --- docs/HUYA_HDID_ALGORITHM_GEN.md | 76 +++ evidence/live_device/guid.xml | 5 + evidence/live_device/hydckey.b64 | 1 + evidence/live_device/hydckey.bin | Bin 0 -> 111 bytes evidence/live_device/resinfo.bin | Bin 0 -> 528 bytes evidence/live_device/uuid.b64 | 1 + evidence/live_device/uuid.bin | Bin 0 -> 128 bytes tools/frida/capture_20260829_043647.jsonl | 0 tools/frida/capture_20260829_043738.jsonl | 0 tools/frida/capture_20260829_044112.jsonl | 7 + tools/frida/capture_20260829_051102.jsonl | 37 + tools/frida/hook_otp_capture.js | 273 ++++++++ tools/frida/run_capture.py | 137 ++++ tools/unidbg/hydev/src/hydev/GoldSweep.java | 674 +++++++++++++++++++ tools/unidbg/hydev/src/hydev/HyDeviceId.java | 77 ++- 15 files changed, 1281 insertions(+), 7 deletions(-) create mode 100644 evidence/live_device/guid.xml create mode 100644 evidence/live_device/hydckey.b64 create mode 100644 evidence/live_device/hydckey.bin create mode 100644 evidence/live_device/resinfo.bin create mode 100644 evidence/live_device/uuid.b64 create mode 100644 evidence/live_device/uuid.bin create mode 100644 tools/frida/capture_20260829_043647.jsonl create mode 100644 tools/frida/capture_20260829_043738.jsonl create mode 100644 tools/frida/capture_20260829_044112.jsonl create mode 100644 tools/frida/capture_20260829_051102.jsonl create mode 100644 tools/frida/hook_otp_capture.js create mode 100644 tools/frida/run_capture.py create mode 100644 tools/unidbg/hydev/src/hydev/GoldSweep.java 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 @@ + + + 0a7dfaa882938a6ab502511452142c57 + 5b434c778490889697170e225029f56aff19ca47 + 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 0000000000000000000000000000000000000000..b4c8b2a734cf92007ced3b5dfe1ec75fcfd38237 GIT binary patch literal 111 zcmV-#0FeIx0002MwRrv_SB=(Uf;sJzuG0^Z#4P^&?QLqk9*-U=Qr`gTCpF{@bV%_; zTK{f6d^VS;n^B^~k`JyaFVDj#-%eX^9KRu3}+k zu2`xq_50zn)XV}bALwqZZw9+E22OI{^~1?6nigC_f^Jx)P{IKCJxH7lkQ70JaCmNOlEt{k)-M-YIi z7$)&u_nU2mY0)y2)RaF)C#8}FkF;qHLlA2=vl)fROLKHa8tr#H7^prj>SA8Q(KL46 z=+{u~66K!hKzl-r^fQtZQ(XK^_bgj1#ZY1|;y*d`h zX$WcF5vmGNPWZoB=v|3{tXZy9WB2MKb|4MA*6*8Q4f{bv17^H#E-80DL&WF0bqWXU z?$qvRV%koDI@h)Z=3DUUM0GzD1#secnGrCEXIZ#)4d&?L4!|>$&r2(PRFBWqJEb_CDP->Ae)!P54Dw2w>-%GjB ztEw1cxXqZk4Y~WG~xv#3Y(u=NHMZ8!PD8!u7Tb2& zt1l%l5-W{sBCHu@t(!k7BCVBcP@`zdqp6|*u2+0cfkBXtD$PQ~1?51QU>R9}FumGf i)-PdHN5o#D;`#ZEg$1)e<_T>w%%ZxDGLqqd0*fd}_BuZR literal 0 HcmV?d00001 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> { @@ -65,7 +67,7 @@ public class HyDeviceId extends AbstractJni { private int unhandledBudget = 80; 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(); Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); @@ -136,6 +138,10 @@ public class HyDeviceId extends AbstractJni { return vm.resolveClass("android/app/Application", 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;")) { System.out.println("[JNI] NativeBridge.b FALLBACK-STUB (native not registered?)"); 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) // 102=l5h.a=ANDROID_ID 104=l5h.f 105=pnc.f 106=l5h.i 107=qimei16 108=qimei36 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 == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df"; else if (i == 104 || i == 105) v = ""; else if (i == 99 || i == 103) v = "7c5387e0539c023c31c4ff0e807e7256117385ee"; else if (i == 5) v = "{\"install\":\"2024-08-24 10:00:00\"}"; else if (i == 107) v = "1e8bdf7d4f7a01d3"; // qimei16 - else if (i == 108) v = "0a7dfaa882938a6ab502511452142c571e8bdf7d"; // qimei36 猜测 + else if (i == 108) v = System.getProperty("hydev.qimei36", "67453f5aef55f776967c748a10001c318101"); // qimei36 猜测 else if (i == 1) v = "5008"; else if (i == 2) v = "13.4.22"; 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); 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;": { 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->getPath()Ljava/lang/String;": case "java/io/File->toString()Ljava/lang/String;": { @@ -274,8 +303,41 @@ public class HyDeviceId extends AbstractJni { 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 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 { return super.callIntMethodV(vm, dvmObject, signature, vaList); } catch (UnsupportedOperationException e) { @@ -348,7 +410,9 @@ public class HyDeviceId extends AbstractJni { public static void main(String[] args) throws Exception { 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); h.callInit(); String guid = h.callString(0x20f8c8L, "getGUID"); @@ -356,7 +420,6 @@ public class HyDeviceId extends AbstractJni { String hdid = h.callString(0x20f0c8L, "getHDID"); String sdid = h.callString(0x20ede8L, "getSDID"); String mid = h.callString(0x20fba8L, "getMID"); - // 32hex 探针: 再调一轮以观察缓存 (若 init 后 getGUID 重取 = 缓存) h.emulator.close(); System.out.println("=== GOLDEN COMPARE ==="); System.out.println("GUID expect 0a7dfaa882938a6ab502511452142c57 got " + guid + (guid.equals("0a7dfaa882938a6ab502511452142c57") ? " MATCH" : " MISMATCH"));