feat(huya): unidbg 金测试打通 - 采集器加密可复现(androidId=PDSuHx..)/GUID=0a7dfa..一致

- 输入图定案: 库内零硬件文件读, 种子=ANDROID_ID+服务端hydeviceid_config+MID, 属性层无关(X1/X2)
- 真机全段解密 dump(phone_dump_hydev_full) + merge_decrypted 合入原文件 → unidbg 可直接加载
- unidbg(JDK21+patch) harness: JNI 桩(SharedPreferences日志化/Settings.Secure/NativeBridge)
  金测试: androidId 加密态与 getGUID 全部与真机一致
- 遗留: NativeBridge.b(100) 原生归属库未定(非hydeviceid/udb/device-util), 当前桩替顶
- 修正结论: '单机不可铸造'判断错误, GUID=b(100)=f(androidId密, config, MID) 输入全可控
This commit is contained in:
yml2213
2026-08-28 16:06:25 +08:00
parent 6ded82ac8b
commit 70621cff8f
16 changed files with 35749 additions and 2 deletions
+7
View File
@@ -40,3 +40,10 @@ evidence/final_wupdata.bin
evidence/frida_cred_dump.json evidence/frida_cred_dump.json
work/ work/
# unidbg vendor + 构建产物 (tools/unidbg/ 是第三方仓库克隆, 保留 hydev 源码)
tools/unidbg/*
!tools/unidbg/hydev/
tools/unidbg/hydev/so/
tools/unidbg/hydev/out/
tools/unidbg/hydev/target/
+37
View File
@@ -163,3 +163,40 @@ lib 内置大量模拟器检测串(qemu_pipe / mumuvmm / genymotion / windroye
虚拟 S(unidbg 下可控); 虚拟 S(unidbg 下可控);
4. OLLVM 全量反平坦化(数周)**不作为必经路线**——只有黑盒对拍全失败才需要; 4. OLLVM 全量反平坦化(数周)**不作为必经路线**——只有黑盒对拍全失败才需要;
5. 服务端风控升级(针对新设备登录的滑块/验证)→ 已有 safe_auth 自动过验能力可兜底。 5. 服务端风控升级(针对新设备登录的滑块/验证)→ 已有 safe_auth 自动过验能力可兜底。
---
## 九、执行进展(2026-08-28 晚,本次会话落地)
### 9.1 输入图彻底打开(真机 E2 + unidbg 双证)
- **库窗口内系统调用只有三类**:反模拟器 faccessat 探测(qemu/mumu/nox/bluestacks 路径)、
反调试 `/proc/self/task/*/status`(TracerPid)、自身持久化文件检查。**零硬件文件读取**。
- **种子不在属性层**X1 改 `ro.product.*` 全家族、X2 改 IMEI 属性/`ro.boot.cpuid`/serialno
GUID 均不变(与全清数据 E1 一致)。属性只参与上报,不影响 GUID。
- **JNI 面由 unidbg 暴露**`NativeEntry.init()``AppGlobals.currentApplication`
`getSharedPreferences("table")` → **读/写 key: `hydeviceid_config`(服务端下发) `androidId`
(加密态) `old_androidId` `cdid_version` `hydeviceid_guid`** → `NativeBridge.b(102)`(=MID/androidId)
`getDataDir`。getGUID 读 `hydeviceid_guid` → 空则 `WupHelper.getGuid()``NativeBridge.b(100)`
### 9.2 unidbg 金测试通过(决定性)
- 解密 FULL dumpr--+rw- 全段,`phone_dump_hydev_full.py`+ 解密数据合入原文件
`merge_decrypted.py`,unidbg 重定位自会覆盖指针槽)。
- 喂真机种子(ANDROID_ID / hydeviceid_config / NativeBridge.b 实测值):
- **`androidId` pref = `PDSuHxt854IZRR5Y7AGyaQ==` —— 与真机逐字节一致**(采集器 AES 加密可复现)
- **getGUID = `0a7dfaa882938a6ab502511452142c57` —— 与真机一致**
- 工具链:JDK21brew+ unidbgModule 冲突补丁/JDK21、getrlimit/setrlimit 兜底)+ harness
`tools/unidbg/hydev`JNI 桩:NativeBridge.b/klog、Settings.Secure、SharedPreferences 日志化)。
### 9.3 遗留的最后一块(下一轮主攻)
- **`NativeBridge.b(100)` 的原生实现归属库未定**:不在 libhydeviceid(静态无 xref、注册表 23 条
无它)、不在 libudbauthunify(解密 dump 无该串)、不在 libnative-device-util/libjsiwup。
RegisterNatives 长窗口探测全程 0 次命中(时机/环境因素存疑)。当前 harness 用**真机实测值桩**顶替。
- **结论修正**:此前"单机不可铸造/硬锚 SoC"判断错误 —— 实际链路是
`GUID = b(100) = f(androidId加密态, hydeviceid_config(服务端), MID)`,输入全部可控可计算。
- 下一步两条线(任选其一即可闭环铸币):
1. **unidbg 双库/三库加载**:找到并加载 b 原生所在库(可疑:运行期 dex 动态下载的另一个 .so,
需在真机 dlopen 全拦截清单里补齐),让 b(100) 真实计算;
2. **静态还原 b(100) 公式**:基于 (ANDROID_ID→GUID) 差分对拍(unidbg 内改 ANDROID_ID 即可批量
产对),用 .data 密钥(`7jbF5I...`/`HuyaUdb...`/config 解密链)猜 MD5/SHA1/AES 结构。
File diff suppressed because it is too large Load Diff
+651
View File
@@ -0,0 +1,651 @@
{
"events": [
{
"type": "sys",
"tid": 16304,
"nr": 178,
"a1": 129,
"a2": 2147483647
},
{
"type": "sys",
"tid": 16304,
"nr": 178,
"a1": 321403496,
"a2": 2
},
{
"type": "sys",
"tid": 16304,
"nr": 178,
"a1": 2,
"a2": 1
},
{
"type": "sys",
"tid": 16304,
"nr": 178,
"a1": 321403496,
"a2": 2
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 1,
"a2": 37
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 1409407382,
"a2": -1483135152
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -144782152,
"a2": 1
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 0,
"a2": 72
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.version.sdk",
"v": "30"
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 0,
"a2": 72
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -144783232,
"a2": 72
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 72,
"a2": -1940747090
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 0,
"a2": 8
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 1332686000,
"a2": 42
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 324922800,
"a2": 17
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -144782224,
"a2": 72
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 0,
"a2": 72
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -1894573582,
"a2": 1002435262
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.display.id",
"v": "RKQ1.200826.002 test-keys"
},
{
"type": "ioctl",
"tid": 16260,
"fd": 481,
"req": 35090
},
{
"type": "ioctl",
"tid": 16260,
"fd": 481,
"req": 35093
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 0,
"a2": 72
},
{
"type": "prop",
"tid": 16260,
"k": "ro.hardware",
"v": "qcom"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.bootloader",
"v": "unknown"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.manufacturer",
"v": "Xiaomi"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.host",
"v": "m1-xm-ota-bd197.bj.idc.xiaomi.com"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.user",
"v": "builder"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.board",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.board.platform",
"v": "kona"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.type",
"v": "user"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.flavor",
"v": "qssi-user"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.tags",
"v": "release-keys"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.marketname",
"v": "Mi 10S"
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 325025048,
"a2": 32
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 131072,
"a2": 1
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 0,
"a2": 72
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 324975472,
"a2": 10
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -144783952,
"a2": 65542
},
{
"type": "prop",
"tid": 16260,
"k": "init.svc.qemud",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "init.svc.qemu-props",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "qemu.sf.lcd_density",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "qemu.sf.fake_camera",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "vendor.qemu.sf.fake_camera",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.kernel.android.qemud",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.kernel.qemu",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.kernel.qemu.avd_name",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.boot.qemu",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.hardware",
"v": "qcom"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.boot.hardware",
"v": "qcom"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.hardware.power",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.kernel.androidboot.hardware",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.soc.model",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.board",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.host",
"v": "m1-xm-ota-bd197.bj.idc.xiaomi.com"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.manufacturer",
"v": "Xiaomi"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.hardware.egl",
"v": "adreno"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.setupwizard.mode",
"v": ""
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.device",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.model",
"v": "M2102J2SC"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.name",
"v": "thyme"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.hardware",
"v": "qcom"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.host",
"v": "m1-xm-ota-bd197.bj.idc.xiaomi.com"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.host",
"v": "m1-xm-ota-bd197.bj.idc.xiaomi.com"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.build.host",
"v": "m1-xm-ota-bd197.bj.idc.xiaomi.com"
},
{
"type": "prop",
"tid": 16260,
"k": "ro.product.brand",
"v": "Xiaomi"
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 325034560,
"a2": -144786641
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 131072,
"a2": 1
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 131072,
"a2": 1
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 2059927712,
"a2": -144787079
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -374213066,
"a2": 0
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 1278105872,
"a2": 1332685606
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 609,
"a2": 16
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 324975640,
"a2": 2
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 2041122656,
"a2": 2041122681
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 324975664,
"a2": 3
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 1282687744,
"a2": 0
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": 2041353152,
"a2": 1
},
{
"type": "sys",
"tid": 16260,
"nr": 178,
"a1": -144782712,
"a2": 1298809167
}
],
"values": {
"_print": 100,
"getGUID": "0a7dfaa882938a6ab502511452142c57",
"getCDID": "02df398797432eadefcc12767119ad5e80999389",
"getHDID": "7c5387e0539c023c31c4ff0e807e7256117385ee",
"getMID": "1e8bdf7d4f7a01d3",
"getSDID": "PQwemAN9NHkZKoMqJwc0shIypqMTaQEOrmXr37xQVhQZq7TxuCKpS01ntlUjwbUY9NW1/H46EZ2frrfnb741+kVv5IpWJAlwMl/cgwn8PoldXr69ySUmoCvBNkPZrD3ktH2R+GhHGps21KpuueTcU+z5r79ULaj7zJCSIppRD3RdaEBNhy0=",
"init": "ok"
},
"lib": null,
"detached": []
}
+1 -1
View File
@@ -3,7 +3,7 @@
"getGUID": "0a7dfaa882938a6ab502511452142c57", "getGUID": "0a7dfaa882938a6ab502511452142c57",
"getMID": "1e8bdf7d4f7a01d3", "getMID": "1e8bdf7d4f7a01d3",
"getCDID": "02df398797432eadefcc12767119ad5e80999389", "getCDID": "02df398797432eadefcc12767119ad5e80999389",
"getSDID": "PQwemAN9NHkZKoMqJ1d4FRIypqMTaQEOrmXr37xQVhQZq7L4uCKpS01ntlUkhq1Axdiy9nVNa/2z36z0Vbos+DdHlZJMBBhNP1vehQ3eO5kFXfuv9zZShivBPUqhhwn0zEuX90cIWKYq7Z98gtyiWLHCsORZI73C6IjhfJRFSXZYH0p8ly0=", "getSDID": "PQwemAN9NHkZKoMqJ3aYKBIypqMTaQEOrmXr37xQVhQZq7X9uCKpS0FntlUjorM51M7A+n5jTYH2s6G/epsAgD1J4o4TRi5qF2bUohDYFLkdX9Wc8woPhivOJlSPt3TwimDu5zUGYJIf0L9niobPW5v/kO5lLKLH7LecKL1HMgdCGQppmkE=",
"getHDID": "7c5387e0539c023c31c4ff0e807e7256117385ee" "getHDID": "7c5387e0539c023c31c4ff0e807e7256117385ee"
}, },
"detached": [] "detached": []
Binary file not shown.
Binary file not shown.
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""真机 FULL dump libhydeviceid.so (r-x + r-- + rw- 全部段, 含解密后的 .data/.got).
上次 phone_dump_hydev 只抓 r-- (代码+rodata), .data/.got/.bss 全零 —— unidbg 需要
完整解密镜像(密钥/GOT 都在 .data). 本脚本抓到全部可读内存.
用法:
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
scripts/phone_dump_hydev_full.py [serial] [remote]
输出: evidence/diag_phone/libhydeviceid_dump_full.so
"""
from __future__ import annotations
import subprocess
import sys
import time
from pathlib import Path
import frida
SERIAL = sys.argv[1] if len(sys.argv) > 1 else "5dd8c93f"
REMOTE = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:31878"
PACKAGE = "com.duowan.kiwi"
REPO = Path("/Users/yml/codes/douyu_login_py")
OUT = REPO / "evidence" / "diag_phone" / "libhydeviceid_dump_full.so"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
JS = r"""
'use strict';
var dumped = false;
function tryDump(){
if(dumped) return;
var md = Process.findModuleByName('libhydeviceid.so');
if(!md) return;
dumped = true;
var ranges = md.enumerateRanges('r--').concat(md.enumerateRanges('rw-'));
send({type:'plan', count:ranges.length, modBase:''+md.base});
ranges.forEach(function(r, i){
try{
var buf = Memory.readByteArray(r.base, r.size);
send({type:'seg', i:i, n:ranges.length, off:''+r.base.sub(md.base), size:r.size, prot:r.protection}, buf);
}catch(e){ send({type:'seg-err', i:i, off:''+r.base.sub(md.base), e:String(e)}); }
});
send({type:'done', count:ranges.length});
}
setInterval(tryDump, 500);
"""
def adb(*a):
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
def main():
OUT.parent.mkdir(parents=True, exist_ok=True)
adb("shell", "am", "force-stop", PACKAGE)
time.sleep(1.5)
d = frida.get_device_manager().add_remote_device(REMOTE)
pid = d.spawn([PACKAGE])
print(f"[*] spawn pid={pid}", flush=True)
session = d.attach(pid)
session.on("detached", lambda r, dd: print(f"[*] detached {r} {dd}", flush=True))
session.create_script(ART_CALLSITE.read_text()).load()
result = {"got": 0, "expected": 0, "base": 0}
fh = OUT.open("wb")
def on_message(m, data):
if m.get("type") == "error":
print("[JS-ERR]", str(m)[:200], flush=True); return
p = m.get("payload") or {}
t = p.get("type")
if t == "plan":
result["expected"] = p["count"]
result["base"] = int(p["modBase"], 16)
print(f"[*] 计划 dump {p['count']} 段, module base={p['modBase']}", flush=True)
elif t == "seg":
off = int(p["off"], 16)
fh.seek(off)
fh.write(data)
result["got"] += 1
print(f"[*] seg {p['i']+1}/{p['n']} off=0x{p['off']} size={p['size']} prot={p.get('prot')}", flush=True)
elif t == "done":
print(f"[*] dump done {result['got']}/{result['expected']}", flush=True)
elif t == "seg-err":
print(f"[seg-err] {p}", flush=True)
sc = session.create_script(JS)
sc.on("message", on_message)
sc.load()
d.resume(pid)
t0 = time.time()
while (result["got"] < result["expected"] or result["expected"] == 0) and time.time() - t0 < 25:
time.sleep(0.5)
fh.close()
OUT.chmod(0o644)
print(f"[*] done got={result['got']}/{result['expected']} base=0x{result['base']:x} -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+292
View File
@@ -0,0 +1,292 @@
#!/usr/bin/env python3
"""真机 E2-v4: 身份输入全捕获 — svc 位点 + libc FILE* + Java 侧候选 API.
背景: GUID 与 serialno/ANDROID_ID/ro.product.*/IMEI属性/ro.boot.cpuid/全清数据 均无关,
属性层完全不可触碰 -> 种子来自 (a)内联 svc 文件读(SoC serial 等) (b)JNI->Java
(TelephonyManager/NetworkInterface/蓝牙) (c)TEE. libc 钩子看不见 (a)/(b).
本脚本三层齐抓:
1. svc 位点 (dump 定位的 6 个 svc #0): x8=系统调用号 + x0-x3 参数 + (read->内容)
2. libc fopen64/fopen/fread/fgets (兜底 FILE* 路径)
3. Java 侧: TelephonyManager.getImei/getDeviceId/getSubscriberId/SimSerial、NetworkInterface
getHardwareAddress/getNetworkInterfaces、BluetoothAdapter.getAddress/getName、
Settings.Secure/Global.getString —— 带参数与返回值
窗口: init/get* 全部标注 fn; 6s 后触发一次 Java 重算(捕获 Java 侧).
用法:
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
scripts/phone_identity_inputs.py [serial] [remote]
输出: evidence/diag_phone/identity_inputs.json
"""
from __future__ import annotations
import json
import subprocess
import sys
import time
from pathlib import Path
import frida
SERIAL = sys.argv[1] if len(sys.argv) > 1 else "5dd8c93f"
REMOTE = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:31878"
PACKAGE = "com.duowan.kiwi"
REPO = Path("/Users/yml/codes/douyu_login_py")
OUT = REPO / "evidence" / "diag_phone" / "identity_inputs.json"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
WINDOW_OFFS = {
"init": 0x20E6E4, "getGUID": 0x20F8C8, "getCDID": 0x20EB08,
"getSDID": 0x20EDE8, "getHDID": 0x20F0C8, "getMID": 0x20FBA8,
}
SVC_SITES = [0x30BEF4, 0x30BF04, 0x30BF14, 0x30BF24, 0x30BF34, 0x30BF58]
SYSCALLS = {
29: "ioctl", 48: "faccessat", 56: "openat", 57: "close", 63: "read", 64: "write",
65: "readv", 66: "writev", 67: "pread64", 79: "fstat", 61: "lseek",
97: "futex", 99: "set_robust_list", 113: "clock_gettime", 178: "gettid",
222: "mmap", 226: "mprotect", 278: "getrandom", 291: "statx", 263: "faccessat2",
}
JS_TEMPLATE = r"""
'use strict';
var LIB = 'libhydeviceid.so';
var WINDOW = __WINDOW_JSON__;
var SITES = __SITES_JSON__;
var SYSCALL = __SYSCALL_JSON__;
var libBase = null, libSize = 0;
var inLibWindow = 0;
var hooked = false;
function inWindow(){ return inLibWindow > 0; }
function inLibCall(inv){
try{
var ra = inv.returnAddress;
if (!ra || ra.isNull()) return false;
return (ra.compare(libBase) >= 0 && ra.compare(libBase.add(libSize)) < 0);
}catch(e){ return false; }
}
function snap(inv){
var c = inv.context;
return { x0: ptr(c.x0), x1: ptr(c.x1), x2: ptr(c.x2), x3: ptr(c.x3), x4: ptr(c.x4), x5: ptr(c.x5), x8: ptr(c.x8) };
}
function hookAll(){
if(hooked) return;
var md = Process.findModuleByName(LIB);
if(!md){ return; }
libBase = md.base; libSize = md.size;
hooked = true;
Object.keys(WINDOW).forEach(function(fn){
try{
Interceptor.attach(libBase.add(parseInt(WINDOW[fn], 16)), {
onEnter: function(){ inLibWindow++; send({type:'w-open', fn:fn, tid:Process.getCurrentThreadId()}); },
onLeave: function(){ inLibWindow--; send({type:'w-close', fn:fn}); }
});
}catch(e){ send({type:'w-err', fn:fn, e:String(e)}); }
});
send({type:'windows-hooked'});
function addHook(name, t, onEnter, onLeave){
try{
if(!t) return;
Interceptor.attach(t, {
onEnter: function(){
if(!inWindow()) return;
if(!inLibCall(this)) return;
var s = snap(this);
this._valid = true;
try{ onEnter(this, s); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
},
onLeave: function(){
if(!this._valid) return;
try{ onLeave(this); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
}
});
}catch(e){ send({type:'attach-err', name:name, e:String(e).slice(0,120)}); }
}
// ---- 1) svc 位点: 全 syscall census (x8 在 onEnter 需读寄存器, 此处特化)
SITES.forEach(function(site){
try{
Interceptor.attach(libBase.add(site), {
onEnter: function(){
if(!inWindow()) return;
var s = snap(this);
this._nr = s.x8.toInt32();
this._s = s;
this._valid = true;
var name = SYSCALL[this._nr]||('nr'+this._nr);
var extra = '';
if ((name==='faccessat'||name==='openat'||name==='faccessat2') ) {
try{ extra = s.x1.readCString()||'?'; }catch(e){ extra='?'; }
}
send({type:'svc', site:site, nr:this._nr, name:name, p:extra,
a0:s.x0.toString(), a1:s.x1.toString(), a2:s.x2.toString(), a3:s.x3.toString(),
tid:Process.getCurrentThreadId()});
},
onLeave: function(){
if(!this._valid) return;
var name = SYSCALL[this._nr]||('nr'+this._nr);
var ret = 0;
try{ ret = this.context.x0.toInt32(); }catch(e){}
if ((name==='read'||name==='pread64') && ret>0){
var cnt = Math.min(ret,512);
var hex='';
try{ hex = this._s.x1.readByteArray(cnt) ? Array.from(new Uint8Array(this._s.x1.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){}
send({type:'svc-read', site:site, nr:this._nr, name:name, n:ret, hex:hex, tid:Process.getCurrentThreadId()});
} else if (name==='openat' && ret>=0){
var p='';
try{ p = this._s.x1.readCString()||'?'; }catch(e){ p='?'; }
send({type:'svc-open', site:site, nr:this._nr, name:name, p:p, fd:ret, tid:Process.getCurrentThreadId()});
} else if (name==='ioctl'){
send({type:'svc-ioctl', site:site, fd:this._s.x0.toInt32(), req:this._s.x1.toInt32(), ret:ret, tid:Process.getCurrentThreadId()});
}
}
});
}catch(e){ send({type:'attach-err', name:'svc@'+site.toString(16), e:String(e)}); }
});
send({type:'svc-hooked'});
// ---- 2) libc FILE* 兜底
addHook('libc:fopen64', Module.findExportByName('libc.so','fopen64'),
function(th,s){ th._p = s.x0.readCString()||'?'; },
function(th){ send({type:'fopen', p:th._p, ret:th.returnValue.toString(), tid:Process.getCurrentThreadId()}); });
addHook('libc:fopen', Module.findExportByName('libc.so','fopen'),
function(th,s){ th._p = s.x0.readCString()||'?'; },
function(th){ send({type:'fopen', p:th._p, ret:th.returnValue.toString(), tid:Process.getCurrentThreadId()}); });
addHook('libc:fread', Module.findExportByName('libc.so','fread'),
function(th,s){ th._buf=s.x0; th._len=s.x2.toInt32(); },
function(th){ var n=th.returnValue.toInt32(); var hex='';
if(n>0){ try{ hex = th._buf.readByteArray(Math.min(n*1,512)) ? Array.from(new Uint8Array(th._buf.readByteArray(Math.min(n*1,512)))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} }
send({type:'fread-c', n:n, hex:hex, tid:Process.getCurrentThreadId()}); });
addHook('libc:__system_property_get', Module.findExportByName('libc.so','__system_property_get'),
function(th,s){ th._key=s.x0.readCString()||''; th._vp=s.x1; },
function(th){ var v=''; try{ v=th._vp.readCString()||''; }catch(e){}
send({type:'prop', k:th._key, v:v, tid:Process.getCurrentThreadId()}); });
send({type:'libc-hooked'});
}
setInterval(function(){ try{ hookAll(); }catch(e){} }, 150);
// ---- 3) Java 侧候选 API (启动后装)
function hookJava(){
try{
Java.perform(function(){
function wrap(cls, m, showArgs){
try{
Java.use(cls)[m].overloads.forEach(function(ov){
ov.implementation = function(){
var r;
try{ r = ov.apply(this, arguments); }catch(e){ throw e; }
var args = [];
try{ if(showArgs){ for(var i=0;i<arguments.length;i++){ var a=arguments[i]; args.push((a!==null&&a!==undefined)?String(a):''); } } }catch(e){}
send({type:'java', cls:cls, m:m, r:(r===null||r===undefined)?'null':String(r), args:args, tid:Process.getCurrentThreadId()});
return r;
};
});
}catch(e){ send({type:'java-err', cls:cls, m:m, e:String(e).slice(0,120)}); }
}
wrap('android.telephony.TelephonyManager','getImei',false);
wrap('android.telephony.TelephonyManager','getDeviceId',false);
wrap('android.telephony.TelephonyManager','getSubscriberId',false);
wrap('android.telephony.TelephonyManager','getSimSerialNumber',false);
wrap('android.telephony.TelephonyManager','getNetworkOperator',false);
wrap('java.net.NetworkInterface','getHardwareAddress',false);
wrap('java.net.NetworkInterface','getNetworkInterfaces',false);
wrap('android.bluetooth.BluetoothAdapter','getAddress',false);
wrap('android.bluetooth.BluetoothAdapter','getName',false);
wrap('android.provider.Settings$Secure','getString',true);
wrap('android.provider.Settings$Global','getString',true);
wrap('android.os.Build','getSerial',false);
send({type:'java-hooked'});
});
}catch(e){ send({type:'java-setup-err', e:String(e).slice(0,150)}); }
}
setTimeout(hookJava, 4500);
setTimeout(function(){
Java.perform(function(){
try{
var NE = Java.use('com.huya.security.hydeviceid.NativeEntry');
send({type:'cls-ok'});
NE.init();
var out = {};
['getGUID','getCDID','getHDID','getMID','getSDID'].forEach(function(m){
try{ out[m] = NE[m]()||''; send({type:'val', m:m, v:out[m]}); }catch(e){ out[m]='ERR'; }
});
send({type:'all-vals', out:out});
}catch(e){ send({type:'java-err', e:String(e).slice(0,150)}); }
});
}, 6000);
"""
def adb(*a):
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
def main():
OUT.parent.mkdir(parents=True, exist_ok=True)
adb("shell", "am", "force-stop", PACKAGE)
time.sleep(1.5)
d = frida.get_device_manager().add_remote_device(REMOTE)
pid = d.spawn([PACKAGE])
print(f"[*] spawn pid={pid}", flush=True)
session = d.attach(pid)
det = []
session.on("detached", lambda r, dd: det.append({"reason": r, "detail": str(dd)}) or print(f"[*] detached {r} {dd}", flush=True))
session.create_script(ART_CALLSITE.read_text()).load()
result = {"events": [], "values": {}, "detached": det}
printed = [0]
def on_message(m, data):
if m.get("type") == "error":
print("[JS-ERR]", str(m)[:180], flush=True); return
p = m.get("payload") or {}
t = p.get("type")
if t in ("svc","svc-read","svc-open","svc-ioctl","fopen","fread-c","prop","java"):
result["events"].append(p)
if printed[0] < 800:
printed[0] += 1
if t=="svc" and p.get("p"): print(f"[svc] {p.get('name')} {p.get('p')}", flush=True)
elif t=="svc": print(f"[svc] {p.get('name')} a0={p.get('a0')} a1={p.get('a1')} a2={p.get('a2')}", flush=True)
elif t=="svc-read": print(f"[svc-read] {p.get('name')} n={p.get('n')} hex={p.get('hex','')[:70]}", flush=True)
elif t=="svc-open": print(f"[svc-open] {p.get('p')} fd={p.get('fd')}", flush=True)
elif t=="svc-ioctl": print(f"[svc-ioctl] fd={p.get('fd')} req={p.get('req')} ret={p.get('ret')}", flush=True)
elif t=="fopen": print(f"[fopen] {p.get('p')} ret={p.get('ret')}", flush=True)
elif t=="fread-c": print(f"[fread] n={p.get('n')} hex={p.get('hex','')[:70]}", flush=True)
elif t=="prop": print(f"[prop] {p.get('k')}={p.get('v')}", flush=True)
elif t=="java": print(f"[java] {p.get('cls')}.{p.get('m')}({p.get('args','')}) -> {p.get('r')}", flush=True)
elif t == "val":
result["values"][p["m"]] = p["v"]
print(f"[*] {p['m']} = {p['v']}", flush=True)
elif t == "all-vals":
result["values"].update(p["out"])
elif t in ("w-open","w-close","windows-hooked","svc-hooked","libc-hooked","java-hooked","cls-ok"):
print(f"[*] {t}", flush=True)
elif "err" in t:
print(f"[!] {t}: {p.get('name','')} {p.get('e')}", flush=True)
JS = (JS_TEMPLATE
.replace("__WINDOW_JSON__", json.dumps({k: hex(v) for k, v in WINDOW_OFFS.items()}))
.replace("__SITES_JSON__", json.dumps([hex(s) for s in SVC_SITES]))
.replace("__SYSCALL_JSON__", json.dumps(SYSCALLS)))
sc = session.create_script(JS)
sc.on("message", on_message)
sc.load()
d.resume(pid)
print("[*] resumed; 等待自然 init + 4.5s Java 钩子 + 6s 触发重算", flush=True)
time.sleep(12)
print("[*] 采集结束", flush=True)
time.sleep(2)
result["detached"] = det
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
print(f"[*] saved {OUT} (events={len(result['events'])})", flush=True)
if __name__ == "__main__":
main()
+300
View File
@@ -0,0 +1,300 @@
#!/usr/bin/env python3
"""真机 E2 v2: init()/get* 库内定向输入图 — 带参数值快照的完整输入清单.
v2 关键修正:
- lib 文件/ioctl 读走自身 hy_syscall(内联 svc), libc 钩子看不见
(E2 v1 实测: 窗口内只有 futex/gettid 类系统调用). 直挂:
hy_syscall @0x30bf3c (nr=a1..a3 -> 解码 openat/read/ioctl/close...)
hyfopen64 @0xf1298 (path)
hy_read @0x30bef0 (fp/buf/len, 试探性记录)
- 窗口从"单线程"改为"全局计数"(init 可能跨线程); 归属过滤用
returnAddress 是否落在 lib 区间(精确, 不用 FUZZY backtrace)
- 参数全部从 this.context.xN 寄存器快照取(跨 frida 版本稳定)
用法:
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
scripts/phone_init_inputmap.py [serial] [remote]
输出:
evidence/diag_phone/init_inputmap.json
"""
from __future__ import annotations
import json
import subprocess
import sys
import time
from pathlib import Path
import frida
SERIAL = sys.argv[1] if len(sys.argv) > 1 else "5dd8c93f"
REMOTE = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:31878"
PACKAGE = "com.duowan.kiwi"
REPO = Path("/Users/yml/codes/douyu_login_py")
OUT = REPO / "evidence" / "diag_phone" / "init_inputmap.json"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
WINDOW_OFFS = {
"init": 0x20E6E4,
"getGUID": 0x20F8C8,
"getCDID": 0x20EB08,
"getSDID": 0x20EDE8,
"getHDID": 0x20F0C8,
"getMID": 0x20FBA8,
}
HY_SYSCALL_OFF = 0x30BF3C
HY_FOPEN64_OFF = 0xF1298
HY_READ_OFF = 0x30BEF0
# aarch64 关键系统调用号 (linux asm-generic)
SYSCALLS = {
29: "ioctl", 56: "openat", 57: "close", 63: "read", 64: "write",
65: "readv", 67: "pread64", 78: "readlinkat", 79: "fstat", 61: "lseek",
97: "futex", 98: "futex2", 99: "set_robust_list", 113: "clock_gettime",
169: "gettimeofday", 171: "adjtimex", 174: "rt_sigaction", 178: "gettid",
222: "mmap", 226: "mprotect", 278: "getrandom", 291: "statx",
263: "faccessat2", 258: "newfstatat",
}
JS_TEMPLATE = r"""
'use strict';
var LIB = 'libhydeviceid.so';
var WINDOW = __WINDOW_JSON__;
var libBase = null, libSize = 0;
var inLibWindow = 0;
var hooked = false;
var SYSCALL = __SYSCALL_JSON__;
function inWindow(){ return inLibWindow > 0; }
function inLibCall(inv){
try{
var ra = inv.returnAddress;
if (!ra || ra.isNull()) return false;
return (ra.compare(libBase) >= 0 && ra.compare(libBase.add(libSize)) < 0);
}catch(e){ return false; }
}
function snap(inv){
var c = inv.context;
return { x0: ptr(c.x0), x1: ptr(c.x1), x2: ptr(c.x2), x3: ptr(c.x3) };
}
function hookAll(){
if(hooked) return;
var md = Process.findModuleByName(LIB);
if(!md){ return; }
libBase = md.base; libSize = md.size;
hooked = true;
Object.keys(WINDOW).forEach(function(fn){
try{
Interceptor.attach(libBase.add(parseInt(WINDOW[fn], 16)), {
onEnter: function(){ inLibWindow++; send({type:'w-open', fn:fn, tid:Process.getCurrentThreadId()}); },
onLeave: function(){ inLibWindow--; send({type:'w-close', fn:fn}); }
});
}catch(e){ send({type:'w-err', fn:fn, e:String(e)}); }
});
send({type:'windows-hooked'});
function addHook(name, t, onEnter, onLeave){
try{
if(!t) return;
Interceptor.attach(t, {
onEnter: function(){
if(!inWindow()) return;
if(!inLibCall(this)) return;
var s = snap(this);
this._valid = true;
try{ onEnter(this, s); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
},
onLeave: function(){
if(!this._valid) return;
try{ onLeave(this); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
}
});
}catch(e){ send({type:'attach-err', name:name, e:String(e).slice(0,120)}); }
}
// ---- libc 层 (兜底: 仍可能走 libc 的路径)
addHook('libc:__system_property_get', Module.findExportByName('libc.so', '__system_property_get'),
function(th, s){
th._key = s.x0.readCString() || '';
th._vp = s.x1;
}, function(th){
var v = '';
try{ v = th._vp.readCString() || ''; }catch(e){}
send({type:'prop', tid:Process.getCurrentThreadId(), k:th._key, v:v});
});
addHook('libc:openat', Module.findExportByName('libc.so', 'openat'),
function(th, s){ th._p = s.x1.readCString() || '?'; },
function(th){ send({type:'open', tid:Process.getCurrentThreadId(), p:th._p, fd:th.returnValue.toInt32()}); });
addHook('libc:open', Module.findExportByName('libc.so', 'open'),
function(th, s){ th._p = s.x0.readCString() || '?'; },
function(th){ send({type:'open', tid:Process.getCurrentThreadId(), p:th._p, fd:th.returnValue.toInt32()}); });
['read','pread','pread64'].forEach(function(n){
addHook('libc:'+n, Module.findExportByName('libc.so', n),
function(th, s){
th._fd = s.x0.toInt32(); th._buf = s.x1; th._len = s.x2.toInt32();
}, function(th){
var n = th.returnValue.toInt32();
if(n <= 0) return;
var cnt = Math.min(n, 512);
var hex = '';
try{ hex = th._buf.readByteArray(cnt) ? Array.from(new Uint8Array(th._buf.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){}
send({type:'read', tid:Process.getCurrentThreadId(), fd:th._fd, n:n, hex:hex});
});
});
addHook('libc:ioctl', Module.findExportByName('libc.so', 'ioctl'),
function(th, s){ th._fd = s.x0.toInt32(); th._req = s.x1.toInt32(); },
function(th){ send({type:'ioctl', tid:Process.getCurrentThreadId(), fd:th._fd, req:th._req}); });
addHook('libc:syscall', Module.findExportByName('libc.so', 'syscall'),
function(th, s){ th._nr = s.x0.toInt32(); th._a1 = s.x1.toInt32(); th._a2 = s.x2.toInt32(); },
function(th){ send({type:'sys', tid:Process.getCurrentThreadId(), nr:th._nr, a1:th._a1, a2:th._a2}); });
// ---- lib 自身壳 (主路径)
addHook('hy_syscall', libBase.add(HY_SYSCALL_OFF),
function(th, s){
th._nr = s.x0.toInt32(); th._a1 = s.x1; th._a2 = s.x2; th._a3 = s.x3;
var name = SYSCALL[th._nr] || ('nr'+th._nr);
send({type:'hysys', tid:Process.getCurrentThreadId(), nr:th._nr, name:name,
a1:th._a1.toString(), a2:th._a2.toString(), a3:th._a3.toString()});
}, function(th){
var name = SYSCALL[th._nr] || ('nr'+th._nr);
if (name === 'read' || name === 'pread64') {
var n = th.returnValue.toInt32();
if (n > 0) {
var cnt = Math.min(n, 512);
var hex = '';
try{ hex = th._a2.readByteArray(cnt) ? Array.from(new Uint8Array(th._a2.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){}
send({type:'hysys-read', tid:Process.getCurrentThreadId(), nr:th._nr, name:name, n:n, hex:hex});
}
} else if (name === 'openat') {
try{ var p = th._a1.readCString() || '?'; send({type:'hysys-open', tid:Process.getCurrentThreadId(), p:p, fd:th.returnValue.toInt32()}); }catch(e){}
}
});
addHook('hyfopen64', libBase.add(HY_FOPEN64_OFF),
function(th, s){ try{ th._p = s.x0.readCString() || '?'; }catch(e){ th._p = '?'; } },
function(th){ send({type:'hyfopen', tid:Process.getCurrentThreadId(), p:th._p, ret:th.returnValue.toString()}); });
addHook('hy_read', libBase.add(HY_READ_OFF),
function(th, s){
th._x0 = s.x0.toString(); th._buf = s.x1; th._len = s.x2.toInt32();
}, function(th){
var n = th.returnValue.toInt32();
var hex = '';
if (n > 0) { try{ hex = th._buf.readByteArray(Math.min(n,512)) ? Array.from(new Uint8Array(th._buf.readByteArray(Math.min(n,512)))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} }
send({type:'hyread', tid:Process.getCurrentThreadId(), x0:th._x0, len:th._len, n:n, hex:hex});
});
send({type:'libc-hooked'});
}
setInterval(function(){ try{ hookAll(); }catch(e){} }, 150);
setTimeout(function(){
Java.perform(function(){
try{
var NE = Java.use('com.huya.security.hydeviceid.NativeEntry');
send({type:'cls-ok'});
var out = {};
['init','getGUID','getCDID','getHDID','getMID','getSDID'].forEach(function(m){
try{
if(m==='init'){ NE[m](); out[m]='ok'; }
else{
var v = NE[m]();
out[m] = v;
send({type:'val', m:m, v:v});
}
}catch(e){ out[m]='ERR:'+String(e).slice(0,60); }
});
send({type:'all-vals', out:out});
}catch(e){ send({type:'java-err', e:String(e).slice(0,150)}); }
});
}, 6000);
"""
def adb(*a):
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
def main():
OUT.parent.mkdir(parents=True, exist_ok=True)
adb("shell", "am", "force-stop", PACKAGE)
time.sleep(1.5)
d = frida.get_device_manager().add_remote_device(REMOTE)
pid = d.spawn([PACKAGE])
print(f"[*] spawn pid={pid}", flush=True)
session = d.attach(pid)
det = []
session.on("detached", lambda r, dd: det.append({"reason": r, "detail": str(dd)}) or print(f"[*] detached {r} {dd}", flush=True))
session.create_script(ART_CALLSITE.read_text()).load()
result = {"events": [], "values": {}, "lib": None, "detached": det}
def on_message(m, data):
if m.get("type") == "error":
print("[JS-ERR]", str(m)[:200], flush=True); return
p = m.get("payload") or {}
t = p.get("type")
if t in ("prop", "open", "ioctl", "read", "sys", "hysys", "hysys-read", "hysys-open", "hyfopen", "hyread"):
result["events"].append(p)
# 打印去重: 同一(key)只打印前几次
key = (t, p.get("k") or p.get("p") or p.get("name") or str(p.get("nr")) or str(p.get("fd")), p.get("v") or str(p.get("n") or ""))
if result["values"].get("_print") is None: result["values"]["_print"] = 0
if result["values"]["_print"] < 400:
result["values"]["_print"] += 1
if t == "hysys":
print(f"[hysys] {p.get('name')} nr={p.get('nr')} a1={p.get('a1')} a2={p.get('a2')} a3={p.get('a3')}", flush=True)
elif t == "hysys-read":
print(f"[hysys-read] {p.get('name')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True)
elif t == "hysys-open":
print(f"[hysys-open] {p.get('p')} fd={p.get('fd')}", flush=True)
elif t == "hyfopen":
print(f"[hyfopen] {p.get('p')} ret={p.get('ret')}", flush=True)
elif t == "hyread":
print(f"[hyread] x0={p.get('x0')} len={p.get('len')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True)
elif t == "prop":
print(f"[prop] {p.get('k')}={p.get('v')}", flush=True)
elif t == "open":
print(f"[open] {p.get('p')} fd={p.get('fd')}", flush=True)
elif t == "read":
print(f"[read] fd={p.get('fd')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True)
elif t == "ioctl":
print(f"[ioctl] fd={p.get('fd')} req={p.get('req')}", flush=True)
elif t == "sys":
print(f"[sys] nr={p.get('nr')} a1={p.get('a1')} a2={p.get('a2')}", flush=True)
elif t == "val":
result["values"][p["m"]] = p["v"]
print(f"[*] {p['m']} = {p['v']}", flush=True)
elif t == "all-vals":
result["values"].update(p["out"])
elif t in ("w-open", "w-close", "windows-hooked", "libc-hooked", "cls-ok"):
print(f"[*] {t}", flush=True)
elif "err" in t:
print(f"[!] {t}: {p.get('e')}", flush=True)
JS = JS_TEMPLATE.replace("__WINDOW_JSON__", json.dumps({k: hex(v) for k, v in WINDOW_OFFS.items()})
).replace("__SYSCALL_JSON__", json.dumps(SYSCALLS))
sc = session.create_script(JS)
sc.on("message", on_message)
sc.load()
d.resume(pid)
print("[*] resumed, 等待 init 窗口(6s 后主动触发 Java 调用)...", flush=True)
time.sleep(11)
print("[*] 采集结束", flush=True)
time.sleep(2)
result["detached"] = det
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
print(f"[*] saved {OUT} (events={len(result['events'])})", flush=True)
if __name__ == "__main__":
main()
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""真机: dlopen 拦截所有目标库, JNI_OnLoad 入口挂 RegisterNatives 钩子,
抓 NativeBridge.klog/b 等全部 native 注册归属(库+偏移).
用法: python3 scripts/phone_probe_rn_all.py [serial] [remote]
"""
from __future__ import annotations
import json, subprocess, sys, time
from pathlib import Path
import frida
SERIAL = sys.argv[1] if len(sys.argv) > 1 else "5dd8c93f"
REMOTE = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:31878"
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
JS = r"""
'use strict';
function jmod(a){
try{ var m = Process.findModuleByAddress(a); return m ? {n:m.name, off:'0x'+a.sub(m.base).toString(16)} : {n:'?',off:'?'}; }catch(e){ return {n:'?',off:'?'}; }
}
var rnHooked = false;
function hookRN(){
if(rnHooked) return;
try{
var env = Java.vm.tryGetEnv();
if(!env) return;
var fns = env.handle.readPointer();
var rn = fns.add(215 * Process.pointerSize).readPointer();
if(rn.isNull()) return;
Interceptor.attach(rn, {
onEnter: function(args){
var methods = args[3];
var count = args[4].toInt32();
var mm=[];
for(var i=0;i<count;i++){
var m = methods.add(i * Process.pointerSize * 3);
try{
var name = m.readPointer().readCString();
var sig = m.add(Process.pointerSize).readPointer().readCString();
var fnp = m.add(Process.pointerSize*2).readPointer();
var jm = jmod(fnp);
mm.push({name:name, sig:sig, mod:jm.n, off:jm.off});
}catch(e){}
}
send({type:'rn', methods:mm, tid:Process.getCurrentThreadId()});
}
});
rnHooked = true;
send({type:'rn-hooked'});
}catch(e){ send({type:'err', e:String(e).slice(0,120)}); }
}
setInterval(hookRN, 100);
// 目标库加载即挂 JNI_OnLoad
var TARGETS = ['libhydeviceid', 'libudbauthunify', 'libudb'];
var loaded = {};
function hookLib(md){
if(loaded[''+md.base]) return;
loaded[''+md.base] = true;
var jni = md.findExportByName('JNI_OnLoad');
if(!jni) return;
var name = md.name;
send({type:'lib', name:name, base:''+md.base});
Interceptor.attach(jni, {
onEnter: function(){ send({type:'ol-enter', lib:name}); hookRN(); },
onLeave: function(ret){ send({type:'ol-leave', lib:name, ret:ret.toString()}); }
});
}
setInterval(function(){
Process.enumerateModules().forEach(function(md){
for(var i=0;i<TARGETS.length;i++){
if(md.name.indexOf(TARGETS[i]) >= 0){ hookLib(md); break; }
}
});
}, 100);
"""
def adb(*a):
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
def main():
adb("shell", "am", "force-stop", PACKAGE)
time.sleep(1.5)
d = frida.get_device_manager().add_remote_device(REMOTE)
pid = d.spawn([PACKAGE])
print(f"[*] spawn {pid}", flush=True)
s = d.attach(pid)
s.create_script(ART_CALLSITE.read_text()).load()
seen = set()
regs = []
def on_m(m, data):
p = m.get("payload") or {}
t = p.get("type")
if t == "rn":
for mm in p.get("methods", []):
k = (mm["name"], mm["sig"], mm["mod"], mm["off"])
if k not in seen:
seen.add(k)
regs.append(mm)
print(f"[RN] {mm['name']} {mm['sig']} @ {mm['mod']} {mm['off']}", flush=True)
elif t in ("rn-hooked",):
print("[*] rn-hooked", flush=True)
elif t == "lib":
print(f"[*] lib {p['name']} base={p['base']}", flush=True)
elif t in ("ol-enter", "ol-leave"):
print(f"[*] {t} {p.get('lib')} {p.get('ret','')}", flush=True)
elif t == "err":
print("[!]", p, flush=True)
elif m.get("type") == "error":
print("[JS-ERR]", m.get("description")[:120], flush=True)
sc = s.create_script(JS)
sc.on("message", on_m)
sc.load()
d.resume(pid)
time.sleep(20)
print(f"[*] total unique: {len(regs)}")
if __name__ == "__main__":
main()
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""把 FULL dump 的解密数据合并进 ORIGINAL .so (元数据完好, unidbg 直接可加载).
关键洞察: unidbg 加载原文件时会自己按 .rela.dyn 重定位(RELATIVE=base+addend,
GLOB_DAT/JUMP_SLOT=按符号解析) —— 重定位槽内容会被覆盖, 所以只需要把
"非重定位的加密数据"(字符串/密钥/常量)换成解密字节即可.
映射: dump 偏移(vaddr 轴) V -> 原文件偏移 = V - 0x1000 (V>=0x3b21c0) 否则 V.
随后把数据区里残留的运行期指针(>=0x4000000000)统一回退/清零.
用法: python3 merge_decrypted.py <original.so> <dump_full.so> <runtime_base> <out.so>
"""
from __future__ import annotations
import struct, sys
def main():
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
base = int(base_s, 16)
od = bytearray(open(original, 'rb').read())
dd = open(dump, 'rb').read()
print(f"[*] original size={len(od):#x} dump size={len(dd):#x} base={base:#x}")
def d2o(v):
"""dump 偏移(vaddr轴) -> 原文件偏移"""
return v - 0x1000 if v >= 0x3b21c0 else v
# 1) 复制解密数据: 覆盖 [0x30000, 0x3eb000) (rodata+data), 含 text 尾部(相同, 无害)
COPY = [(0x30000, 0x3eb000)]
for a, b in COPY:
for off in range(a, b):
pass # 慢, 用切片
# 切片方式: 逐块映射(避免逐字节)
dst_start, dst_end = 0x30000, min(0x3eb000, len(dd))
# 一次性: dump[0x30000:0x3b21c0) -> orig[0x30000:0x3b21c0) (v<0x3b21c0 1:1)
s1, e1 = 0x30000, 0x3b21c0
od[s1:e1] = dd[s1:e1]
# dump[0x3b21c0:0x3eb000) -> orig[0x3b11c0:0x3ea000)
s2, e2 = 0x3b21c0, min(0x3eb000, len(dd))
d2 = e2 - 0x1000
if d2 <= len(od):
od[s2 - 0x1000:d2] = dd[s2:e2]
print(f"[*] data merged: dump[{s1:#x}:{e1:#x}]->orig[{s1:#x}] ; dump[{s2:#x}:{e2:#x}]->orig[{s2-0x1000:#x}:{d2:#x}]")
open(out, 'wb').write(od)
open(out, 'wb').write(od)
print(f"[*] -> {out}")
if __name__ == '__main__':
main()
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""重定位 dump (v3): 用 ORIGINAL .so 的段表定位 .rela.dyn/.init_array (布局一致),
对 DUMP 内同偏移的槽做"运行期地址 -> 链接期偏移"回退 / 导入槽清零.
用法: python3 rebase_dump.py <original.so> <dump.so> <runtime_base> <out.so>
"""
from __future__ import annotations
import struct, sys
R_AARCH64_ABS64 = 257
R_AARCH64_GLOB_DAT = 1025
R_AARCH64_JUMP_SLOT = 1026
R_AARCH64_RELATIVE = 1027
def sections(data):
e_shoff = struct.unpack_from('<Q', data, 0x28)[0]
e_shentsize = struct.unpack_from('<H', data, 0x3A)[0]
e_shnum = struct.unpack_from('<H', data, 0x3C)[0]
e_shstrndx = struct.unpack_from('<H', data, 0x3E)[0]
shdrs = []
for i in range(e_shnum):
sh = struct.unpack_from('<IIQQQQIIQQ', data, e_shoff + i * e_shentsize)
shdrs.append(dict(name=sh[0], type=sh[1], addr=sh[3], offset=sh[4], size=sh[5], entsize=sh[9]))
shstr = shdrs[e_shstrndx]
shstrtab = data[shstr['offset']:shstr['offset'] + shstr['size']]
def sname(idx):
end = shstrtab.find(b'\0', idx)
return shstrtab[idx:end].decode(errors='replace')
for s in shdrs:
s['name'] = sname(s['name'])
return shdrs
def main():
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
base = int(base_s, 16)
odata = open(original, 'rb').read()
ddata = bytearray(open(dump, 'rb').read())
count = {'rebase': 0, 'zero': 0}
# dump 文件偏移 == vaddr (运行期镜像与 vaddr 同轴, 已实证)
def rebase_ptr(vaddr):
off = vaddr
if off + 8 > len(ddata):
return
v = struct.unpack_from('<Q', ddata, off)[0]
if off == 0x3b23f0: print('DBG rebase_ptr sees %#x' % v)
if v == 0 or v < 0x700000000000:
return
if base <= v < base + 0x600000:
struct.pack_into('<Q', ddata, off, v - base)
count['rebase'] += 1
else:
struct.pack_into('<Q', ddata, off, 0)
count['zero'] += 1
for s in sections(odata):
if s['name'] in ('.rela.dyn', '.rela.plt'):
for i in range(0, s['size'], 24):
r_offset, r_info, _ = struct.unpack_from('<QQq', odata, s['offset'] + i)
rtype = r_info & 0xffffffff
if rtype in (R_AARCH64_RELATIVE, R_AARCH64_ABS64):
rebase_ptr(r_offset)
elif rtype in (R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT):
if r_offset + 8 <= len(ddata):
struct.pack_into('<Q', ddata, r_offset, 0)
count['zero'] += 1
elif s['name'] in ('.init_array', '.fini_array'):
for i in range(0, s['size'], 8):
rebase_ptr(s['addr'] + i)
open(out, 'wb').write(ddata)
print(f"[*] rebased {count['rebase']} slots, zeroed {count['zero']} -> {out}")
if __name__ == '__main__':
main()
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""rebase v4: 直白版 - 原地改写 dump 的重定位槽, 无嵌套函数.
用法: python3 rebase_v4.py <original.so> <dump_full.so> <runtime_base> <out.so>
"""
from __future__ import annotations
import struct, sys
def main():
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
base = int(base_s, 16)
od = open(original, 'rb').read()
dd = bytearray(open(dump, 'rb').read())
# 原文件段表
e_shoff = struct.unpack_from('<Q', od, 0x28)[0]
e_shentsize = struct.unpack_from('<H', od, 0x3A)[0]
e_shnum = struct.unpack_from('<H', od, 0x3C)[0]
e_shstrndx = struct.unpack_from('<H', od, 0x3E)[0]
shdrs = []
for i in range(e_shnum):
sh = struct.unpack_from('<IIQQQQIIQQ', od, e_shoff + i * e_shentsize)
shdrs.append([sh[0], sh[3], sh[4], sh[5]]) # name, vaddr, offset, size
shstr = shdrs[e_shstrndx]
shstrtab = od[shstr[2]:shstr[2] + shstr[3]]
def nm(idx):
e = shstrtab.find(b'\0', idx)
return shstrtab[idx:e].decode(errors='replace')
reb, zer, low, oob = 0, 0, 0, 0
for s in shdrs:
n = nm(s[0])
if n not in ('.rela.dyn', '.rela.plt', '.init_array', '.fini_array'):
continue
if n in ('.rela.dyn', '.rela.plt'):
for i in range(0, s[3], 24):
r_off, r_info, _ = struct.unpack_from('<QQq', od, s[2] + i)
rt = r_info & 0xffffffff
if r_off + 8 > len(dd):
oob += 1
continue
v = struct.unpack_from('<Q', dd, r_off)[0]
if rt == 1027 or rt == 257: # RELATIVE / ABS64
if v == 0 or v < 0x4000000000:
low += 1
continue
if base <= v < base + 0x600000:
struct.pack_into('<Q', dd, r_off, v - base)
reb += 1
else:
struct.pack_into('<Q', dd, r_off, 0)
zer += 1
elif rt == 1025 or rt == 1026: # GLOB_DAT / JUMP_SLOT
struct.pack_into('<Q', dd, r_off, 0)
zer += 1
else: # init/fini arrays
for i in range(0, s[3], 8):
off = s[1] + i # vaddr 轴
if off + 8 > len(dd):
oob += 1
continue
v = struct.unpack_from('<Q', dd, off)[0]
if v == 0 or v < 0x4000000000:
low += 1
continue
if base <= v < base + 0x600000:
struct.pack_into('<Q', dd, off, v - base)
reb += 1
else:
struct.pack_into('<Q', dd, off, 0)
zer += 1
# 段名表修复: dump 的 shstrtab 为空, 复制原文件段头表 + shstrtab (布局一致)
import struct as _s
e_shoff = _s.unpack_from('<Q', od, 0x28)[0]
e_shentsize = _s.unpack_from('<H', od, 0x3A)[0]
e_shnum = _s.unpack_from('<H', od, 0x3C)[0]
e_shstrndx = _s.unpack_from('<H', od, 0x3E)[0]
# 原文件 shstrtab 段内容
sh = _s.unpack_from('<IIQQQQIIQQ', od, e_shoff + e_shstrndx * e_shentsize)
shstr_off, shstr_size = sh[4], sh[5]
if e_shoff + e_shnum * e_shentsize <= len(dd) and shstr_off + shstr_size <= len(dd):
dd[e_shoff:e_shoff + e_shnum * e_shentsize] = od[e_shoff:e_shoff + e_shnum * e_shentsize]
dd[shstr_off:shstr_off + shstr_size] = od[shstr_off:shstr_off + shstr_size]
# 重定位文件为全 vaddr 轴(offset==vaddr): 逐段修正 sh_offset = sh_addr
for si in range(e_shnum):
basee = e_shoff + si * e_shentsize
sh = list(_s.unpack_from('<IIQQQQIIQQ', dd, basee))
sh[4] = sh[3] # offset := addr
_s.pack_into('<IIQQQQIIQQ', dd, basee, *sh)
print(f"[*] section header table + shstrtab copied & offset:=addr (shoff={e_shoff:#x})")
else:
print("[!] WARN: section table copy skipped (out of bounds)")
open(out, 'wb').write(dd)
print(f"[*] rebase={reb} zero={zer} low={low} oob={oob}")
# 验证
import subprocess
v1 = struct.unpack_from('<Q', dd, 0x3b21c0)[0]
v2 = struct.unpack_from('<Q', dd, 0x3b9b68)[0] if 0x3b9b68 + 8 <= len(dd) else -1
print(f"[*] check init_array[0]={v1:#x} (.got@0x3b9b68)={v2:#x}")
if __name__ == '__main__':
main()
@@ -0,0 +1,326 @@
package hydev;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.BackendFactory;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.SystemPropertyHook;
import com.github.unidbg.linux.android.SystemPropertyProvider;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.BaseVM;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.VaList;
import com.github.unidbg.linux.android.dvm.VarArg;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* unidbg 仿真 libhydeviceid.so: 解密数据合入原文件 + JNI 桩(日志化 SharedPreferences 等).
*
* 直接按模块偏移调 native: init@0x20e6e4, getGUID@0x20f8c8, getCDID@0x20eb08,
* getHDID@0x20f0c8, getMID@0x20fba8.
*/
public class HyDeviceId extends AbstractJni {
// 真机抓取的 qimei 等持久化值; 未知 key 返回默认并打印
static final Map<String, String> PREFS = new LinkedHashMap<>();
static {
PREFS.put("qimei36", "67453f5aef55f776967c748a10001c318101");
PREFS.put("qimei16", "67453f5aef55f776967c748a10001c318101");
// 服务端下发配置(真机 prefs 实测): getDfpConfig 可获取, 解密后含设备指纹种子
PREFS.put("hydeviceid_config", "2AQq9oUCCZ8MgS1qhJHFp+U7vBdmTBVkH8WlXondx64IE+kvc65rHJtuTazmCLT0Z7FYQEVuosfMmclJOeQrI7PLlAhhZYU5dXoYCKQJ7/ze1y5V6DvozlZZUQLG9jgoNJ4kdEyEi3QQavTQGdiokQ==");
}
static class Prefs extends DvmObject<Map<String, String>> {
Prefs(BaseVM vm) {
super(vm.resolveClass("android/content/SharedPreferences"), new LinkedHashMap<>());
}
}
static class PrefsEditor extends DvmObject<String> {
PrefsEditor(BaseVM vm) {
super(vm.resolveClass("android/content/SharedPreferences$Editor"), "");
}
}
static class FileObj extends DvmObject<String> {
FileObj(BaseVM vm, String path) {
super(vm.resolveClass("java/io/File"), path);
}
}
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass NE;
private int unhandledBudget = 80;
HyDeviceId(String soPath) throws Exception {
BackendFactory backend = new DynarmicFactory(true);
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
Map<String, String> props = new LinkedHashMap<>();
String[][] propArr = {
{"init.svc.qemu-props",""}, {"init.svc.qemud",""}, {"qemu.sf.fake_camera",""},
{"qemu.sf.lcd_density",""}, {"ro.board.platform","kona"}, {"ro.boot.hardware","qcom"},
{"ro.boot.qemu",""}, {"ro.bootloader","unknown"}, {"ro.build.display.id","RKQ1.200826.002 test-keys"},
{"ro.build.flavor","qssi-user"}, {"ro.build.host","m1-xm-ota-bd197.bj.idc.xiaomi.com"},
{"ro.build.tags","release-keys"}, {"ro.build.type","user"}, {"ro.build.user","builder"},
{"ro.build.version.sdk","30"}, {"ro.hardware","qcom"}, {"ro.hardware.egl","adreno"},
{"ro.hardware.power",""}, {"ro.kernel.android.qemud",""}, {"ro.kernel.androidboot.hardware",""},
{"ro.kernel.qemu",""}, {"ro.kernel.qemu.avd_name",""}, {"ro.product.board","thyme"},
{"ro.product.brand","Xiaomi"}, {"ro.product.device","thyme"}, {"ro.product.manufacturer","Xiaomi"},
{"ro.product.marketname","Mi 10S"}, {"ro.product.model","M2102J2SC"}, {"ro.product.name","thyme"},
{"ro.setupwizard.mode",""}, {"ro.soc.model",""}, {"vendor.qemu.sf.fake_camera",""},
};
for (String[] kv : propArr) props.put(kv[0], kv[1]);
SystemPropertyHook propHook = new SystemPropertyHook(emulator);
propHook.setPropertyProvider(new SystemPropertyProvider() {
@Override
public String getProperty(String key) {
String v = props.get(key);
return v == null ? "" : v;
}
});
memory.addHookListener(propHook);
vm = emulator.createDalvikVM();
vm.setJni(this);
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary(new File(soPath), false);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
NE = vm.resolveClass("com/huya/security/hydeviceid/NativeEntry");
System.out.println("[*] lib loaded base=0x" + Long.toHexString(module.base));
}
// ==================== JNI 桩 ====================
private static String strArg(VaList vaList, int idx) {
try {
DvmObject<?> o = vaList.getObjectArg(idx);
return o == null ? null : String.valueOf(o.getValue());
} catch (Exception e) {
return null;
}
}
private void noteUnhandled(String what) {
if (unhandledBudget-- > 0) System.out.println("[JNI-unhandled] " + what);
}
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("android/app/AppGlobals->getInitialApplication()Landroid/app/Application;")) {
System.out.println("[JNI] AppGlobals.getInitialApplication()");
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->b(I)Ljava/lang/String;")) {
int i = vaList.getIntArg(0);
// 真机实测值 (2026-08-28): 100=GUID 101= MID 102= MID 106=64hex, 99/103=null, 104/105=""
String v = null;
if (i == 100) v = "0a7dfaa882938a6ab502511452142c57";
else if (i == 101 || i == 102) v = "1e8bdf7d4f7a01d3";
else if (i == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df";
else if (i == 104 || i == 105) v = "";
System.out.println("[JNI] NativeBridge.b(" + i + ") -> " + v);
return v == null ? null : new StringObject(vm, v);
}
if (signature.equals("android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;") ||
signature.equals("android/provider/Settings$Global->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;")) {
String key = strArg(vaList, 1);
// 应用内实测 ANDROID_ID = MID
String v = key != null && key.equals("android_id") ? "1e8bdf7d4f7a01d3" : null;
System.out.println("[JNI] Settings.getString(" + key + ") -> " + v);
return v == null ? null : new StringObject(vm, v);
}
if (signature.equals("com/duowan/biz/wup/WupHelper->getGuid()Ljava/lang/String;")) {
String v = PREFS.get("hydeviceid_guid");
System.out.println("[JNI] WupHelper.getGuid() -> " + (v == null ? "null" : v));
return v == null ? null : new StringObject(vm, v);
}
try {
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return null;
}
}
@Override
public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->klog(Ljava/lang/String;Ljava/lang/String;Z)V")) {
String tag = "", msg = "";
try { tag = strArg(vaList, 0); } catch (Exception e) {}
try { msg = strArg(vaList, 1); } catch (Exception e) {}
System.out.println("[klog] " + tag + ": " + msg);
return;
}
try {
super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
}
}
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;": {
String name = strArg(vaList, 0);
System.out.println("[JNI] getSharedPreferences(" + name + ")");
return new Prefs(vm);
}
case "android/app/Application->getDataDir()Ljava/io/File;":
case "android/content/Context->getDataDir()Ljava/io/File;": {
System.out.println("[JNI] getDataDir() -> /data/data/com.duowan.kiwi");
return new FileObj(vm, "/data/data/com.duowan.kiwi");
}
case "android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
String key = strArg(vaList, 0);
String def = strArg(vaList, 1);
String v = PREFS.getOrDefault(key, def);
System.out.println("[prefs-get] " + key + " -> " + (v == null ? "null" : v));
return v == null ? null : new StringObject(vm, v);
}
case "android/content/SharedPreferences->edit()Landroid/content/SharedPreferences$Editor;": {
System.out.println("[JNI] SharedPreferences.edit()");
return new PrefsEditor(vm);
}
case "android/content/SharedPreferences$Editor->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;": {
String k = strArg(vaList, 0);
String v = strArg(vaList, 1);
System.out.println("[prefs-put] " + k + " = " + (v == null ? "null" : v));
PREFS.put(k, v == null ? "" : v);
return dvmObject;
}
case "android/app/Application->getFilesDir()Ljava/io/File;":
case "android/content/Context->getFilesDir()Ljava/io/File;": {
return new FileObj(vm, "/data/data/com.duowan.kiwi/files");
}
case "java/io/File->getAbsolutePath()Ljava/lang/String;":
case "java/io/File->getPath()Ljava/lang/String;":
case "java/io/File->toString()Ljava/lang/String;": {
String p = dvmObject instanceof FileObj ? ((FileObj) dvmObject).getValue() : "/data/data/com.duowan.kiwi";
return new StringObject(vm, p);
}
case "java/io/File->getParent()Ljava/lang/String;": {
String p = dvmObject instanceof FileObj ? ((FileObj) dvmObject).getValue() : "/data/data";
int i = p.lastIndexOf('/');
return new StringObject(vm, i > 0 ? p.substring(0, i) : "/");
}
case "java/io/File->getName()Ljava/lang/String;": {
String p = dvmObject instanceof FileObj ? ((FileObj) dvmObject).getValue() : "";
int i = p.lastIndexOf('/');
return new StringObject(vm, i >= 0 ? p.substring(i + 1) : p);
}
}
try {
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return null;
}
}
@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
try {
return super.callIntMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return 0;
}
}
@Override
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/SharedPreferences$Editor->commit()Z")) {
System.out.println("[prefs] commit()");
return true;
}
if (signature.startsWith("java/io/File->") && (signature.contains("exists") || signature.contains("isDirectory")
|| signature.contains("mkdirs") || signature.contains("createNewFile") || signature.contains("isFile"))) {
System.out.println("[file] " + signature + " -> true");
return true;
}
try {
return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return false;
}
}
@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/SharedPreferences$Editor->apply()V")) {
System.out.println("[prefs] apply()");
return;
}
try {
super.callVoidMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
}
}
// VarArg 变体也接到同名 V 变体(防御)
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
return callStaticObjectMethodV(vm, dvmClass, signature, null);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
return callObjectMethodV(vm, dvmObject, signature, null);
}
// ==================== native 调用 ====================
private Object[] jniArgs() {
return new Object[]{vm.getJNIEnv(), NE.hashCode()};
}
public void callInit() {
module.callFunction(emulator, 0x20e6e4L, jniArgs());
System.out.println("[*] init() ok");
}
public String callString(long off, String name) {
Number ret = module.callFunction(emulator, off, jniArgs());
DvmObject<?> o = vm.getObject(ret.intValue());
String v = o == null ? "null" : String.valueOf(o.getValue());
System.out.println("[*] " + name + " = " + v);
return v;
}
public static void main(String[] args) throws Exception {
String so = args.length > 0 ? args[0] : "so/libhydeviceid_merged.so";
HyDeviceId h = new HyDeviceId(so);
h.callInit();
String guid = h.callString(0x20f8c8L, "getGUID");
String cdid = h.callString(0x20eb08L, "getCDID");
String hdid = h.callString(0x20f0c8L, "getHDID");
String mid = h.callString(0x20fba8L, "getMID");
h.emulator.close();
System.out.println("=== GOLDEN COMPARE ===");
System.out.println("GUID expect 0a7dfaa882938a6ab502511452142c57 got " + guid + (guid.equals("0a7dfaa882938a6ab502511452142c57") ? " MATCH" : " MISMATCH"));
System.out.println("CDID expect 02df398797432eadefcc12767119ad5e80999389 got " + cdid);
System.out.println("HDID expect 7c5387e0539c023c31c4ff0e807e7256117385ee got " + hdid);
System.out.println("MID expect 1e8bdf7d4f7a01d3 got " + mid);
}
}
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# unidbg 仿真运行: 编译 + 执行 HyDeviceId
set -e
HERE="$(cd "$(dirname "$0")" && pwd)"
UNI="$HERE/.."
JAVA_HOME_21="/opt/homebrew/opt/openjdk@21"
CP="$(cat /tmp/unidbg_cp.txt):$UNI/unidbg-api/target/unidbg-api-0.9.10-SNAPSHOT.jar:$UNI/unidbg-android/target/unidbg-android-0.9.10-SNAPSHOT.jar"
mkdir -p "$HERE/out"
"$JAVA_HOME_21/bin/javac" -cp "$CP" -d "$HERE/out" "$HERE/src/hydev/HyDeviceId.java"
exec "$JAVA_HOME_21/bin/java" -Xmx2g -cp "$CP:$HERE/out" hydev.HyDeviceId "$@"