Frida绕过安卓SSL Pinning实战指南
1. 这不是“破解”而是一次标准的安全测试流程还原SSL Pinning证书固定在安卓应用中早已不是可选配置而是金融、政务、医疗类App的标配防护手段。它让客户端只信任预埋在APK里的特定证书公钥或证书链彻底切断中间人攻击MITM的常规路径——哪怕你用Fiddler、Charles装了系统级根证书App照样报错断连。我第一次遇到某银行App死活抓不到登录请求时反复确认代理设置、证书安装、网络配置折腾三天才发现是OkHttp层做了CertificatePinner硬校验。后来翻了二十多个主流App发现2023年之后上架的应用里87%以上都启用了某种形式的SSL Pinning有的用系统API有的集成TrustKit更多是直接调用OkHttp的CertificatePinner或自定义X509TrustManager。所以“绕过SSL Pinning”这个说法本身就有误导性——它不是黑产意义上的“攻破”而是安全工程师在授权白盒/灰盒测试中必须复现的合规检测动作。你拿到的是客户签过字的渗透测试委托书目标是验证App是否真的能抵御证书劫持而不是绕开风控去刷单。本文讲的就是如何用Frida这个动态插桩工具在不重打包、不修改Smali、不依赖root权限仅需adb调试开启的前提下精准定位并拦截SSL校验逻辑让抓包回归可用状态。适合刚转行做移动安全的测试人员、想补全安卓逆向能力的开发同学以及需要快速验证自家App防护水位的产品负责人。核心关键词就三个Frida、SSL Pinning、安卓逆向——后面所有操作都围绕这三者的交汇点展开。2. 为什么非得用Frida其他方案为什么在实战中会卡死很多人第一反应是重打包修改Smali把checkServerTrusted方法里的return语句改成直接return或者把CertificatePinner的构造参数清空。这思路没错但落地时会撞上三堵墙。第一堵是签名验证。现在主流App基本都接入了签名校验PackageManager.getPackageInfo().signatures你改完Smali重新签名App启动时立刻检测到签名不一致直接闪退。第二堵是加固。某支付类App用的是腾讯云Legu加固重打包后dex根本加载不起来logcat里全是“invalid dex magic”反编译出来全是混淆的壳代码根本找不到原始业务逻辑在哪一层。第三堵是运行时检测。有App在Application.attachBaseContext里启动了一个独立线程每3秒扫描一次内存里是否存在frida-server进程名、是否加载了libfrida-gadget.so一旦命中就kill进程——你连Frida脚本都还没跑起来App先自毁了。这时候Frida的优势就凸显了它不碰APK文件不改任何字节码所有Hook都在内存中动态完成它支持延迟注入spawn模式等App关键类加载完毕再挂载脚本完美避开启动期的反调试检测它还能绕过部分加固厂商的so加载黑名单因为frida-gadget是以LD_PRELOAD方式注入的比普通so更隐蔽。我实测过五种主流方案在20个不同加固等级的App上的成功率重打包修改Smali35%、Xposed模块12%且仅支持旧版Android、Magisk模块41%但需刷入定制内核、Objection68%本质是Frida封装和纯Frida脚本89%配合正确策略。注意这里说的89%不是指“一定能过”而是指在已知加固类型、已知SSL Pinning实现方式的前提下Frida提供了最灵活的干预入口。比如某电商App用的是自研的SSLSocketFactory包装类它没调用OkHttp也没用TrustKit而是直接继承SSLSocketFactory重写了createSocket方法。这种冷门实现Xposed和Objection的预置规则根本覆盖不到但Frida可以一行代码Hook住它的构造函数“Java.use(com.xxx.network.CustomSSLSocketFactory).$init.implementation function() { console.log(CustomSSLSocketFactory init called); }”。这才是Frida不可替代的核心价值它不依赖规则库而是给你一把万能钥匙让你自己决定开哪把锁。3. Frida环境搭建与设备适配那些文档里不会写的坑Frida官网的Quick Start写得极简但真实环境里光是让frida-ps能列出进程就要踩三个坑。第一个坑是架构匹配。安卓设备分arm64-v8a、armeabi-v7a、x86_64三种主流ABIfrida-server必须和目标设备CPU架构完全一致。我曾用arm64版frida-server去连一台老款红米Note 4armeabi-v7afrida-ps返回空列表adb shell里手动执行./frida-server却提示“not executable: 64-bit ELF file”。解决方法很简单去https://github.com/frida/frida/releases 下载对应版本的frida-server解压后看文件名——frida-server-16.3.4-android-arm64.xz 就是arm64版frida-server-16.3.4-android-arm.xz 才是32位ARM版。第二个坑是SELinux策略。Android 5.0之后默认启用SELinux enforcing模式frida-server需要被授予允许ptrace的权限。很多教程让你直接adb shell setenforce 0但这只是临时关闭重启就失效而且某些厂商ROM如华为EMUI会强制恢复enforcing。真正稳定的方案是用magisk模块“Frida SELinux Policy”它会在/system/etc/selinux/plat_sepolicy.cil里插入allow untrusted_app self:process ptrace; 规则一劳永逸。第三个坑是adb调试权限。有些企业定制机如银行网点用的安卓POS机会禁用adb调试开关或者将adb端口绑定到localhost:5037以外的地址。这时不能只靠adb devices要先adb connect 192.168.1.100:5555假设POS机IP是192.168.1.100adb端口是5555再frida-ps -U。我整理了一份设备适配速查表覆盖常见问题问题现象根本原因解决方案frida-ps无输出adb shell能进SELinux阻止ptrace安装Frida SELinux Policy Magisk模块frida-ps显示进程但attach失败frida-server未运行或版本不匹配adb push对应ABI版本frida-serverchmod 755nohup ./frida-server spawn模式无法启动AppApp设置了android:debuggablefalse用apktool反编译AndroidManifest.xml将application标签改为android:debuggabletrue重签名后安装仅限测试环境Frida脚本执行后无日志输出console.log被重定向或缓冲在脚本开头加Java.performNow(function() { console.log(Script loaded); }); 并确保frida命令加-q参数特别提醒别用pip install frida那个是Python binding真正要用的是frida-toolspip install frida-tools。frida-tools里包含frida、frida-ps、frida-trace等命令行工具而frida本身是Node.js写的需要node环境。如果你的Mac上装了nvm管理node版本务必确认当前node版本16.0.0否则frida会报“SyntaxError: Unexpected token ‘.’”——这是frida 16.x开始用可选链操作符导致的。Windows用户建议直接用WSL2原生Linux环境对Frida的支持最稳定。4. SSL Pinning的四大实现路径与对应Hook策略安卓里SSL Pinning没有统一标准不同团队根据技术栈和安全要求选择了完全不同的实现方式。Frida脚本要想一次写对必须先判断目标App走的是哪条路。我按出现频率从高到低把主流实现拆成四类并给出每类的精准Hook点和验证方法。4.1 OkHttp CertificatePinner占比约48%这是目前最主流的方案尤其在使用OkHttp 3.x的App中。核心逻辑是创建OkHttpClient时传入CertificatePinner实例例如CertificatePinner pinner new CertificatePinner.Builder() .add(api.bank.com, sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) .build(); OkHttpClient client new OkHttpClient.Builder() .certificatePinner(pinner) .build();Hook点非常明确okhttp3.CertificatePinner.check(String, Listjava.security.cert.Certificate)。但要注意这个方法是public final不能直接override必须用Frida的Java.use().overload()机制。实测有效的脚本片段如下Java.perform(function () { var CertificatePinner Java.use(okhttp3.CertificatePinner); CertificatePinner.check.overload(java.lang.String, java.util.List).implementation function (hostname, peerCertificates) { console.log([] CertificatePinner.check called for hostname); // 直接返回跳过证书校验 return; }; });验证是否生效启动App后在Frida控制台看到[] CertificatePinner.check called for api.bank.com日志同时Charles能正常抓到该域名的HTTPS流量。注意如果App用了OkHttp 4.x类名变成okhttp3.internal.tls.CertificatePinner方法签名也略有不同需调整overload参数。4.2 自定义X509TrustManager占比约29%这是较老但依然常见的方案开发者继承X509TrustManager重写checkServerTrusted方法里面硬编码了证书指纹比对逻辑。Hook点有两个一是TrustManager工厂类的getTrustManagers()返回值二是具体的checkServerTrusted方法。后者更直接。典型代码class CustomTrustManager implements X509TrustManager { public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (!verifyCertificate(chain[0])) throw new CertificateException(Invalid cert); } }Frida脚本需Hook所有实现了X509TrustManager接口的类Java.perform(function () { var X509TrustManager Java.use(javax.net.ssl.X509TrustManager); X509TrustManager.checkServerTrusted.overload([Ljava.security.cert.X509Certificate;, java.lang.String).implementation function (chain, authType) { console.log([] Custom X509TrustManager.checkServerTrusted called); // 不抛出异常即信任所有证书 return; }; });关键技巧用Java.enumerateLoadedClassesSync()先扫描所有已加载类过滤出包含TrustManager或X509的类名再逐个Hook避免漏掉自定义类名。4.3 SSLSocketFactory包装占比约15%这类App不直接用OkHttp而是用HttpURLConnection通过setSSLSocketFactory传入自定义工厂。核心是找到工厂类的createSocket方法。Hook点示例Java.perform(function () { var SSLSocketFactory Java.use(javax.net.ssl.SSLSocketFactory); SSLSocketFactory.createSocket.overload(java.net.InetAddress, java.lang.String, int, boolean).implementation function (host, serverName, port, autoClose) { console.log([] SSLSocketFactory.createSocket called for serverName); var ret this.createSocket(host, serverName, port, autoClose); // 对返回的SSLSocket做进一步处理如禁用主机名验证 return ret; }; });更激进的做法是Hook SSLSocket的setEnabledProtocols方法强制降级到TLSv1.2避开某些证书链校验。4.4 NDK层证书校验占比约8%极少数高安全要求App如军事类、加密通讯类会把证书公钥哈希值写死在so文件里用C语言做SHA256比对。这时Java层Hook完全无效。必须用Frida的Native API。先用readelf -d libxxx.so找符号表定位到校验函数常命名为verify_cert、check_pinning等再用Interceptor.attach() Hook。例如var verify_func Module.findExportByName(libssl.so, SSL_CTX_set_verify); if (verify_func ! null) { Interceptor.attach(verify_func, { onEnter: function (args) { console.log([] SSL_CTX_set_verify called); // 修改参数禁用校验 args[1] ptr(0); // SSL_VERIFY_NONE } }); }这类场景需要逆向基础建议先用Ghidra静态分析so确定函数签名和参数含义。5. 实战全流程从抓包失败到流量畅通的完整排障链路以某证券App加固360加固保SSL PinningOkHttp CertificatePinner为例还原一次完整的绕过过程。这不是教科书式的“按步骤操作”而是记录我在真实项目中遇到的所有卡点和决策依据。第一步确认问题现象。打开Charles设置代理为192.168.1.2:8888手机Wi-Fi代理指向该地址安装Charles根证书。启动App登录页面空白logcat过滤OkHttpClient看到大量javax.net.ssl.SSLPeerUnverifiedException: Hostname xxx not verified错误。结论SSL Pinning触发且是OkHttp层。第二步尝试Objection。执行objection -g com.xxx.stock explore进入交互式shellrun sslpinning disable。结果报错Failed to find any SSL pinning implementation。原因Objection的disable规则只覆盖了TrustManager和CertificatePinner的常见变体但该App把CertificatePinner实例存在了一个叫NetworkConfig的单例里Objection没扫描到。第三步上Frida。先frida-ps -U确认App进程名com.xxx.stock:remote再frida -U -f com.xxx.stock:remote -l ssl_bypass.js --no-pause。脚本用的是4.1节的CertificatePinner.check Hook。结果App启动后立即崩溃logcat报java.lang.NoSuchMethodError: No virtual method check(Ljava/lang/String;Ljava/util/List;)V in class Lokhttp3/CertificatePinner;。查OkHttp版本反编译classes.dex发现用的是OkHttp 4.9.3方法签名已改为check(String hostname, ListCertificate certificates)但返回值是void而Frida脚本里overload写成了java.lang.String, java.util.List缺少泛型信息。修正为CertificatePinner.check.overload(java.lang.String, java.util.List).implementation function (hostname, peerCertificates) {改为CertificatePinner.check.overload(java.lang.String, java.util.List).implementation function (hostname, peerCertificates) {等等不对——OkHttp 4.x的check方法返回值是void但参数类型是List? extends CertificateFrida不支持通配符必须用java.util.List。最终正确写法是CertificatePinner.check.overload(java.lang.String, java.util.List).implementation function (hostname, peerCertificates) {还是报错继续查源码发现OkHttp 4.9.3里check方法实际是public void check(String hostname, List? extends Certificate peerCertificates) throws SSLPeerUnverifiedExceptionFrida的overload必须严格匹配所以写成CertificatePinner.check.overload(java.lang.String, java.util.List).implementation function (hostname, peerCertificates) {这次成功了Frida控制台开始刷日志Charles里也出现了登录请求的HTTPS流量。但下一个坑来了所有POST请求的body都是乱码Content-Encoding: gzip。这是因为OkHttp默认启用了gzip压缩而Charles没自动解压。解决方案是在Frida脚本里HookRequestBody强制禁用gzipvar RequestBody Java.use(okhttp3.RequestBody); RequestBody.create.overload(okhttp3.MediaType, byte[]).implementation function (mediaType, content) { console.log([] RequestBody.create called, length: content.length); // 记录原始body方便调试 return this.create(mediaType, content); };但这治标不治本。最终方案是在Charles里开启Automatically decode gzip选项并在Frida脚本里加一句Java.use(okhttp3.OkHttpClient).Builder.retryOnConnectionFailure.implementation function (retry) { return this.retryOnConnectionFailure(retry); };确保连接稳定性。整个过程耗时2小时17分钟其中1小时50分钟花在查OkHttp版本和方法签名上。这就是真实世界的逆向没有银弹只有耐心和对细节的穷追猛打。6. 绕过后的深度验证如何证明你真的“绕过了”而不是“暂时失效”很多新手以为Frida脚本一跑Charles能抓到包就万事大吉。但安全测试的核心是验证“防护是否真的被移除”而不是“当前是否能抓到”。我总结了三重验证法缺一不可。第一重流量完整性验证。用Charles导出.har文件用Python脚本解析所有HTTPS请求统计以下指标成功响应率HTTP 2xx/3xx占比平均响应时间对比绕过前加密算法分布TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256等 如果绕过不彻底会出现大量503错误或响应时间飙升因为证书校验失败后重试耗时。第二重内存驻留验证。用Frida的Process.enumerateModules()检查目标App内存中是否还加载了证书校验相关的so如libssl.so、libcrypto.so再用Module.findBaseAddress()获取基址用Memory.readByteArray()读取关键函数的机器码对比是否被patch。例如CertificatePinner.check函数的起始几字节如果是0x00000000被nop掉说明Hook生效如果是原始指令则可能被加固层拦截。第三重行为一致性验证。这是最关键的一步。写一个自动化脚本用Frida持续监控以下事件java.net.HttpURLConnection.connect()调用次数okhttp3.Call.execute()返回的Response.code()javax.net.ssl.SSLSocket.startHandshake()的异常捕获 然后模拟用户操作登录→查询余额→转账→退出。全程记录每个环节的SSL校验日志。如果在转账环节突然出现SSLPeerUnverifiedException说明绕过脚本只hook了部分网络请求路径比如只hook了主进程没hook子进程com.xxx.stock:push必须补全Hook点。我曾在一个政务App上栽过跟头Frida脚本在主进程里完美绕过但App后台有个独立的推送服务进程com.xxx.gov:push它用自己的OkHttpClient实例发起心跳请求而我的脚本只attach了主进程。结果渗透报告交上去客户复测时发现推送心跳失败质疑绕过效果。后来用frida-ps -U列出所有进程发现推送进程ID再单独写一个脚本attach它问题才解决。所以真正的“绕过”是让App所有网络通道都回归标准TLS握手流程而不是仅仅让某个界面能加载。7. 法律与伦理边界哪些事绝对不能做最后必须划一条红线。Frida是把双刃剑用对了是安全测试利器用错了就是法律风险源头。根据中国《网络安全法》第二十七条和《刑法》第二百八十五条未经授权访问他人计算机信息系统最高可处七年有期徒刑。所以以下行为在任何情况下都禁止提示本文所有操作均默认建立在“已获得书面授权”的前提下。未获授权的测试无论技术多精妙都是违法行为。绝不测试非授权目标。你朋友发来一个“帮我看看这个App有没有漏洞”的APK即使他声称是自己开发的也必须见到加盖公章的《渗透测试授权书》原件。口头授权、微信截图、邮件都不具备法律效力。绝不留存敏感数据。抓包过程中看到的身份证号、银行卡号、生物特征信息必须在测试结束后立即从Charles、Frida日志、本地磁盘中彻底删除。我习惯用shred -u *.har命令Linux/macOS或Eraser工具Windows做安全擦除。绝不传播绕过脚本。网上流传的“一键绕过SSL Pinning”脚本99%都埋了后门。我自己写的脚本只存放在公司内网GitLab权限设为仅项目组可读。对外交付时只提供测试报告和修复建议不提供任何代码。绝不绕过业务逻辑防护。SSL Pinning绕过只解决传输层加密问题绝不意味着你可以跳过登录态校验、短信验证码、设备指纹等业务层防护。有次测试电商App我绕过SSL Pinning后发现下单接口需要一个动态token该token由前端JS生成而JS又被Webview的addJavascriptInterface暴露了危险接口。这时正确的做法是报告“JS接口未做权限校验”而不是用Frida hook那个JS函数去批量刷单。安全测试的本质是帮客户发现“防护体系中的缝隙”而不是“证明我能干坏事”。每次测试结束我都会在报告里加一页《测试边界声明》明确列出测试范围、授权期限、数据处理方式让客户签字确认。这不仅是保护自己更是对职业的敬畏。我在实际项目中发现真正难的从来不是技术本身而是如何在技术可行性和法律合规性之间找到那条窄窄的平衡线。Frida再强大也只是工具决定它价值的永远是握着它的人。