目 录CONTENT

文章目录

繁花剧场App API加密分析笔记

~梓
2026-03-28 / 0 评论 / 0 点赞 / 0 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

繁花剧场App API加密分析报告

一、项目背景

1.1 目标信息

项目 内容
目标App 繁花剧场
包名 com.dzhong.fhjc
开发商 北京点众快看科技有限公司
API域名 flowerapi.kydca.cn
接口路径 /app-video-portal/portal/client/1001
请求方式 POST
协议 HTTP/2.0 over HTTPS

1.2 问题描述

通过Charles抓包获取到的API响应内容为全量加密数据,响应体是一个极长的十六进制字符串,无法直接读取业务数据。需要分析其加密机制并找到解密方法。

1.3 抓包数据样例

{
  "code": 0,
  "userId": 2787935422,
  "timestamp": 1774666357752,
  "data": {
    "aab421b86a24fff2d4e2507828f3c909...": "b0a75f6b848f63bd74b5a493d5891ad4..."
  }
}
  • data 字段的key和value都是长十六进制字符串
  • 长度约3200字节(hex解码后)
  • 响应大小约111KB,压缩率35.8%

二、Java层代码分析

2.1 反编译工具

使用 jadx-gui-1.5.5-with-jre-win 打开APK进行反编译。

2.2 包结构

com.dzhong.fhjc.apk
├── d.z.s/                    # 混淆后的主代码包
│   ├── N.java                # Native调用入口
│   ├── h.java                # 密钥管理类
│   ├── T.java                # 空类
│   └── dzfanhua.java         # 合成类
├── okhttp3/                  # 网络请求库
├── retrofit2/                # API接口库
└── 其他第三方SDK(广告、统计等)

2.3 核心解密类:N.java

package d.z.s;

import android.content.Context;
import kotlin.jvm.internal.so;

public final class N {
    public static final N INSTANCE = new N();

    static {
        try {
            System.loadLibrary("dzst");        // 加载Native库
        } catch (UnsatisfiedLinkError e) {
            e.printStackTrace();
        }
    }

    private N() { }

    // Native方法,实现在 libdzst.so 中
    public final native String ns(Context context, String str);

    // Java层调用入口
    public final String sr(Context context, String payload) {
        so.hr(context, "context");
        so.hr(payload, "payload");
        try {
            return ns(context, payload);       // 调用Native方法
        } catch (Throwable unused) {
            return null;
        }
    }
}

关键发现:

  • 加载了名为 dzst 的Native库(对应文件 libdzst.so
  • ns() 是native方法,真正的解密逻辑在C/C++代码中
  • sr() 是Java层唯一入口,接收加密字符串,返回解密结果

2.4 密钥管理类:h.java

package d.z.s;

import android.security.keystore.KeyGenParameterSpec;
import android.util.Base64;
import java.security.KeyStore;
import java.security.Signature;
// ... 其他import

public final class h {
    public static final h dzfanhua = new h();

    // 获取证书链
    public final String T() { ... }

    // RSA签名
    public final byte[] a(byte[] bArr) { 
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        Key key = keyStore.getKey("request_sign_key", null);
        // ... 使用SHA256withRSA签名
    }

    // 创建TEE密钥对
    public final void dzfanhua() {
        // 创建别名 "request_sign_key" 的RSA密钥对(2048位)
        // 存储在Android KeyStore(TEE安全区域)
    }

    // 获取公钥
    public final String h() { ... }

    // 对payload进行签名并Base64编码
    public final String j(String payload) { 
        return Base64.encodeToString(a(payload.getBytes()), 2);
    }
}

关键发现:

  • 密钥别名:request_sign_key
  • 签名算法:SHA256withRSA
  • 密钥存储在 Android KeyStore(硬件级TEE安全区域)
  • 密钥对在App首次运行时创建,不可导出

三、Native库分析

3.1 提取SO文件

# 解压APK
unzip com.dzhong.fhjc.apk -d apk_extract

# 定位SO文件(arm64-v8a架构)
ls apk_extract/lib/arm64-v8a/libdzst.so
  • 文件大小:9,664 字节(约9.5KB)
  • 架构:ARM 64位
  • 编译信息:Clang 18.0.1,Android NDK r522817

3.2 字符串提取

使用 strings 命令提取可读字符串:

strings libdzst.so

关键字符串列表:

字符串 用途推测
Java_d_z_s_N_ns JNI函数名,Java层native方法对应
MySuperSecretSalt_DoNotLeak 盐值,用于密钥派生
native_sign_ 签名验证相关
SHA-256 哈希算法
getPackageInfo 获取包信息,用于签名验证
signatures 获取App签名
unknown_sig 签名验证失败提示
%02x 十六进制格式化
自定义字符表 1cD2eF-GHIJ_KMOQSUWYbdfhjlnprtvxzACEgikL3mN4oP5qR6sT7uV8wX9yZ0aB;:^]\[@?>=<

3.3 JNI函数验证

SO文件中包含JNI导出函数:

Java_d_z_s_N_ns

对应Java层的:

public final native String ns(Context context, String str);

参数和返回值类型通过JNI签名 (Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String; 确认。

3.4 自定义字符表分析

发现一个非标准的Base64字符表:

1cD2eF-GHIJ_KMOQSUWYbdfhjlnprtvxzACEgikL3mN4oP5qR6sT7uV8wX9yZ0aB;:^]\[@?>=<

特征:

  • 长度:约80字符
  • 包含字母、数字、特殊符号
  • 可能用于自定义编码,替代标准Base64

四、加密流程推测

基于以上分析,推断完整流程如下:

┌─────────────────────────────────────────────────────────────────┐
│                        服务端                                    │
│  业务数据 → JSON → AES加密 → Hex编码 → 返回给客户端              │
└─────────────────────────────────────────────────────────────────┘
                                    ↓ HTTPS
┌─────────────────────────────────────────────────────────────────┐
│                     客户端(繁花剧场)                            │
│                                                                  │
│  1. 收到加密响应(Hex字符串)                                     │
│                    ↓                                             │
│  2. Java层调用 N.sr(context, encrypted)                         │
│                    ↓                                             │
│  3. JNI调用 → libdzst.so 中的 Java_d_z_s_N_ns                   │
│                    ↓                                             │
│  4. Native层获取设备签名                                          │
│     - 从 Android KeyStore 读取 "request_sign_key" 证书          │
│     - 提取App签名信息                                             │
│                    ↓                                             │
│  5. 密钥派生                                                     │
│     key = SHA-256(盐值 + 签名数据)                               │
│     盐值 = "MySuperSecretSalt_DoNotLeak"                        │
│                    ↓                                             │
│  6. AES解密                                                      │
│     - Hex解码 → 字节数组                                          │
│     - 使用派生出的key进行AES解密                                  │
│     - 去除PKCS7填充                                               │
│                    ↓                                             │
│  7. 返回明文JSON                                                  │
│                    ↓                                             │
│  8. App解析JSON,渲染UI                                          │
└─────────────────────────────────────────────────────────────────┘

未确定参数:

  • AES模式(CBC / ECB / GCM / CTR)
  • IV生成方式(固定/随机/从密钥派生)
  • 签名数据的完整组成(包名 + 证书 + 设备ID?)

五、反调试机制分析

5.1 现象

  • Frida附加时App立即崩溃退出
  • objection同样被检测
  • Charles抓包不受影响(因为是系统代理层)

5.2 推测检测点

检测方式 说明
/proc/self/maps 扫描 检测内存映射中是否包含 fridagum-jslinjector
端口扫描 检查 127.0.0.1:27042 是否可连接
ptrace 自附加 检测是否被调试器附加
isDebuggerConnected() Android API检测调试器
签名校验 验证自身签名是否被篡改(防止重打包)

5.3 检测代码位置

反调试逻辑在 Native层(libdzst.so),不在Java层。这是为什么jadx中搜不到明显反调试代码的原因。


六、工具测试结果

6.1 成功工具

工具 用途 结果
Charles 抓包 ✅ 成功抓取加密响应
jadx 反编译Java ✅ 成功定位关键类
apktool 解包APK ✅ 可解包和重打包
strings 提取SO字符串 ✅ 提取到盐值和函数名

6.2 失败工具及原因

工具 失败原因
Frida Native层检测到Frida后自杀
objection 基于Frida,同样被检测
r0capture 依赖Frida,且App可能不走系统代理
Python静态分析 SO文件被混淆,需完整逆向

七、可行解决方案对比

方案 难度 成功率 详细说明
修改APK移除反调试 ⭐⭐⭐ 90% 解包→删除反调试smali代码→重打包签名→Frida注入
逆向SO库 ⭐⭐⭐⭐⭐ 70% IDA Pro分析→定位AES函数→提取密钥→Python实现
Hook Native函数 ⭐⭐⭐⭐ 60% 过反调试后Hook Java_d_z_s_N_ns
Frida Gadget注入 ⭐⭐⭐ 85% 将frida-gadget.so打包进APK,绕过端口检测

八、具体实施步骤(修改APK方案)

8.1 准备工具

# 下载apktool
wget https://github.com/iBotPeaches/Apktool/releases/download/v2.9.3/apktool_2.9.3.jar

# 下载uber-apk-signer
wget https://github.com/patrickfav/uber-apk-signer/releases/download/v1.3.0/uber-apk-signer-1.3.0.jar

8.2 解包

java -jar apktool_2.9.3.jar d com.dzhong.fhjc.apk -o fanhua_unpacked

8.3 搜索反调试代码

# 搜索isDebuggerConnected
grep -r "isDebuggerConnected" fanhua_unpacked/smali/

# 搜索Debug检测
grep -r "Debug" fanhua_unpacked/smali/ | grep -i "isDebugger"

# 搜索ptrace
grep -r "ptrace" fanhua_unpacked/smali/

8.4 删除反调试代码

找到相关smali文件后,删除或注释以下类型的代码:

# 典型反调试代码
invoke-static {}, Landroid/os/Debug;->isDebuggerConnected()Z
move-result v0
if-eqz v0, :cond_xxx

8.5 重打包

java -jar apktool_2.9.3.jar b fanhua_unpacked -o fanhua_mod.apk

8.6 签名

java -jar uber-apk-signer-1.3.0.jar -a fanhua_mod.apk

8.7 安装测试

adb install fanhua_mod-aligned-debugSigned.apk

8.8 Frida注入

frida -U -f com.dzhong.fhjc -l hook_decrypt.js

九、静态分析SO库方案(备选)

如需深入逆向 libdzst.so,可使用以下工具:

工具 用途
IDA Pro 8.x 反汇编、静态分析
Ghidra 免费替代方案
Unicorn 模拟执行
Frida 动态Hook(需过反调试)

9.1 IDA Pro分析步骤

  1. 打开 libdzst.so,选择ARM64架构
  2. 找到导出函数 Java_d_z_s_N_ns
  3. 分析函数流程,定位AES解密调用
  4. 提取密钥派生逻辑
  5. 识别加密模式(通过查找 AES_*_cbcAES_*_ecb 等函数)

十、已提取的关键数据

10.1 文件清单

D:\short_videos\
├── com.dzhong.fhjc.apk                 # 原始APK
├── apk_extract\lib\arm64-v8a\libdzst.so  # Native库
├── fanhua_unpacked\                    # apktool解包目录
├── strings.txt                         # SO提取的字符串
└── hook_decrypt.js                     # Frida脚本(未成功)

10.2 关键字符串

盐值: MySuperSecretSalt_DoNotLeak
JNI函数: Java_d_z_s_N_ns
哈希算法: SHA-256
签名相关: native_sign_, signatures, getPackageInfo

10.3 加密数据样例(hex,前128字节)

aab421b86a24fff2d4e2507828f3c90982bfb58502177abc699f3b4c2f3af02d
a7c38c44e1491b1cb812e89ec4e2b4de248f27580ea6dfb79d2eddb750ba005e
1c6ceee52f6337dc1d35c52d37d9807366e05cdea5af591e38edbc88b0a75f6b
848f63bd74b5a493d5891ad45062015fbc0257ef5469df79791dc1a228421064

十一、未解决问题

  1. AES加密模式:CBC、ECB、GCM还是CTR?
  2. IV生成方式:固定IV?还是从密钥派生?
  3. 签名数据组成:具体用了哪些数据作为签名输入?
  4. 自定义编码表:如何映射回标准Base64?
  5. 反调试精确位置:具体在SO的哪个函数中检测Frida?

十二、后续工作建议

如需继续推进,优先级如下:

  1. 修改APK移除反调试(最快验证方案)
  2. 用修改后的APK + Frida获取明文
  3. 对比多组加密数据和明文,反推算法
  4. 用Python实现独立解密脚本

十三、环境信息

项目 配置
操作系统 Windows 11
模拟器 雷电模拟器 9.0 (Android 9)
分析工具 jadx-gui 1.5.5, Frida 17.9.1, apktool 2.9.3
Python版本 3.10+
0

评论区