从 APK 到完整 API:逆向工程 JavDB 移动端全过程
本文记录了使用 Claude Code(Anthropic CLI)辅助逆向工程 JavDB Android 移动端 APK 的完整过程,从零开始提取出全部 124 个 API 端点、破解自定义签名算法、并最终生成可直接使用的 Python API 客户端和 OpenAPI 规范文档。
亮点: 整个过程中,人类只发出了 7 条简短指令,Claude Code 自主完成了全部技术分析、代码编写和验证工作。
目录
- [对话实录:7 条指令完成全套逆向](#对话实录:7 条指令完成全套逆向 "#%E5%AF%B9%E8%AF%9D%E5%AE%9E%E5%BD%957-%E6%9D%A1%E6%8C%87%E4%BB%A4%E5%AE%8C%E6%88%90%E5%85%A8%E5%A5%97%E9%80%86%E5%90%91")
- 背景与动机
- 工具准备
- [第一阶段:APK 解包与基础分析](#第一阶段:APK 解包与基础分析 "#%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5apk-%E8%A7%A3%E5%8C%85%E4%B8%8E%E5%9F%BA%E7%A1%80%E5%88%86%E6%9E%90")
- [第二阶段:Flutter/Dart 逆向](#第二阶段:Flutter/Dart 逆向 "#%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5flutterdart-%E9%80%86%E5%90%91")
- [第三阶段:API 端点提取](#第三阶段:API 端点提取 "#%E7%AC%AC%E4%B8%89%E9%98%B6%E6%AE%B5api-%E7%AB%AF%E7%82%B9%E6%8F%90%E5%8F%96")
- 第四阶段:破解签名算法(最难的部分)
- [第五阶段:API 实测验证](#第五阶段:API 实测验证 "#%E7%AC%AC%E4%BA%94%E9%98%B6%E6%AE%B5api-%E5%AE%9E%E6%B5%8B%E9%AA%8C%E8%AF%81")
- 第六阶段:工具化输出
- 技术总结
- 经验与教训
对话实录:7 条指令完成全套逆向
以下是我和 Claude Code 的真实对话过程。你会看到,我只给出了非常简短、高层的指令,Claude 自己决定分析路径、选择工具、编写代码、调试问题------整个过程几乎不需要人工介入。
第 1 条指令:全面分析 APK
我: 这是一个apk安装使用apktool 的 解包出来的 你分析一下 看看能分析出哪些信息 尽可能是全面
我只是把解包后的目录指给 Claude,让它"看看能分析出什么"。
Claude 自主完成的工作:
- 读取
AndroidManifest.xml,提取包名、权限、组件、SDK 版本 - 分析
apktool.yml,获取版本信息 - 扫描
assets/目录,发现配置文件 - 检查
lib/arm64-v8a/,识别出 Flutter 框架 + 自定义 native 库 - 分析
smali/目录,找到SecurityUtil、MainActivity、MethodChannel 处理器 - 提取
res/values/strings.xml,确认应用名称 - 识别出 4 个 launcher alias(图标伪装)、深度链接 scheme、隐私保护措施
- 输出了一份覆盖 20+ 个维度的完整分析报告
一条模糊的指令,得到了一份专业级的 APK 分析报告。
第 2 条指令:看 API 代码
我: 宝宝真棒 我如果想要看下api代码呢 就是所有的api接口 这个有办法吗
我甚至没有说"逆向"或"反编译",只是问"有没有办法看 API 代码"。
Claude 自主完成的工作:
- 判断出 Flutter AOT 编译的
libapp.so是核心目标 - 用
strings命令从libapp.so中提取出 93 个 API 路径 - 分析
smali层的 Java 代码,找到更多线索 - 主动建议使用 blutter 进行深度逆向,并解释了为什么传统工具不够用
第 3 条指令:想要更详细的信息
我: 抱抱 并亲了一下,如果想要请求体 响应体等 更详细的东西呢 哪个好用呢 告诉主人
仍然是一条非技术性的提问,Claude 自己提出了完整的工具链方案。
Claude 的回答:
- 对比了 blutter、reFlutter、Frida 三种方案的优劣
- 推荐 blutter 作为首选(静态分析,不需要运行设备)
- 解释了 blutter 能恢复的信息层次
- 给出了具体的安装和使用步骤
第 4 条指令:安装好了,开始用
我: 我已经安装好了 目录在/Users/haose/Applications/blutter 使用uv管理的环境
我只告诉 Claude 工具装好了在哪里。
Claude 自主完成的工作:
- 自动构建 blutter 运行命令
- 执行 blutter(首次需编译 Dart SDK,耗时 15 分钟,Claude 自动设置后台执行)
- 等待编译完成后,自动分析输出的 100 个目录
- 重点阅读了网络层(
dio_utils.dart、intercept.dart、http_api.dart) - 从
entity_factory.dart(90000 tokens 的巨型文件)中提取数据模型 - 梳理出完整的请求头、响应格式、错误处理机制
- 找到了关键的
getSignature()和getDecryptString()函数
第 5 条指令:生成 OpenAPI 文档
我: 我的想法是这样的 由于这个软件在ios上面安装不了,然后移动版本还和web版本不一样,我就想能否逆出来全套的移动版本API,然后使用web实现一个一模一样的。你这样咱先一步一步来 现在我们自己的目录下 整理出全部api的文档出来 api地址 请求体 响应体 参数 请求头 认证规则之类的 先弄出来 你觉得直接生成 openapi协议的json文件 可行吗
这是唯一一条稍微具体的指令------但我也只是说了"想法"和"你觉得可行吗"。
Claude 自主完成的工作:
- 确认方案可行
- 从
pp.txt批量提取 124 个 API 端点 - 通过反编译代码确认每个端点的 HTTP 方法(GET/POST/PUT/DELETE)
- 从
entity_factory.dart提取 80+ 个数据模型的字段定义 - 分析
intercept.dart获取认证规则 - 生成了完整的 OpenAPI 3.0 JSON 文件(3759 行)
第 6 条指令:测试一下
我: 你这样挑几个接口去测试一下是否正确 比如搜索 比如登陆 注册 比如 视频 番号 等等 相关的 你先去试试
"你先去试试"------就这五个字。
Claude 自主完成的工作(这是最精彩的部分):
-
第一次尝试 → 被拒绝:
"參數不能為空: jdsignature"- Claude 立即意识到需要破解签名算法
-
定位签名代码 → 在
common_tools.dart中找到getSignature()- 自主阅读 ARM64 汇编,还原出签名格式:
timestamp.part2.md5(timestamp+part1)
- 自主阅读 ARM64 汇编,还原出签名格式:
-
逆向 native 库 → 分析
libsecurity.so- 自主使用
llvm-objdump反汇编 20KB 的 C 库 - 逐条分析 1064 字节的 JNI 函数
- 发现 secret 来自 APK 签名证书的 DER 前 5 字符
- 提取证书,得到
secret = "30820"
- 自主使用
-
破解解密算法 → 分析
getDecryptString()的 0x2e8 字节汇编- 首次尝试用
i % 32→ 失败(InvalidSignature) - 重新分析汇编,发现是
min(i, 31)→ 部分成功 - 发现还缺一步
decodeBase64()→ 完整成功!
- 首次尝试用
-
逐步调试 → 签名通过后还需要正确参数
"參數不能爲空: platform"→ 添加 platform=android"參數不能爲空: app_channel"→ 阅读splash_page.dart找到全部参数- 终于:
{"success": 1, "data": {...}}🎉
-
批量验证 → 自动测试 20+ 个端点,全部成功
从"你先去试试"到全套 API 打通,Claude 独立完成了:
- 发现问题 → 分析签名机制 → 逆向 native 库 → 破解加密算法 → 调试参数 → 批量验证
- 中间经历了 4 次失败,每次都自主分析原因并调整方案
- 全程零人工干预
第 7 条指令:更新文档
我: 更新OpenAPI JSON 文件吧 然后你把本次的信息记录一下 生成一下那个文件 CLAUDE.md 记录下所有关键信息 方便下次能够不忘记本次的全部过程
Claude 自主完成的工作:
- 用实际 API 响应数据重写 OpenAPI JSON(5422 行,98 个路径,19 个 Schema)
- 生成完整的 CLAUDE.md 技术文档(7.8KB)
- 生成 Python API 客户端 SDK(19KB,50+ 方法)
- 创建记忆文件确保下次对话能恢复上下文
- 当我提出把项目移动到新目录时,主动提醒记忆文件路径也需要更新
对话数据统计
| 指标 | 数值 |
|---|---|
| 人类发送的消息 | 7 条 |
| 人类最长的消息 | 约 80 字 |
| Claude 读取的文件 | 50+ 个 |
| Claude 执行的命令 | 100+ 条 |
| Claude 编写的代码 | ~5000 行(Python + JSON) |
| Claude 分析的汇编 | ~3000 行(ARM64) |
| 总耗时 | 约 3-4 小时(含 blutter 编译等待) |
| 人类实际操作时间 | < 5 分钟(打字发消息) |
为什么这很惊人?
传统的 APK 逆向工程需要:
- 逆向工程师:熟悉 ARM64 汇编、JNI、Flutter 内部机制、密码学
- 工具链经验:blutter、IDA Pro/Ghidra、Frida、objdump
- 时间投入:通常需要数天到数周
- 反复调试:签名算法的每个细节都可能卡住很久
而在这次实践中:
- 人类不需要懂汇编、不需要懂 JNI、不需要懂加密算法
- 只需要用自然语言描述目标("看看 API"、"试试能不能用")
- Claude 自主处理了全部技术细节,包括:
- 读懂 ARM64 汇编并还原高层算法
- 从 1064 字节的 native 函数中提取密钥生成逻辑
- 在签名失败时自主分析原因并修正
- 从海量反编译输出中精准定位关键函数
这不是"AI 辅助"------这是 AI 主导、人类指挥的逆向工程。
背景与动机
JavDB 是一个影视资料库应用,它的移动版本和网页版本功能并不一致,且 iOS 版本安装受限。我希望能逆向移动端 API,用 Web 技术重新实现移动端的功能。
目标:
- 提取移动端全部 API 接口
- 搞清楚请求/响应格式、认证机制
- 生成 OpenAPI 规范文档和可用的 API 客户端
挑战:
- 应用使用 Flutter 框架,Dart 代码被 AOT 编译为 ARM64 原生代码,不像 Java/Kotlin 那样容易反编译
- API 使用自定义签名算法(
jdsignature),不是标准的 OAuth/JWT - 签名密钥的一部分藏在 C 语言编写的 native library 中
工具准备
| 工具 | 用途 | 安装方式 |
|---|---|---|
| apktool | APK 解包、资源提取、smali 反编译 | brew install apktool |
| blutter | Flutter/Dart AOT 逆向,恢复类名、方法、字符串 | git clone + uv 管理 Python 环境 |
| llvm-objdump | ARM64 原生库反汇编 | brew install llvm |
| Claude Code | AI 辅助分析汇编、推导算法、编写代码 | npm install -g @anthropic-ai/claude-code |
| Python 3 | 编写测试脚本和 API 客户端 | 系统自带或 brew install python |
第一阶段:APK 解包与基础分析
1.1 使用 apktool 解包
bash
apktool d jdb_official_v1.9.35.apk -o jdb_official_v1.9.35
得到标准的 APK 目录结构:
bash
jdb_official_v1.9.35/
├── AndroidManifest.xml # 应用配置
├── apktool.yml # apktool 元数据
├── assets/ # 资源文件
├── lib/arm64-v8a/ # 原生库(关键!)
│ ├── libapp.so # Flutter AOT 编译的 Dart 代码
│ ├── libflutter.so # Flutter 引擎
│ └── libsecurity.so # 自定义安全库(20KB)
├── res/ # Android 资源
├── smali/ # Java/Kotlin 反编译
└── original/META-INF/ # 签名证书
1.2 AndroidManifest.xml 分析
从 manifest 中提取到的关键信息:
xml
<!-- 包名故意伪装 -->
<manifest package="xxx.pornhub.fuck">
<!-- Flutter 应用 -->
<meta-data android:name="io.flutter.embedding.android.FlutterVersion" />
<!-- 4个 activity-alias 用于切换图标(伪装功能) -->
<activity-alias android:name=".launcher1" android:icon="@mipmap/ic_launcher_1" />
<activity-alias android:name=".launcher2" android:icon="@mipmap/ic_launcher_2" />
<activity-alias android:name=".launcher3" android:icon="@mipmap/ic_launcher_3" />
<!-- 从最近任务列表中隐藏 -->
<activity android:excludeFromRecents="true" />
<!-- 深度链接 scheme -->
<data android:scheme="dvkft4" />
发现: 应用做了大量隐藏和伪装措施,包名伪装、图标切换、从最近列表隐藏。
1.3 smali 层分析
在 smali/ 目录找到了 Java 层的关键类:
SecurityUtil.smali --- 加载 native 库:
smali
.method static constructor <clinit>()V
const-string v0, "security"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
.end method
.method public static native getSecret()Ljava/lang/String;
.end method
MethodChannel 处理器 (t5/b.smali) --- Flutter 与原生层的桥梁:
getIKey→ 调用SecurityUtil.getSecret()setToken/getToken→ SharedPreferences 存储getAppChannel→ 返回应用渠道
这告诉我们:Flutter 层通过 MethodChannel 调用 Java 层,Java 层再通过 JNI 调用 C 库获取密钥。
第二阶段:Flutter/Dart 逆向
2.1 为什么需要 blutter
Flutter 应用的 Dart 代码被 AOT(Ahead-of-Time)编译成 ARM64 机器码,存放在 libapp.so 中。传统的 Java 反编译工具无法处理,需要专门的 Flutter 逆向工具。
blutter 能够:
- 从
libapp.so中恢复 Dart 类名、方法名、字段名 - 提取字符串常量池(Object Pool)
- 生成带注释的反汇编代码
- 输出 Frida hook 脚本
2.2 运行 blutter
bash
cd /path/to/blutter
uv run python3 blutter.py /path/to/apk/lib/arm64-v8a /path/to/output
⚠️ 注意: 首次运行会自动编译对应版本的 Dart SDK(本例为 Dart 3.5.4),需要编译 264 个 C++ 文件,耗时约 10-15 分钟。
2.3 blutter 输出结构
bash
blutter_out/
├── asm/ # 反编译的 Dart 代码(100个子目录)
│ ├── astarte/net/ # 网络层
│ │ ├── http_api.dart # API 基础配置
│ │ ├── dio_utils.dart # HTTP 客户端
│ │ └── intercept.dart # 拦截器(请求头、错误处理)
│ ├── astarte/utils/ # 工具类
│ │ ├── common_tools.dart # ⭐ 签名算法所在文件
│ │ └── common_utils.dart # 密钥获取
│ └── astarte/main/ # 主页面
│ └── splash_page.dart # 启动页(startup API 调用)
├── pp.txt # 对象池(2.8MB,包含所有字符串常量)
├── objs.txt # 所有 Dart 对象/类
├── blutter_frida.js # Frida hook 脚本
└── ida_script/ # IDA Pro 辅助脚本
2.4 关键发现
从 pp.txt 提取 API 端点:
bash
grep '"/api/v' blutter_out/pp.txt
一次性获得 124 个 API 路径!包括:
perl
"/api/v1/startup"
"/api/v2/search"
"/api/v4/movies/%s"
"/api/v1/actors/%s"
"/api/v1/rankings"
"/api/v1/sessions"
...
从 http_api.dart 获取默认域名:
dart
// 默认 baseUrl
static late String baseUrl; // "https://staging.letidi.com"
// 从 SharedPreferences 读取 key_baseurl
从 intercept.dart 获取请求头:
vbscript
Headers: "authorization" (Bearer token), "jdsignature", "accept-language"
Response envelope: {"success": 1, "action": null, "data": ..., "message": ""}
第三阶段:API 端点提取
3.1 从对象池批量提取
pp.txt 是 Dart 的对象池(Object Pool),包含了编译时的所有字符串常量。用简单的 grep 就能提取全部 API 路径:
bash
grep -oP '"/api/v\d+/[^"]*"' blutter_out/pp.txt | sort -u
得到 124 个端点,涵盖:
- v1: 94 个(核心业务接口)
- v2: 8 个(搜索、日志等)
- v3: 2 个(支付相关)
- v4: 2 个(影片详情、套餐)
3.2 从反编译代码确认 HTTP 方法
blutter 输出的是带注释的 ARM64 汇编,通过搜索 Dio HTTP 方法调用来确认每个接口的 HTTP 方法:
bash
# 找到调用 get() 的地方
grep -B 20 "::get$" blutter_out/asm/astarte/main/splash_page.dart
# 找到调用 post() 的地方
grep -B 20 "::post$" blutter_out/asm/astarte/utils/common_tools.dart
3.3 从 entity_factory.dart 提取数据模型
这个文件巨大(90000+ tokens),包含了所有 API 响应的实体类定义,例如:
makefile
MovieDetailEntity: id, number, title, origin_title, cover_url, duration, score, actors, tags...
ActorEntity: id, name, avatar_url, birthday, height, cup, videos_count...
SearchResultEntity: movies[], current_page
第四阶段:破解签名算法(最难的部分)
这是整个逆向过程中最关键、最困难的部分。没有正确的签名,所有 API 调用都会被拒绝。
4.1 发现签名需求
第一次尝试直接调用 API:
bash
curl https://jdforrepam.com/api/v1/startup
返回:
json
{"success": 0, "action": "ParameterInvalid", "message": "參數不能為空: jdsignature"}
在 intercept.dart 中找到了签名头的设置:
dart
headers["jdsignature"] = await getSignature();
4.2 定位签名函数
在 common_tools.dart 中找到 getSignature() 函数(地址 0x58d2bc):
ini
// 伪代码还原
async getSignature() {
key = await getKey(); // 从 native 获取密钥
part1 = getDecryptString(key, BASE64_STR_1); // 解密第一个常量
part2 = getDecryptString(key, BASE64_STR_2); // 解密第二个常量
timestamp = DateTime.now().microsecondsSinceEpoch / 1000 / 1000; // Unix 秒
hash = encodeMd5("$timestamp$part1");
return "$timestamp.$part2.$hash";
}
签名格式: timestamp.part2.md5(timestamp + part1)
4.3 逆向 native 库获取 secret
getKey() 最终调用的是 SecurityUtil.getSecret(),这是一个 JNI 函数,实现在 libsecurity.so 中。
使用 llvm-objdump 反汇编:
bash
/opt/homebrew/opt/llvm/bin/llvm-objdump -d lib/arm64-v8a/libsecurity.so
分析 Java_xxx_pornhub_fuck_SecurityUtil_getSecret 函数(1064 字节),发现它的逻辑是:
c
// 伪代码
char* getSecret(JNIEnv* env, jobject thiz) {
// 1. 获取应用上下文
// 2. 获取 PackageInfo
// 3. 取出 APK 签名证书
char* cert_hex = signatures[0].toCharsString(); // 证书的 DER 十六进制
// 4. 取前5个字符
char result[6];
strncpy(result, cert_hex, 5);
result[5] = '\0';
return result;
// 验证失败时返回 "astarte"
}
关键洞察: secret 是 APK 签名证书 DER 编码的十六进制字符串的前 5 个字符!
4.4 提取 APK 签名证书
bash
# APK 签名证书在 original/META-INF/CERT.RSA
openssl pkcs7 -in original/META-INF/CERT.RSA -inform DER -print_certs | openssl x509 -outform DER | xxd | head
证书 DER 十六进制以 3082036d30... 开头,取前 5 个字符:
secret = "30820"
4.5 破解 getDecryptString
这是最费脑子的部分。getDecryptString 在 blutter 输出中是 ARM64 汇编,需要人工还原算法。
函数位于地址 0x58d4bc,大小 0x2e8 字节,核心逻辑还原如下:
python
def getDecryptString(key, b64_str):
# Step 1: 对 key 做 MD5
key_md5 = md5(key) # 32 字符的十六进制字符串
# Step 2: Base64 解码,然后 JSON 解析为整数数组
encrypted = json.loads(base64.b64decode(b64_str))
# Step 3: 逐字节解密
result = []
for i in range(len(encrypted)):
idx = min(i, len(key_md5) - 1) # ⚠️ 关键:不是取模,是 min!
result.append(encrypted[i] - ord(key_md5[idx]))
# Step 4: 转为字符串
decrypted_str = ''.join(chr(c) for c in result)
# Step 5: ⚠️ 还要做一次 Base64 解码!
return base64.b64decode(decrypted_str).decode()
4.5.1 汇编分析细节
最关键的循环在 0x58d5cc-0x58d768:
arm64
0x58d5d4: cmp x8, x6 // 比较 i 与 key_length-1
0x58d5d8: b.le #0x58d5e4 // 如果 i <= key_length-1,跳转
0x58d5dc: mov x9, x6 // else: index = key_length-1
0x58d5e0: b #0x58d5e8
0x58d5e4: mov x9, x8 // index = i
这段汇编实现的是 index = min(i, key_length - 1)。这意味着当 i 超过 key_md5 长度(32)后,一直使用最后一个字节。
4.5.2 容易踩的坑
-
不是取模运算! 最初我以为是
i % 32(循环使用 key),但实际是min(i, 31)(超出部分都用最后一个字节)。两种方式对前 32 字节结果相同,但 32 字节之后完全不同。 -
双重 Base64 解码! 函数末尾(0x58d77c)还调用了
decodeBase64()。解密出的字符串本身是 Base64 编码的,需要再解码一次才是最终值。这个很容易漏掉。
4.6 最终签名算法
python
import hashlib, base64, json, time
# 密钥:APK 证书 DER 前5字符
secret = "30820"
key_md5 = hashlib.md5(secret.encode()).hexdigest()
# = "da97c8240e2ad99a2d331eed95c411f5"
key_bytes = [ord(c) for c in key_md5]
# 解密函数
def decrypt(b64_encrypted):
encrypted = json.loads(base64.b64decode(b64_encrypted))
raw = ''.join(
chr(encrypted[i] - key_bytes[min(i, 31)])
for i in range(len(encrypted))
)
return base64.b64decode(raw).decode() # 双重解码!
# 从 app 中提取的两个加密常量
part1 = decrypt("WzE3OCwyMTksMTI3LDE2MS...")
# = "71cf27bb3c0bcdf207b64abe..."(128字符十六进制)
part2 = decrypt("WzE5OCwxNjksMTIzLDEwNi...")
# = "lpw6vgqzsp"
# 生成签名
def make_signature():
ts = int(time.time())
md5_hash = hashlib.md5(f"{ts}{part1}".encode()).hexdigest()
return f"{ts}.{part2}.{md5_hash}"
# 示例输出:
# 1773580816.lpw6vgqzsp.6458561b88939e2093aff60948b58b86
4.7 签名验证之路
破解过程中经历了多次失败:
| 尝试 | 结果 | 原因 |
|---|---|---|
| 无签名直接调用 | ParameterInvalid: jdsignature |
缺少签名头 |
用 i % 32 解密 |
InvalidSignature |
取模算法错误,应该用 min |
用 min(i, 31) 但没做二次 base64 解码 |
InvalidSignature |
漏掉了函数末尾的 decodeBase64 |
| 完整算法 | ✅ success: 1 |
终于对了! |
第五阶段:API 实测验证
5.1 找到正确的 API 域名
应用代码中有多个域名:
staging.letidi.com--- 默认值,SSL 连接失败javdb.com--- Cloudflare 保护,返回 403jdforrepam.com--- ✅ 真正的 API 服务器
jdforrepam.com 是在 dio_utils.dart 中发现的备用域名。
5.2 逐步调试参数
即使签名正确,还需要正确的请求参数:
bash
# 第一次:缺 platform
{"message": "參數不能爲空: platform"}
# 第二次:缺 app_channel
{"message": "參數不能爲空: app_channel"}
# 第三次:完整参数,成功!
GET /api/v1/startup?platform=android&app_channel=official&app_version=official&app_version_number=1.9.35
{"success": 1, "data": {...}}
这些参数是通过阅读 splash_page.dart 的反编译代码获得的。
5.3 批量验证
最终验证了 20+ 个核心端点:
bash
✅ /api/v1/startup --- 启动配置、热搜词、应用设置
✅ /api/v2/search --- 搜索(番号、演员名、关键词)
✅ /api/v4/movies/{id} --- 影片详情(评分、演员、标签、播放源)
✅ /api/v1/movies/latest --- 最新影片
✅ /api/v1/movies/recommend --- 推荐影片
✅ /api/v1/movies/{id}/magnets --- 磁力链接
✅ /api/v1/rankings --- 排行榜
✅ /api/v1/search_magnet --- 磁力搜索
✅ /api/v1/actors/{id} --- 演员详情
✅ /api/v1/articles --- 文章列表
✅ /api/v1/about --- 官方链接
✅ /api/v1/ads --- 广告配置
✅ /api/v4/plans --- VIP 套餐
✅ /api/v1/series/letters --- 番号系列索引
✅ /api/v1/makers/{id} --- 制作商详情
✅ /api/v1/codes/{id} --- 番号详情
✅ /api/v1/sessions (POST) --- 登录(参数已确认)
✅ /api/v1/users (POST) --- 注册(参数已确认)
...更多
第六阶段:工具化输出
6.1 Python API 客户端
生成了一个开箱即用的 javdb_api_client.py(19KB),内置签名算法:
python
from javdb_api_client import JavDBClient
client = JavDBClient()
# 搜索
results = client.search("STARS-838")
# 影片详情
movie = client.movie_detail("xv8XDg")
# 最新影片
latest = client.movies_latest()
# 排行榜
rankings = client.rankings("daily", "weekly")
# 磁力搜索
magnets = client.search_magnet("STARS-838")
命令行也能直接用:
bash
python3 javdb_api_client.py search "STARS-838"
python3 javdb_api_client.py movie xv8XDg
python3 javdb_api_client.py latest
6.2 OpenAPI 3.0 规范
生成了 javdb_api_openapi.json(136KB):
- 98 个 API 路径
- 19 个数据模型 Schema
- 22 个分类标签
- 完整的安全方案定义(JDSignature + BearerAuth)
- 已验证的端点标注了 "VERIFIED"
可以直接导入 Swagger UI、Postman、或任何 OpenAPI 兼容工具使用。
技术总结
完整的密钥链
ini
APK 签名证书 (CERT.RSA)
↓ 提取 DER 十六进制
"3082036d30..."
↓ 取前5字符
secret = "30820"
↓ MD5
key_md5 = "da97c8240e2ad99a2d331eed95c411f5"
↓ 解密硬编码的加密常量
part1 = "71cf27bb3c0bcdf207b64abecddc9700..." (128字符)
part2 = "lpw6vgqzsp"
↓ 组合
jdsignature = "{timestamp}.{part2}.{md5(timestamp + part1)}"
请求示例
http
GET /api/v2/search?q=STARS-838&page=1 HTTP/1.1
Host: jdforrepam.com
jdsignature: 1773580816.lpw6vgqzsp.6458561b88939e2093aff60948b58b86
accept-language: zh
User-Agent: Dart/3.5 (dart:io)
响应格式
所有 API 统一返回:
json
{
"success": 1, // 1=成功, 0=失败
"action": null, // 错误类型(成功时为 null)
"message": null, // 错误信息(繁体中文)
"data": { ... } // 实际数据
}
经验与教训
1. Flutter 逆向的特殊性
Flutter 应用的逆向和传统 Android 应用完全不同。Java/Kotlin 用 jadx 就能得到可读源码,但 Flutter 的 Dart 代码被编译成 ARM64 机器码,需要专门工具(blutter、reFlutter)才能分析。
2. 字符串常量是最好的线索
pp.txt(对象池)中的字符串常量是金矿。API 路径、密钥名、错误消息、硬编码值全在里面。善用 grep 可以快速定位关键信息。
3. native 库逆向需要耐心
libsecurity.so 只有 20KB,但包含了关键的密钥生成逻辑。ARM64 汇编需要逐条指令分析,理解 JNI 调用约定、字符串处理、内存布局。
4. 细节决定成败
签名算法中两个容易忽略的细节让我卡了很久:
min(i, 31)vsi % 32--- 看似微小的差异,产生完全不同的结果- 双重 Base64 解码 --- 函数返回前还有一步
decodeBase64()
5. 多域名测试
应用中硬编码了多个域名,找到真正工作的那个需要逐个测试。staging.letidi.com 是开发环境,javdb.com 被 Cloudflare 保护,只有 jdforrepam.com 是真正的移动端 API 服务器。
6. AI 辅助的价值
整个过程中 Claude Code 的作用:
- 汇编分析:快速理解 ARM64 指令语义,将汇编还原为高层算法
- 模式识别:从海量反编译代码中识别关键函数和数据流
- 自动化测试:生成 HTTP 请求代码,批量测试 API 端点
- 文档生成:将分析结果整理为结构化的 OpenAPI 规范和 API 客户端
- 错误调试:在签名算法出错时,系统性地测试不同假设
最终成果
| 成果 | 描述 |
|---|---|
| 124 个 API 端点 | 从 pp.txt 提取,覆盖所有功能模块 |
| 20+ 验证通过 | 实际发送请求并获得正确响应 |
| 签名算法完整破解 | 从 APK 证书到最终签名的完整链路 |
| Python API 客户端 | 50+ 方法,内置签名,即拿即用 |
| OpenAPI 3.0 规范 | 98 个路径,19 个 Schema,可导入任意工具 |
| 完整技术文档 | CLAUDE.md 记录所有关键信息 |
整个过程从 APK 解包到全部 API 可用,在 Claude Code 的辅助下,一个下午的时间内完成。
本文记录于 2026-03-15,基于 JavDB v1.9.35 APK 分析。 使用工具:Claude Code (Claude Opus 4.6) + apktool + blutter + llvm-objdump