云计算百科
云计算领域专业知识百科平台

Frida绕过SSL Pinning实战:从系统层到Native层,HTTPS流量与加密参数全提取

在这里插入图片描述

三层Hook覆盖系统/OkHttp/OpenSSL全校验链路,附通用脚本、踩坑清单与请求提取方案

做APP接口分析和数据采集的同学,大概率都遇到过这个死局:手机装了抓包证书、代理也配好了,Charles里要么全是unknown,要么直接报SSL handshake failed,APP干脆直接断网。你以为是证书没装对,折腾半天系统证书、用户证书全装了一遍,结果还是不行——这就是**SSL Pinning(证书钉扎)**在起作用。

网上能搜到的绕过方案大多停留在三四年前,要么只能绕过系统默认校验,遇到OkHttp自定义Pinning直接失效;要么就是让你重打包、改smali,一遇到加固、签名校验就闪退。更不用说现在很多APP还叠了双向认证、TLS指纹校验,普通的Hook脚本根本碰不到校验点。

本文从TLS校验的全链路出发,用Frida实现从系统层、框架层到底层SSL库的三层Hook绕过,同时完成HTTPS请求体、加密参数的完整提取。所有方案均经过数十款主流APP实测,覆盖安卓7~14,附可直接复用的脚本和完整踩坑清单。

一、先搞懂:SSL Pinning到底卡在哪一步

HTTPS抓包的本质是中间人攻击:代理用自己的证书冒充服务器,客户端信任代理证书后,流量就能被解密。而SSL Pinning的作用,就是从根源上废掉这种中间人。

正常的TLS握手流程中,客户端只需要验证服务器证书由受信任的CA签发即可;而开启Pinning后,APP会在代码里内置服务器证书的公钥哈希、证书指纹或完整证书链,只信任指定的证书。哪怕你把抓包证书安装到系统根目录,APP自己的校验逻辑也会拒绝握手,直接抛出证书验证异常。

从实现层级上看,Pinning通常分布在三个层面,越往下越难绕过:

  • 系统证书校验层:基于Android系统X509TrustManager完成证书链校验,安卓7+默认不信任用户证书,这是最基础的拦截点,也是很多新手卡壳的地方。
  • 应用框架层:最常见的是OkHttp的CertificatePinner,Retrofit、FastAndroidNetworking等绝大多数网络框架都基于它实现,直接在代码里写死公钥哈希。
  • Native原生层:C/C++实现的网络库直接调用OpenSSL/BoringSSL完成校验,Java层Hook完全无效,多见于加固应用、游戏、自研网络库。
  • 更进阶的对抗还包括双向客户端证书认证、TLS指纹校验、证书链完整性校验、动态下发证书等,这也是单一脚本无法通杀所有APP的根本原因。

    二、整体绕过架构与Hook点选型

    Frida的核心优势是动态注入,无需重打包、不用修改APP,既能Hook Java层也能Hook Native层,同时可以在内存中直接拦截请求数据。

    完整的绕过与提取链路如下图所示,我们按照“从上到下、逐层排查”的思路定位校验点,优先Hook Java层,无效再下探到Native层,最后配合请求拦截完成数据提取。 在这里插入图片描述

    三层Hook的适用场景:

    • 仅系统校验拦截:只需要Hook X509TrustManager 即可,适用于原生网络请求、WebView场景。
    • OkHttp自定义Pinning:需要Hook CertificatePinner,这是绝大多数APP的实现方式。
    • Native层校验:需要Hook OpenSSL/BoringSSL的底层校验函数,适用于加固、自研网络库的场景。

    三、前置环境准备

    在开始Hook之前,必须先把基础环境搭好,否则再强的脚本也跑不起来。

    1. 基础环境

    • 运行环境:Root手机或安卓模拟器,推荐安卓10~13,兼容性最好;安卓14需要配合Magisk和最新版Frida。
    • Frida环境:PC端安装frida-tools,手机端安装对应架构、对应版本的frida-server,并运行在后台。
    • 抓包工具:Charles或mitmproxy,提前导出根证书。
    • 系统证书安装:安卓7+默认不信任用户证书,必须将抓包证书移动到/system/etc/security/cacerts/目录,格式为哈希.0。
    • 目标APP:确认包名、CPU架构(32/64位)、是否加固,加固应用建议使用Spawn模式启动注入。

    2. 快速定位校验层级

    不用上来就堆全套脚本,可以先快速排查:

  • 装完系统证书后,APP仍无法联网、报SSL错误,说明存在Pinning。
  • 用Frida枚举类名,搜索CertificatePinner、X509TrustManager,能搜到说明是Java层校验。
  • Java层Hook后仍握手失败,说明存在Native层校验,需要下探到so层。
  • 四、第一层:系统证书校验绕过

    这是最基础的一层,核心是Hook安卓系统的X509TrustManager,让它信任所有证书,同时绕过主机名校验。

    实现原理

    安卓所有HTTPS请求的证书校验,最终都会调用X509TrustManager的checkServerTrusted方法,校验失败就抛出CertificateException。我们直接Hook该方法,空实现不抛出异常,就等于让系统信任所有证书。

    同时还要Hook HostnameVerifier,强制返回true,避免主机名不匹配的问题。

    核心脚本

    Java.perform(function () {
    // 绕过X509TrustManager证书校验
    var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
    TrustManagerImpl.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function (chain, authType) {
    // 直接返回,不抛出异常即视为校验通过
    return;
    };

    // 绕过主机名校验
    var HostnameVerifier = Java.use("javax.net.ssl.HostnameVerifier");
    var OkHostnameVerifier = Java.use("okhttp3.internal.tls.OkHostnameVerifier");

    OkHostnameVerifier.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (hostname, session) {
    return true;
    };

    // 兼容HttpURLConnection的主机名校验
    var DefaultHostnameVerifier = Java.use("org.apache.http.conn.ssl.DefaultHostnameVerifier");
    DefaultHostnameVerifier.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (hostname, session) {
    return true;
    };
    });

    注意:不同安卓版本的TrustManagerImpl类名可能不同,部分ROM使用android.net.http.X509TrustManagerExtensions,如果Hook失败可以用Java.enumerateClassLoaders遍历查找。

    这一层只能绕过系统默认的证书校验,无法绕过OkHttp自定义Pinning。如果Hook完APP还是无法联网,继续往下走。

    五、第二层:OkHttp CertificatePinner 绕过

    这是目前最常见的Pinning实现方式,绝大多数使用OkHttp/Retrofit的APP都采用这种方案。

    实现原理

    OkHttp通过CertificatePinner类实现证书钉扎,在check方法中对比服务器证书的公钥哈希和代码中内置的哈希,不匹配就抛出SSLPeerUnverifiedException。

    最稳定的绕过方式有两种:

  • 直接Hook check方法,空实现,不抛出异常;
  • Hook CertificatePinner.Builder的add方法,把内置的公钥哈希替换成抓包证书的哈希。
  • 第一种最简单,兼容性也最好,优先使用。

    核心脚本

    Java.perform(function () {
    try {
    var CertificatePinner = Java.use("okhttp3.CertificatePinner");
    CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (hostname, peerCertificates) {
    // 直接返回,不做校验
    return;
    };

    // 兼容混淆后的类名,如果上面的类找不到,可以尝试下面的方式
    // 遍历所有类,搜索包含CertificatePinner特征的类
    Java.enumerateLoadedClasses({
    onMatch: function (className) {
    if (className.indexOf("CertificatePinner") !== -1) {
    console.log("找到CertificatePinner类: " + className);
    }
    },
    onComplete: function () {}
    });
    } catch (e) {
    console.log("OkHttp CertificatePinner Hook失败: " + e.message);
    }
    });

    混淆与适配

    如果APP做了代码混淆,okhttp3.CertificatePinner的类名会被改成a.b.c,这时候不要硬写类名,通过字符串特征或者方法签名去匹配:

    • 搜索Certificate pinning failure异常字符串,定位抛出异常的类;
    • 枚举所有实现了check方法、参数包含List<Certificate>的类;
    • 用Frida的trace命令跟踪SSL相关的Java调用,定位校验点。

    到这一步,90%以上的普通APP都可以正常抓包了。如果还是不行,说明校验逻辑在Native层。

    六、第三层:Native层OpenSSL/BoringSSL 绕过

    很多加固应用、游戏、自研网络库不会走Java层的证书校验,而是直接在Native层调用OpenSSL/BoringSSL完成TLS握手和证书校验,这时候Java层的Hook完全无效。

    实现原理

    Native层的证书校验核心是两个函数:

    • SSL_get_verify_result:返回证书校验结果,返回0表示成功,非0表示失败;
    • X509_verify_cert:执行证书链校验,返回1表示成功。

    我们用Frida的Native Hook拦截这两个函数,强制返回成功,就能绕过Native层的Pinning。

    核心脚本

    // 等待so库加载完成后执行
    setTimeout(function () {
    var libssl = null;
    var libs = ["libssl.so", "libboringssl.so", "libssl.so.1.1", "libssl.so.3"];

    for (var i = 0; i < libs.length; i++) {
    try {
    libssl = Module.findBaseAddress(libs[i]);
    if (libssl) {
    console.log("找到SSL库: " + libs[i]);
    break;
    }
    } catch (e) {}
    }

    if (!libssl) {
    console.log("未找到SSL库,尝试全局搜索");
    return;
    }

    // Hook SSL_get_verify_result,强制返回0(校验成功)
    var SSL_get_verify_result = Module.findExportByName(libs[i], "SSL_get_verify_result");
    if (SSL_get_verify_result) {
    Interceptor.attach(SSL_get_verify_result, {
    onLeave: function (retval) {
    retval.replace(0);
    }
    });
    console.log("SSL_get_verify_result Hook成功");
    }

    // Hook X509_verify_cert,强制返回1(校验成功)
    var X509_verify_cert = Module.findExportByName(libs[i], "X509_verify_cert");
    if (X509_verify_cert) {
    Interceptor.attach(X509_verify_cert, {
    onLeave: function (retval) {
    retval.replace(1);
    }
    });
    console.log("X509_verify_cert Hook成功");
    }
    }, 2000);

    注意事项

    • 不同APP使用的SSL库不同,除了系统的libssl.so,还可能是APP自带的libcurl.so、libmbedtls.so,需要先枚举模块确认。
    • 部分APP会自定义证书校验函数,不是标准的OpenSSL接口,这时候需要通过字符串、导入表或者堆栈定位自定义校验函数。
    • 加固APP要注意Hook时机,必须等壳加载完成、so库加载后再注入,建议使用Spawn模式启动。

    七、HTTPS请求与加密参数完整提取

    绕过Pinning只是第一步,很多时候我们需要提取请求URL、Header、Body、加密参数,甚至响应数据。这里提供两种方案,覆盖不同场景。

    方案一:抓包工具直接提取

    绕过Pinning后,Charles或mitmproxy可以直接解密HTTPS流量,查看完整的请求和响应,适合绝大多数场景。如果APP不走系统代理,可以用iptables转发流量,或者使用VPN模式的抓包工具。

    方案二:Frida内存直接提取

    针对有代理检测、走私有通道、或者流量加密的APP,直接在内存中Hook网络框架的请求方法,提取明文数据,完全不需要走代理。

    以OkHttp为例,Hook RealCall的execute和enqueue方法,打印请求信息:

    Java.perform(function () {
    try {
    var RealCall = Java.use("okhttp3.RealCall");
    RealCall.execute.overload().implementation = function () {
    var request = this.request();
    var url = request.url().toString();
    var method = request.method();
    var body = request.body();

    console.log("=== 请求 ===");
    console.log("Method: " + method);
    console.log("URL: " + url);
    console.log("Headers: " + request.headers().toString());

    // 打印请求体
    if (body != null) {
    var buffer = Java.use("okio.Buffer").$new();
    body.writeTo(buffer);
    console.log("Body: " + buffer.readUtf8());
    }

    var response = this.execute();
    console.log("响应码: " + response.code());
    return response;
    };
    } catch (e) {
    console.log("OkHttp请求Hook失败: " + e.message);
    }
    });

    进阶用法是直接Hook加密、签名函数,在参数加密前提取明文,在签名计算前提取原始参数,省去逆向算法的时间。

    八、进阶对抗与必踩的坑

    实际场景中,很多APP不止有Pinning,还叠加了各种对抗手段,这里整理了最常见的8个坑和解决方案。

  • 双向客户端证书认证 APP要求客户端携带证书才能完成握手,这时候光绕过服务端校验没用。需要从APK或内存中提取客户端证书和私钥,Hook KeyManager的chooseClientAlias方法,指定使用抓包工具的客户端证书,或者直接导入证书到系统。
  • 代理检测 APP检测系统代理后直接不走代理,甚至断网。可以Hook ProxySelector强制返回NO_PROXY,或者用iptables将流量转发到抓包端口,也可以使用VPN模式的抓包工具。
  • TLS指纹校验 服务器通过JA3/JA3S指纹识别抓包工具,即使证书校验通过也会返回异常。可以用Frida修改底层TLS扩展、密码套件顺序,或者使用支持指纹定制的代理工具。
  • Frida与Root检测 APP检测Frida、检测Root、检测调试,会直接闪退或返回假数据。需要先过反调试:使用Frida隐身脚本、修改frida-server端口和包名、配合Magisk Hide隐藏Root,必要时使用Frida的Spawn模式提前注入。
  • 多进程与多Dex 很多APP有多个进程,网络请求可能在子进程中,需要用–enable-jit并注入所有进程;加固APP的Dex会动态加载,需要等Dex加载完成后再Hook。
  • 安卓14兼容性 安卓14加强了SELinux限制和命名空间隔离,旧版Frida会注入失败。建议使用Frida 16.2以上版本,配合Magisk的frida-magisk模块,关闭SELinux的严格模式。
  • Hook时机错误导致崩溃 如果在APP启动早期就Hook还没加载的类,会导致崩溃。建议使用延迟Hook、类加载监听,或者用Spawn模式启动APP,在合适的时机执行Hook。
  • 证书链与公钥校验 部分APP不校验完整证书,只校验公钥哈希、证书序列号或者证书链长度,这时候需要针对性Hook对应的校验函数,而不是只Hook标准接口。
  • 九、写在最后

    SSL Pinning的绕过从来没有万能脚本,本质是找到校验点,强制返回成功。不同APP的校验层级、实现方式都不一样,需要按照“系统层→框架层→Native层”的顺序逐层排查,而不是上来就堆一堆脚本。

    Frida的价值在于动态调试能力,它可以让我们在不重打包、不修改APP的情况下,快速定位校验点、验证绕过方案,同时直接在内存中提取数据。但也要清楚,绕过证书校验只是接口分析的第一步,后面还有签名算法、参数加密、设备指纹、风控对抗,每一步都需要对应的逆向和工程能力。

    最后提醒:所有技术方案仅用于合法的接口分析、安全测试与学习,请遵守相关法律法规,不要用于非法数据采集。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Frida绕过SSL Pinning实战:从系统层到Native层,HTTPS流量与加密参数全提取
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!