王麑 2025-11-22 06:55 采纳率: 98.5%
浏览 4
已采纳

安卓8高版本安装Charles证书失败

在安卓8及以上版本中,系统默认不再信任用户自行安装的CA证书,导致Charles证书安装后无法正常抓取HTTPS流量。常见问题表现为:即使已将Charles根证书手动添加至“受信任的凭据”,应用仍提示SSL错误或连接被拒绝。其根本原因在于Android 8开始强化安全策略,仅预置在系统证书存储区(system trust store)中的CA才被全局信任,而用户安装的证书仅对部分系统应用生效,且多数App通过网络安全性配置(network-security-config)明确限制了用户证书的信任。因此,即便证书安装成功,也无法实现全局HTTPS解密,给移动端抓包调试带来显著阻碍。
  • 写回答

1条回答 默认 最新

  • 祁圆圆 2025-11-22 09:06
    关注

    1. 问题背景与现象描述

    在Android 8(API级别26)及以上版本中,Google引入了更严格的网络安全策略,系统默认不再信任用户手动安装的CA证书。这直接影响了如Charles、Fiddler等抓包工具的HTTPS流量解密能力。

    开发者在使用Charles进行移动端调试时,即使成功将Charles根证书(charles-proxy-ca.pem)导入至“设置 → 安全 → 受信任的凭据 → 用户”中,仍会遇到目标App提示SSL异常、连接被拒绝或直接中断HTTPS请求的现象。

    典型表现包括:

    • 浏览器访问正常,但特定App无法加载数据
    • Charles显示TLS握手失败(Client Alert: Unknown CA)
    • Logcat输出javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    • 部分应用完全忽略用户证书,仅信任系统预置CA

    2. 根本原因分析:Android安全机制演进

    自Android 7开始,Google引入了应用级网络安全性配置(Network Security Config),并在Android 8中全面强化其默认行为。核心变化如下表所示:

    Android 版本用户证书信任范围默认 network-security-config 行为
    ≤ Android 6.0 (API 23)全局信任用户安装的CA无显式配置,继承系统信任链
    Android 7–9 (API 24–28)仅对未定义 config 的应用有效默认不信任用户CA,除非显式声明
    ≥ Android 10 (API 29)完全隔离用户CA,需强制配置覆盖明确定义 trust-anchors 策略

    关键点在于:从Android 8起,应用可通过<network-security-config>明确指定只信任系统CA(system trust anchors),从而主动排斥用户安装的证书。

    3. 技术原理深入:证书信任链与Trust Manager行为

    当App发起HTTPS请求时,Java/OkHttp的TrustManager会验证服务器证书是否由可信CA签发。该过程依赖于KeyStore中的信任锚点(Trust Anchors)。

    在Android系统中,存在两个主要的信任存储区:

    1. System Trust Store:存放OEM预置的根证书(如DigiCert、GlobalSign等),路径通常为/system/etc/security/cacerts/
    2. User Trust Store:存放用户导入的证书,位于/data/misc/user/0/cacerts_added/

    现代App通过以下方式限制信任范围:

    <network-security-config>
        <domain-config>
            <domain includeSubdomains="true">api.example.com</domain>
            <trust-anchors>
                <certificates src="system" />
                
            </trust-anchors>
        </domain-config>
    </network-security-config>

    上述配置意味着:即使用户安装了Charles证书,只要不在system store中,就不会被纳入验证链。

    4. 解决方案全景图:从绕行到系统级突破

    针对此问题,业界已发展出多种应对策略,按侵入性和可行性分为多个层级:

    graph TD A[HTTPS抓包受阻] --> B{解决方案} B --> C[修改App的network-security-config] B --> D[Root设备并注入系统证书区] B --> E[使用Xposed/Frida Hook SSL验证] B --> F[借助虚拟化环境如Magisk+模块] C --> G[需反编译/重打包APK] D --> H[需root权限, adb remount] E --> I[动态Hook X509TrustManager] F --> J[如Move Certs模块自动迁移证书]

    每种方法均有适用场景与风险边界。

    5. 实践路径一:非Root环境下的妥协方案

    若设备未Root,可尝试以下步骤实现有限抓包:

    1. 导出Charles证书(.pem格式)并通过USB传输到手机
    2. 进入“设置 → 安全 → 加密与凭据 → 安装证书 → CA证书”完成导入
    3. 定位目标App的AndroidManifest.xml,检查是否声明android:networkSecurityConfig
    4. 若未声明,则可能仍可抓包;若已声明,则需反编译APK
    5. 使用apktool d app.apk反编译
    6. 编辑res/xml/network_security_config.xml,添加<certificates src="user"/>
    7. 重新签名并安装(注意:违反多数平台政策,仅限测试)

    代码示例修改后片段:

    <trust-anchors>
        <certificates src="system" />
        <certificates src="user" />
    </trust-anchors>

    6. 实践路径二:Root设备中的终极解决

    对于具备开发调试权限的设备,推荐将Charles证书写入系统证书区:

    # 获取root权限
    adb root
    adb remount
    
    # 将PEM转换为DER格式
    openssl x509 -in charles-proxy-ca.pem -outform DER -out charles.der
    
    # 计算证书哈希名(Linux/macOS)
    CERT_HASH=$(openssl x509 -inform PEM -subject_hash_old -in charles-proxy-ca.pem | head -1)
    mv charles.der $CERT_HASH.0
    
    # 推送至系统证书目录
    adb push $CERT_HASH.0 /system/etc/security/cacerts/
    adb shell chmod 644 /system/etc/security/cacerts/$CERT_HASH.0

    重启设备后,绝大多数App将信任该CA,实现全局HTTPS解密。

    7. 高阶技巧:动态Hook绕过证书校验

    利用Frida或Xposed框架,可在运行时篡改SSL验证逻辑。以Frida为例:

    Java.perform(function () {
        var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
        var SSLContext = Java.use('javax.net.ssl.SSLContext');
    
        // Hook所有X509TrustManager的checkServerTrusted方法
        X509TrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function (certs, authType) {
            console.log("[*] Ignoring SSL certificate validation");
            return;
        };
    
        // 强制使用自定义TrustManager
        SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom').implementation = function (keyManager, trustManager, random) {
            return this.init(keyManager, null, random); // 使用空TrustManager
        };
    });

    此方法无需修改APK或系统证书,适用于复杂加固App的逆向分析。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 11月23日
  • 创建了问题 11月22日