SSL Pinning绕过技术

SSL Pinning(SSL证书锁定)是App安全防护的重要手段,但也是爬虫需要突破的关键点。

SSL Pinning原理与绕过方法

1. 传统SSL Pinning绕过

// Java层SSL Pinning绕过示例(Frida脚本)
Java.perform(function() {
    // 绕过OkHttp3的CertificatePinner
    try {
        var CertificatePinner = Java.use('okhttp3.CertificatePinner');
        CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(str, obj) {
            console.log('[+] Bypassing OkHTTP v3.x Pinning');
            return;
        };
    } catch(err) {
        console.log('[-] OkHTTP v3.x Pinning not found');
    }

    // 绕过TrustManager
    try {
        var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
        TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
            console.log('[+] Bypassing TrustManagerImpl pinning: ' + host);
            return untrustedChain;
        };
    } catch(err) {
        console.log('[-] TrustManagerImpl not found');
    }

    // 绕过X509TrustManager
    try {
        var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
        var SSLContext = Java.use('javax.net.ssl.SSLContext');

        // Hook SSLContext.init方法
        SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom').implementation = function(keyManager, trustManager, secureRandom) {
            console.log('[+] Hooking SSLContext.init()');
            SSLContext.init.call(this, keyManager, [X509TrustManager.$new()], secureRandom);
        };

        // 实现X509TrustManager接口
        var TrustAllCerts = Java.registerClass({
            name: 'com.example.TrustAllCerts',
            implements: [X509TrustManager],
            methods: {
                checkClientTrusted: function(chain, authType) {},
                checkServerTrusted: function(chain, authType) {},
                getAcceptedIssuers: function() { return []; }
            }
        });
    } catch(err) {
        console.log('[-] X509TrustManager bypass not found');
    }
});

2. SSL Pinning绕过Python脚本

import frida
import sys
import time
from typing import Optional, Dict, Any

class SSLBypassInjector:
    """SSL Pinning绕过注入器"""
    
    def __init__(self, app_package: str):
        self.app_package = app_package
        self.device = None
        self.session = None
        self.script = None
        
        # SSL Pinning绕过脚本
        self.ssl_bypass_script = """
        // SSL Pinning绕过脚本
        Java.perform(function() {
            // 绕过OkHttp3
            try {
                var CertificatePinner = Java.use('okhttp3.CertificatePinner');
                CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(str, obj) {
                    console.log('[+] Bypassing OkHTTP v3.x Pinning for: ' + str);
                    return;
                };
                console.log('[+] Hooked OkHTTP v3.x CertificatePinner');
            } catch(e) {
                console.log('[-] OkHTTP v3.x CertificatePinner not found: ' + e.message);
            }

            // 绕过OkHttp4
            try {
                var CertificatePinner4 = Java.use('okhttp3.internal.tls.CertificatePinnerChainCleaner');
                console.log('[+] Hooked OkHTTP v4 CertificatePinnerChainCleaner');
            } catch(e) {
                console.log('[-] OkHTTP v4 CertificatePinnerChainCleaner not found: ' + e.message);
            }

            // 绕过TrustManager
            try {
                var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
                var SSLContext = Java.use('javax.net.ssl.SSLContext');

                var TrustManager = Java.registerClass({
                    name: 'dev.asd.test.TrustManager',
                    implements: [X509TrustManager],
                    methods: {
                        checkClientTrusted: function(chain, authType) {},
                        checkServerTrusted: function(chain, authType) {},
                        getAcceptedIssuers: function() { return []; }
                    }
                });

                var TrustManagers = [TrustManager.$new()];
                var SSLContext_init = SSLContext.init.overload(
                    '[Ljavax.net.ssl.KeyManager;', 
                    '[Ljavax.net.ssl.TrustManager;', 
                    'java.security.SecureRandom'
                );
                
                SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
                    console.log('[+] Bypassing TrustManager init');
                    SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
                };

                console.log('[+] Hooked SSLContext.init');
            } catch(e) {
                console.log('[-] TrustManager bypass failed: ' + e.message);
            }

            // 绕过Apache HTTP Client
            try {
                var CustomSSLSocketFactory = Java.use('org.apache.http.conn.ssl.SSLSocketFactory');
                CustomSSLSocketFactory.isSecure.implementation = function(socket) {
                    console.log('[+] Bypassing Apache HTTP Client SSL');
                    return true;
                };
                console.log('[+] Hooked Apache HTTP Client SSLSocketFactory');
            } catch(e) {
                console.log('[-] Apache HTTP Client bypass not found: ' + e.message);
            }

            // 绕过NetworkSecurityConfig (Android 7.0+)
            try {
                var NetworkSecurityConfig = Java.use('android.security.net.config.NetworkSecurityConfigProvider');
                console.log('[+] Hooked NetworkSecurityConfigProvider');
            } catch(e) {
                console.log('[-] NetworkSecurityConfigProvider not found: ' + e.message);
            }
        });
        """
    
    def connect_device(self) -> bool:
        """连接到Frida设备"""
        try:
            # 获取USB设备
            devices = frida.enumerate_usb_devices()
            if not devices:
                print("❌ 未找到USB连接的设备")
                return False
            
            # 选择第一个设备
            self.device = devices[0]
            print(f"✅ 连接到设备: {self.device.name} - {self.device.id}")
            return True
        except Exception as e:
            print(f"❌ 连接设备失败: {e}")
            return False
    
    def attach_to_app(self) -> bool:
        """附加到目标应用"""
        try:
            # 附加到正在运行的应用或启动应用
            self.session = self.device.attach(self.app_package)
            print(f"✅ 附加到应用: {self.app_package}")
            return True
        except frida.ProcessNotFoundError:
            print(f"⚠️  应用未运行,尝试启动: {self.app_package}")
            try:
                pid = self.device.spawn([self.app_package])
                self.session = self.device.attach(pid)
                self.device.resume(pid)  # 启动应用
                print(f"✅ 启动并附加到应用: {self.app_package}")
                return True
            except Exception as e:
                print(f"❌ 启动应用失败: {e}")
                return False
        except Exception as e:
            print(f"❌ 附加到应用失败: {e}")
            return False
    
    def inject_ssl_bypass(self) -> bool:
        """注入SSL Pinning绕过脚本"""
        try:
            # 创建脚本
            self.script = self.session.create_script(self.ssl_bypass_script)
            
            # 加载脚本
            self.script.load()
            print("✅ SSL Pinning绕过脚本注入成功")
            
            # 保持连接
            print("ℹ️  SSL Pinning绕过已激活,开始监控网络请求...")
            
            # 保持脚本运行
            return True
        except Exception as e:
            print(f"❌ 脚本注入失败: {e}")
            return False
    
    def start_ssl_bypass(self) -> bool:
        """启动完整的SSL Pinning绕过流程"""
        print(f"🚀 开始绕过 {self.app_package} 的SSL Pinning...")
        
        if not self.connect_device():
            return False
        
        if not self.attach_to_app():
            return False
        
        if not self.inject_ssl_bypass():
            return False
        
        print("🎉 SSL Pinning绕过设置完成!")
        print("📝 提示: 现在可以使用Fiddler/Charles等工具监控HTTPS流量")
        
        return True
    
    def cleanup(self):
        """清理资源"""
        if self.script:
            try:
                self.script.unload()
                print("🧹 SSL绕过脚本已卸载")
            except:
                pass
        
        if self.session:
            try:
                self.session.detach()
                print("🧹 会话已分离")
            except:
                pass

# Frida SSL Pinning绕过工具类
class AdvancedSSLBypass:
    """高级SSL Pinning绕过工具"""
    
    @staticmethod
    def create_universal_bypass_script() -> str:
        """创建通用的SSL Pinning绕过脚本"""
        return """
        console.log("[+] Universal SSL Pinning Bypass Script Loaded");

        // 通用绕过函数
        function universalBypass() {
            // 1. 绕过OkHttp系列
            var okHttpClasses = [
                'okhttp3.CertificatePinner',
                'okhttp3.internal.tls.CertificatePinnerChainCleaner',
                'com.squareup.okhttp.CertificatePinner'
            ];

            okHttpClasses.forEach(function(className) {
                try {
                    var clazz = Java.use(className);
                    if (clazz.check) {
                        clazz.check.implementation = function() {
                            console.log('[+] Bypassing ' + className);
                        };
                    }
                    if (clazz.checkChain) {
                        clazz.checkChain.implementation = function() {
                            console.log('[+] Bypassing ' + className + ' chain check');
                        };
                    }
                    console.log('[+] Hooked ' + className);
                } catch(e) {
                    console.log('[-] Not found: ' + className);
                }
            });

            // 2. 绕过系统TrustManager
            try {
                var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
                var SSLContext = Java.use('javax.net.ssl.SSLContext');

                var MyTrustManager = Java.registerClass({
                    name: 'com.example.MyTrustManager',
                    implements: [X509TrustManager],
                    methods: {
                        checkClientTrusted: function(chain, authType) {
                            console.log('[+] checkClientTrusted bypassed');
                        },
                        checkServerTrusted: function(chain, authType) {
                            console.log('[+] checkServerTrusted bypassed for: ' + authType);
                        },
                        getAcceptedIssuers: function() {
                            console.log('[+] getAcceptedIssuers called');
                            return [];
                        }
                    }
                });

                // Hook SSLContext初始化
                if (SSLContext.init.overload) {
                    var initOverloads = SSLContext.init.overloads;
                    initOverloads.forEach(function(overload) {
                        overload.implementation = function(keyManager, trustManager, secureRandom) {
                            console.log('[+] SSLContext.init bypassed');
                            this.init(keyManager, [MyTrustManager.$new()], secureRandom);
                        };
                    });
                }
            } catch(e) {
                console.log('[-] System TrustManager bypass failed: ' + e.message);
            }

            // 3. 绕过WebView SSL
            try {
                var WebViewClient = Java.use('android.webkit.WebViewClient');
                if (WebViewClient.onReceivedSslError) {
                    WebViewClient.onReceivedSslError.implementation = function(view, handler, error) {
                        console.log('[+] WebView SSL error bypassed');
                        handler.proceed(); // 忽略SSL错误
                    };
                }
                console.log('[+] Hooked WebViewClient SSL error handling');
            } catch(e) {
                console.log('[-] WebViewClient not found: ' + e.message);
            }

            // 4. 绕过Apache HttpClient
            try {
                var SSLSocketFactory = Java.use('org.apache.http.conn.ssl.SSLSocketFactory');
                SSLSocketFactory.isSecure.implementation = function(socket) {
                    console.log('[+] Apache HttpClient SSL bypassed');
                    return true;
                };
                console.log('[+] Hooked Apache HttpClient');
            } catch(e) {
                console.log('[-] Apache HttpClient not found: ' + e.message);
            }

            // 5. 绕过 conscrypt
            try {
                var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
                if (TrustManagerImpl.verifyChain) {
                    TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
                        console.log('[+] Conscrypt TrustManagerImpl bypassed for: ' + host);
                        return untrustedChain;
                    };
                }
                console.log('[+] Hooked Conscrypt TrustManagerImpl');
            } catch(e) {
                console.log('[-] Conscrypt classes not found: ' + e.message);
            }
        }

        // 在Java加载完成后执行
        Java.perform(universalBypass);
        """
    
    @staticmethod
    def bypass_with_spawn(app_package: str) -> Any:
        """使用spawn方式绕过SSL Pinning"""
        try:
            device = frida.get_usb_device(timeout=5)
            pid = device.spawn([app_package])
            session = device.attach(pid)
            
            script_content = AdvancedSSLBypass.create_universal_bypass_script()
            script = session.create_script(script_content)
            script.load()
            
            device.resume(pid)
            
            print(f"[+] Spawn bypass activated for {app_package}")
            return session, script
        except Exception as e:
            print(f"[-] Spawn bypass failed: {e}")
            return None, None

def demonstrate_ssl_bypass():
    """演示SSL Pinning绕过"""
    print("🔍 SSL Pinning绕过技术演示")
    
    # 注意:实际使用时需要替换为真实的应用包名
    app_package = "com.example.target.app"  # 替换为实际目标应用包名
    
    print(f"\n📦 目标应用: {app_package}")
    print("⚠️  注意: 需要设备已ROOT并安装Frida Server")
    
    # 创建绕过工具实例
    bypass_tool = SSLBypassInjector(app_package)
    
    try:
        # 启动绕过
        success = bypass_tool.start_ssl_bypass()
        
        if success:
            print("\n🎯 SSL Pinning绕过已激活!")
            print("📋 现在可以使用抓包工具监控HTTPS流量")
            
            # 保持运行一段时间
            try:
                print("⏳ 保持连接中... 按Ctrl+C退出")
                while True:
                    time.sleep(1)
            except KeyboardInterrupt:
                print("\n🛑 用户中断")
        else:
            print("\n❌ SSL Pinning绕过失败")
    
    except Exception as e:
        print(f"\n❌ 演示过程中出现错误: {e}")
    
    finally:
        # 清理资源
        bypass_tool.cleanup()

if __name__ == "__main__":
    demonstrate_ssl_bypass()