某派av解说逆向分析笔记

xuying 于 2026-07-28 发布

感谢deepseek deepseek flash nb!


图书馆好啊 要多去图书馆

基于 APK pie_V2.5.3_release_06031753_ldy_253_0508.apk 分析


一、项目概览

项目
包名 com.fruit.goodgood
版本号 2.5.3 (Code 15)
技术栈 Flutter + Dart
热更新 Shorebird
CPU 架构 仅 arm64-v8a

二、抓包内容

从 Reqable 导出的请求/响应数据:

端口 请求路径 用途
4242 POST /v2_0/libraryDetail 视频详情 API(加密)
4243 POST /v2_0/universalAdvert 广告列表 API(加密)

三、API 加密机制

请求与响应的 body 均使用同一套加密方案:

{"encrypt_data":"...","encrypt_key":"..."}

3.1 算法拆解

层级 算法 作用
外层 RSA-PKCS1 (2048 bit) 加密 AES 密钥
内层 AES-128-CBC + PKCS7 加密实际数据
密钥嵌套 RSA 解密结果 = {"key":"...","iv":"..."} 内含 AES 密钥与 IV

3.2 密钥文件位置

位于 assets/flutter_assets/assets/key/,包含客户端私钥与服务端公钥配对。

3.3 解密流程

响应 body: {"encrypt_key":"...", "encrypt_data":"..."}
                        │
                        ▼
┌─────────────────────────────────────────────┐
│ ① RSA-PKCS1 解密 encrypt_key               │
│   用客户端私钥                              │
│   得到: {"key":"5+e/utNudfRV73FRdZz03w==", │
│           "iv":"ShO4AGaX/n79EkBb2K08pA=="}  │
└─────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────┐
│ ② AES-128-CBC/PKCS7 解密 encrypt_data      │
│   用上一步得到的 key 和 iv                   │
└─────────────────────────────────────────────┘
                        │
                        ▼
                  原始 JSON 响应

3.4 解密示例

libraryDetail 接口(点击展开) ```json { "code": 1, "msg": "请求成功", "data": { "library_id": 1997, "title": "爱上同父异母的姐姐", "film_id": "360780", "detail": [{ "video_id": 3123, "encryption_url": "(在线播放 HLS 路径)", "download_url": "(mp4 相对路径)" }] } } ```
universalAdvert 接口(点击展开) ```json { "code": 1, "msg": "请求成功", "data": { "list": [ {"ad_id": 520, "title": "one游戏.710136", "outer_type_link": "https://example.com"}, {"ad_id": 552, "title": "<第一直播>", "outer_type_link": "https://example.com"}, ... ] } } ``` </details> --- ## 四、Reqable 解密脚本 ### 4.1 纯 Python 实现(零外部依赖) 核心逻辑: **① RSA-PKCS1 解密(用 `pow` 实现模幂):** ```python def _rsa_decrypt(ct_bytes, n, d): ct = int.from_bytes(ct_bytes, 'big') pt = pow(ct, d, n) # 模幂运算 plain = pt.to_bytes(256, 'big') # PKCS#1 v1.5 去填充: 00 02 [随机] 00 [数据] assert plain[0] == 0 and plain[1] == 2 i = 2 while plain[i] != 0: i += 1 return plain[i+1:] ``` **② AES-128-CBC 解密(手写全部轮函数):** 实现了 AES 解密所需的 InvSubBytes、InvShiftRows、InvMixColumns、AddRoundKey,约 150 行。 **③ Reqable 适配:** ```python def onResponse(context, response): body_text = str(response.body) # HttpBody → str if '"encrypt_key"' not in body_text: return response # 跳过非加密响应 plain = decrypt_body(body_text) response.body = plain.decode('utf-8') response.headers['Content-Type'] = 'application/json; charset=utf-8' return response ``` ### 4.2 配置步骤 1. Reqable → **设置 → 脚本 → 添加脚本** 2. 粘贴脚本代码(需包含 RSA 私钥解析与 AES 解密) 3. 匹配规则: - 域名:目标 API 域名 - 方向:**响应** 4. 保存后重新请求,响应体会自动变为明文 JSON --- ## 五、视频下载 ### 5.1 下载直链 API 响应中 `detail[].download_url` 是**未加密的 mp4 直链**(相对路径)。 ### 5.2 获取完整 URL 进入"我的下载"页面,暂停某条目后点"恢复下载",Reqable 会抓到完整的 mp4 链接。此时可看到实际 CDN 域名,拼接方式如下: ``` CDN域名 + download_url = 完整可下载链接 ``` 域名由 App 动态下发,API 中返回的 `download_url` 拼接同一域名即可下载其他视频。 --- ## 六、API 域名 从 `libapp.so` 中提取到多组硬编码 API 域名,用于容灾切换。实际抓包中使用的域名来自同一套分发体系。 其他服务域名包括视频 CDN、对象存储(华为云 OBS)、统计分析(Matomo)和分享链接。 --- ## 七、AOT 快照中的关键函数 | 符号 | 说明 | |------|------| | `package:fruitpie/utils/encrypt_tool.dart` | API 加解密工具 | | `package:fruitpie/utils/video_decrypt.dart` | 视频解密 | | `package:fruitpie/services/net_manager.dart` | 网络请求管理器 | | `checkCDNDomain` | 动态解析 CDN 域名 | | `_baseUrl` | API 基础 URL | | `_normalizeDownloadUrl` | 下载 URL 拼接 | | `_getVideoUrl` | 视频 URL 获取 | --- ## 八、使用到的工具 | 工具 | 用途 | |------|------| | APKTool / 解压 | 解包 APK | | Reqable | HTTP 抓包 & 脚本解密 | | PowerShell / .NET | 验证 RSA/AES 解密算法 | | `libapp.so` 字符串搜索 | 提取 Dart 符号与硬编码字符串 | | Python | 纯 Python 解密实现(零依赖)|