记录一次 Android 服务端的证书校验的详细过程
字数 1591 2025-08-22 12:23:18

Android 服务端证书校验分析与绕过实战指南

前言

本教程详细记录了对一个使用 org.conscrypt 库进行服务端证书校验的 Android 应用的逆向分析过程。通过该案例,您将学习到如何识别、分析和绕过复杂的证书校验机制,特别是针对使用 OpenSSL 封装库的应用。

准备工作

所需工具

  1. 逆向工具:Jadx-gui (用于反编译 APK)
  2. 抓包工具:Burp Suite (用于拦截和分析网络请求)
  3. Hook 工具:Frida (用于动态分析应用行为)
  4. 证书工具:XCA (用于证书管理和分析)
  5. 脚本工具:r0capture (用于自动化抓包)
  6. 代理工具:Proxifier (用于强制应用流量通过代理)

环境配置

  1. 已 root 的 Android 设备
  2. Burp 证书已安装至系统信任区
  3. Frida-server 运行在设备上

初步分析

1. 设置代理

  • 使用 Proxifier 开启 VPN 让目标应用流量走 Burp 代理
  • 配置 Burp 代理监听端口

2. 识别证书校验

  • 应用请求返回 400 No required SSL certificate was sent 错误
  • 表明服务端要求客户端提供有效的证书

静态分析

1. 解包 APK

使用 Jadx-gui 反编译 APK,重点关注以下文件:

  • grp_sp.bks
  • hmsincas.bks
  • hmsrootcas.bks
  • trust.crt

2. 分析证书文件

  • 使用 XCA 查看 trust.crt 内容
  • 发现包含多个公钥证书和 CA 证书
  • 确认这些是证书信任链,非客户端证书

动态分析

1. 尝试 Frida 自吐脚本

使用 android-keystore-audit 中的 tracer-keystore.js 脚本:

adb forward tcp:28042 tcp:28042
frida -H 127.0.0.1:28042 -f <包名> -l hook.js

2. 手动抛出异常

通过打印堆栈发现关键类:

ak.im.module.AkeyChatX509PrivateCA.clientBootstrapCertInfo

3. 尝试 r0capture

python r0capture.py -H 127.0.0.1:28042 -f <包名> -v

发现数据仍加密,证书未导出

深入分析

1. Hook 证书相关方法

针对 AkeyChatX509PrivateCA.clientBootstrapCertInfo 方法编写 Hook 脚本:

function hook() {
    Java.perform(function() {
        let AkeyChatX509PrivateCA = Java.use("ak.im.module.AkeyChatX509PrivateCA");
        AkeyChatX509PrivateCA["clientBootstrapCertInfo"].implementation = function() {
            console.log(`AkeyChatX509PrivateCA.clientBootstrapCertInfo is called`);
            let result = this["clientBootstrapCertInfo"]();
            console.log(`AkeyChatX509PrivateCA.clientBootstrapCertInfo result=${result}`);
            return result;
        };
    });
}

2. 导出证书

成功获取 X509 证书,包含 "Web Client Authentication" 用途标识:

let cert = result.getEncoded();
let bytes = Memory.readByteArray(cert, cert.length);
const file = new File("/sdcard/Download/private.pem", "wb");
file.write(bytes);

获取私钥

1. 分析 org.conscrypt 库

  • 下载源码:https://github.com/google/conscrypt
  • 搜索关键词发现关键函数:EVP_parse_private_key

2. Hook So 层函数

针对 libconscrypt_jni.so 编写 Hook 脚本:

function hookFunc(funcAddr, name) {
    Interceptor.attach(funcAddr, {
        onEnter: function(args) {
            console.log(name + " enter");
            const bytes = Memory.readByteArray(args[1], 0x1000);
            const file = new File("/sdcard/Download/private.pem", "wb");
            file.write(bytes);
        }
    });
}

function hook() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                if (path.indexOf("libconscrypt_jni.so") !== -1) {
                    console.log("dlopen: " + path);
                    this.path = path;
                }
            }
        },
        onLeave: function(retval) {
            if (this.path !== undefined) {
                var baseAddress = Module.findBaseAddress(this.path);
                if (baseAddress !== null) {
                    Module.enumerateExports(this.path, {
                        onMatch: function(symbol) {
                            if (symbol.name.indexOf("EVP_parse_private_key") !== -1) {
                                console.log(symbol.name + "---" + symbol.address.toString());
                                hookFunc(symbol.address, symbol.name)
                            }
                        }
                    });
                }
            }
        }
    });
}
hook();

证书与私钥配对

  1. 使用 XCA 导入导出的私钥
  2. 验证私钥与之前获取的证书是否匹配
  3. 在 XCA 中将证书和私钥导出为 PKCS#12 (.p12) 格式

配置 Burp Suite

  1. 将导出的 PKCS#12 证书导入 Burp
  2. 配置 Burp 使用该客户端证书
  3. 重新发送请求,成功绕过证书校验

总结

关键点

  1. 证书定位:通过堆栈分析找到关键证书类
  2. Hook 技巧:Java 层和 Native 层结合 Hook
  3. 私钥提取:针对 org.conscrypt 库的特定函数进行 Hook
  4. 证书配对:确保证书和私钥匹配

适用性

该方法可能适用于其他使用 org.conscrypt 库的应用,但需要根据具体实现调整 Hook 点。

后续研究方向

  1. 自动化识别 org.conscrypt 的关键函数
  2. 研究其他 OpenSSL 封装库的类似方法
  3. 探索不依赖 root 的证书提取方法

通过本教程,您应该掌握了分析复杂证书校验机制的基本方法,特别是针对使用 OpenSSL 封装库的应用。记住在实际应用中要遵守相关法律法规,仅将此技术用于合法授权的安全测试。

Android 服务端证书校验分析与绕过实战指南 前言 本教程详细记录了对一个使用 org.conscrypt 库进行服务端证书校验的 Android 应用的逆向分析过程。通过该案例,您将学习到如何识别、分析和绕过复杂的证书校验机制,特别是针对使用 OpenSSL 封装库的应用。 准备工作 所需工具 逆向工具 :Jadx-gui (用于反编译 APK) 抓包工具 :Burp Suite (用于拦截和分析网络请求) Hook 工具 :Frida (用于动态分析应用行为) 证书工具 :XCA (用于证书管理和分析) 脚本工具 :r0capture (用于自动化抓包) 代理工具 :Proxifier (用于强制应用流量通过代理) 环境配置 已 root 的 Android 设备 Burp 证书已安装至系统信任区 Frida-server 运行在设备上 初步分析 1. 设置代理 使用 Proxifier 开启 VPN 让目标应用流量走 Burp 代理 配置 Burp 代理监听端口 2. 识别证书校验 应用请求返回 400 No required SSL certificate was sent 错误 表明服务端要求客户端提供有效的证书 静态分析 1. 解包 APK 使用 Jadx-gui 反编译 APK,重点关注以下文件: grp_sp.bks hmsincas.bks hmsrootcas.bks trust.crt 2. 分析证书文件 使用 XCA 查看 trust.crt 内容 发现包含多个公钥证书和 CA 证书 确认这些是证书信任链,非客户端证书 动态分析 1. 尝试 Frida 自吐脚本 使用 android-keystore-audit 中的 tracer-keystore.js 脚本: 2. 手动抛出异常 通过打印堆栈发现关键类: 3. 尝试 r0capture 发现数据仍加密,证书未导出 深入分析 1. Hook 证书相关方法 针对 AkeyChatX509PrivateCA.clientBootstrapCertInfo 方法编写 Hook 脚本: 2. 导出证书 成功获取 X509 证书,包含 "Web Client Authentication" 用途标识: 获取私钥 1. 分析 org.conscrypt 库 下载源码:https://github.com/google/conscrypt 搜索关键词发现关键函数: EVP_parse_private_key 2. Hook So 层函数 针对 libconscrypt_jni.so 编写 Hook 脚本: 证书与私钥配对 使用 XCA 导入导出的私钥 验证私钥与之前获取的证书是否匹配 在 XCA 中将证书和私钥导出为 PKCS#12 (.p12) 格式 配置 Burp Suite 将导出的 PKCS#12 证书导入 Burp 配置 Burp 使用该客户端证书 重新发送请求,成功绕过证书校验 总结 关键点 证书定位 :通过堆栈分析找到关键证书类 Hook 技巧 :Java 层和 Native 层结合 Hook 私钥提取 :针对 org.conscrypt 库的特定函数进行 Hook 证书配对 :确保证书和私钥匹配 适用性 该方法可能适用于其他使用 org.conscrypt 库的应用,但需要根据具体实现调整 Hook 点。 后续研究方向 自动化识别 org.conscrypt 的关键函数 研究其他 OpenSSL 封装库的类似方法 探索不依赖 root 的证书提取方法 通过本教程,您应该掌握了分析复杂证书校验机制的基本方法,特别是针对使用 OpenSSL 封装库的应用。记住在实际应用中要遵守相关法律法规,仅将此技术用于合法授权的安全测试。