
三层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通常分布在三个层面,越往下越难绕过:
更进阶的对抗还包括双向客户端证书认证、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. 快速定位校验层级
不用上来就堆全套脚本,可以先快速排查:
四、第一层:系统证书校验绕过
这是最基础的一层,核心是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。
最稳定的绕过方式有两种:
第一种最简单,兼容性也最好,优先使用。
核心脚本
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个坑和解决方案。
九、写在最后
SSL Pinning的绕过从来没有万能脚本,本质是找到校验点,强制返回成功。不同APP的校验层级、实现方式都不一样,需要按照“系统层→框架层→Native层”的顺序逐层排查,而不是上来就堆一堆脚本。
Frida的价值在于动态调试能力,它可以让我们在不重打包、不修改APP的情况下,快速定位校验点、验证绕过方案,同时直接在内存中提取数据。但也要清楚,绕过证书校验只是接口分析的第一步,后面还有签名算法、参数加密、设备指纹、风控对抗,每一步都需要对应的逆向和工程能力。
最后提醒:所有技术方案仅用于合法的接口分析、安全测试与学习,请遵守相关法律法规,不要用于非法数据采集。
网硕互联帮助中心



评论前必须登录!
注册