从 APK 到完整 API:逆向工程 JavDB 移动端全过程

从 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/ 目录,找到 SecurityUtilMainActivity、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.dartintercept.darthttp_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 自主完成的工作(这是最精彩的部分):

  1. 第一次尝试 → 被拒绝:"參數不能為空: jdsignature"

    • Claude 立即意识到需要破解签名算法
  2. 定位签名代码 → 在 common_tools.dart 中找到 getSignature()

    • 自主阅读 ARM64 汇编,还原出签名格式:timestamp.part2.md5(timestamp+part1)
  3. 逆向 native 库 → 分析 libsecurity.so

    • 自主使用 llvm-objdump 反汇编 20KB 的 C 库
    • 逐条分析 1064 字节的 JNI 函数
    • 发现 secret 来自 APK 签名证书的 DER 前 5 字符
    • 提取证书,得到 secret = "30820"
  4. 破解解密算法 → 分析 getDecryptString() 的 0x2e8 字节汇编

    • 首次尝试用 i % 32 → 失败(InvalidSignature
    • 重新分析汇编,发现是 min(i, 31) → 部分成功
    • 发现还缺一步 decodeBase64() → 完整成功!
  5. 逐步调试 → 签名通过后还需要正确参数

    • "參數不能爲空: platform" → 添加 platform=android
    • "參數不能爲空: app_channel" → 阅读 splash_page.dart 找到全部参数
    • 终于:{"success": 1, "data": {...}} 🎉
  6. 批量验证 → 自动测试 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 技术重新实现移动端的功能。

目标:

  1. 提取移动端全部 API 接口
  2. 搞清楚请求/响应格式、认证机制
  3. 生成 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 容易踩的坑
  1. 不是取模运算! 最初我以为是 i % 32(循环使用 key),但实际是 min(i, 31)(超出部分都用最后一个字节)。两种方式对前 32 字节结果相同,但 32 字节之后完全不同。

  2. 双重 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 保护,返回 403
  • jdforrepam.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) vs i % 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

相关推荐
Alsian2 小时前
Day43 随机张量与广播机制
人工智能·深度学习·神经网络·机器学习
hixiong1232 小时前
C# OpenvinoSharp使用RAD进行缺陷检测
开发语言·人工智能·c#·openvino
小和尚同志2 小时前
还有人在问 Skills 是啥?感觉和 prompt 一样
人工智能·aigc
星和月2 小时前
人工智能与神经网络
人工智能
田里的水稻2 小时前
ubuntu22.04_构建openclaw开发框架
运维·人工智能·python
Trisyp2 小时前
Word2vec核心模型精讲:CBOW与Skip-gram
人工智能·自然语言处理·word2vec
liuccn2 小时前
技能管理工具npx skills 跟openskills的关系以及区别
人工智能
新缸中之脑2 小时前
AI Harness 工程的崛起
人工智能
大写-凌祁3 小时前
[2026年03月15日] AI 深度早报
人工智能·深度学习·机器学习·计算机视觉·agi