dfpReport: 确定加密为逐字节XOR(动态keystream)+同步pair捕获+IDA导出线索, 记录破解进度

This commit is contained in:
yml2213
2026-08-26 14:17:58 +08:00
parent acb0a737eb
commit 4eec01568f
38 changed files with 4942 additions and 3 deletions
+44
View File
@@ -0,0 +1,44 @@
# Hook 脚本运行与捕获约定(强制遵守)
> 适用范围:本仓库所有 `scripts/hook_*.py` / `probe_*.py` / `scan_*.py` 的创建、修改与运行。
> 原则:**脚本挂上 hooks 后,用后台任务运行;数据实时写日志;目标数据一命中,立刻主动停止任务。绝不用 bash 前台 `sleep` 等超时结束再看结果。**
## 1. 运行方式(必须)
- 启动:一律 `bash` 工具的 `run_in_background: true`,例如:
```
cd /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0 && \
.venv/bin/python -u scripts/hook_xxx.py > /tmp/hook_xxx.log 2>&1 &
```
脚本内部必须 `print(..., flush=True)`(或 `-u`),保证日志实时落盘。
- 观察:用 `bash` 前台 `tail -f /tmp/hook_xxx.log`(或 `grep` 关键字)实时查看日志,**不要** `sleep 120` 后一次性读。
- 停止:一旦日志中出现目标数据(`[hit]` / `[wire]` / `[FOUND]` 等命中标记),立即:
```
pkill -f hook_xxx.py # 或 job_kill 对应后台任务
```
保存 `evidence/*.json` 后即可分析。不需要等脚本自身的超时窗口走完。
- 配套交互:需要用户在手机上操作(退出登录→重新登录)时,脚本打印提示后立即进入等待,hook 数据实时刷日志;用户操作完成后我们随时 `tail` 查看,命中即杀。
## 2. 脚本自身结构(必须)
- 主循环用 `time.sleep(5)` 轮询,但**每条捕获都立即 `send()`**`on_message` 里 `print(flush=True)`。
- 超时窗口(如 150s)只是兜底,不是等待方式:数据命中即可提前退出(`pkill`/`job_kill`)。
- 捕获结果写 `evidence/` 下 JSON`script.on('message')` 全程收集事件,退出前统一落盘;但如果提前被杀,落盘逻辑可能不执行 —— 因此**命中即打印+写入**:`on_message` 里命中类型直接 `OUT` 文件追加写入,保证被杀前数据已持久化。
- 统一模块:`RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")`frida 环境 + bypass 脚本在此)。
- 设备连接:`frida.get_device_manager().add_remote_device("127.0.0.1:31877")`(设备上 frida-server 14.2.18 监听 0.0.0.0:31877macOS 侧 `adb forward tcp:31877 tcp:31877`)。
- spawn 流程(稳定版,参考 `hook_genbiz.py`):
1. `device.spawn(["com.duowan.kiwi"])`
2. 加载 `bypass_msaoaid_maps_art_callsite.js`
3. 加载 `mask_frida_maps_only.js`
4. `device.resume(pid)``sleep 11`
5. 加载 `patch_guard_block_termination.js`
6. 再 `create_script` 主 hook JS
- 注意:**不要直接 attach 运行中的 kiwi 进程**App 有 msaoaid 反调试,attach 会被杀/闪退);必须 spawn + 三件套 bypass。
- 注意:脚本里的 JS 若在 `resume` 前 `load()`,目标 so 尚未加载,hook 会落空 —— 模块相关枚举/attach 用 `setInterval` 轮询或放在 resume+11s 之后。
## 3. 常见坑
- `frida-ps -U` 正常但 remote spawn 报 `TransportError`:检查 `adb forward --list`,必须 `tcp:31877 -> tcp:31877`。
- 设备上会同时存在多个 kiwi 进程(主进程 + cloudpatch/logcat 子进程);**hook 必须挂在我们 spawn 的主进程 pid 上**,用户手动操作的若为旧实例,hook 会空转(日志 0 命中)。操作前先 `am force-stop com.duowan.kiwi` 清掉旧实例。
- datadiv 导出名是 `.datadiv_decode...`(带点前缀),匹配用 `name.indexOf('datadiv') >= 0`,不要用 `=== 0`。
- `libhydeviceid.so` 业务符号全部混淆/内部化(只导出 `hy_*` syscall 包装 + JNI_OnLoad),dfpReport 加密体生成函数不在导出表;静态搜不到魔数 `571882cf664bb39401ee` 属正常(运行时拼装)。
+129
View File
@@ -0,0 +1,129 @@
# dfpReport 破解进度总结 (待破解#2)
> **当前状态**:加密算法已确定为**逐字节 XOR keystream**(非 XXTEA),明文 JSON 已全部拿到。
> 剩余关键问题:**keystream 每会话动态生成**,需找到其生成函数/种子。
> IDA 已导出两个 so 的完整反编译+反汇编:`/Users/yml/Downloads/so/{libhydeviceid,libudbauthunify}.so_export_for_ai`
---
## 一、已确定的结论(铁证)
### 1. dfpReport 加密 = 逐字节 XOR keystream(不是 XXTEA!)
**证据链**:
- 同会话 5 次 dfpReport(wire 间隔 3-6s),密文两两 XOR 非零位置 3947/3984 → keystream 每会话动态
- 但**密文前 10B 魔数恒定** `57 18 82 cf 66 4b b3 94 01 ee`(跨天、跨会话)→ 魔数是**明文头 XOR 固定值**或者**固定前缀**
- 8.23 各样本位置 11-22 部分相同 → 是明文固定字段,不是 keystream 泄漏
- 若为 XXTEA(hyudbxxt),则密文任何字节变化会扩散到全部 → 但实测前 10B 不动 → 排除 XXTEA
### 2. tag2 密文结构(3984B)
```
tag2 = [魔数 10B][XOR密文 3974B?]
```
- 明文 ≈ JSON(554B) + 采集二进制(3420B) ≈ 3974B,与"密文长度-魔数"吻合
- 但注意:3954B 对齐实验(偏移 0..9 all ~1% 相同率)未找到明文精确起点 → **加密输入可能不是纯 JSON,JSON 只是其中一部分**(pa 结构待最终确认)
### 3. 明文 JSON 完整捕获(554B,可铸造)
```json
{"appId":"5008","appVer":"13.4.22","appkey":"865a4924a40897ac1fcfe6b4c2cbb0e3",
"channel":"xiaomi","deviceId":"02df398797432eadefcc12767119ad5e80999389",
"deviceName":"M2102J2SC","hdid":"7c5387e0539c023c31c4ff0e807e7256117385ee",
"heightPixels":"2120","isCloud":0,"isForbidLog":1,"isHome":0,"isPre":0,"openAppId":"",
"savePath":"/data/user/0/com.duowan.kiwi/files/huyaudb","sdkVer":"1.0.80138",
"servantName":"huyaudbwebui","shareAppDataPath":"/data/user/0/com.duowan.kiwi/files/huyaudb",
"systemInfo":"android","systemVer":"M2102J2SC,30,11","terminalType":1,"testEnv":0,"widthPixels":"1080"}
```
- **hdid 字段 = 服务端返回的 device_id (t5)** `7c5387...` → 服务端回显本地明文,本地可完全控制设备身份!
- appkey `865a...cbb0e3` ≠ 静态 t1 `865a...cbb045` → appkey 来自运行时配置
### 4. 响应回显验证(t1/t5 本地可铸造)
- 重放任意 wire body → HTTP 200,344B 响应,同一 t1+t5,全新 t2
- t1(`865a4924a40897ac1fcfe6b4c2cbb045`) 静态埋于 libhydeviceid .data(0x3bb190, 0x3bb230, 0x3e0e50),datadiv 加载时解密
- **patch 明文实验**:改明文 JSON 的 hdid/appkey/deviceId(等长替换)后 app 崩溃 → 说明有校验或时序问题,但方向正确(见下"待办")
### 5. keystream 前 24B(从 5 组同步 pair 恢复)
```
# 修正对齐 off=0 (密文[10:] XOR 明文[0:]):
t=1379: 07 f8 60 f9 b8 bc 30 bf 76 4e d4 8d a8 f6 54 21 31 fa 47 53 72 63 ef 29
t=7268: 17 f9 70 e5 b8 3c 6c be 77 e6 ac cb c3 2a 6a 8c ef aa cd ce eb e5 67 a7
t=3695: 17 f9 70 f9 b1 3c 4c be 77 74 14 ee 5e 7c e0 88 3a e2 c3 2c 72 63 8c 10
...
```
各会话 keystream 不同 → **动态生成**,极可能种子 = 时间戳或会话随机 + 固定密钥
---
## 二、IDA 导出可用资源(用户新提供,重点!)
```
/Users/yml/Downloads/so/libhydeviceid.so_export_for_ai/
decompile/ # 3895 个 C 反编译
disassembly/ # 逐函数汇编
exports.txt # 1832 个导出 (含 70 个 .datadiv_decode)
function_index.txt, strings.txt, memory/...
/Users/yml/Downloads/so/libudbauthunify.so_export_for_ai/
decompile/252700.c # hyudbxxt::xxtea_encrypt 标准 XXTEA (dfp 无关,登录用)
```
**优先排查的加密函数**:
- `decompile/1107CC.c` 引用 `0x5718` 魔数常量 ×3!(sub_1107CC,调用者 0xa6a3c)← **最可疑是真加密/解密函数**
- `decompile/1D52C8.c``1D5844.c``294FB0.c``7BF8C.c` 也含 0x5718
- 大 XOR 循环候选:17F75C, 212B04, 21F98C, 2332D4, 23755C, 23C534, 26D7B0, 274BBC, B5A18
---
## 三、动态 hook 关键资产
### 抓同步 (明文, 密文) 对 —— 已成功!
`scripts/capture_pair_sync.py`:spawn+bp3 绕过 → SSL_write 命中 dfpReport 时 80ms 后全内存扫描 `{"appId":"5008"` → 同时刻明文。5 组同步 pair 在 `evidence/dfp_pair_sync.json`
### 环境
- RE repo venv: `/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python -u`
- 远程 frida: `adb forward tcp:31877 tcp:31877` + `frida.get_device_manager().add_remote_device("127.0.0.1:31877")`
- 绕过:spawn 后先 `bypass_msaoaid_maps_art_callsite.js` + `mask_frida_maps_only.js` → resume → 11s 后 `patch_guard_block_termination.js`
- **hook 规则**:后台任务 + tail 实时看 + 命中即 pkill(见 `docs/Hook脚本运行与捕获约定.md`)
---
## 四、下一步行动(按优先级)
1. **读 IDA 反编译确认加密**:重点 `decompile/1107CC.c`(引用 0x5718!)及其调用者链 0xa6a3c → 找 keystream 生成函数(输入时间戳/随机种子 + 固定 key,输出 3974B 流)
2. **hook 加密函数**:定位后 hook,抓 (明文输入, key/种子, 密文输出) 三元组 → 直接复现纯 Python 加密
3. **验证 keystream 种子假设**:比对 5 组 keystream 的 Δ 与 wire 时间戳 Δ(3-6s 间隔,keystream 差异是否对应计数/时间)→ 若种子=时间戳,纯代码可复现
4. **patch 明文实验修复**:上次改明文崩溃可能因 datadiv 重解码或时序;改用 hook 加密函数输入 buffer 后直接改明文再放行,避免物理内存写
5. **最终目标**:纯 Python 生成 dfpReport 请求体 = 构造明文 JSON(改 hdid/deviceId/appkey 铸造新设备) + 魔数 + 复现 keystream 加密 → 重放得到全新 t1/t5
---
## 五、账号/数据资产
- 账号 `hy_300026595` / `aa778899`(老 ref `hy_300024708`)
- HAR:`/Users/yml/Desktop/抓包/hy/8.23/登录-{1,2,3,5}.har`(6 个 dfpReport wire,t2 len 3408-4237)
- 同一设备(小米 M2102J2SC, Android 11, 快照 `5dd8c93f`)所有 wire 魔数头相同
- 关键回显:hdid `7c5387e0539c023c31c4ff0e807e7256117385ee` = 明文 hdid = 响应 t5
## 六、关键文件索引
| 文件 | 内容 |
|---|---|
| `evidence/dfp_pair_sync.json` | **5 组同步 (密文+明文) pair**(最新最有价值) |
| `evidence/dfp_pair.json` | 早期 3 组 pair(wire len 4278/4397/4413) |
| `evidence/dfp_wire_stack.json` | 8 事件 5 dfpReport wire HEX |
| `evidence/dfp_plain_big.json` | 187 个堆命中(明文 JSON @0x7814096d19 等) |
| `evidence/dfp_datadiv_full.json` | .data 7895 个已解码字符串(t1 所在) |
| `scripts/capture_pair_sync.py` | 同步 pair 抓取脚本(可复用) |
| `/Users/yml/Downloads/so/libhydeviceid.so_export_for_ai/` | **IDA 反编译(新!)** |
## 七、新会话起点(最重要的下一步)
**IDA 反编译确认加密**:`/Users/yml/Downloads/so/libhydeviceid.so_export_for_ai/decompile/1107CC.c`
- 引用 `0x5718` 魔数 ×3,调用者 `0xa6a3c` → 极可能是 dfpReport 加解密核心
- 同时看:1D52C8.c, 1D5844.c, 294FB0.c, 7BF8C.c(均含 0x5718)
- 反汇编对应文件在 `disassembly/`.asm
**验证 keystream 种子**:evidence/dfp_pair_sync.json 有 5 组同步 pair,
两组 keystream 差 vs 时间戳差 → 判断种子是否时间戳/计数器。
+1
View File
@@ -0,0 +1 @@
[]
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
[]
+496
View File
@@ -0,0 +1,496 @@
[
{
"section": "36aec0",
"strings": [
{
"s": "@St13runtime_error",
"off": 3583727
},
{
"s": "St9exception",
"off": 3583746
},
{
"s": "St12length_error",
"off": 3583759
},
{
"s": "St11logic_error",
"off": 3583776
},
{
"s": "N8nlohmann16json_abi_v3_11_36detail9exceptionE",
"off": 3583792
},
{
"s": "N6udbjce21JceDecodeInvalidValueE",
"off": 3583839
},
{
"s": "N6udbjce18JceDecodeExceptionE",
"off": 3583872
},
{
"s": "N6udbjce12JceExceptionE",
"off": 3583902
},
{
"s": "N6udbjce17JceDecodeMismatchE",
"off": 3583927
},
{
"s": "N6udbjce24JceDecodeRequireNotExistE",
"off": 3583956
},
{
"s": "N8nlohmann16json_abi_v3_11_36detail10type_errorE",
"off": 3583992
},
{
"s": "000102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798990001020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989900010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899",
"off": 3584441
},
{
"s": "U1(\\Q",
"off": 3585114
},
{
"s": "~)p$w",
"off": 3585449
},
{
"s": "11eU%",
"off": 3585529
},
{
"s": "St12out_of_range",
"off": 3586312
},
{
"s": "NSt6__ndk117bad_function_callE",
"off": 3586329
},
{
"s": "N8nlohmann16json_abi_v3_11_36detail16invalid_iteratorE",
"off": 3586360
},
{
"s": "N17hyudb_packet_util12cred_packerrE",
"off": 3586416
},
{
"s": "N17hyudb_packet_util14cred_unpackerrE",
"off": 3586452
},
{
"s": "hy_fclose",
"off": 3586512
},
{
"s": "hy_fileno_unlocked",
"off": 3586522
},
{
"s": "hy_fileno",
"off": 3586541
},
{
"s": "hy_clearerr_unlocked",
"off": 3586551
},
{
"s": "hy_clearerr",
"off": 3586572
},
{
"s": "hy_feof_unlocked",
"off": 3586584
},
{
"s": "hy_feof",
"off": 3586601
},
{
"s": "hy_ferror_unlocked",
"off": 3586609
},
{
"s": "hy_ferror",
"off": 3586628
},
{
"s": "hy_fseek",
"off": 3586638
},
{
"s": "hy_fseeko",
"off": 3586647
},
{
"s": "hy_fseeko64",
"off": 3586657
},
{
"s": "hy_fsetpos",
"off": 3586669
},
{
"s": "hy_fsetpos64",
"off": 3586680
},
{
"s": "hy_ftello",
"off": 3586693
},
{
"s": "hy_ftello64",
"off": 3586703
},
{
"s": "hy_fgetpos",
"off": 3586715
},
{
"s": "hy_fgetpos64",
"off": 3586726
},
{
"s": "hy_fgetc",
"off": 3586739
},
{
"s": "hy_fgetc_unlocked",
"off": 3586748
},
{
"s": "hy_fgets",
"off": 3586766
},
{
"s": "hy_fputc",
"off": 3586775
},
{
"s": "hy_fputc_unlocked",
"off": 3586784
},
{
"s": "hy_fputs",
"off": 3586802
},
{
"s": "hy_fputs_unlocked",
"off": 3586811
},
{
"s": "hy_getc",
"off": 3586829
},
{
"s": "hy_getc_unlocked",
"off": 3586837
},
{
"s": "hy_getline",
"off": 3586854
},
{
"s": "hy_putc",
"off": 3586865
},
{
"s": "hy_putc_unlocked",
"off": 3586873
},
{
"s": "hyrewind",
"off": 3586890
},
{
"s": "hysetbuf",
"off": 3586899
},
{
"s": "hy_setbuffer",
"off": 3586908
},
{
"s": "hysetlinebuf",
"off": 3586921
},
{
"s": "hy_fread",
"off": 3586934
},
{
"s": "hy_fread_unlocked",
"off": 3586943
},
{
"s": "hy_fwrite",
"off": 3586961
},
{
"s": "hy_fwrite_unlocked",
"off": 3586971
},
{
"s": "hy_pclose",
"off": 3586990
},
{
"s": "?Infinity",
"off": 3587023
}
]
},
{
"section": "3b2460",
"strings": []
},
{
"section": "3bb000",
"strings": [
{
"s": "/device/fingerprint/check",
"off": 3911680
},
{
"s": "/device/fingerprint/log",
"off": 3911712
},
{
"s": "/device/fingerprint/link",
"off": 3911744
},
{
"s": "/dckey/check",
"off": 3911776
},
{
"s": "STATUS_RESULT_CODE",
"off": 3911792
},
{
"s": "STATUS_RESULT_DESC",
"off": 3911824
},
{
"s": "1.2.7",
"off": 3911844
},
{
"s": "deflateInit2 failed",
"off": 3911856
},
{
"s": "gzip compression failed",
"off": 3911888
},
{
"s": "inflateInit2 failed",
"off": 3911920
},
{
"s": "gzip decompression failed",
"off": 3911952
},
{
"s": "collecting begin",
"off": 3911984
},
{
"s": "collecting end",
"off": 3912016
},
{
"s": "huyaudbwebui",
"off": 3912032
},
{
"s": "getDfpConfig",
"off": 3912048
},
{
"s": "865a4924a40897ac1fcfe6b4c2cbb045",
"off": 3912080
},
{
"s": "hydeviceid_config",
"off": 3912128
},
{
"s": "repeat collect start",
"off": 3912160
},
{
"s": "debug",
"off": 3912184
},
{
"s": "repeat collect end",
"off": 3912192
},
{
"s": "appid",
"off": 3912212
},
{
"s": "terminal",
"off": 3912224
},
{
"s": "865a4924a40897ac1fcfe6b4c2abc798",
"off": 3912240
},
{
"s": "huyaudbnimoui",
"off": 3912280
},
{
"s": "dfpReport",
"off": 3912296
},
{
"s": "HuyaProxy_uploadDfp",
"off": 3912320
},
{
"s": "sdid is empty",
"off": 3912344
},
{
"s": "report",
"off": 3912360
},
{
"s": "netPostNS response is empty",
"off": 3912368
},
{
"s": "version",
"off": 3912400
},
{
"s": "android",
"off": 3912408
},
{
"s": "adb_input_list",
"off": 3912448
},
{
"s": "event",
"off": 3912464
},
{
"s": "upload_motion_event",
"off": 3912480
},
{
"s": "deviceinfo",
"off": 3912504
},
{
"s": "touch_position",
"off": 3912520
},
{
"s": "is_applist_upload",
"off": 3912544
},
{
"s": "installed_appname",
"off": 3912576
},
{
"s": "scanned_appname",
"off": 3912608
},
{
"s": "script_push",
"off": 3912624
},
{
"s": "screenrecord",
"off": 3912640
},
{
"s": "display_info",
"off": 3912656
},
{
"s": "update_display_info",
"off": 3912672
},
{
"s": "table",
"off": 3912692
},
{
"s": "7jbF5IiqqIDdgEtiNQLzJSuTZmRk8sbc",
"off": 3912704
},
{
"s": "allocator<T>::allocate(size_t n) 'n' exceeds maximum supported size",
"off": 3912752
},
{
"s": "vector",
"off": 3912820
},
{
"s": "sdid: ",
"off": 3912828
},
{
"s": "hdid: ",
"off": 3912836
},
{
"s": "basic_string",
"off": 3912848
},
{
"s": "961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3",
"off": 3912864
},
{
"s": "[json.exception.",
"off": 3912912
},
{
"s": "unique_lock::try_lock_for: references null mutex",
"off": 3912944
},
{
"s": "unique_lock::try_lock_for: already locked",
"off": 3913008
},
{
"s": "invalid string size, tag: %d, size: %u",
"off": 3913056
},
{
"s": "wup.GetDfpConfigReq",
"off": 3913104
},
{
"s": "ServantName must not be empty",
"off": 3913136
},
{
"s": "FuncName must not be empty",
"off": 3913168
},
{
"s": "packet length too short",
"off": 3913200
},
{
"s": "read 'Short' type mismatch, tag: %d, get type: %d.",
"off": 3913232
}
]
}
]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+312
View File
@@ -0,0 +1,312 @@
[
{
"type": "plain",
"addr": "0x6e62f26602",
"s": "android-movedRN_DrawerLayoutAndroid.android.permission.ACCESS_COARSE_LOCATIONativePerformanceCxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxxhdpixels_sampledRN_infoLoget ScrollViewriteBigUInt64BEN_STRUCTENDropping session because custom transport doesn't implement sendSessionAggregatesprevD"
},
{
"type": "plain",
"addr": "0x6ff481c395",
"s": "android-ndk-r20b/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++ (ver 8.0)\n C++ flags (Release): -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -fno-addrsig -Wa,--noexecstack -Wformat -Werror=format-security -fsigned"
},
{
"type": "plain",
"addr": "0x6ff481c9f4",
"s": "android-ndk-r20b/toolchains/llvm/prebuilt/linux-x86_64/bin/clang\n C flags (Release): -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -fno-addrsig -Wa,--noexecstack -Wformat -Werror=format-security -fsigned-char -W -Wall"
},
{
"type": "plain",
"addr": "0x6ff481d697",
"s": "android-ndk-r20b (ver 20.1.5948944)\n Android ABI: arm64-v8a\n NDK toolchain: aarch64-linux-android-clang\n STL type: c++_static\n Native API level: 21\n Android SDK: not used, projects are not built\n\n GUI: \n\n Me"
},
{
"type": "plain",
"addr": "0x6ff481d715",
"s": "android-clang\n STL type: c++_static\n Native API level: 21\n Android SDK: not used, projects are not built\n\n GUI: \n\n Media I/O: \n ZLib: build (ver 1.2.11)\n JPEG: build-libjpeg-turbo (ver 2.0.5-"
},
{
"type": "plain",
"addr": "0x6ff62e6042",
"s": "android-movedRN_DrawerLayoutAndroid.android.permission.ACCESS_COARSE_LOCATIONativePerformanceCxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxxhdpixels_sampledRN_infoLoget ScrollViewriteBigUInt64BEN_STRUCTENDropping session because custom transport doesn't implement sendSessionAggregatesprevD"
},
{
"type": "plain",
"addr": "0x6ff6426642",
"s": "android-movedRN_DrawerLayoutAndroid.android.permission.ACCESS_COARSE_LOCATIONativePerformanceCxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxxhdpixels_sampledRN_infoLoget ScrollViewriteBigUInt64BEN_STRUCTENDropping session because custom transport doesn't implement sendSessionAggregatesprevD"
},
{
"type": "plain",
"addr": "0x74d4022502",
"s": "android-movedRN_DrawerLayoutAndroid.android.permission.ACCESS_COARSE_LOCATIONativePerformanceCxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxxhdpixels_sampledRN_infoLoget ScrollViewriteBigUInt64BEN_STRUCTENDropping session because custom transport doesn't implement sendSessionAggregatesprevD"
},
{
"type": "plain",
"addr": "0x74d4d7e702",
"s": "android-skeleton-lottie/game-site-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/game-site-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/game-site-skeleton-fold.json\",\n \"folded_dark_lottie_js"
},
{
"type": "plain",
"addr": "0x74d4d7e76c",
"s": "android-skeleton-lottie/game-site-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/game-site-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/game-site-skeleton-fold-dark.json\"\n },\n {\n "
},
{
"type": "plain",
"addr": "0x74d4d7e7dd",
"s": "android-skeleton-lottie/game-site-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/game-site-skeleton-fold-dark.json\"\n },\n {\n \"style\": 5,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-category-sk"
},
{
"type": "plain",
"addr": "0x74d4d7e853",
"s": "android-skeleton-lottie/game-site-skeleton-fold-dark.json\"\n },\n {\n \"style\": 5,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-category-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-category-ske"
},
{
"type": "plain",
"addr": "0x74d4d7e8da",
"s": "android-skeleton-lottie/short-story-category-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-category-skeleton.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-category-skeleton-fold.json\""
},
{
"type": "plain",
"addr": "0x74d4d7e94f",
"s": "android-skeleton-lottie/short-story-category-skeleton.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-category-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton\n[I][2026-08-26 +8.0 13:12:04.950][3"
},
{
"type": "plain",
"addr": "0x74d4d7e9c6",
"s": "android-skeleton-lottie/short-story-category-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton\n[I][2026-08-26 +8.0 13:12:04.950][32260, 32301][NetService-MtpMarsTransporter][[13:12:04.949]-lottie/short-story-category-skeleton-fold.json\"\n },\n {\n "
},
{
"type": "plain",
"addr": "0x74d4d7ea47",
"s": "android-skeleton\n[I][2026-08-26 +8.0 13:12:04.950][32260, 32301][NetService-MtpMarsTransporter][[13:12:04.949]-lottie/short-story-category-skeleton-fold.json\"\n },\n {\n \"style\": 6,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-skeleton.json\",\n \"dark_lot"
},
{
"type": "plain",
"addr": "0x74d4d7eb32",
"s": "android-skeleton-lottie/short-story-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-skeleton.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-skeleton-fold.json\",\n \"folded_dark_lottie_j"
},
{
"type": "plain",
"addr": "0x74d4d7eb9e",
"s": "android-skeleton-lottie/short-story-skeleton.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-skeleton-fold.json\"\n },\n {\n \""
},
{
"type": "plain",
"addr": "0x74d4d7ec0c",
"s": "android-skeleton-lottie/short-story-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/short-story-skeleton-fold.json\"\n },\n {\n \"style\": 7,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/style-07.json\",\n \"dar"
},
{
"type": "plain",
"addr": "0x74d4d7ec84",
"s": "android-skeleton-lottie/short-story-skeleton-fold.json\"\n },\n {\n \"style\": 7,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/style-07.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/style-07-dark.json\",\n \"folded_lottie_json\": \""
},
{
"type": "plain",
"addr": "0x74d4d7ed08",
"s": "android-skeleton-lottie/style-07.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/style-07-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-07.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstat"
},
{
"type": "plain",
"addr": "0x74d4d7ed68",
"s": "android-skeleton-lottie/style-07-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-07.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-07-dark.json\"\n },\n {\n \"style\": 8,\n \"lottie_jso"
},
{
"type": "plain",
"addr": "0x74d4d7edcf",
"s": "android-skeleton-lottie/fold-style-07.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-07-dark.json\"\n },\n {\n \"style\": 8,\n \"lottie_json\": \"https://s1-static.msstatic.c\n[I][2026-08-26 +8.0 13:12:04.950][32260, 32301][NetService-MtpMarsTra"
},
{
"type": "plain",
"addr": "0x74d4d7ee3b",
"s": "android-skeleton-lottie/fold-style-07-dark.json\"\n },\n {\n \"style\": 8,\n \"lottie_json\": \"https://s1-static.msstatic.c\n[I][2026-08-26 +8.0 13:12:04.950][32260, 32301][NetService-MtpMarsTransporter][[13:12:04.949]om/android-skeleton-lottie/style-08.json\",\n \"dark_lottie_json\": \"https://s1-stati"
},
{
"type": "plain",
"addr": "0x74d4d7ef16",
"s": "android-skeleton-lottie/style-08.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/style-08-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-08.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstat"
},
{
"type": "plain",
"addr": "0x74d4d7ef76",
"s": "android-skeleton-lottie/style-08-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-08.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-08-dark.json\"\n },\n {\n \"style\": 9,\n \"lottie_jso"
},
{
"type": "plain",
"addr": "0x74d4d7efdd",
"s": "android-skeleton-lottie/fold-style-08.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/fold-style-08-dark.json\"\n },\n {\n \"style\": 9,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton.json\",\n \"dark_lottie_js"
},
{
"type": "plain",
"addr": "0x74d4d7f049",
"s": "android-skeleton-lottie/fold-style-08-dark.json\"\n },\n {\n \"style\": 9,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton-dark.json\",\n \"folded_lottie"
},
{
"type": "plain",
"addr": "0x74d4d7f0c6",
"s": "android-skeleton-lottie/malltab-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton-fold-adr.json\",\n \"folded_dark_lottie_json"
},
{
"type": "plain",
"addr": "0x74d4d7f12e",
"s": "android-skeleton-lottie/malltab-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton-fold-adr.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton-fold-dark-adr.json\"\n },\n {"
},
{
"type": "plain",
"addr": "0x74d4d7f19d",
"s": "android-skeleton-lottie/malltab-skeleton-fold-adr.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/malltab-skeleton-fold-dark-adr.json\"\n },\n {\n \"style\": 10,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-skeleton.j"
},
{
"type": "plain",
"addr": "0x74d4d7f215",
"s": "android-skeleton-lottie/malltab-skeleton-fold-dark-adr.json\"\n },\n {\n \"style\": 10,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skel\n[I][2026-08-26 +8.0 13:12:04.950][32260, 3230"
},
{
"type": "plain",
"addr": "0x74d4d7f29f",
"s": "android-skeleton-lottie/tooltab-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skel\n[I][2026-08-26 +8.0 13:12:04.950][32260, 32301][NetService-MtpMarsTransporter][[13:12:04.949]eton-lottie/tooltab-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msst"
},
{
"type": "plain",
"addr": "0x74d4d7f307",
"s": "android-skel\n[I][2026-08-26 +8.0 13:12:04.950][32260, 32301][NetService-MtpMarsTransporter][[13:12:04.949]eton-lottie/tooltab-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1"
},
{
"type": "plain",
"addr": "0x74d4d7f3d4",
"s": "android-skeleton-lottie/tooltab-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-skeleton-fold-dark.json\"\n },\n {\n \"style\": 11,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton.jso"
},
{
"type": "plain",
"addr": "0x74d4d7f448",
"s": "android-skeleton-lottie/tooltab-skeleton-fold-dark.json\"\n },\n {\n \"style\": 11,\n \"lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton-dark.json"
},
{
"type": "plain",
"addr": "0x74d4d7f4ce",
"s": "android-skeleton-lottie/tooltab-inner-skeleton.json\",\n \"dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton-fold.json\",\n \"folded_da"
},
{
"type": "plain",
"addr": "0x74d4d7f53c",
"s": "android-skeleton-lottie/tooltab-inner-skeleton-dark.json\",\n \"folded_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton-fold-dark.json"
},
{
"type": "plain",
"addr": "0x74d4d7f5b1",
"s": "android-skeleton-lottie/tooltab-inner-skeleton-fold.json\",\n \"folded_dark_lottie_json\": \"https://s1-static.msstatic.com/android-skeleton-lottie/tooltab-inner-skeleton-fold-dark.json\"\n }\n], lz.MyTabOneAndNModule=_name=MyTabOneAndNModule&_ver=11&_url=https%3A%2F%2Fs1-static.msstatic.com%2Fkiwi-liza"
},
{
"type": "plain",
"addr": "0x74d4d7f62b",
"s": "android-skeleton-lottie/tooltab-inner-skeleton-fold-dark.json\"\n }\n], lz.MyTabOneAndNModule=_name=MyTabOneAndNModule&_ver=11&_url=https%3A%2F%2Fs1-static.msstatic.com%2Fkiwi-lizard-MyTabOneAndNModule%2Fkiwi_lizard%2FMyTabOneAndNModule%2F11%2FMyTabOneAndNModule.android.lzc&_md5=09a586f0c7e0b549491513"
},
{
"type": "plain",
"addr": "0x74d51e2ec2",
"s": "android-movedRN_DrawerLayoutAndroid.android.permission.ACCESS_COARSE_LOCATIONativePerformanceCxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxxhdpixels_sampledRN_infoLoget ScrollViewriteBigUInt64BEN_STRUCTENDropping session because custom transport doesn't implement sendSessionAggregatesprevD"
},
{
"type": "plain",
"addr": "0x775211f5f8",
"s": "android- --enable-cross-compile --target-os=android --enable-pic --enable-asm --enable-inline-asm --enable-optimizations --disable-debug --enable-small --extra-cflags='-O3 -Wall -pipe -std=c99 -ffast-math -fstrict-aliasing -Werror=strict-aliasing -Wno-psabi -Wa,--noexecstack -DANDROID -DNDEBUG ' --e"
},
{
"type": "plain",
"addr": "0x7774345720",
"s": "android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc --arch=aarch64 --cpu=armv8-a --target-os=android --sysroot=/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/ --prefix=/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffm"
},
{
"type": "plain",
"addr": "0x777434574a",
"s": "android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc --arch=aarch64 --cpu=armv8-a --target-os=android --sysroot=/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/ --prefix=/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/andr"
},
{
"type": "plain",
"addr": "0x777434577e",
"s": "android-gcc --arch=aarch64 --cpu=armv8-a --target-os=android --sysroot=/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/ --prefix=/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/ffmpeg/arm64-v8a --cross-prefix=/opt/tools/andro"
},
{
"type": "plain",
"addr": "0x77743457d0",
"s": "android-ndk-r11c/platforms/android-21/arch-arm64/ --prefix=/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/ffmpeg/arm64-v8a --cross-prefix=/opt/tools/android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64"
},
{
"type": "plain",
"addr": "0x77743457eb",
"s": "android-21/arch-arm64/ --prefix=/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/ffmpeg/arm64-v8a --cross-prefix=/opt/tools/android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android- --extra-cfl"
},
{
"type": "plain",
"addr": "0x77743458a5",
"s": "android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android- --extra-cflags='-Wall -O3 -U_FORTIFY_SOURCE -fstack-protector-all -fomit-frame-pointer -DANDROID -DNDEBUG -std=c99 -fpic -I/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/inc -I/dat"
},
{
"type": "plain",
"addr": "0x77743458cf",
"s": "android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android- --extra-cflags='-Wall -O3 -U_FORTIFY_SOURCE -fstack-protector-all -fomit-frame-pointer -DANDROID -DNDEBUG -std=c99 -fpic -I/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/inc -I/data1/jenkins/workspace/aplus_ffmpeg_git-andr"
},
{
"type": "plain",
"addr": "0x7774345903",
"s": "android- --extra-cflags='-Wall -O3 -U_FORTIFY_SOURCE -fstack-protector-all -fomit-frame-pointer -DANDROID -DNDEBUG -std=c99 -fpic -I/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/inc -I/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/build"
},
{
"type": "plain",
"addr": "0x7774345992",
"s": "android-ndk-r11c/platforms/android-21/arch-arm64/usr/inc -I/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/x264/arm64-v8a/include -I/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/an"
},
{
"type": "plain",
"addr": "0x77743459ad",
"s": "android-21/arch-arm64/usr/inc -I/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/x264/arm64-v8a/include -I/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/fdk-aac/arm64-v8a/inc"
},
{
"type": "plain",
"addr": "0x7774345b96",
"s": "android-ndk-r11c/platforms/android-21/arch-arm64/usr/lib -L/opt/tools/android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/lib/gcc/aarch64-linux-android/4.9/ -L/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-andro"
},
{
"type": "plain",
"addr": "0x7774345bb1",
"s": "android-21/arch-arm64/usr/lib -L/opt/tools/android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/lib/gcc/aarch64-linux-android/4.9/ -L/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_"
},
{
"type": "plain",
"addr": "0x7774345bdc",
"s": "android-ndk-r11c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/lib/gcc/aarch64-linux-android/4.9/ -L/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/x264/a"
},
{
"type": "plain",
"addr": "0x7774345c06",
"s": "android-4.9/prebuilt/linux-x86_64/lib/gcc/aarch64-linux-android/4.9/ -L/opt/tools/android-ndk-r11c/platforms/android-21/arch-arm64/usr/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/x264/arm64-v8a/lib -L/data1/jenkins/workspace/ap"
},
{
"type": "plain",
"addr": "0x7774345c58",
"s": "android-ndk-r11c/platforms/android-21/arch-arm64/usr/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/x264/arm64-v8a/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/androi"
},
{
"type": "plain",
"addr": "0x7774345c73",
"s": "android-21/arch-arm64/usr/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/x264/arm64-v8a/lib -L/data1/jenkins/workspace/aplus_ffmpeg_git-android_yspwl-codec_ffmpeg_4981_android_prod/build/buildgene/android/fdk-aac/arm64-v8a/lib -L/"
},
{
"type": "plain",
"addr": "0x779d3891c6",
"s": "deviceinfo\":{\"Hebe_D1\":\"31816ccdba3f9bdb5b0c9908ee62c1e\",\"Hebe_D2\":\"13001d4f69d73472641795acf04622e7\",\"Hebe_D3\":\"6337f98ecac5c7390639c7f23d609e1c\",\"Hebe_D4\":\"fd54bd93cc3737d1017a46ed4f49cb14\",\"Hebe_D5\":\"7e3263485740057253990d5ee9d8c640\",\"Poseidon_D1\":\"path:/data/user/0/com.duowan.kiwi|\",\"Poseidon_D1"
},
{
"type": "plain",
"addr": "0x77b46d7846",
"s": "deviceinfo\":{\"Hebe_D1\":\"31816ccdba3f9bdb5b0c9908ee62c1e\",\"Hebe_D2\":\"13001d4f69d73472641795acf04622e7\",\"Hebe_D3\":\"6337f98ecac5c7390639c7f23d609e1c\",\"Hebe_D4\":\"fd54bd93cc3737d1017a46ed4f49cb14\",\"Hebe_D5\":\"7e3263485740057253990d5ee9d8c640\",\"Poseidon_D1\":\"path:/data/user/0/com.duowan.kiwi|\",\"Poseidon_D1"
},
{
"type": "plain",
"addr": "0x7823afff6d",
"s": "android-5.0.1_r1 and fixed in Android 5.1.0_r1 by calling context.getApplicationContext(). Fix: https://github.com/android/platform_frameworks_base/commit/9b5257c9c99c4cb541d8e8e78fb04f008b1a9091 To fix this, you could call MediaSessionLegacyHelper.getHelper() early in Application.onCreate() and pas"
},
{
"type": "plain",
"addr": "0x7823b31ec8",
"s": "android-5.1.0_r1: https://github.com/android/platform_frameworks_base/commit/893d6fe48d37f71e683f722457bea646994a10 The second was fixed, not released yet: https://github.com/android/platform_frameworks_base/commit/b3a9bc038d3a218b1dbdf7b5668e3d6c12be5e To fix this, you could access TextLine.sCached"
}
]
+6
View File
@@ -0,0 +1,6 @@
[
{
"patched": true,
"new_t1": "eaec26c78e453579da39b73ec81512e6"
}
]
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -1,11 +1,11 @@
# account: hy_300026595
web_qrlogin_confirm_id=41457fe1-8296-482b-b274-f3d7653adeb7
udb_cred=ChAgFf4SfcAnDyl5ujW3vO21-t1a7wC4tNqcX8DIMTN2SM6AqJ-_TSLo4pDnjfaEcXgYYevEYtufcSnRWZ2kPDVQczshoLXLSS6I-5NQCmCuUC-eQOT3cWHfRsDGPVF0iYQCSmIhkW0PbbJYa1aXkrCq
web_qrlogin_confirm_id=f790423a-6af3-49a8-8a4b-ea20b5b6267c
udb_cred=CnAgAzQ6tBkyC3POWFhOCcwA8WPd0TvDbiVswIWKc8eZkJsI2HriEKoGGEhqWYrTiq4WOPxRGrZlVrjPo9LTODY9rARe8cnIe3lZZaMQIxIIScJEx-8TZT9FsgxrhgaKjV65SeQZxKvmpf1H_iqL8yKF
udb_uid=1199666913640
yyuid=1199666913640
udb_passport=hy_300026595
username=hy_300026595
udb_version=1.0
udb_biztoken=AQBKhawIP7YIaR6zGjd7zRodGzzmnHBZw5-3Am1SfabWP-IBgRn4b_KL5e7ow9k8GOlN7LhW21Rb6C0z8lA3xSyJ8QEWz65QSdfYjfkoyRcWWbHxbcJxQJ057_4W6kv1Y47d3lqxHnAS_yc3rbzun9ODDb5VfIbN-dmDRWruJ8VMU4BTm5DIKdAt6QM8T7H-dDG6k5-mnjuxqTT3UMaCCOnrC4rJ_Jbag-VnLjijr_4_QJjAxw2pJAqK5K5Ht-JVJ50Vz7G0IvUQoiLmggPSzevNfrPIQgmLT0ZBJdRZxvSSX19VuV6FfdgbPVC5fjsnxO0Op3yQH87ImehkxwnKKn9C
udb_biztoken=AQC5-IivGXiwQyg6D6K7zpBQkEcyWK5QWhYAQ-EBwP3EaLJ0RZiaAvSiPaMU2ye7K8kN88kcn6qNWOP0dCBSBC55rxNdo4zeXChg2fhzUIYoDSExssT0rGhjk0q_HM2MlkKidDcFGDYy7z2l749CpsxHFTRf27RTEDGpuZo_uDbyPqgf5uY-kSQRjwopIIDglqtuaj4m60SITy4SnyWrk8-UV8yW33bFiZSOSC2m8x6sX8-9sT8plSg4NnA9P1H2CjiqtHwgAatT9PVF5G5PyWLh3Rae5DIMptoGnghfgFGF2WKD0XnQsUKHG3DAMOizR1_sJ8rEzPFIG-lhxyK9yT7Q
udb_origin=0
udb_status=1
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""捕获 (明文JSON, 密文) 配对: 解 dfpReport 流式 keystream。
同时:
1. hook SSL_write 抓 dfpReport 密文 (魔数开头 3984B)
2. 扫描内存找明文 JSON ({"appId":"5008"...), dump 前 512 + 后 6000
命中配对后立即 pkill。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_pair.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var captured = {wire: null, plain: null};
// ---- 1. SSL_write 抓密文 ----
try{
var fns = [];
var r = new ApiResolver('module');
fns = r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){ return x.address; });
fns.slice(0, 6).forEach(function(p){
Interceptor.attach(p, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 100 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('dfpReport') < 0) return;
var arr = new Uint8Array(a[1].readByteArray(len));
var hex = '';
for (var j = 0; j < arr.length; j++) hex += ('0'+arr[j].toString(16)).slice(-2);
captured.wire = {len: len, hex: hex};
send({type:'wire_cap', len: len});
trySend();
}
});
});
}catch(e){}
// ---- 2. 扫描明文 JSON ----
function scanPlain(){
try{
var r2 = new ApiResolver('module');
Process.enumerateRanges('r--').forEach(function(rng){
if (rng.size > 1024*1024*256) return;
try{
var hits = Memory.scanSync(rng.base, rng.size, '7b 22 61 70 70 49 64 22 3a 22 35 30 30 38');
hits.slice(0, 3).forEach(function(x){
var post = '';
try{
var arr = new Uint8Array(x.address.readByteArray(4096));
post = Array.from(arr).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('');
}catch(e){ return; }
captured.plain = {addr: String(x.address), hex: post};
send({type:'plain_cap', addr: String(x.address)});
trySend();
});
}catch(e){}
});
}catch(e){}
}
function trySend(){
if (captured.wire && captured.plain) {
var out = {wire: captured.wire, plain: captured.plain};
send({type:'PAIR', data: out});
}
}
setInterval(function(){ try{ scanPlain(); }catch(e){} }, 3000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "wire_cap":
print(f"[wire] 密文捕获 len={p.get('len')}", flush=True)
elif t == "plain_cap":
print(f"[plain] 明文JSON @{p.get('addr')}", flush=True)
elif t == "PAIR":
OUT.write_text(json.dumps(p.get('data')))
print(f"[PAIR] 保存 -> {OUT}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, 请触发登录, PAIR 出现即 pkill", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""抓取真正"同时刻"的 (明文JSON, 密文) 对。
SSL_write 命中 dfpReport 时, 明文刚生成/正在内存, 立即全内存扫描
{"appId":"5008" 快照 -> 与本次密文构成同步 pair。命中即持久化。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_pair_sync.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hexb(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return null; }
}
function hexs(buf){ return Array.from(new Uint8Array(buf)).map(function(x){return ('0'+x.toString(16)).slice(-2);}).join(''); }
// 同步: SSL_write 后 50ms 扫描明文 (此时 JSON 应仍在堆)
function scanPlainNow(tag){
var out = [];
try{
Process.enumerateRanges('r--').forEach(function(rng){
if (rng.size > 1024*1024*256) return;
try{
var hits = Memory.scanSync(rng.base, rng.size, '7b 22 61 70 70 49 64 22 3a 22 35 30 30 38');
hits.slice(0, 4).forEach(function(x){
var post = null;
try{ post = x.address.readByteArray(4096); }catch(e){ return; }
out.push({addr: String(x.address), hex: hexs(post)});
});
}catch(e){}
});
}catch(e){}
if (out.length) send({type:'sync_plain', tag:tag, plains:out});
}
try{
var r = new ApiResolver('module');
r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){
Interceptor.attach(m.address, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 100 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('dfpReport') < 0) return;
var hex = hexb(a[1], len);
var t = Date.now();
send({type:'wire', len:len, hex:hex, t:t});
setTimeout(function(){ scanPlainNow(t); }, 80);
}
});
});
}catch(e){}
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "wire":
print(f"[wire] len={p.get('len')}", flush=True)
events.append({"type": "wire", "t": p.get('t'), "hex": p.get('hex')})
OUT.write_text(json.dumps(events))
elif t == "sync_plain":
print(f"[sync_plain] tag={p.get('tag')} {len(p.get('plains'))}", flush=True)
events.append({"type": "plain", "t": p.get('tag'), "plains": p.get('plains')})
OUT.write_text(json.dumps(events))
for pl in p.get('plains'):
try:
h = pl['hex']
j = h.index('7b226170704964223a2235303038')
asc = bytes.fromhex(h[j:j+700])
s = ''.join(chr(x) if 32<=x<127 else '.' for x in asc)
print(f" @{pl['addr']}: {s[:200]}", flush=True)
except Exception: pass
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume. 请 退出登录->重新登录 (wire 后立即抓同刻明文)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""诊断: spawn + bypass 后进程是否存活, 崩溃原因是什么。
对照组: 不改写数据段。对比之前 patch_t1_experiment(改写 3 处)是否崩溃。
"""
from __future__ import annotations
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){
send({type:'exception', code:d.code, addr:String(d.address), ctx:d.context});
return true;
});
send({type:'armed'});
setInterval(function(){ send({type:'alive', t:Date.now()}); }, 3000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
session.on("detached", lambda reason, crash: print(
f"[DETACHED] reason={reason} crash={crash}", flush=True))
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "armed":
print("[*] armed", flush=True)
elif t == "exception":
print(f"[EXCEPTION] code={p.get('code')} addr={p.get('addr')}", flush=True)
elif t == "alive":
print("[*] alive", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
print("[*] resumed, 观察 30s", flush=True)
time.sleep(30)
print("[*] 结束", flush=True)
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""dump libhydeviceid.so .data 段 (datadiv 就地解密区), 提取全部明文串。
datadiv 解码器把 .data 段 0x3c8xxx 区域的密文就地解密为明文。
dump 整个 .data(0x3bb000 ~ 0x3eac10), 提取可读字符串 + 搜魔数。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_datadiv_data.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var DATA_RANGES = [
[0x36aec0, 0x6e75], // .rodata (含 datadiv 密文? 可能)
[0x3b2460, 0x74d8], // .data.rel.ro
[0x3bb000, 0x2fc08], // .data (datadiv 就地解密区)
];
function dumpData(m){
DATA_RANGES.forEach(function(r){
var off = r[0], size = r[1];
try{
var base = m.base.add(off);
var bytes = Array.from(new Uint8Array(base.readByteArray(size)));
// 提取可读字符串 (>=5 连续可打印)
var strs = [];
var cur = '';
for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
if (c >= 32 && c < 127) cur += String.fromCharCode(c);
else {
if (cur.length >= 5) strs.push({s: cur, off: off + i - cur.length});
cur = '';
}
}
// 搜魔数
var hasMagic = false;
for (var i = 0; i + 5 < bytes.length; i++) {
if (bytes[i]==0x57 && bytes[i+1]==0x18 && bytes[i+2]==0x82 && bytes[i+3]==0xcf) { hasMagic = true; break; }
}
send({type:'data', sec:off.toString(16), size:size, strs:strs.length,
hasMagic: hasMagic,
sample: strs.slice(0, 60)});
}catch(e){
send({type:'data_err', sec:off.toString(16), e:String(e)});
}
});
}
var tries = 0;
var iv = setInterval(function(){
tries += 1;
var m = null;
try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){}
if (m) {
clearInterval(iv);
send({type:'mod', base:String(m.base), size:m.size});
dumpData(m);
} else if (tries > 100) { clearInterval(iv); send({type:'no_mod'}); }
}, 50);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "data":
print(f"[data] sec=0x{p.get('sec')} size={p.get('size')} "
f"strs={p.get('strs')} magic={p.get('hasMagic')}", flush=True)
for it in p.get('sample', [])[:40]:
print(f" 0x{it['off']:x}: {it['s']}", flush=True)
events.append({"section": p.get('sec'), "strings": p.get('sample')})
OUT.write_text(json.dumps(events, indent=1))
elif t == "data_err":
print(f"[data_err] {p.get('sec')}: {p.get('e')}", flush=True)
elif t == "mod":
print(f"[*] libhydeviceid @ {p.get('base')} size={p.get('size')}", flush=True)
elif t == "no_mod":
print("[*] 模块未加载", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, dump 完成即 pkill", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""完整导出 libhydeviceid.so .data 段解密后的全部字符串(不截断)。
.resume 后 .data 已被 datadiv 就地解密为明文。dump 全部字节 + 全部
字符串(带偏移)到 JSON, 供离线分析 XXTEA key / 魔数 / t1 来源。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_datadiv_full.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var RANGES = [
[0x36aec0, 0x6e75], // .rodata
[0x3b2460, 0x74d8], // .data.rel.ro
[0x3bb000, 0x2fc08], // .data
];
function dumpSec(m, off, size){
try{
var base = m.base.add(off);
var raw = base.readByteArray(size);
var bytes = Array.from(new Uint8Array(raw));
var strs = [];
var cur = '';
var curOff = 0;
for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
if (c >= 32 && c < 127) { if (!cur) curOff = i; cur += String.fromCharCode(c); }
else {
if (cur.length >= 4) strs.push([off + curOff, cur]);
cur = '';
}
}
if (cur.length >= 4) strs.push([off + curOff, cur]);
return {sec: off.toString(16), size: size, n: strs.length, strs: strs};
}catch(e){ return {sec: off.toString(16), err: String(e)}; }
}
var tries = 0;
var iv = setInterval(function(){
tries += 1;
var m = null;
try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){}
if (m) {
clearInterval(iv);
var out = [];
RANGES.forEach(function(r){ out.push(dumpSec(m, r[0], r[1])); });
send({type:'all', ranges: out});
} else if (tries > 100) { clearInterval(iv); send({type:'no_mod'}); }
}, 50);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "all":
total = sum(r.get('n', 0) for r in p.get('ranges', []))
print(f"[*] 完成, 共 {total} 条字符串 -> {OUT}", flush=True)
OUT.write_text(json.dumps(p.get('ranges'), ensure_ascii=False))
elif t == "no_mod":
print("[*] 模块未加载", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
time.sleep(5)
print("[*] dump 完成, 结束", flush=True)
if __name__ == "__main__":
main()
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""dump 魔数缓冲区: 找到魔数地址后, dump 前后完整内存到证据文件。
命中即写入文件(不依赖退出), 打印明文头与密文长度。
"""
from __future__ import annotations
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_buffer_dump.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hex(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
function doScan(){
var found = false;
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*256) return;
try{
var m = Memory.scanSync(r.base, r.size, '57 18 82 cf 66 4b b3 94');
m.forEach(function(x){
var addr = x.address;
// 只 dump 每个 4KB 页内第一个命中 (去重)
var pre256 = hex(addr.sub(64), 64);
var post = hex(addr.add(10), 4096);
send({type:'dump', addr:String(addr), pre:pre256, post:post});
found = true;
});
}catch(e){}
});
}catch(e){}
send({type:'tick', found:found});
}
var iv = setInterval(function(){ doScan(); }, 3000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
dumped = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:160], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "dump":
addr = p.get("addr")
# 去重 (同地址不重复存)
if any(d.get("addr") == addr for d in dumped):
return
rec = {"addr": addr, "pre": p.get("pre"), "post": p.get("post")}
dumped.append(rec)
# 立即持久化
OUT.write_text(__import__("json").dumps(dumped, indent=1))
print(f"[DUMP] @{addr} pre={p.get('pre','')[:64]} postlen={len(p.get('post',''))//2} saved={len(dumped)}", flush=True)
elif t == "tick":
print(f"[tick] found={p.get('found')} total_dumps={len(dumped)}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] dump 扫描中 (每3s), 命中即持久化。请触发登录 (命中后可 pkill)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, 共 {len(dumped)} 个 dump -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""完整 dump 明文采集数据区域 (0x7814096d19 JSON 附近 + 0x78133bf220 键值结构)。
拿到加密前的完整明文 = 解密 dfpReport 的关键! dump 前后 8KB 到文件。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_plaintext_full.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
// 锚点: JSON 明文 + t1 键值结构
var ANCHORS = [
['json', '74 65 72 6d 69 6e 61 6c 54 79 70 65 22 3a 31'], // terminalType":1
['t1kv', '74 65 72 6d 69 6e 61 6c'], // terminal (键值结构内)
];
function hex(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
function scan(){
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*256) return;
try{
ANCHORS.forEach(function(a){
var hits = Memory.scanSync(r.base, r.size, a[1]);
hits.slice(0, 5).forEach(function(x){
var addr = x.address;
// JSON: dump 前 512 + 后 8192
var pre = '', post = '';
try{ pre = hex(addr.sub(512), 512); }catch(e){}
try{ post = hex(addr, 8192); }catch(e){}
send({type:'dump', anchor:a[0], addr:String(addr), pre:pre, post:post});
});
});
}catch(e){}
});
}catch(e){}
send({type:'tick'});
}
setInterval(function(){ try{ scan(); }catch(e){} }, 4000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "dump":
addr = p.get('addr')
if any(e.get('addr') == addr for e in events): return
rec = {"anchor": p.get('anchor'), "addr": addr,
"pre": p.get('pre'), "post": p.get('post')}
events.append(rec)
OUT.write_text(json.dumps(events, ensure_ascii=False))
# 打印 post 的可读部分 (前 400B)
post = bytes.fromhex(p.get('post', ''))
asc = ''.join(chr(b) if 32 <= b < 127 else '.' for b in post[:400])
print(f"[dump:{p.get('anchor')}] @{addr}", flush=True)
print(f" {asc}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, dump 明文区 (需触发登录采集后可 pkill)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""dump 设备运行时 libhydeviceid.so 的 datadiv 解密区(就地解密=明文)。
datadiv 解码器在 JNI_OnLoad 时把 0x3c8xxx 数据段密文就地解密为明文。
直接 dump 该区域 + 扫描全进程可读字符串(android- 等), 命中即持久化。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_runtime_strings.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hex(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
// 1. dump libhydeviceid.so 数据段 (datadiv 就地解密区 0x3c8000 ~ 0x3ca000)
function dumpModuleData(){
var m = null;
try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){ return; }
[0x3c8000, 0x3ca000, 0x3cc000, 0x3d0000, 0x3e0000].forEach(function(off){
try{
var base = m.base.add(off);
var buf = base.readByteArray(0x2000);
var bytes = Array.from(new Uint8Array(buf));
// 提取可读字符串
var strs = [];
var cur = '';
for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
if (c >= 32 && c < 127) { cur += String.fromCharCode(c); }
else {
if (cur.length >= 5) strs.push(cur);
cur = '';
}
}
if (strs.length) {
send({type:'datastr', off:off.toString(16), n:strs.length,
strs: strs.slice(0, 40)});
}
// 搜魔数
var hexs = bytes.map(function(x){return ('0'+x.toString(16)).slice(-2);}).join('');
if (hexs.indexOf('571882cf') >= 0) send({type:'magic_in_data', off:off.toString(16)});
}catch(e){}
});
}
// 2. 全进程扫描明文设备数据 (android- / UA / JSON)
function scanPlain(){
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*128) return;
try{
// 找 "android-" 出现且周围有可读文本
var hits = Memory.scanSync(r.base, r.size, '61 6e 64 72 6f 69 64 2d');
hits.slice(0, 30).forEach(function(x){
var ctx = '';
try{ ctx = x.address.sub(64).readUtf8String(); }catch(e){}
send({type:'plain', addr:String(x.address), ctx: (ctx || '').slice(0, 200)});
});
}catch(e){}
});
}catch(e){}
send({type:'scan_done'});
}
dumpModuleData();
setTimeout(function(){ scanPlain(); }, 2000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "datastr":
print(f"[datastr] off=0x{p.get('off')} {p.get('n')} 条:", flush=True)
for s in p.get('strs', []):
print(f" {s}", flush=True)
events.append(p)
OUT.write_text(json.dumps(events, indent=1))
elif t == "magic_in_data":
print(f"[MAGIC-IN-DATA] off=0x{p.get('off')}", flush=True)
elif t == "plain":
print(f"[plain] @{p.get('addr')}: {p.get('ctx')!r}", flush=True)
events.append(p)
elif t == "scan_done":
print("[*] scan done", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, dump 完成即 pkill", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""重 hook datadiv 解码器: 读返回值指向内存的前 64B hex。
datadiv 函数解密一个数据块(不只是字符串), 魔数 5718 / 设备数据 / key
可能藏在解码后的内存里。命中(含 5718 或 android)即持久化。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_datadiv_hex.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hex(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
var hooked = 0;
function hookMod(modName){
var m = null;
try{ m = Process.getModuleByName(modName); }catch(e){ return false; }
m.enumerateExports().forEach(function(exp){
if (exp.name.indexOf('datadiv') < 0) return;
try{
Interceptor.attach(exp.address, {
onLeave: function(ret){
var p = ret;
if (p.isNull()) return;
var h64 = '';
try{ h64 = hex(p, 64); }catch(e){ return; }
if (h64.indexOf('ERR') >= 0) return;
// 命中关键词: 魔数 / android / 可读字符串
var low = h64.toLowerCase();
var hit = low.indexOf('5718') >= 0 || low.indexOf('616e64726f6964') >= 0;
var str = '';
try{ str = p.readUtf8String(); }catch(e){}
if (hit || (str && str.length > 4 && str.length < 100 && /[a-z]{4,}/i.test(str))) {
send({type:'dd', mod:modName, fn:exp.name, h64:h64, str:str});
}
}
});
hooked += 1;
}catch(e){}
});
return true;
}
var tries = 0;
var iv = setInterval(function(){
tries += 1;
var ok = hookMod('libhydeviceid.so') | hookMod('libudbauthunify.so');
if (tries % 5 === 0) send({type:'waiting', tries:tries, hooked:hooked});
if (ok) { clearInterval(iv); send({type:'hooked_count', n:hooked}); }
if (tries > 60) clearInterval(iv);
}, 1000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:160], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "dd":
print(f"[dd] {p.get('mod')} {p.get('fn')}: {p.get('h64')} {p.get('str')!r}", flush=True)
events.append(p)
OUT.write_text(json.dumps(events, indent=1))
elif t == "hooked_count":
print(f"[*] hooked {p.get('n')} datadiv", flush=True)
elif t == "waiting":
print(f"[waiting] tries={p.get('tries')} hooked={p.get('hooked')}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] datadiv 重hook中 (每1s重试), 请触发登录 (命中即持久化)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""resume 前加载 datadiv hook: 抓 JNI_OnLoad 阶段批量解密的字符串。
libhydeviceid.so 的 .datadiv_decode* 在 so 加载/JNI_OnLoad 时执行,
用内部 XXTEA 解密业务字符串(含可能的魔数/dfp 配置)。必须在 resume 前
装载 hook 才能抓到加载期调用。命中(5718 或长可读串)即持久化。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_datadiv_preload.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hex(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
var hookedCount = 0;
function hookModule(modName){
var m = null;
try{ m = Process.getModuleByName(modName); }catch(e){ return; }
m.enumerateExports().forEach(function(exp){
if (exp.name.indexOf('datadiv') < 0) return;
try{
Interceptor.attach(exp.address, {
onEnter: function(){ this.t0 = Date.now(); },
onLeave: function(ret){
var p = ret;
if (p.isNull()) return;
var h64 = '';
try{ h64 = hex(p, 96); }catch(e){ return; }
if (h64.indexOf('ERR') >= 0) return;
var low = h64.toLowerCase();
var str = '';
try{ str = p.readUtf8String(); }catch(e){}
var hit = low.indexOf('5718') >= 0 || (str && str.length >= 8);
if (hit) {
send({type:'dd', mod:modName, fn:exp.name, off:exp.address.sub(m.base).toString(),
h96:h64, str:str, dt:Date.now()-this.t0});
}
}
});
hookedCount += 1;
}catch(e){}
});
}
// 轮询等 so 加载 (JNI_OnLoad 前必须挂上!)
var iv = setInterval(function(){
hookModule('libhydeviceid.so');
hookModule('libudbauthunify.so');
if (hookedCount > 100) { clearInterval(iv); send({type:'hooked', n:hookedCount}); }
else if (hookedCount > 0) { clearInterval(iv); send({type:'hooked', n:hookedCount}); }
}, 20);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "dd":
print(f"[dd] {p.get('mod')} {p.get('fn')}({p.get('off')}) "
f"dt={p.get('dt')}ms str={p.get('str')!r}", flush=True)
print(f" h96: {p.get('h96')}", flush=True)
events.append(p)
OUT.write_text(json.dumps(events, indent=1))
elif t == "hooked":
print(f"[*] hooked {p.get('n')} datadiv (resume前)", flush=True)
elif t == "armed":
pass
# 主 hook 在 resume 前 load!
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主hook 已装载(resume前), 现在加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, 请触发登录 (命中即 pkill, 脚本会持续到手动停)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""attach 到已运行的虎牙App进程, hook dfpReport 加密链 + datadiv 解码器。
不 spawn,直接 attach 到用户正在操作的实例, 避免挂错进程。
用法: 脚本跑起来后, 在手机上 退出登录 -> 重新登录 一次。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_attach_capture.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hx(p,n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
function rdStr(p){
try{
var b0=p.readU8();
if((b0&1)===0){ var l=b0>>1; if(l>524288) return {t:'s?',len:l,v:''};
return {t:'s',len:l,v:l?hx(p.add(1),Math.min(l,16384)):''}; }
var len=parseInt(p.add(8).readU64().toString());
if(len>1048576) return {t:'l?',len:len,v:''};
return {t:'l',len:len,v:hx(p.add(16).readPointer(),Math.min(len,16384))};
}catch(e){ return {t:'err',len:-1,v:String(e)}; }
}
function ra(ctx){
try{ return DebugSymbol.fromAddress(ctx.returnAddress).toString().slice(0,90);}catch(e){return '?';}
}
var KEYWORDS = ['dfp', '5718', '82cf', 'b394', 'collect', 'report'];
// 1) datadiv 解码器 (libhydeviceid + libudbauthunify)
var ddHooked = 0;
['libhydeviceid.so', 'libudbauthunify.so'].forEach(function(modName){
var m = null;
try{ m = Process.getModuleByName(modName); }catch(e){ return; }
m.enumerateExports().forEach(function(exp){
if (exp.name.indexOf('datadiv') < 0) return;
try{
Interceptor.attach(exp.address, {
onLeave: function(ret){
var s = '';
try{ s = ret.readUtf8String(); }catch(e){}
if (!s || s.length < 3 || s.length > 2000) return;
var low = s.toLowerCase();
var hit = false;
KEYWORDS.forEach(function(k){ if (low.indexOf(k) >= 0) hit = true; });
if (hit) send({type:'dd', mod:modName, fn:exp.name, str:s.slice(0,300)});
}
});
ddHooked += 1;
}catch(e){}
});
});
// 2) xxtea (hyudb_crypt_util + hyudbxxt)
var base = Process.getModuleByName('libudbauthunify.so').base;
try{
Interceptor.attach(base.add(0x32e83c), {
onEnter: function(a){
send({type:'xxtea', r:ra(this), in:rdStr(a[1]), key:rdStr(a[2])});
}
});
}catch(e){}
// 3) UdbAESUtil::encrypt (in len 大 -> dfp)
try{
Interceptor.attach(base.add(0x250038), {
onEnter: function(a){
var inp = rdStr(a[0]);
if (inp.len > 500) {
send({type:'aes_in', r:ra(this), in:inp});
}
},
onLeave: function(ret){
var out = rdStr(this.context.x0);
if (out.len > 500) send({type:'aes_out', out:out});
}
});
}catch(e){}
// 4) enpack_header / enpack_body (打包)
try{
Interceptor.attach(base.add(0x32ff44), {
onEnter: function(a){ this.h=a[0].toInt32()&0xff; this.r=ra(this); },
onLeave: function(){ send({type:'enpack_hdr', h:this.h, r:this.r, out:rdStr(this.context.x2)}); }
});
}catch(e){}
try{
Interceptor.attach(base.add(0x3300c8), {
onEnter: function(a){
this.h=a[0].toInt32()&0xff; this.r=ra(this);
send({type:'enpack_body_in', h:this.h, r:this.r,
s1:rdStr(a[1]), s2:rdStr(a[2]), s3:rdStr(a[3]), s4:rdStr(a[4])});
},
onLeave: function(){ send({type:'enpack_body_out', h:this.h, out:rdStr(this.context.x5)}); }
});
}catch(e){}
send({type:'dd_hooked', n:ddHooked});
"""
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--pid", type=int, default=7296)
args = parser.parse_args()
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
session = device.attach(args.pid)
print(f"[*] attached pid={args.pid}")
# attach 模式不需要 msaoaid bypass (进程已过检测点), 但保险起见加载 patch_guard
try:
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
except Exception as e:
print("[warn] patch_guard:", str(e)[:80])
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:160]); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "dd":
print(f"[dd] {p.get('mod')} {p.get('fn')}: {p.get('str')!r}")
elif t == "xxtea":
inp = p.get("in", {}); key = p.get("key", {})
print(f"[xxtea] in.len={inp.get('len')} key={key.get('v','')[:40]} r={p.get('r','')[:50]}")
elif t == "aes_in":
inp = p.get("in", {})
print(f"[aes_in] len={inp.get('len')} head={inp.get('v','')[:64]} r={p.get('r','')[:50]}")
elif t == "aes_out":
out = p.get("out", {})
print(f"[aes_out] len={out.get('len')} head={out.get('v','')[:64]}")
elif t == "enpack_hdr":
print(f"[enpack_hdr] h={p.get('h')} out.len={p.get('out',{}).get('len','?')} r={p.get('r','')[:40]}")
elif t == "enpack_body_in":
print(f"[enpack_body_in] h={p.get('h')} s1={p.get('s1',{}).get('v','')[:48]} r={p.get('r','')[:40]}")
elif t == "enpack_body_out":
print(f"[enpack_body_out] h={p.get('h')} out.len={p.get('out',{}).get('len','?')}")
elif t == "armed":
print("[*] armed")
elif t == "dd_hooked":
print(f"[*] datadiv hooked: {p.get('n')}")
events.append(p)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] hooks 装好, 请在手机上 退出登录 -> 重新登录 (180s)")
deadline = time.time() + 180
try:
while time.time() < deadline:
time.sleep(5)
except KeyboardInterrupt:
pass
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
print(f"[*] 捕获 {len(events)} 条 -> {OUT}")
if __name__ == "__main__":
main()
+262
View File
@@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""动态捕获 dfpReport 加密体生成链 (真机 Frida)。
目标: 搞清 dfpReport t2 加密体 (10B魔数 571882cf664bb39401ee + 变长密文) 的
明文结构 / 加密算法 / key 来源, 以便纯代码铸造全新设备身份。
Hook 点 (libudbauthunify.so, 偏移来自 work/sos 符号表):
0x32e83c hyudb_crypt_util::xxtea_encrypt(out&, in&, key&) # XXTEA 加密
0x2527ec hyudbxxt::xxtea_encrypt(string, string) # 另一套 XXTEA
0x250038 UdbAESUtil::encrypt(in&, out&) # AES 加密
0x32ff44 enpack_header(h, NEWKEY, out&) # 打包头
0x3300c8 enpack_body(h, string, ...) # 打包体
0x26871c AESkeyMgr::getkey(i, i) # AES key 源
dfpReport 响应处理 (wsapi) -> 记录服务端下发 t1/t2/t5
用法: 脚本跑起来后 spawn 虎牙App (com.duowan.kiwi), 触发一次设备注册链
(App 启动即自动 getDfpConfig->selectOperator->dfpReport), 抓取明文/key。
"""
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_hook_capture.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){
send({type:'segv', info:{type:d.type, addr:String(d.address)}});
return true;
});
var base = Process.getModuleByName('libudbauthunify.so').base;
send({type:'armed', base:String(base)});
function hx(p,n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR:'+String(e).slice(0,40); }
}
function rdStr(p){
try{
var b0=p.readU8();
if((b0&1)===0){ var l=b0>>1; if(l>524288) return {t:'s?',len:l,v:''};
return {t:'s',len:l,v:l?hx(p.add(1),Math.min(l,16384)):''}; }
var len=parseInt(p.add(8).readU64().toString());
if(len>1048576) return {t:'l?',len:len,v:''};
return {t:'l',len:len,v:hx(p.add(16).readPointer(),Math.min(len,16384))};
}catch(e){ return {t:'err',len:-1,v:String(e)}; }
}
function ra(ctx){
try{ return DebugSymbol.fromAddress(ctx.returnAddress).toString().slice(0,100);}catch(e){return '?';}
}
function hookStr(fn, off, name){
try{
Interceptor.attach(base.add(off), {
onEnter: function(a){
this.r=ra(this);
try{
var in1=rdStr(a[1]);
send({type:'enc', name:name, off:off, r:this.r,
in:in1, key:rdStr(a[2]), out_before:rdStr(a[0])});
}catch(e){ send({type:'enc_err', name:name, off:off, e:String(e)}); }
},
onLeave: function(ret){
send({type:'enc_out', name:name, off:off,
out:rdStr(this.context.x0)});
}
});
send({type:'hooked', name:name, off:off});
}catch(e){ send({type:'hook_fail', name:name, off:off, e:String(e)}); }
}
// hyudb_crypt_util::xxtea_encrypt(string& out, const string& in, const string& key)
hookStr(0x32e83c, 0x32e83c, 'xxtea_crypt_util');
// hyudbxxt::xxtea_encrypt(const string& in, const string& key, string& out) 参数顺序不同
try{
Interceptor.attach(base.add(0x2527ec), {
onEnter: function(a){
send({type:'enc', name:'xxtea_xxt', off:0x2527ec, r:ra(this),
in:rdStr(a[0]), key:rdStr(a[1]), out_before:rdStr(a[2])});
},
onLeave: function(ret){
send({type:'enc_out', name:'xxtea_xxt', out:rdStr(this.context.x0)});
}
});
send({type:'hooked', name:'xxtea_xxt', off:0x2527ec});
}catch(e){ send({type:'hook_fail', name:'xxtea_xxt', e:String(e)}); }
// UdbAESUtil::encrypt(string& in, string& out) —— 打印输入前 64B + 完整长度
try{
Interceptor.attach(base.add(0x250038), {
onEnter: function(a){
this.r=ra(this);
var inp=rdStr(a[0]);
send({type:'aes_in', r:this.r, in:inp});
},
onLeave: function(ret){
send({type:'aes_out', out:rdStr(this.context.x0)});
}
});
send({type:'hooked', name:'aes_encrypt', off:0x250038});
}catch(e){ send({type:'hook_fail', name:'aes_encrypt', e:String(e)}); }
// AESkeyMgr::getkey(int a, int b)
try{
Interceptor.attach(base.add(0x26871c), {
onEnter: function(a){ this.a=a[0].toInt32(); this.b=a[1].toInt32(); },
onLeave: function(ret){
send({type:'aeskey', a:this.a, b:this.b, out:rdStr(this.context.x0)});
}
});
send({type:'hooked', name:'aeskey', off:0x26871c});
}catch(e){ send({type:'hook_fail', name:'aeskey', e:String(e)}); }
// enpack_header(uchar w0, NEWKEY x1, string& x2)
try{
Interceptor.attach(base.add(0x32ff44), {
onEnter: function(a){
this.h=a[0].toInt32()&0xff;
this.r=ra(this);
},
onLeave: function(ret){
send({type:'enpack_header', h:this.h, r:this.r,
out:rdStr(this.context.x2)});
}
});
send({type:'hooked', name:'enpack_header', off:0x32ff44});
}catch(e){ send({type:'hook_fail', name:'enpack_header', e:String(e)}); }
// enpack_body(uchar w0, string& x1, const string& x2, const string& x3, const string& x4, string& x5)
try{
Interceptor.attach(base.add(0x3300c8), {
onEnter: function(a){
this.h=a[0].toInt32()&0xff;
this.r=ra(this);
send({type:'enpack_body_in', h:this.h, r:this.r,
s1:rdStr(a[1]), s2:rdStr(a[2]), s3:rdStr(a[3]), s4:rdStr(a[4])});
},
onLeave: function(ret){
send({type:'enpack_body_out', h:this.h, out:rdStr(this.context.x5)});
}
});
send({type:'hooked', name:'enpack_body', off:0x3300c8});
}catch(e){ send({type:'hook_fail', name:'enpack_body', e:String(e)}); }
// datadiv 解码器 (libhydeviceid + libudbauthunify) —— 魔数可能是运行时解码的字符串
var KEYWORDS = ['dfp', '5718', '82cf', 'b394', 'collect', 'report', 'device'];
var ddHooked = 0;
['libhydeviceid.so', 'libudbauthunify.so'].forEach(function(modName){
var m = null;
try{ m = Process.getModuleByName(modName); }catch(e){ return; }
m.enumerateExports().forEach(function(exp){
if (exp.name.indexOf('datadiv') < 0) return;
try{
Interceptor.attach(exp.address, {
onLeave: function(ret){
var s = '';
try{ s = ret.readUtf8String(); }catch(e){}
if (!s || s.length < 3 || s.length > 2000) return;
var low = s.toLowerCase();
var hit = false;
KEYWORDS.forEach(function(k){ if (low.indexOf(k) >= 0) hit = true; });
if (hit) send({type:'dd', mod:modName, fn:exp.name, str:s.slice(0,300)});
}
});
ddHooked += 1;
}catch(e){}
});
});
send({type:'dd_hooked', n:ddHooked});
"""
def on_message(message, data):
if message.get("type") == "send":
payload = message.get("payload", {})
events.append(payload)
t = payload.get("type")
if t in ("enc", "enc_out", "aeskey", "enpack_header", "enpack_body_in", "enpack_body_out"):
# 只打印关键摘要, 防刷屏
name = payload.get("name", t)
inp = payload.get("in", {})
key = payload.get("key", {})
if t == "enc":
print(f"[{name}] in.len={inp.get('len') if isinstance(inp, dict) else '?'} "
f"key.len={key.get('len') if isinstance(key, dict) else '?'} "
f"r={payload.get('r','')}")
if isinstance(inp, dict) and inp.get('len') and inp.get('len') < 600:
print(f" in: {inp.get('v','')[:200]}")
if isinstance(key, dict) and key.get('len') and key.get('len') < 200:
print(f" key: {key.get('v','')[:120]}")
elif t == "enc_out":
out = payload.get("out", {})
print(f"[{name}] out.len={out.get('len') if isinstance(out, dict) else '?'}")
elif t == "aes_in":
inp = payload.get("in", {})
v = inp.get("v", "") if isinstance(inp, dict) else ""
print(f"[aes_in] r={payload.get('r','')[:60]} len={inp.get('len')} head={v[:64]}")
elif t == "aes_out":
out = payload.get("out", {})
v = out.get("v", "") if isinstance(out, dict) else ""
print(f"[aes_out] len={out.get('len')} head={v[:64]}")
elif t == "enpack_header":
print(f"[enpack_header] h={payload.get('h')} out.len={payload.get('out',{}).get('len','?')}")
elif t == "enpack_body_in":
print(f"[enpack_body] h={payload.get('h')} s1={payload.get('s1',{}).get('v','')[:60]}")
elif t == "enpack_body_out":
out = payload.get("out", {})
print(f"[enpack_body] out.len={out.get('len') if isinstance(out, dict) else '?'}")
elif t == "dd":
print(f"[dd] {payload.get('mod')} {payload.get('fn')}: {payload.get('str')!r}")
elif t == "dd_hooked":
print(f"[+] datadiv hooked: {payload.get('n')}")
elif t == "hooked":
print(f"[+] hooked: {payload.get('name')}")
elif t == "hook_fail":
print(f"[-] hook fail: {payload.get('name')}: {payload.get('e')}")
elif message.get("type") == "error":
print(f"[frida-error] {message.get('description')}", file=sys.stderr)
events = []
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}")
session = device.attach(pid)
# 双 bypass + patch_guard (与 hook_genbiz.py 同款, 稳定)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] resumed, 等待 dfpReport 链触发 (App 启动自动执行 + 请手动退出登录->重新登录)...")
try:
time.sleep(150)
except KeyboardInterrupt:
pass
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
print(f"[*] 捕获 {len(events)} 条 -> {OUT}")
print(f" 类型分布: { {t: sum(1 for e in events if e.get('type')==t) for t in set(e.get('type') for e in events)} }")
if __name__ == "__main__":
main()
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""hook SSL_write, 抓 dfpReport 请求发出时的完整调用栈, 定位加密体组装函数。
dfpReport 是 TAF 请求(含 wupudbrequest/huyaudbwebui 特征), 发送时栈上
必然经过 libhydeviceid.so / libudbauthunify.so 的组装函数。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_wire_stack.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hx(p,n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
function ra(ctx, depth){
var out = [];
try{
var bt = Thread.backtrace(ctx, Backtracer.ACCURATE);
for (var i = 0; i < bt.length && i < 40; i++) {
var name = '';
try{ name = DebugSymbol.fromAddress(bt[i]).toString().slice(0, 110); }catch(e){}
if (name.indexOf('libudbauthunify.so') >= 0 || name.indexOf('libhydeviceid.so') >= 0 ||
name.indexOf('libhycrypto.so') >= 0 || name.indexOf('libtscsdk.so') >= 0) {
out.push(name);
}
}
}catch(e){}
return out;
}
var hooked = 0;
var fns = [];
try{ fns = DebugSymbol.findFunctionsNamed('SSL_write'); }catch(e){}
if (!fns.length) {
try{
var r = new ApiResolver('module');
fns = r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){ return x.address; });
}catch(e2){}
}
fns.slice(0, 6).forEach(function(p, i){
Interceptor.attach(p, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 40 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('wupudbrequest') < 0 && head.indexOf('huyaudbwebui') < 0 &&
head.indexOf('dfpReport') < 0 && head.indexOf('getDfpConfig') < 0) return;
var arr = new Uint8Array(a[1].readByteArray(len));
var hex = '';
for (var j = 0; j < arr.length; j++) hex += ('0'+arr[j].toString(16)).slice(-2);
send({type:'wire', idx:i, len:len, stack:ra(this.context, 40), hex:hex});
}
});
hooked++;
send({type:'hooked', idx:i, at:String(p)});
});
if (hooked === 0) send({type:'nowrite'});
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}")
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200]); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "wire":
print(f"[wire] len={p.get('len')}")
for s in (p.get("stack") or []):
print(f" {s}")
print(f" hex head: {p.get('hex','')[:64]}")
events.append(p)
elif t == "hooked":
print(f"[+] SSL_write hooked idx={p.get('idx')} at={p.get('at')}")
elif t == "nowrite":
print("[-] SSL_write not found!")
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 请在手机上 退出登录 -> 重新登录 (150s)")
deadline = time.time() + 150
try:
while time.time() < deadline:
time.sleep(5)
except KeyboardInterrupt:
pass
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
print(f"[*] 捕获 {len(events)} 条 -> {OUT}")
if __name__ == "__main__":
main()
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""hook xxtea_crypt_util + hyudbxxt::xxtea_*: 抓 dfpReport 加密的 (in, key, out)。
xxtea_crypt_util @ libudbauthunify.so offset 3336252 (0x32e9bc附近)。
调用者 UdbUserFilterUtils::xxTeaAndBase64 处理 WUP编码体 = 与 dfpReport 同构。
修复 string 参数解析(SSO), onLeave 读输出。命中 len>2000 即持久化。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_xxtea_crypt.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function hexb(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return null; }
}
// 解析 __ndk1 basic_string (SSO): 字节0低bit: 1=内联(sso), 0=堆指针
function readString(p){
try{
var b0 = p.readU8();
var sso = (b0 & 1) === 1;
if (sso) {
var ln = b0 >> 1;
var data = p.add(1);
return {len: ln, data: data};
} else {
var ln = p.add(8).readPointer().toInt32();
var data = p.add(16).readPointer();
return {len: ln, data: data};
}
}catch(e){ return null; }
}
function hookOne(name, off, maxLen){
var mod = Process.findModuleByName('libudbauthunify.so');
if (!mod) return false;
var addr = mod.base.add(off);
try{
Interceptor.attach(addr, {
onEnter: function(args){
this.a = args;
// 尝试解析 (ret, in, key) / (in, key) / (this, in, key) 多种
this.in0 = readString(args[1]); // 常见: (this, in, key)
this.key0 = readString(args[2]);
if (!this.in0 && readString(args[0])) { this.in0 = readString(args[0]); this.key0 = readString(args[1]); }
if (!this.in0) { this.in0 = readString(args[2]); this.key0 = readString(args[3]); }
this.t0 = Date.now();
},
onLeave: function(ret){
if (!this.in0) return;
var ln = this.in0.len;
if (ln > 5000 || ln < 100) return; // dfpReport 明文 ~3980B, 只抓大输入
var inhex = hexb(this.in0.data, Math.min(ln, 250));
if (!inhex) return;
// 过滤: 找 TAF/JSON 特征
var low = inhex;
var isWup = low.indexOf('dfpReport') >= 0 ||
low.indexOf('74726571') >= 0 || // 'tReq'
low.indexOf('68757961') >= 0 || // 'huya'
low.indexOf('61707049') >= 0 || // 'appI'
low.indexOf('7b226170') >= 0; // '{"ap'
if (!isWup && ln < 2900) return;
var keyhex = this.key0 ? hexb(this.key0.data, Math.min(this.key0.len, 32)) : null;
send({type:'xxtea', fn:name, off:off.toString(16),
inlen:ln, in:inhex, keylen: this.key0 ? this.key0.len : -1, key:keyhex,
ret:String(ret)});
}
});
return true;
}catch(e){ return false; }
}
var T = setTimeout(function(){
var n1 = hookOne('xxtea_crypt_util', 3336252, 99999);
// hyudbxxt 家族
var n2 = hookOne('hyudb_xxtea_enc1', 0x25261c, 99999);
var n3 = hookOne('hyudb_xxtea_dec1', 0x252708, 99999);
var n4 = hookOne('hyudb_xxtea_enc2', 0x2528e0, 99999);
send({type:'hooked', n:[n1,n2,n3,n4].filter(Boolean).length});
if (!n1 && !n2 && !n3 && !n4) setTimeout(arguments.callee, 1000);
}, 800);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "hooked":
print(f"[*] hooked {p.get('n')} 个 xxtea", flush=True)
elif t == "xxtea":
print(f"[XXTEA] {p.get('fn')}@{p.get('off')} inlen={p.get('inlen')} "
f"keylen={p.get('keylen')}", flush=True)
if p.get('key'):
print(f" key: {p.get('key')}", flush=True)
inh = p.get('in', '')
print(f" in[:100]: {inh[:100]}", flush=True)
# 尝试 ascii
try:
bb = bytes.fromhex(inh[:200])
asc = ''.join(chr(x) if 32<=x<127 else '.' for x in bb)
print(f" in ascii: {asc[:120]}", flush=True)
except Exception: pass
events.append(p)
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, hooking xxtea. 请 退出登录->重新登录, 命中即 pkill", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+187
View File
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""改写明文JSON字段实验: 验证服务端回显本地明文。
明文JSON {hdid: 7c5387..., appkey: 865a...cbb0e3, deviceId: ...} 在堆中。
等长替换 hdid/appkey/deviceId -> 新值, 触发登录, 看 dfpReport 响应 t5 是否变化。
同时 hook SSL_write 抓密文 + SSL_read 抓响应。
"""
from __future__ import annotations
import json
import random
import string
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_plain_patch.json")
NEW_HDID = "".join(random.choice("0123456789abcdef") for _ in range(40))
NEW_APPKEY = "".join(random.choice("0123456789abcdef") for _ in range(32))
NEW_DEVID = "".join(random.choice("0123456789abcdef") for _ in range(40))
print(f"[*] 新 hdid = {NEW_HDID}")
print(f"[*] 新 appkey = {NEW_APPKEY}")
print(f"[*] 新 deviceId= {NEW_DEVID}")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var NEW_HDID = '%s'; // 40hex
var NEW_APPKEY = '%s'; // 32hex
var NEW_DEVID = '%s'; // 40hex
function hexb(p, n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return null; }
}
// 找明文 JSON {"appId":"5008" 并等长替换字段
function patchJson(){
try{
Process.enumerateRanges('r--').forEach(function(rng){
if (rng.size > 1024*1024*256) return;
try{
var hits = Memory.scanSync(rng.base, rng.size, '7b 22 61 70 70 49 64 22 3a 22 35 30 30 38');
hits.slice(0, 6).forEach(function(x){
var p = x.address;
var s = '';
try{ s = p.readUtf8String(1200); }catch(e){ return; }
if (s.indexOf('"hdid"') < 0) return;
send({type:'found_json', addr:String(p), len:s.length});
// 等长替换 hdid / appkey / deviceId
try{
Memory.protect(p, s.length + 16, 'rwx');
var out = s;
out = out.replace(/"hdid":"[0-9a-f]{40}"/, '"hdid":"' + NEW_HDID + '"');
out = out.replace(/"appkey":"[0-9a-f]{32}"/, '"appkey":"' + NEW_APPKEY + '"');
out = out.replace(/"deviceId":"[0-9a-f]{40}"/, '"deviceId":"' + NEW_DEVID + '"');
if (out !== s) {
p.writeUtf8String(out);
var after = p.readUtf8String(120);
send({type:'patched_json', addr:String(p), after:after.slice(0, 180)});
}
}catch(e){ send({type:'patch_err', addr:String(p), e:String(e)}); }
});
}catch(e){}
});
}catch(e){}
}
// SSL_write 抓 dfpReport 密文
try{
var r = new ApiResolver('module');
r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){
Interceptor.attach(m.address, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 100 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('dfpReport') < 0) return;
var hex = hexb(a[1], len);
send({type:'wire', len:len, hex:hex});
}
});
});
}catch(e){}
// SSL_read 抓响应 (找 t5 / t1)
try{
var r2 = new ApiResolver('module');
r2.enumerateMatchesSync('exports:*!SSL_read').forEach(function(m){
Interceptor.attach(m.address, {
onEnter: function(a){
this.buf = a[1]; this.len = a[2].toInt32();
},
onLeave: function(ret){
var n = ret.toInt32();
if (n <= 0 || n > 65536) return;
try{
var hex = hexb(this.buf, n);
send({type:'sslread', len:n, hex:hex});
}catch(e){}
}
});
});
}catch(e){}
// 每 5s patch 一次 (登录时 JSON 重新生成)
setInterval(function(){ try{ patchJson(); }catch(e){} }, 5000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "found_json":
print(f"[json] @{p.get('addr')} len={p.get('len')}", flush=True)
events.append({"found": p.get('addr'), "new_hdid": NEW_HDID,
"new_appkey": NEW_APPKEY, "new_devid": NEW_DEVID})
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
elif t == "patched_json":
print(f"[PATCH-OK] @{p.get('addr')}: {p.get('after')!r}", flush=True)
elif t == "patch_err":
print(f"[patch-err] @{p.get('addr')}: {p.get('e')}", flush=True)
elif t == "wire":
body = bytes.fromhex(p.get('hex'))
try: hp = body.index(b'\r\n\r\n') + 4
except ValueError: hp = 0
b = body[hp:]
j = b.find(b'\x57\x18\x82\xcf\x66\x4b\xb3\x94')
print(f"[wire] len={p.get('len')} 魔数@{j}", flush=True)
if j >= 0:
print(f" 密文头: {b[j+10:j+30].hex(' ')}", flush=True)
events.append({"wire": p.get('hex')})
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
elif t == "sslread":
h = p.get('hex', '')
low = h.lower()
# dfpReport 响应里找 40hex t5
if len(h) > 200 and ('7c5387' in low or 't1' in low or 't5' in low or len(h) < 4000):
print(f"[read] len={p.get('len')} hex[:120]: {h[:120]}", flush=True)
events.append({"read": h})
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print(f"[*] 已 resume. 将把 hdid={NEW_HDID} appkey={NEW_APPKEY} deviceId={NEW_DEVID} 写入明文JSON", flush=True)
print("[*] 请 退出登录->重新登录, 观察响应 t5 是否为新 hdid", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""改写 libhydeviceid.so .data 中的 t1 指纹, 验证 dfpReport 响应变化。
t1 = 865a4924a40897ac1fcfe6b4c2cbb045 (3处静态内置, datadiv 解密)。
改为随机 32hex 后重新登录, 看 dfpReport 响应 t1 是否变化。
同时 hook SSL_write 抓新请求 body 对比魔数后密文是否变化。
"""
from __future__ import annotations
import json
import random
import string
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_t1_patch.json")
# 新 t1 (随机 32hex)
NEW_T1 = "".join(random.choice("0123456789abcdef") for _ in range(32))
print(f"[*] 新 t1 = {NEW_T1}")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var NEW_T1 = '%s';
var T1_OFFS = [0x3bb190, 0x3bb230, 0x3e0e50];
// 等待: so 加载后延时 800ms, 确保 datadiv 就地解密已完成
var tries = 0;
var iv = setInterval(function(){
tries += 1;
var m = null;
try{ m = Process.getModuleByName('libhydeviceid.so'); }catch(e){}
if (m) {
clearInterval(iv);
setTimeout(function(){
patchAll(m);
}, 800);
} else if (tries > 100) { clearInterval(iv); send({type:'no_mod'}); }
}, 50);
function patchAll(m){
// 先验证当前明文, 若仍是密文说明 datadiv 未跑完, 再等
try{
var cur = m.base.add(0x3bb190).readUtf8String(32);
if (cur.indexOf('865a') !== 0 && cur.indexOf('c1') !== 0) {
// 还没解密好, 再等
setTimeout(function(){ patchAll(m); }, 400);
return;
}
}catch(e){}
T1_OFFS.forEach(function(off){
try{
var p = m.base.add(off);
var cur = p.readUtf8String(32);
send({type:'backup', off:off.toString(16), cur:cur});
Memory.protect(p, 64, 'rwx');
p.writeUtf8String(NEW_T1);
var after = p.readUtf8String(32);
send({type:'patched', off:off.toString(16), after:after, ok: after === NEW_T1});
}catch(e){ send({type:'patch_err', off:off.toString(16), e:String(e)}); }
});
send({type:'patch_done'});
}
// hook SSL_write 抓 dfpReport 请求
try{
var fns = [];
var r = new ApiResolver('module');
fns = r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){ return x.address; });
fns.slice(0, 6).forEach(function(p){
Interceptor.attach(p, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 100 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('dfpReport') < 0) return;
var arr = new Uint8Array(a[1].readByteArray(len));
var hex = '';
for (var j = 0; j < arr.length; j++) hex += ('0'+arr[j].toString(16)).slice(-2);
send({type:'wire', len:len, hex:hex});
}
});
});
}catch(e){ send({type:'ssl_err', e:String(e)}); }
""" % NEW_T1
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "backup":
print(f"[备份] 0x{p.get('off')}: {p.get('cur')}", flush=True)
elif t == "patched":
print(f"[改写] 0x{p.get('off')}: -> {p.get('after')} ok={p.get('ok')}", flush=True)
elif t == "patch_done":
print(f"[*] 改写完成 {p.get('patched')}", flush=True)
events.append({"patched": True, "new_t1": NEW_T1})
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
elif t == "wire":
body = bytes.fromhex(p.get('hex'))
try: hp = body.index(b'\r\n\r\n') + 4
except ValueError: hp = 0
b = body[hp:]
j = b.find(b'\x57\x18\x82\xcf\x66\x4b\xb3\x94')
print(f"[wire] len={p.get('len')} 魔数@{j}", flush=True)
if j >= 0:
print(f" 密文头: {b[j+10:j+30].hex(' ')}", flush=True)
rec = {"type": "wire", "len": p.get('len'), "hex": p.get('hex')}
events.append(rec)
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print(f"[*] 已 resume, t1 已改为 {NEW_T1}。请在手机 退出登录->重新登录", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""定位 dfpReport 加密体生成函数: 批量 hook libhydeviceid.so / libtscsdk.so
导出函数, 记录返回长度 >500B 的调用。登录触发后, 找输出含魔数
571882cf664bb39401ee 或长度 ≈3908B 的函数。
"""
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_bigout_capture.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){
send({type:'segv', info:{type:d.type, addr:String(d.address)}});
return true;
});
send({type:'armed'});
function hx(p,n){
try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }
catch(e){ return 'ERR'; }
}
function ra(ctx){
try{ return DebugSymbol.fromAddress(ctx.returnAddress).toString().slice(0,90);}catch(e){return '?';}
}
var TARGETS = ['libhydeviceid.so', 'libtscsdk.so', 'libturingga.so'];
var MIN_LEN = 3000;
var MAX_LEN = 6000;
var hooked = 0;
TARGETS.forEach(function(modName){
try{
var m = Process.getModuleByName(modName);
}catch(e){ send({type:'skip', mod:modName}); return; }
var exps = m.enumerateExports();
exps.forEach(function(exp){
var n = exp.name;
if (n.indexOf('datadiv') === 0) return; // 字符串解码器, 跳过
if (n.indexOf('std::') === 0) return; // std 符号, 跳过
if (/^(_Z|Java_|JNI_)/.test(n)) return; // C++ 符号/Java 方法名太长, 跳过
try{
Interceptor.attach(exp.address, {
onEnter: function(args){
this.t0 = Date.now();
this.args = [];
for (var i = 0; i < Math.min(4, args.length); i++) {
try{
var v = args[i].toInt32();
if (v > 0 && v < 10000000) this.args.push(v);
}catch(e){}
}
},
onLeave: function(ret){
var dt = Date.now() - this.t0;
var v = ret.toInt32();
if ((v >= MIN_LEN && v <= MAX_LEN) || dt > 30) {
send({type:'big', mod:modName, fn:exp.name, ret:v, dt:dt,
args:this.args, r:ra(this.ctx)});
}
}
});
hooked += 1;
}catch(e){}
});
});
send({type:'hooked_count', n:hooked});
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}")
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:160]); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "big":
print(f"[big] {p.get('mod')} {p.get('fn')} ret={p.get('ret')} dt={p.get('dt')}ms r={p.get('r','')}")
events.append(p)
elif t == "hooked_count":
print(f"[*] hooked {p.get('n')} exports")
elif t == "skip":
print(f"[skip] {p.get('mod')}")
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] hooks 装好, 请在手机上 退出登录 -> 重新登录 触发 dfpReport 链 (120s)")
deadline = time.time() + 120
try:
while time.time() < deadline:
time.sleep(5)
except KeyboardInterrupt:
pass
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
print(f"[*] 捕获 {len(events)} 条 -> {OUT}")
if __name__ == "__main__":
main()
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Hook .datadiv_decode* 字符串解码器, 找 571882cf 魔数 / dfp 相关字符串的来源。
libhydeviceid.so 和 libudbauthunify.so 的业务字符串全被 datadiv 混淆,
运行时才解码。魔数 571882cf664bb39401ee 静态搜不到 -> 一定是解码产物。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_datadiv_capture.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
var MAGIC_HEX = '571882cf664bb39401ee';
var KEYWORDS = ['dfp', '5718', '82cf', 'b394', 'collect', 'encrypt', 'device'];
var hooked = 0;
function hookMod(modName){
var m = null;
try{ m = Process.getModuleByName(modName); }catch(e){ return false; }
var exps = m.enumerateExports();
exps.forEach(function(exp){
if (exp.name.indexOf('datadiv') < 0) return;
try{
Interceptor.attach(exp.address, {
onEnter: function(args){ this.t0=Date.now(); },
onLeave: function(ret){
var p = ret;
var s = '';
try{ s = p.readUtf8String(); }catch(e){}
var hit = false;
if (s && s.length > 3 && s.length < 2000) {
var low = s.toLowerCase();
KEYWORDS.forEach(function(k){
if (low.indexOf(k) >= 0) hit = true;
});
}
if (hit) {
send({type:'hit', mod:modName, fn:exp.name,
str: s.slice(0, 300)});
}
}
});
hooked += 1;
}catch(e){}
});
return true;
}
// 轮询等待模块加载
var tries = 0;
var iv = setInterval(function(){
tries += 1;
var ok1 = hookMod('libhydeviceid.so');
var ok2 = hookMod('libudbauthunify.so');
if (tries % 5 === 0) send({type:'waiting', tries:tries, hooked:hooked});
if (ok1 && ok2) { clearInterval(iv); send({type:'hooked_count', n:hooked}); }
if (tries > 60) clearInterval(iv);
}, 1000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}")
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:160]); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "hit":
print(f"[hit] {p.get('mod')} {p.get('fn')}: {p.get('str')!r}")
events.append(p)
elif t == "hooked_count":
print(f"[*] hooked {p.get('n')} datadiv decoders")
elif t == "skip":
print(f"[skip] {p.get('mod')}")
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 请在手机上 退出登录 -> 重新登录 触发 dfpReport 链 (120s)")
deadline = time.time() + 120
try:
while time.time() < deadline:
time.sleep(5)
except KeyboardInterrupt:
pass
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
print(f"[*] 捕获 {len(events)} 条 -> {OUT}")
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""侦察: 虎牙App 加载的模块中, 哪些含 dfp/hydevice/tsc/xxtea/aes 相关符号。
决定 dfpReport 加密体生成所在的 so, 再针对性 hook。
"""
from __future__ import annotations
import sys
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
JS = r"""
'use strict';
var mods = Process.enumerateModules();
var kw = /dfp|hydevice|deviceid|tsc|udb|xxtea|aes|finger|crypt|secsdk|turing|risk/;
var out = [];
mods.forEach(function(m){
var n = m.name;
if (kw.test(n.toLowerCase())) out.push({name:n, base:String(m.base), size:m.size});
});
send({type:'mods', list:out});
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}")
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
script = session.create_script(JS)
def on_message(message, data):
if message.get("type") == "send":
payload = message.get("payload") or {}
if payload.get("type") == "mods":
for m in payload["list"]:
print(f" {m['name']:<32} {m['base']} size={m['size']}")
script.on("message", on_message)
script.load()
time.sleep(8)
print("[*] done")
session.detach()
if __name__ == "__main__":
import time
main()
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""扫描堆中"加密前的明文":设备采集数据(含 android- 开头、JSON/键值)。
dfpReport 明文 = [魔数][时间戳][设备数据], 加密前明文在堆里。
扫描含 'android''deviceinfo' 等键值的大块可读内存, dump 前后 8KB。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_plain_scan.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
function tryRead(p, n){
try{ return p.readUtf8String(n); }catch(e){ return null; }
}
function scan(){
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*256) return;
try{
// 搜 "android-" 明文 (设备 UA 前缀, 加密前会出现)
var hits = Memory.scanSync(r.base, r.size, '61 6e 64 72 6f 69 64 2d');
hits.forEach(function(x){
var s = tryRead(x.address, 300);
if (s && s.length > 30 && /[a-z0-9]{3,}/.test(s)) {
send({type:'plain', addr:String(x.address), s:s.slice(0, 300)});
}
});
// 搜 "deviceinfo"
var hits2 = Memory.scanSync(r.base, r.size, '64 65 76 69 63 65 69 6e 66 6f');
hits2.forEach(function(x){
var s = tryRead(x.address, 300);
if (s) send({type:'plain', addr:String(x.address), s:s.slice(0, 300)});
});
}catch(e){}
});
}catch(e){}
send({type:'tick'});
}
setInterval(function(){ try{ scan(); }catch(e){} }, 4000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "plain":
s = p.get('s', '')
# 过滤明显的噪音 (路径/类名)
if '/data/app' in s or 'library' in s or '.jar' in s: return
key = p.get('addr')
if any(e.get('addr') == key for e in events): return
events.append(p)
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
print(f"[plain] @{key}: {s[:180]!r}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, 扫描明文 (4s轮询), 请触发登录, 命中即 pkill", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} 条 -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""扫描内存找加密前的 dfpReport 明文采集数据。
明文 = 设备采集序列化(含 deviceinfo/appid/机型 thyme/M2102J2SC 等)。
找到后 dump 前后 16KB, 与密文(魔数开头)配对解 keystream。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_plain_big.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
// 设备特征串 (加密前明文会出现)
var PATTERNS = [
['thyme', '74 68 79 6d 65'],
['M2102J2SC', '4d 32 31 30 32 4a 32 53 43'],
['deviceinfo', '64 65 76 69 63 65 69 6e 66 6f'],
['appid', '61 70 70 69 64'],
['terminal', '74 65 72 6d 69 6e 61 6c'],
['android-', '61 6e 64 72 6f 69 64 2d'],
['Xiaomi', '58 69 61 6f 6d 69'],
['sdk', '73 64 6b'],
];
function tryRead(p, n){
try{ return p.readByteArray(n); }catch(e){ return null; }
}
function scan(){
var reported = {};
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*256) return;
try{
PATTERNS.forEach(function(pat){
// 用打头模式避免每次整个段 MB 级扫描太慢: 只对前几个模式做全扫
var key = pat[0];
if (reported[key] > 20) return;
var m = Memory.scanSync(r.base, r.size, pat[1]);
if (!m.length) return;
reported[key] = (reported[key] || 0) + m.length;
m.slice(0, 8).forEach(function(x){
var addr = x.address;
// dump 前后: 前找结构头, 后 4KB
var pre = null, post = null;
try{ pre = addr.sub(256).readByteArray(512); }catch(e){}
try{ post = addr.readByteArray(4096); }catch(e){}
if (post) {
var arr = Array.from(new Uint8Array(post));
var hex = arr.map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('');
send({type:'hit', pat:key, addr:String(addr), hex:hex});
}
});
});
}catch(e){}
});
}catch(e){}
send({type:'tick'});
}
setInterval(function(){ try{ scan(); }catch(e){} }, 5000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "hit":
h = p.get('hex', '')
addr = p.get('addr')
# 去重: 同地址只留一次
if any(e.get('addr') == addr for e in events): return
tag = p.get('pat')
events.append({"addr": addr, "pat": tag, "hex": h})
OUT.write_text(json.dumps(events, ensure_ascii=False))
# 打印: 特征串 + 前 64B 明文上下文
try:
idx = h.encode().decode('hex') if False else h
except Exception: pass
print(f"[hit:{tag}] @{addr}", flush=True)
# 提取特征串前后 3B 看明文结构
print(f" hex[:96]: {h[:96]}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载(resume前), 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
print("[*] 已 resume, 扫描中 (特征: thyme/M2102J2SC/deviceinfo/appid/android-)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] 结束, {len(events)} -> {OUT}", flush=True)
if __name__ == "__main__":
main()
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""内存扫描: 找魔数 571882cf664bb39401ee 在进程内存中的位置。
App 组装 dfpReport 时魔数一定在内存中。扫描模块内存,
定位后 dump 周围字节 + 反查所在模块/偏移。
"""
from __future__ import annotations
import time
from pathlib import Path
import frida
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var MAGIC8 = [0x57,0x18,0x82,0xcf,0x66,0x4b,0xb3,0x94];
function scanRange(base, size, name){
try{
var m = Memory.scanSync(base, size, '57 18 82 cf 66 4b b3 94');
m.forEach(function(x){
var addr = x.address;
var ctx = [];
try{
var bytes = addr.sub(16).readByteArray(48);
ctx = Array.from(new Uint8Array(bytes));
}catch(e){}
send({type:'found', mod:name, addr:String(addr),
offInMod: addr.sub(base).toString(),
ctx: ctx.map(function(b){return ('0'+b.toString(16)).slice(-2);}).join(' ')});
});
}catch(e){}
}
function doScan(){
var mods = Process.enumerateModules();
mods.forEach(function(m){
var n = m.name.toLowerCase();
if (n.indexOf('hydevice')>=0 || n.indexOf('udb')>=0 || n.indexOf('tsc')>=0 ||
n.indexOf('hycrypto')>=0 || n.indexOf('libc')>=0 || n.indexOf('kiwi')>=0) {
try{ scanRange(m.base, m.size, m.name); }catch(e){}
}
});
// 也扫匿名可读内存 (堆)
try{
Process.enumerateRanges('r--').forEach(function(r){
if (r.size > 1024*1024*256) return; // 跳过超大段
try{
var m = Memory.scanSync(r.base, r.size, '57 18 82 cf 66 4b b3 94');
m.forEach(function(x){
var ctx = [];
try{ ctx = Array.from(new Uint8Array(x.address.sub(16).readByteArray(48))); }catch(e){}
// dump 魔数前 256B + 后 512B 的 ascii 找明文
var pre = '', post = '';
try{ pre = x.address.sub(256).readUtf8String(); }catch(e){}
try{ post = x.address.add(10).readUtf8String(); }catch(e){}
send({type:'found_heap', addr:String(x.address),
ctx: ctx.map(function(b){return ('0'+b.toString(16)).slice(-2);}).join(' '),
pre: pre, post: post});
});
}catch(e){}
});
}catch(e){}
send({type:'scan_done'});
}
// 每 5 秒扫一次, 实时找魔数 (App 登录触发 dfpReport 时出现)
var iv = setInterval(function(){
try{ doScan(); }catch(e){ send({type:'scan_err', e:String(e)}); }
}, 5000);
"""
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn(["com.duowan.kiwi"])
print(f"[*] spawned pid={pid}")
session = device.attach(pid)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:160], flush=True); return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "found":
print(f"[FOUND] {p.get('mod')} @{p.get('addr')} off={p.get('offInMod')}", flush=True)
print(f" ctx: {p.get('ctx')}", flush=True)
elif t == "found_heap":
print(f"[FOUND-HEAP] @{p.get('addr')}", flush=True)
print(f" ctx: {p.get('ctx')}", flush=True)
pre = p.get('pre', '') or ''
post = p.get('post', '') or ''
if pre.strip(): print(f" pre256: {pre[:256]!r}", flush=True)
if post.strip(): print(f" post512: {post[:512]!r}", flush=True)
elif t == "mods":
print(f"[*] modules: {p.get('n')}", flush=True)
elif t == "scan_done":
print("[*] scan tick done", flush=True)
elif t == "scan_err":
print(f"[scan_err] {p.get('e')}", flush=True)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 内存扫描中 (每5s), 请在手机上 退出登录->重新登录 (持续扫描, 命中即停止)", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print("[*] done", flush=True)
session.detach()
if __name__ == "__main__":
main()