某电商平台 App 签名校验破解实战:Frida + Xposed 双路 Hook 过掉 SSL Pinning # 某电商平台 App 签名校验破解实战Frida Xposed 双路 Hook 过掉 SSL Pinning ## 一、前言 最近在研究某电商平台的支付接口抓包发现所有请求都是 HTTPS而且 App 做了 SSL Pinning证书绑定。直接用 Charles 抓包全部是乱码连握手都过不去。 这篇文章记录我是怎么绕过去的。两种方案都写了Frida临时调试和 Xposed持久化看场景选。 先交代背景 - 目标 App某知名电商平台最新版 v9.8.6 - 设备Pixel 3Android 12已 root - 工具Frida 16.x Xposed 框架 Charles 4.6 ## 二、什么是 SSL Pinning 简单说SSL Pinning 就是 App 在代码里写死了服务器的证书指纹SHA256 值只信任这个证书。 Charles 的中间人证书不在信任列表里所以 App 检测到证书不一致就直接断开连接。 破解思路就是Hook 掉证书校验过程让它信任任何一个证书。 ## 三、方案一Frida 动态 Hook快速验证 ### 3.1 先确认 App 有没有 root 检测 先得绕过 root 检测不然 Frida 都挂不上去 javascript // frida-root-bypass.js function bypassRootCheck() { Java.perform(function() { // 常见 root 检测类 var RootBeer Java.use(com.scottyab.rootbeer.RootBeer); RootBeer.isRooted.implementation function() { return false; }; // 文件路径检测 var File Java.use(java.io.File); File.exists.implementation function() { var path this.getAbsolutePath(); var sensitivePaths [/su, /magisk, /xposed]; for (var i 0; i sensitivePaths.length; i) { if (path.indexOf(sensitivePaths[i]) 0) { return false; } } return this.exists(); }; }); } setImmediate(bypassRootCheck); bash frida -U -l frida-root-bypass.js com.target.app ### 3.2 Hook 证书校验 接下来是重头戏——过 SSL Pinning javascript // frida-ssl-bypass.js Java.perform(function() { // 方案AHook TrustManager var TrustManagerImpl Java.use(com.android.org.conscrypt.TrustManagerImpl); TrustManagerImpl.verifyChain.implementation function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log([] Bypassing SSL verification for: host); return untrustedChain; }; // 方案BHook HostnameVerifier var HostnameVerifier Java.use(javax.net.ssl.HostnameVerifier); HostnameVerifier.verify.implementation function(hostname, session) { console.log([] Hostname verify bypass: hostname); return true; }; // 方案C全局设置所有HostnameVerifier var HttpsURLConnection Java.use(javax.net.ssl.HttpsURLConnection); HttpsURLConnection.setDefaultHostnameVerifier.implementation function(v) { // do nothing }; console.log([] SSL Bypass hooks installed); }); 但有时候只 Hook TrustManager 不够因为有些 App 用了 OkHttp 的 certificatePinner javascript // 针对 OkHttp 的 CertificatePinner Java.perform(function() { var CertificatePinner Java.use(okhttp3.CertificatePinner); CertificatePinner.check$okhttp.implementation function(hostname, peerCertificates) { console.log([] OkHttp SSL pinning bypass: hostname); // 不抛异常就是过掉了 }; // 或者直接返回 null不校验 var Builder Java.use(okhttp3.CertificatePinner$Builder); Builder.build.implementation function() { return null; }; }); ### 3.3 启动 Frida 同时加载所有脚本 bash frida -U -l frida-root-bypass.js -l frida-ssl-bypass.js -f com.target.app --no-pause 启动后 Charles 就能正常抓到包了。 ## 四、方案二Xposed 模块持久化一劳永逸 Frida 的方案适合开发调试但每次都要电脑连着。如果你需要长期抓包分析用 Xposed 做个模块更省事。 ### 4.1 新建 Xposed 模块 xml ### 4.2 核心 Hook 逻辑 java public class MainHook implements IXposedHookLoadPackage { Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { if (lpparam.packageName.equals(com.target.app)) { // Hook TrustManager XposedHelpers.findAndHookMethod( com.android.org.conscrypt.TrustManagerImpl, lpparam.classLoader, verifyChain, List.class, List.class, String.class, boolean.class, byte[].class, byte[].class, new XC_MethodHook() { Override protected Object afterHookedMethod(MethodHookParam param) { XposedBridge.log([SSL] Bypassed verification); return param.args[0]; // 直接返回未校验链 } } ); // Hook CertificatePinner try { Class pinner lpparam.classLoader.loadClass(okhttp3.CertificatePinner); XposedBridge.hookAllMethods(pinner, check, new XC_MethodHook() { Override protected void beforeHookedMethod(MethodHookParam param) { XposedBridge.log([SSL] OkHttp pinner bypassed); param.setResult(null); // 跳过校验 } }); } catch (Exception e) { XposedBridge.log([SSL] OkHttp not found, skipping: e.getMessage()); } } } } ### 4.3 编译安装 bash # 用 Android Studio 打包或者命令行 gradlew assembleRelease adb install build/outputs/apk/release/module.apk 装完后在 Xposed Installer 里勾上模块重启。 之后打开目标 AppCharles 就能直接抓包了不需要再插电脑。 ## 五、抓包分析与发现 SSL Pinning 破解后来看看能抓到什么 ### 5.1 请求结构分析 POST /api/v2/order/create ----- Headers ----- sid: a1b2c3d4e5 ← session id token: eyJhbGciOiJIUzI1Ni... ← JWT ts: 1747692345678 ← 时间戳 sign: b3f7a9c1... ← 签名 ----- Body ----- { product_id: PID_10086, sku_id: SKU_9527, pay_type: wxpay, coupon_id: CPN_001 } ### 5.2 签名算法的发现 sign 字段最有意思。通过 Hook MessageDigest 类能追踪到签名的生成过程 javascript // Frida Hook 追踪 MD5/SHA Java.perform(function() { var MessageDigest Java.use(java.security.MessageDigest); MessageDigest.digest.overload([B).implementation function(input) { var result this.digest(input); console.log([MD] digest: bytesToHex(input) - bytesToHex(result)); if (input.length 500) { console.log( Input string: String.fromCharCode.apply(null, new Uint8Array(input))); } return result; }; }); function bytesToHex(bytes) { var arr []; for (var i 0; i bytes.length; i) { arr.push((0 (bytes[i] 0xFF).toString(16)).slice(-2)); } return arr.join(); } 通过日志追踪发现签名的生成方式是MD5(参数key排序拼接 secret_key)。这个 secret_key 是嵌在 so 层里的需要进一步逆向。 ### 5.3 Hook so 层进阶 对于嵌在 so 层的 secret用 Frida 的 NativeFunction javascript var secretAddr Module.findExportByName(libnative-lib.so, getSecret); if (secretAddr) { var getSecret new NativeFunction(secretAddr, pointer, []); var secret getSecret().readCString(); console.log([] Secret found: secret); } ## 六、踩坑记录 ### 6.1 App 检测 Frida 部分 App 会检测 frida-server 进程名、默认端口27042、/data/local/tmp 目录下的 frida 文件。 应对方法 bash # 重命名 frida-server cp /data/local/tmp/frida-server /data/local/tmp/frida64 /data/local/tmp/frida64 -l 0.0.0.0:29999 # 连接时指定端口 frida -H 192.168.1.100:29999 ### 6.2 App 检测 Xposed 更精明的 App 会检测 Xposed 自身找 de.robv.android.xposed.XposedBridge 等类。 方案是用 EdXposedLSPosed 的前身配合黑名单模式只对目标 App 启用模块。 ## 七、总结 SSL Pinning 本质上是客户端信任链的问题。只要 App 的控制权在用户手里root 设备理论上没有绕不过去的证书校验。 技术路线总结 1. **Frida 方案**适合临时调试、验证灵活但需要连接电脑 2. **Xposed 方案**适合持久化使用装一次管永久 3. **so 层方案**适合算法逆向找到密钥就能离线计算 最后提醒一句技术无罪但别拿这个搞违法的事。保护好自己的路。 ## 八、参考 - [Frida 官方文档 - SSL Pinning Bypass](https://frida.re/docs/examples/android/) - [OkHttp CertificatePinner 源码分析](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-certificate-pinner/) - [Xposed Framework API](https://api.xposed.info/reference/packages.html)