简介:破解支付宝小程序SSL验证的Frida辅助工具包,面向移动端安全测试、逆向分析及日常抓包调试人员,尤其适合需要分析小程序接口数据、开展支付链路安全评估的读者。针对支付宝小程序证书锁定导致代理无法抓包的问题,资源提供绕过SSL验证的完整思路与可运行脚本,并附有原理说明,帮助读者快速搭建代理抓包环境。压缩包共3个文件,包含Python主脚本、JavaScript Hook脚本及Markdown说明文档:Py脚本负责Frida注入与参数传递,JS脚本实现Hook证书校验函数,MD文档覆盖使用步骤、脚本逻辑与注意事项,整体仅2KB,轻量且便于按需修改。目前已有3014人学习下载,适合具备基础Frida使用经验、希望深入小程序安全检测的读者。完成配置后,读者可掌握基于Frida的SSL绕过技巧,并理解支付宝小程序启动流程与证书校验机制,为后续漏洞挖掘、接口分析或合规测试提供实用参考。
1. 某支付平台小程序SSL验证与代理抓包:为什么全局证书不够用
接到一个授权测试任务,目标是某支付平台的小程序,需要把它发出的 HTTPS 请求完整抓下来分析签名逻辑。常规操作是:电脑上开好代理,手机把 WiFi 代理指过去,安装抓包工具的根证书,然后信心满满地打开小程序。结果流量面板里刷出来一片 TLS 握手失败,或者干脆只有 CONNECT 隧道,看不到任何明文请求。换一台没装证书的手机对比,表现完全一样。这时候才意识到,真正挡住我们的不是系统证书信任,而是小程序内置的 SSL 验证逻辑。这篇文章记录我拆这个问题的完整过程:先讲清楚 SSL 验证为什么让代理失效,再给出一条可复现的绕过路线,最后附上实战里最容易踩的五个坑和验证方法。内容面向移动端开发和安全测试,上手不需要太多逆向基础,但得能接受折腾。
2. 代理抓包为什么只能看到TLS:SSL验证的两个层次
2.1 系统CA信任与代理证书:第一层校验
HTTPS 抓包工具的原理是把自己伪装成目标服务器。代理工具会拦截客户端发起的 TLS 连接,然后用自己的根证书动态签发一张目标域名的证书。只要客户端信任这个根证书,握手就能正常完成,代理在中间既能看到请求,也能解密响应。
这一套能否生效,第一步取决于客户端操作系统的证书信任链。iOS 和 Android 都内置了一个系统 CA 列表,抓包工具的根证书如果没被安装,系统在验证服务器证书链时就会直接报错。所以常规抓包教程都会要求“安装并信任根证书”。但这一步只能解决系统层面的校验,对大多数普通 App 有效,对小程序未必。
某支付平台的小程序虽然跑在宿主 App 里,但它的网络请求并不是完全走系统默认网络栈。为了安全,开发者在封装请求库时通常会额外开启一层校验:在代码里指定只信任某个固定证书或公钥。这就是常说的 SSL Pinning,也就是第二层校验。这层校验不受系统 CA 列表控制,因为校验逻辑跑在 JavaScript 或者原生层里,即使你信任了抓包工具的证书,它照样拒绝握手。
2.2 代码内SSL Pinning:真正的黑匣子
第二层校验常见实现方式有三种:第一种是证书校验,代码里预埋了服务器的证书内容,握手时拿到服务器证书后逐字节对比;第二种是公钥校验,只比对服务器证书里的公钥哈希;第三种是自定义校验逻辑,在系统回调里追加一堆 base64 字符串比对逻辑。第三种最难缠,因为它的结果不一定走系统标准错误码,经常是错得莫名其妙。
对小程序来说,这层逻辑通常被编译进了运行时的 JS 包,或者宿主 App 的 native 层。前者可以直接在解包后的 JavaScript 里搜关键字定位,后者则要借用 Frida 之类动态插桩工具来 Hook 底层 OpenSSL 和系统网络库。很多人在这时候会去改代理工具的高级设置,比如启用 SSL 代理、忽略证书校验等,但这些选项作用在代理端,改变不了客户端的校验行为。正确的思路是让客户端根本不做校验,或者让它以为握手的是真正的服务器。
2.3 双向校验与服务端指纹:更极端的组合
更麻烦的情况是双向 TLS 校验。不仅客户端要验证服务器,服务器也要求客户端提供证书。代理工具在中间模拟服务器时,拿不到客户端原本要发送的证书,即使客户端不校验服务器,代理也会因缺少客户端证书而握手失败。某支付平台的小程序在部分内部接口上确实启用了双向校验,这也是很多人照着网上通用 Frida 脚本改了之后仍然抓不到包的原因。
另一个极端是服务端指纹校验。服务器会在 TLS 握手时校验 ClientHello 里的指纹特征,比如 JA3。代理工具的指纹和真机客户端不一样,即使证书骗过去了,服务器也会中断连接。这类指纹校验通常不在小程序客户端里,而是服务端安全策略的一部分,绕过时要连代理工具的指纹一起伪装。不过这次我处理的这个目标没有上 JA3,所以后续内容不展开,但要知道有这个可能性。
3. 从wxapkg到Frida Hook:定位SSL校验逻辑的常规路线
3.1 解包小程序:拿到JavaScript与WXML
小程序安装后,代码文件会以 wxapkg 格式存放在手机存储里。不同宿主平台路径略有差异,但解包思路一致:把对应小程序目录下的 wxapkg 文件复制出来,用开源工具解包成原始 JavaScript 文件。常见工具有 wxappUnpacker 这类,解包后能看到 app.js、app.json,以及每个页面和组件的 js/wxml/wxss 文件。
我这边用的是模拟器环境,先把目标小程序跑起来,让它释放包体,然后从/data/data/某宿主包名/app_wxapkg/目录下复制目标 wxapkg。复制前要确认模拟器已 root,或者用可调试版本。解包前先看文件大小,如果只有几百 KB,说明主包可能只是入口,业务逻辑在分包里面,要一并复制。
adb pull /data/data/com.example.host/app_wxapkg/__APP__/pages.wxapkg . node ./wxappUnpacker.js ./pages.wxapkg -o ./decoded_pages逻辑说明:adb pull把设备上的包体拉到本地,wxappUnpacker.js负责解包,-o指定输出目录。执行后,decoded_pages里就能看到分包对应的 JavaScript 文件。参数说明:包名和路径要根据实际宿主替换,如果拉取时报权限错误,先检查 adb root 状态。解包工具对部分加密包体会报错,这时需要先解密,常见做法是在__app__.json里搜索encrypted标记,但绝大多数普通小程序没有加密,这里不做展开。
3.2 搜索校验关键字:正则与字符串定位
拿到 JS 文件后,先不要盲目读代码。直接搜索一些网络库和证书校验常见的特征字符串,定位效率最高。目标网络请求代码一般会用到 wx.request 的底层封装,但是 SSL 校验逻辑往往会出现在公共模块里,搜索时优先看 app-service.js 或 vendor.js 这类大文件。
我习惯先在解包目录下跑一条 grep,把带 “certificate”“publicKey”“ssl”“pinning”“x509” 的 js 文件筛出来。不同宿主平台搜索方式一样,但匹配结果多少取决于 JS 是否被混淆。如果代码被压缩混淆过,字符串可能还是明文的,因为证书指纹这类常量没法彻底隐藏;如果做了字符串加密,则要借助 Hook 或运行时日志来找。
grep -rE "certificatePinning|publicKey|ssl_pinning|x509|certificate" ./decoded_pages -l逻辑说明:这条命令会列出包含上述关键字的文件路径。-r递归,-E扩展正则,-l只显示文件名。拿到文件列表后,优先打开体积最大的那个 vendor 文件,因为第三方请求库通常被打包在那里。如果你搜索的结果是空白,可以换一批关键字再试,比如sha256/、pin、checkServerTrusted。对某支付平台小程序,我最后是在vendor.js里找到了一个名为verifySsl的函数,里面做了证书链校验。
3.3 选型:Hook系统API还是替换证书库
定位到校验逻辑后,选择绕过方案。常见三种做法:
第一种是 Hook 系统证书校验 API。Android 上就是网络库最终调用的 OpenSSL 或 Java 的TrustManager。用 Frida Hook 住这些函数,直接让校验返回成功。优点是通用性强,不需要理解具体业务代码;缺点是如果目标用 NDK 自己编译了静态 OpenSSL,Hook 点可能落在不常用函数上,要花时间找。
第二种是运行时替换证书库。把抓包工具的根证书直接塞进系统 CA 目录,或者替换掉代码里预埋的证书常量。这种做法适合代码里做了明文证书对比的场景,但需要频繁修改包体,有时还得过签名校验,不建议用于快速抓包。
第三种是动态调试思路,直接用 Frida 的Java.perform去调用小程序内部方法,绕过前端校验逻辑。这个不够通用,但偶尔能救命。我最终选择的是 Hook 系统 OpenSSL 校验函数,原因是它不依赖具体业务代码,而且对某支付平台小程序这样同时跑在 multiple 进程里的应用,一个脚本能全覆盖。
4. 实操:用Frida绕过SSL验证,让抓包工具看到明文
4.1 环境准备:模拟器/root设备与Frida安装
先准备一台可 root 的 Android 设备或模拟器。我这里用的是某常见模拟器,root 后方便做全局代理和证书安装。准备好 frida-server 与服务端 frida 版本一致,否则连不上会报unable to start之类的错误。
安装步骤不复杂:先确认设备架构,adb shell getprop ro.product.cpu.abi输出 arm64-v8a 就下载对应的 frida-server。用 adb push 推到/data/local/tmp/,给执行权限后后台运行。宿主机上安装 frida-tools,然后跑frida-ps -U看看能不能列出进程。
adb push frida-server /data/local/tmp/frida-server adb shell chmod 755 /data/local/tmp/frida-server adb shell "/data/local/tmp/frida-server &"逻辑说明:第一行把 frida-server 推送到设备临时目录,第二行加执行权限,第三行启动服务。&表示后台运行。如果是模拟器,可能还要加-D参数以调试模式启动,否则偶尔会被系统杀掉。参数说明:frida-server 版本必须与宿主机frida一致,直接pip install frida-tools安装的配套版本即可。启动后不要关终端,否则 server 会退出。
4.2 编写Frida脚本:Hook SSL_CTX_set_verify与X509_VERIFY_PARAM
绕过校验的核心思路很简单:让 SSL 握手中的所有证书校验回调都直接通过。常规脚本只需 Hook 两个地方:SSL_CTX_set_verify和X509_VERIFY_PARAM_set1_host。前者管是否校验,后者管主机名校验。
下面是一个能用的 Frida 脚本骨架,适用于 Android 上使用 OpenSSL 的 native 库。脚本里 Hook 了SSL_CTX_set_verify,把校验模式强制改成SSL_VERIFY_NONE;同时 HookX509_VERIFY_PARAM_set1_host,让它忽略域名检查。如果目标使用 BoringSSL,函数名会略有不同,但逻辑是同一套。
Interceptor.attach(Module.findExportByName(null, "SSL_CTX_set_verify"), { onEnter: function (args) { args[2] = ptr(0); // SSL_VERIFY_NONE console.log("[+] SSL_CTX_set_verify -> SSL_VERIFY_NONE"); } }); Interceptor.attach(Module.findExportByName(null, "X509_VERIFY_PARAM_set1_host"), { onEnter: function (args) { args[2] = ptr(0); // hostname check disabled console.log("[+] hostname verification disabled"); } });逻辑说明:Interceptor.attach在函数入口处修改参数。第一个 Hook 里args[2]是第三个参数,也就是 SSL 校验模式,改成 0 表示完全不校验证书链。第二个 Hook 把主机名校验参数清空,因为代理工具的证书域名和目标不一致时会在这里失败。参数说明:Module.findExportByName(null, ...)会在所有已加载模块里查找导出函数,如果目标进程把符号表剥了,可能找不到导出名,需要改成从特定 so 文件里查找。如果 Hook 后提示找不到导出,可以先frida -U 进程名 -l script.js跑一下看日志。
4.3 配置代理与证书:让HTTP层请求浮现
脚本运行只是第一步,抓包工具还得能解密流量。代理端设置好监听端口,把模拟器的 WiFi 代理指到宿主机的 IP 和端口,然后在模拟器上安装抓包工具的根证书。安装证书要注意 Android 7 之后用户证书默认不被 App 信任,必须把证书复制到系统 CA 目录,重启模拟器才生效。
adb push charles_ca.der /data/local/tmp/charles_ca.der adb shell "openssl x509 -inform DER -in /data/local/tmp/charles_ca.der -out /system/etc/security/cacerts/charles_ca.pem" adb shell "chmod 644 /system/etc/security/cacerts/charles_ca.pem" adb reboot逻辑说明:第一行把抓包工具的 DER 格式证书推到设备,第二行用 openssl 把它转成 PEM 格式并写入系统 CA 目录,第三行改权限,第四行重启。因为系统证书目录只读,如果 adb root 后仍然不能写,需要先执行adb remount或使用可写系统镜像。参数说明:证书文件名不能带特殊字符,文件名中的 CN 字段最好和证书内容一致,否则某些版本系统解析会失败。重启后别忘了重新设置代理,因为重启会清掉 WiFi 设置。
完成这些后,打开小程序,抓包工具里就能看到 HTTPS 请求变成明文。注意如果之前已经用脚本 Hook 了 SSL 校验,这一步理论上会直接成功。
4.4 处理双向认证:额外Hook客户端证书回调
如果目标接口开了双向认证,单纯让客户端不校验服务器还不够,因为代理拿不到客户端证书。这时要用 Frida 再 Hook 两个函数:SSL_CTX_set_client_cert_cb和SSL_CTX_use_certificate。前者是客户端证书回调,后者是直接设置证书。常见的做法是让回调返回 0,表示“没有客户端证书”,但服务端会拒绝。另一个做法是劫持握手过程,把目标 App 真正要用的客户端证书提取出来喂给代理。
实际操作中我一般会先在日志里看报错信息。如果抓包工具显示 “client certificate required”,说明服务端强制要客户端证书。此时需要从 App 的资源或 native 库里找到预置的 PKCS12 文件,提取证书和私钥,导入代理工具当客户端证书用。这个操作偏业务,不同小程序差异很大,这里不展开。但记住一个判断:如果绕过脚本和证书都配置好了,抓包工具里依旧只有 CONNECT 记录,十有八九是双向校验没处理。
5. 避坑指南:绕过SSL验证后的五个典型翻车点
5.1 现象:代理已连接,却一直无流量
代理和证书都配置好,打开小程序后抓包工具里只有初始的 CONNECT 请求,之后没有任何数据。最常见原因是 Frida 脚本没有生效或进程没选对。某支付平台小程序不止一个进程,网络请求跑在子进程里,而脚本只 attach 了主进程。
解决方法是先看 Frida 控制台输出,确认SSL_CTX_set_verify -> SSL_VERIFY_NONE有没有打出来。如果没有,改用frida-ps -U找到网络子进程的进程名,重新 attach。还有一种可能:脚本虽然 Hook 了,但目标用的不是 OpenSSL 而是 BoringSSL,函数名对不上。这时需要在 native 模块里搜SSL_CTX_set_verify的替代导出,比如SSL_CTX_set_custom_verify。
5.2 现象:SSL握手失败,报错unexpected eof
客户端和代理之间建立了 TCP 连接,但 TLS 握手在 ClientHello 之后立刻中断。原因一般是代理没有正确解密,或者客户端发送了代理无法理解的扩展字段。某支付平台上这个报错特别常出现在 HTTP/2 或 TLS 1.3 场景。
解决方法是先在抓包工具里把 SSL 代理的 TLS 版本降到 1.2,并关闭 HTTP/2 的优先知识,让连接回退到 HTTP/1.1。如果还报错,检查证书路径是否被系统目录的 Pinning 逻辑干扰。偶尔是模拟器时间不准确,导致证书校验时把有效期判断成过期,同步系统时间后再试。
5.3 现象:小程序启动即闪退或弹窗警告
绕过后小程序直接崩溃,通常不是因为 SSL Hook,而是因为目标检测到了代理环境或 Frida 注入。某支付平台的反调试逻辑会做端口扫描,检测到 8080 代理端口就主动退出。模拟器里更容易触发,因为宿主 App 对模拟器特征敏感。
解决方法是换用透明代理方案,避免使用固定的 HTTP 代理端口,或者用 iptables 把流量转发到透明代理,这样客户端感觉不到代理设置。另一种思路是把 Frida 的检测绕过也加上,比如 Hookopen和connect系统调用,把端口扫描结果伪装成空。但这类对抗是动态的,建议优先选择代理方式升级,而不是花时间对抗反调试。
5.4 现象:只能抓到部分请求,图片和消息仍加密
部分请求是明文,但图片、音视频或长连接的请求仍然是 TLS。原因是脚本只 Hook 了 SSL_CTX_set_verify,但这个函数只作用于当前网络栈。如果小程序内部有多套网络库或者走了 QUIC,就会漏掉一部分流量。
解决办法是观察漏掉的流量特征,如果走 QUIC,会占用 UDP 443 端口,代理工具默认不拦截 UDP,需要再用 Frida Hook QUIC 的证书校验函数,或者直接把 QUIC 禁用。如果走的是系统 WebView 发起的长连接,额外 Hook 一下android.security.net.config.config相关调用也能覆盖。这一步比较费时,但能完整抓到所有接口。
5.5 现象:抓包成功,但重放请求被拒绝
抓到了明文请求,用 curl 原样重放却返回 401 或业务错误码。原因通常是服务端做了内容签名或时效校验,而签名计算逻辑依赖本地密钥,不是单纯拼一个头就能通过。
解决方法是不要跳过签名模块。用 Frida 在 App 内存里找到签名函数,把签名计算过程完整复刻,或者直接在重放时动态调用签名函数。常见做法是写一个小的辅助脚本,把参数传给 App 内的签名方法,拿返回值后再发 HTTP 请求。这一步真正考验耐心,也是这类安全测试里最需要反复验证的部分。
6. 验证与进阶:从一次抓包到可回归的自动化验证
6.1 用curl验证抓包结果
抓包工具里看到明文后,我一般会先复制一条请求,用 curl 在宿主机上直接发一遍。如果原样返回成功,说明代理中间的改动没有影响请求完整性。如果返回失败,就按照前一条重放被拒的原因排查签名或时序问题。这一小步能提前暴露“抓得到但用不了”的尴尬。
6.2 把Frida Hook固化到回归脚本
临时抓包场景跑一次就够了,但如果要持续观察接口变更,就把 Frida 脚本整理成一个独立文件,和代理配置、证书安装步骤写在一个启动脚本里。每次拉起模拟器后执行一遍,自动启动 frida-server、设置代理、导入证书,最后 attach 到目标进程。这样不需要重复踩配置的坑。
6.3 合规边界与我的习惯
回到开头那个任务。我最后通过 Frida Hook 绕过了 SSL 校验,抓齐了某支付平台小程序的全部接口,还把签名逻辑复刻进了回归脚本。但这里要划一条清晰线:整个过程只发生在授权测试环境,目标是自己公司或客户明确委托的资产,绝不能拿这套方法去碰未授权的系统。
从那以后,我每次开始这类尝试前都会强制走一遍三件事:确认授权范围、确认测试设备属于自己、确认抓包数据不落地到公共存储。这个习惯帮我避开过不少麻烦,也希望帮到你。如果哪一步卡住,优先检查进程、证书和函数名,实操里九成问题都出在这三处。
本文还有配套的精品资源,点击获取