app抓包专题

一、系统代理-NO_PROXY

  • 安卓开发时设置NO_PROXY,手机会不走系统代理,比如得物
  • 绕过方式:
    • 1、软件

      • Drony、SocksDroid(推荐)、ProxyDroid、Postern
      • 注意:需要关闭手机上的系统代理
    • 2、frida Hook

      Java.perform(function () {
          var Builder = Java.use("okhttp3.OkHttpClient$Builder");
      
          Builder.proxy.implementation = function (proxy) {
              var res = this.proxy(null);
              return res;
          }
      });
      
      // frida -U -f com.shizhuang.duapp -l  hook-proxy.js 

      注意:frida检测,对于得物,需要删除 libmsaoaidsec.so 文件
      适用于okhttp3发送请求,对于第三方包进行混淆,Hook方式无法绕过,需要借助于不会被混淆的系统包。

二、客户端证书校验

  • - 在客户端中预设证书信息
    - 客户端向服务端发送请求,将服务端返回的证书信息(公钥)和客户端预设证书信息进行校验
  • SSL PINNING是Google官方推荐的校验方式,原理是在客户端(安卓APP)中预先设置好证书信息,握手时与服务端返回的证书进行比较。如果有这种客户端校验,那么charles等抓包工具,就无法抓包了。因为charles作为中间人返回给客户端的证书信息与原客户端预先设置的不一致,所以,客户端检测到,就拒绝发送请求了。
  • 绕过方式:
    • 1、frida Hook

      • frida_multiple_unpinning.js
        /*  Android ssl certificate pinning bypass script for various methods
            by Maurizio Siddu
        
            Run with:
            frida -U -f [APP_ID] -l frida_multiple_unpinning.js --no-pause
            frida -UF -l frida_multiple_unpinning.js
        */
        
        setTimeout(function() {
            Java.perform(function() {
                console.log('');
                console.log('======');
                console.log('[#] Android Bypass for various Certificate Pinning methods [#]');
                console.log('======');
        
        
                var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
                var SSLContext = Java.use('javax.net.ssl.SSLContext');
        
                // TrustManager (Android < 7) //
                ////////////////////////////////
                var TrustManager = Java.registerClass({
                    // Implement a custom TrustManager
                    name: 'dev.asd.test.TrustManager',
                    implements: [X509TrustManager],
                    methods: {
                        checkClientTrusted: function(chain, authType) {},
                        checkServerTrusted: function(chain, authType) {},
                        getAcceptedIssuers: function() {return []; }
                    }
                });
                // Prepare the TrustManager array to pass to SSLContext.init()
                var TrustManagers = [TrustManager.$new()];
                // Get a handle on the init() on the SSLContext class
                var SSLContext_init = SSLContext.init.overload(
                    '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');
                try {
                    // Override the init method, specifying the custom TrustManager
                    SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
                        console.log('[+] Bypassing Trustmanager (Android < 7) pinner');
                        SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
                    };
                } catch (err) {
                    console.log('[-] TrustManager (Android < 7) pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // OkHTTPv3 (quadruple bypass) //
                /////////////////////////////////
                try {
                    // Bypass OkHTTPv3 {1}
                    var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');
                    okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
                        console.log('[+] Bypassing OkHTTPv3 {1}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] OkHTTPv3 {1} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass OkHTTPv3 {2}
                    // This method of CertificatePinner.check is deprecated but could be found in some old Android apps
                    var okhttp3_Activity_2 = Java.use('okhttp3.CertificatePinner');
                    okhttp3_Activity_2.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function(a, b) {
                        console.log('[+] Bypassing OkHTTPv3 {2}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] OkHTTPv3 {2} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass OkHTTPv3 {3}
                    var okhttp3_Activity_3 = Java.use('okhttp3.CertificatePinner');
                    okhttp3_Activity_3.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(a, b) {
                        console.log('[+] Bypassing OkHTTPv3 {3}: ' + a);
                        return;
                    };
                } catch(err) {
                    console.log('[-] OkHTTPv3 {3} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass OkHTTPv3 {4}
                    var okhttp3_Activity_4 = Java.use('okhttp3.CertificatePinner');
                    //okhttp3_Activity_4['check$okhttp'].implementation = function(a, b) {
                    okhttp3_Activity_4.check$okhttp.overload('java.lang.String', 'kotlin.jvm.functions.Function0').implementation = function(a, b) {
                        console.log('[+] Bypassing OkHTTPv3 {4}: ' + a);
                        return;
                    };
                } catch(err) {
                    console.log('[-] OkHTTPv3 {4} pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Trustkit (triple bypass) //
                //////////////////////////////
                try {
                    // Bypass Trustkit {1}
                    var trustkit_Activity_1 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier');
                    trustkit_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b) {
                        console.log('[+] Bypassing Trustkit {1}: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Trustkit {1} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass Trustkit {2}
                    var trustkit_Activity_2 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier');
                    trustkit_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(a, b) {
                        console.log('[+] Bypassing Trustkit {2}: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Trustkit {2} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass Trustkit {3}
                    var trustkit_PinningTrustManager = Java.use('com.datatheorem.android.trustkit.pinning.PinningTrustManager');
                    trustkit_PinningTrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function(chain, authType) {
                        console.log('[+] Bypassing Trustkit {3}');
                        //return;
                    };
                } catch (err) {
                    console.log('[-] Trustkit {3} pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // TrustManagerImpl (Android > 7) //
                ////////////////////////////////////
                try {
                    // Bypass TrustManagerImpl (Android > 7) {1}
                    var array_list = Java.use("java.util.ArrayList");
                    var TrustManagerImpl_Activity_1 = Java.use('com.android.org.conscrypt.TrustManagerImpl');
                    TrustManagerImpl_Activity_1.checkTrustedRecursive.implementation = function(certs, ocspData, tlsSctData, host, clientAuth, untrustedChain, trustAnchorChain, used) {
                        console.log('[+] Bypassing TrustManagerImpl (Android > 7) checkTrustedRecursive check: '+ host);
                        return array_list.$new();
                    };
                } catch (err) {
                    console.log('[-] TrustManagerImpl (Android > 7) checkTrustedRecursive check not found');
                    //console.log(err);
                }
                try {
                    // Bypass TrustManagerImpl (Android > 7) {2} (probably no more necessary)
                    var TrustManagerImpl_Activity_2 = Java.use('com.android.org.conscrypt.TrustManagerImpl');
                    TrustManagerImpl_Activity_2.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
                        console.log('[+] Bypassing TrustManagerImpl (Android > 7) verifyChain check: ' + host);
                        return untrustedChain;
                    };
                } catch (err) {
                    console.log('[-] TrustManagerImpl (Android > 7) verifyChain check not found');
                    //console.log(err);
                }
        
        
        
        
        
                // Appcelerator Titanium PinningTrustManager //
                ///////////////////////////////////////////////
                try {
                    var appcelerator_PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');
                    appcelerator_PinningTrustManager.checkServerTrusted.implementation = function(chain, authType) {
                        console.log('[+] Bypassing Appcelerator PinningTrustManager');
                        return;
                    };
                } catch (err) {
                    console.log('[-] Appcelerator PinningTrustManager pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Fabric PinningTrustManager //
                ////////////////////////////////
                try {
                    var fabric_PinningTrustManager = Java.use('io.fabric.sdk.android.services.network.PinningTrustManager');
                    fabric_PinningTrustManager.checkServerTrusted.implementation = function(chain, authType) {
                        console.log('[+] Bypassing Fabric PinningTrustManager');
                        return;
                    };
                } catch (err) {
                    console.log('[-] Fabric PinningTrustManager pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // OpenSSLSocketImpl Conscrypt (double bypass) //
                /////////////////////////////////////////////////
                try {
                    var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
                    OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, JavaObject, authMethod) {
                        console.log('[+] Bypassing OpenSSLSocketImpl Conscrypt {1}');
                    };
                } catch (err) {
                    console.log('[-] OpenSSLSocketImpl Conscrypt {1} pinner not found');
                    //console.log(err);
                }
                try {
                    var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
                    OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certChain, authMethod) {
                        console.log('[+] Bypassing OpenSSLSocketImpl Conscrypt {2}');
                    };
                } catch (err) {
                    console.log('[-] OpenSSLSocketImpl Conscrypt {2} pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // OpenSSLEngineSocketImpl Conscrypt //
                ///////////////////////////////////////
                try {
                    var OpenSSLEngineSocketImpl_Activity = Java.use('com.android.org.conscrypt.OpenSSLEngineSocketImpl');
                    OpenSSLEngineSocketImpl_Activity.verifyCertificateChain.overload('[Ljava.lang.Long;', 'java.lang.String').implementation = function(a, b) {
                        console.log('[+] Bypassing OpenSSLEngineSocketImpl Conscrypt: ' + b);
                    };
                } catch (err) {
                    console.log('[-] OpenSSLEngineSocketImpl Conscrypt pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // OpenSSLSocketImpl Apache Harmony //
                //////////////////////////////////////
                try {
                    var OpenSSLSocketImpl_Harmony = Java.use('org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl');
                    OpenSSLSocketImpl_Harmony.verifyCertificateChain.implementation = function(asn1DerEncodedCertificateChain, authMethod) {
                        console.log('[+] Bypassing OpenSSLSocketImpl Apache Harmony');
                    };
                } catch (err) {
                    console.log('[-] OpenSSLSocketImpl Apache Harmony pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // PhoneGap sslCertificateChecker //
                ////////////////////////////////////
                try {
                    var phonegap_Activity = Java.use('nl.xservices.plugins.sslCertificateChecker');
                    phonegap_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function(a, b, c) {
                        console.log('[+] Bypassing PhoneGap sslCertificateChecker: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] PhoneGap sslCertificateChecker pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // IBM MobileFirst pinTrustedCertificatePublicKey (double bypass) //
                ////////////////////////////////////////////////////////////////////
                try {
                    // Bypass IBM MobileFirst {1}
                    var WLClient_Activity_1 = Java.use('com.worklight.wlclient.api.WLClient');
                    WLClient_Activity_1.getInstance().pinTrustedCertificatePublicKey.overload('java.lang.String').implementation = function(cert) {
                        console.log('[+] Bypassing IBM MobileFirst pinTrustedCertificatePublicKey {1}: ' + cert);
                        return;
                    };
                    } catch (err) {
                    console.log('[-] IBM MobileFirst pinTrustedCertificatePublicKey {1} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass IBM MobileFirst {2}
                    var WLClient_Activity_2 = Java.use('com.worklight.wlclient.api.WLClient');
                    WLClient_Activity_2.getInstance().pinTrustedCertificatePublicKey.overload('[Ljava.lang.String;').implementation = function(cert) {
                        console.log('[+] Bypassing IBM MobileFirst pinTrustedCertificatePublicKey {2}: ' + cert);
                        return;
                    };
                } catch (err) {
                    console.log('[-] IBM MobileFirst pinTrustedCertificatePublicKey {2} pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass) //
                ///////////////////////////////////////////////////////////////////////////////////////////////////////
                try {
                    // Bypass IBM WorkLight {1}
                    var worklight_Activity_1 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
                    worklight_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSocket').implementation = function(a, b) {
                        console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {1}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {1} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass IBM WorkLight {2}
                    var worklight_Activity_2 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
                    worklight_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(a, b) {
                        console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {2}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {2} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass IBM WorkLight {3}
                    var worklight_Activity_3 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
                    worklight_Activity_3.verify.overload('java.lang.String', '[Ljava.lang.String;', '[Ljava.lang.String;').implementation = function(a, b) {
                        console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {3}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {3} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass IBM WorkLight {4}
                    var worklight_Activity_4 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
                    worklight_Activity_4.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b) {
                        console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {4}: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {4} pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Conscrypt CertPinManager //
                //////////////////////////////
                try {
                    var conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager');
                    conscrypt_CertPinManager_Activity.checkChainPinning.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
                        console.log('[+] Bypassing Conscrypt CertPinManager: ' + a);
                        //return;
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Conscrypt CertPinManager pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Conscrypt CertPinManager (Legacy) //
                ///////////////////////////////////////
                try {
                    var legacy_conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager');
                    legacy_conscrypt_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
                        console.log('[+] Bypassing Conscrypt CertPinManager (Legacy): ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Conscrypt CertPinManager (Legacy) pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager //
                ///////////////////////////////////////////////////////////////////////////////////
                try {
                    var cwac_CertPinManager_Activity = Java.use('com.commonsware.cwac.netsecurity.conscrypt.CertPinManager');
                    cwac_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
                        console.log('[+] Bypassing CWAC-Netsecurity CertPinManager: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] CWAC-Netsecurity CertPinManager pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Worklight Androidgap WLCertificatePinningPlugin //
                /////////////////////////////////////////////////////
                try {
                    var androidgap_WLCertificatePinningPlugin_Activity = Java.use('com.worklight.androidgap.plugin.WLCertificatePinningPlugin');
                    androidgap_WLCertificatePinningPlugin_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function(a, b, c) {
                        console.log('[+] Bypassing Worklight Androidgap WLCertificatePinningPlugin: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Worklight Androidgap WLCertificatePinningPlugin pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Netty FingerprintTrustManagerFactory //
                //////////////////////////////////////////
                try {
                    var netty_FingerprintTrustManagerFactory = Java.use('io.netty.handler.ssl.util.FingerprintTrustManagerFactory');
                    //NOTE: sometimes this below implementation could be useful
                    //var netty_FingerprintTrustManagerFactory = Java.use('org.jboss.netty.handler.ssl.util.FingerprintTrustManagerFactory');
                    netty_FingerprintTrustManagerFactory.checkTrusted.implementation = function(type, chain) {
                        console.log('[+] Bypassing Netty FingerprintTrustManagerFactory');
                    };
                } catch (err) {
                    console.log('[-] Netty FingerprintTrustManagerFactory pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Squareup CertificatePinner [OkHTTP<v3] (double bypass) //
                ////////////////////////////////////////////////////////////
                try {
                    // Bypass Squareup CertificatePinner  {1}
                    var Squareup_CertificatePinner_Activity_1 = Java.use('com.squareup.okhttp.CertificatePinner');
                    Squareup_CertificatePinner_Activity_1.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function(a, b) {
                        console.log('[+] Bypassing Squareup CertificatePinner {1}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] Squareup CertificatePinner {1} pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass Squareup CertificatePinner {2}
                    var Squareup_CertificatePinner_Activity_2 = Java.use('com.squareup.okhttp.CertificatePinner');
                    Squareup_CertificatePinner_Activity_2.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
                        console.log('[+] Bypassing Squareup CertificatePinner {2}: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] Squareup CertificatePinner {2} pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Squareup OkHostnameVerifier [OkHTTP v3] (double bypass) //
                /////////////////////////////////////////////////////////////
                try {
                    // Bypass Squareup OkHostnameVerifier {1}
                    var Squareup_OkHostnameVerifier_Activity_1 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier');
                    Squareup_OkHostnameVerifier_Activity_1.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(a, b) {
                        console.log('[+] Bypassing Squareup OkHostnameVerifier {1}: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Squareup OkHostnameVerifier check not found');
                    //console.log(err);
                }
                try {
                    // Bypass Squareup OkHostnameVerifier {2}
                    var Squareup_OkHostnameVerifier_Activity_2 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier');
                    Squareup_OkHostnameVerifier_Activity_2.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b) {
                        console.log('[+] Bypassing Squareup OkHostnameVerifier {2}: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Squareup OkHostnameVerifier check not found');
                    //console.log(err);
                }
        
        
        
        
                // Android WebViewClient (quadruple bypass) //
                //////////////////////////////////////////////
                try {
                    // Bypass WebViewClient {1} (deprecated from Android 6)
                    var AndroidWebViewClient_Activity_1 = Java.use('android.webkit.WebViewClient');
                    AndroidWebViewClient_Activity_1.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function(obj1, obj2, obj3) {
                        console.log('[+] Bypassing Android WebViewClient check {1}');
                    };
                } catch (err) {
                    console.log('[-] Android WebViewClient {1} check not found');
                    //console.log(err)
                }
                try {
                    // Bypass WebViewClient {2}
                    var AndroidWebViewClient_Activity_2 = Java.use('android.webkit.WebViewClient');
                    AndroidWebViewClient_Activity_2.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function(obj1, obj2, obj3) {
                        console.log('[+] Bypassing Android WebViewClient check {2}');
                    };
                } catch (err) {
                    console.log('[-] Android WebViewClient {2} check not found');
                    //console.log(err)
                }
                try {
                    // Bypass WebViewClient {3}
                    var AndroidWebViewClient_Activity_3 = Java.use('android.webkit.WebViewClient');
                    AndroidWebViewClient_Activity_3.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(obj1, obj2, obj3, obj4) {
                        console.log('[+] Bypassing Android WebViewClient check {3}');
                    };
                } catch (err) {
                    console.log('[-] Android WebViewClient {3} check not found');
                    //console.log(err)
                }
                try {
                    // Bypass WebViewClient {4}
                    var AndroidWebViewClient_Activity_4 = Java.use('android.webkit.WebViewClient');
                    AndroidWebViewClient_Activity_4.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function(obj1, obj2, obj3) {
                        console.log('[+] Bypassing Android WebViewClient check {4}');
                    };
                } catch (err) {
                    console.log('[-] Android WebViewClient {4} check not found');
                    //console.log(err)
                }
        
        
        
        
                // Apache Cordova WebViewClient //
                //////////////////////////////////
                try {
                    var CordovaWebViewClient_Activity = Java.use('org.apache.cordova.CordovaWebViewClient');
                    CordovaWebViewClient_Activity.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function(obj1, obj2, obj3) {
                        console.log('[+] Bypassing Apache Cordova WebViewClient check');
                        obj3.proceed();
                    };
                } catch (err) {
                    console.log('[-] Apache Cordova WebViewClient check not found');
                    //console.log(err);
                }
        
        
        
        
                // Boye AbstractVerifier //
                ///////////////////////////
                try {
                    var boye_AbstractVerifier = Java.use('ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier');
                    boye_AbstractVerifier.verify.implementation = function(host, ssl) {
                        console.log('[+] Bypassing Boye AbstractVerifier check: ' + host);
                    };
                } catch (err) {
                    console.log('[-] Boye AbstractVerifier check not found');
                    //console.log(err);
                }
        
        
        
        
                // Apache AbstractVerifier //
                /////////////////////////////
                try {
                    var apache_AbstractVerifier = Java.use('org.apache.http.conn.ssl.AbstractVerifier');
                    apache_AbstractVerifier.verify.implementation = function(a, b, c, d) {
                        console.log('[+] Bypassing Apache AbstractVerifier check: ' + a);
                        return;
                    };
                } catch (err) {
                    console.log('[-] Apache AbstractVerifier check not found');
                    //console.log(err);
                }
        
        
        
        
                // Chromium Cronet //
                /////////////////////
                try {
                    var CronetEngineBuilderImpl_Activity = Java.use("org.chromium.net.impl.CronetEngineBuilderImpl");
                    // Setting argument to TRUE (default is TRUE) to disable Public Key pinning for local trust anchors
                    CronetEngine_Activity.enablePublicKeyPinningBypassForLocalTrustAnchors.overload('boolean').implementation = function(a) {
                        console.log("[+] Disabling Public Key pinning for local trust anchors in Chromium Cronet");
                        var cronet_obj_1 = CronetEngine_Activity.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
                        return cronet_obj_1;
                    };
                    // Bypassing Chromium Cronet pinner
                    CronetEngine_Activity.addPublicKeyPins.overload('java.lang.String', 'java.util.Set', 'boolean', 'java.util.Date').implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
                        console.log("[+] Bypassing Chromium Cronet pinner: " + hostName);
                        var cronet_obj_2 = CronetEngine_Activity.addPublicKeyPins.call(this, hostName, pinsSha256, includeSubdomains, expirationDate);
                        return cronet_obj_2;
                    };
                } catch (err) {
                    console.log('[-] Chromium Cronet pinner not found')
                    //console.log(err);
                }
        
        
        
                // Flutter Pinning packages http_certificate_pinning and ssl_pinning_plugin (double bypass) //
                //////////////////////////////////////////////////////////////////////////////////////////////
                try {
                    // Bypass HttpCertificatePinning.check {1}
                    var HttpCertificatePinning_Activity = Java.use('diefferson.http_certificate_pinning.HttpCertificatePinning');
                    HttpCertificatePinning_Activity.checkConnexion.overload("java.lang.String", "java.util.List", "java.util.Map", "int", "java.lang.String").implementation = function (a, b, c ,d, e) {
                        console.log('[+] Bypassing Flutter HttpCertificatePinning : ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Flutter HttpCertificatePinning pinner not found');
                    //console.log(err);
                }
                try {
                    // Bypass SslPinningPlugin.check {2}
                    var SslPinningPlugin_Activity = Java.use('com.macif.plugin.sslpinningplugin.SslPinningPlugin');
                    SslPinningPlugin_Activity.checkConnexion.overload("java.lang.String", "java.util.List", "java.util.Map", "int", "java.lang.String").implementation = function (a, b, c ,d, e) {
                        console.log('[+] Bypassing Flutter SslPinningPlugin: ' + a);
                        return true;
                    };
                } catch (err) {
                    console.log('[-] Flutter SslPinningPlugin pinner not found');
                    //console.log(err);
                }
        
        
        
        
                // Dynamic SSLPeerUnverifiedException Patcher                                //
                // An useful technique to bypass SSLPeerUnverifiedException failures raising //
                // when the Android app uses some uncommon SSL Pinning methods or an heavily //
                // code obfuscation. Inspired by an idea of: https://github.com/httptoolkit  //
                ///////////////////////////////////////////////////////////////////////////////
                function rudimentaryFix(typeName) {
                    // This is a improvable rudimentary fix, if not works you can patch it manually
                    if (typeName === undefined){
                        return;
                    } else if (typeName === 'boolean') {
                        return true;
                    } else {
                        return null;
                    }
                }
                try {
                    var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException');
                    UnverifiedCertError.$init.implementation = function (str) {
                        console.log('\x1b[36m[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...\x1b[0m');
                        try {
                            var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace();
                            var exceptionStackIndex = stackTrace.findIndex(stack =>
                                stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException"
                            );
                            // Retrieve the method raising the SSLPeerUnverifiedException
                            var callingFunctionStack = stackTrace[exceptionStackIndex + 1];
                            var className = callingFunctionStack.getClassName();
                            var methodName = callingFunctionStack.getMethodName();
                            var callingClass = Java.use(className);
                            var callingMethod = callingClass[methodName];
                            console.log('\x1b[36m[!] Attempting to bypass uncommon SSL Pinning method on: '+className+'.'+methodName+'\x1b[0m');
                            // Skip it when already patched by Frida
                            if (callingMethod.implementation) {
                                return;
                            }
                            // Trying to patch the uncommon SSL Pinning method via implementation
                            var returnTypeName = callingMethod.returnType.type;
                            callingMethod.implementation = function() {
                                rudimentaryFix(returnTypeName);
                            };
                        } catch (e) {
                            // Dynamic patching via implementation does not works, then trying via function overloading
                            //console.log('[!] The uncommon SSL Pinning method has more than one overload);
                            if (String(e).includes(".overload")) {
                                var splittedList = String(e).split(".overload");
                                for (let i=2; i<splittedList.length; i++) {
                                    var extractedOverload = splittedList[i].trim().split("(")[1].slice(0,-1).replaceAll("'","");
                                    // Check if extractedOverload has multiple arguments
                                    if (extractedOverload.includes(",")) {
                                        // Go here if overloaded method has multiple arguments (NOTE: max 6 args are covered here)
                                        var argList = extractedOverload.split(", ");
                                        console.log('\x1b[36m[!] Attempting overload of '+className+'.'+methodName+' with arguments: '+extractedOverload+'\x1b[0m');
                                        if (argList.length == 2) {
                                            callingMethod.overload(argList[0], argList[1]).implementation = function(a,b) {
                                                rudimentaryFix(returnTypeName);
                                            }
                                        } else if (argNum == 3) {
                                            callingMethod.overload(argList[0], argList[1], argList[2]).implementation = function(a,b,c) {
                                                rudimentaryFix(returnTypeName);
                                            }
                                        }  else if (argNum == 4) {
                                            callingMethod.overload(argList[0], argList[1], argList[2], argList[3]).implementation = function(a,b,c,d) {
                                                rudimentaryFix(returnTypeName);
                                            }
                                        }  else if (argNum == 5) {
                                            callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4]).implementation = function(a,b,c,d,e) {
                                                rudimentaryFix(returnTypeName);
                                            }
                                        }  else if (argNum == 6) {
                                            callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4], argList[5]).implementation = function(a,b,c,d,e,f) {
                                                rudimentaryFix(returnTypeName);
                                            }
                                        }
                                    // Go here if overloaded method has a single argument
                                    } else {
                                        callingMethod.overload(extractedOverload).implementation = function(a) {
                                            rudimentaryFix(returnTypeName);
                                        }
                                    }
                                }
                            } else {
                                console.log('\x1b[36m[-] Failed to dynamically patch SSLPeerUnverifiedException '+e+'\x1b[0m');
                            }
                        }
                        //console.log('\x1b[36m[+] SSLPeerUnverifiedException hooked\x1b[0m');
                        return this.$init(str);
                    };
                } catch (err) {
                    //console.log('\x1b[36m[-] SSLPeerUnverifiedException not found\x1b[0m');
                    //console.log('\x1b[36m'+err+'\x1b[0m');
                }
            });
        
        }, 0);
        View Code
      • 脚本启动方式:
        frida -U -f [APP_ID] -l frida_multiple_unpinning.js
        frida -UF -l frida_multiple_unpinning.js

        注意:frida的hook检测,比如安居客需要删除文件:libmsaoaidsec.so

    • 2、Xposed Hook

      • 安装 Magisk 面具(手机root)

      • 在面具中刷入LSPosed框架
        • 下载Zygisk-LSPosed:https://github.com/LSPosed/LSPosed/releases
        • Magisk开启Zygisk
        • 上传Zygisk-LSPosed zip文件,并在Magisk中进行安装
        • 刷入成功后,就可以看到 LSPosed 的图标,如果没有出现的话,可以通过MT、NP等管理器去手机的 /data/adb/lspd/目录下找manager.apk包,然后再点击安装即可。
      • 安装JustTrustMePlus.apk
        • 在LSPosed中启用模块,并勾选上需要启用该模块的app

三、服务端证书校验

  • - 在客户端预设证书(p12/bks)
    - 客户端向服务端发送请求时,携带证书信息,在服务端会校验客户端携带过来证书的合法性
  • 校验时逻辑
    • apk打包时,通常将 bksp12 格式的证书保存在assetsraw 等目录
    • 安卓代码发送请求时,会读取【证书内容】+【证书密码
    • 注意:一般大点的公司不会采用服务端证书校验,因为会增加服务器压力
  • 逆向
    • 1、Hook密码

      Java.perform(function () {
          var KeyStore = Java.use("java.security.KeyStore");
      
          KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (v1, v2) {
              var pwd = Java.use("java.lang.String").$new(v2);
              console.log('\n------------')
              console.log("类型:" + this.getType());
              console.log("密码:" + pwd);
              // console.log(JSON.stringify(v1));
              //console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
              var res = this.load(v1, v2);
              return res;
          };
      });
      // frida -U -f com.paopaotalk.im -l 1.hook_password.js

      KeyStore.load是系统函数,所以这是一个通用hook脚本

    • 2、获取证书文件

      • 方式1:定位代码,找到加载证书的文件路径,然后去apk中寻找
      • 方式2:Hook证书文件【注意:手机需要对当前app开启存储权限
        Java.perform(function () {
            var KeyStore = Java.use("java.security.KeyStore");
            var String = Java.use("java.lang.String");
        
        
            KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (inputStream, v2) {
                var pwd = String.$new(v2);
                console.log('\n------------')
                console.log("密码:" + pwd, "类型:" + this.getType());
        
                if (this.getType() === "BKS") {
                    var myArray = new Array(1024);
                    for (var i = 0; i < myArray.length; i++) {
                        myArray[i] = 0x0;
                    }
                    var buffer = Java.array('byte', myArray);
                    var file = Java.use("java.io.File").$new("/sdcard/Download/cert-" + new Date().getTime() + ".bks");
                    var out = Java.use("java.io.FileOutputStream").$new(file);
                    var r;
                    while ((r = inputStream.read(buffer)) > 0) {
                        out.write(buffer, 0, r);
                    }
                    console.log("save success!")
                    out.close()
                } else if (this.getType() === "pkcs12") {
                    var myArray = new Array(1024);
                    for (var i = 0; i < myArray.length; i++) {
                        myArray[i] = 0x0;
                    }
                    var buffer = Java.array('byte', myArray);
                    var file = Java.use("java.io.File").$new("/sdcard/Download/cert-" + new Date().getTime() + ".p12");
                    var out = Java.use("java.io.FileOutputStream").$new(file);
                    var r;
                    while ((r = inputStream.read(buffer)) > 0) {
                        out.write(buffer, 0, r);
                    }
                    console.log("save success!")
                    out.close()
                }
        
                var res = this.load(inputStream, v2);
                return res;
            };
        
        });
        
        // frida -U -f com.paopaotalk.im -l hook_cert.js
        View Code
    • 3、转换证书格式

      • charles不支持导入bks格式的证书,如果逆向过程中得到了bks格式证书,需要使用 portecle 将bks证书转化弄成p12格式,然后再处理。
        • 启动portecle 
          java -jar portecle.jar

          或者直接双击portecle.jar也可以打开

        • 打开bks文件:【File】-【Open Keystore File...】-【找到文件,输入上述hook到的密码】
        • 导出p12文件:【右击文件】-【Export】-【导出类型选择:Private Key and Certificates】-【导出格式选择:PKCS12】
    • 4、charles导入证书

      • 【Proxy】-【SSL Proxying Settings】-【选择Client Certificates】-【add】-【Host和Port可以选填:*】-【选择Import P12】-【输入密码】
    • 5、Python如何发送请求

      • 方式1:
        • 使用requests-pkcs12模块
          pip install requests-pkcs12
        • 代码示例:
          from requests_pkcs12 import get, post
          
          res = post(
              url='https://39.108.102.14:46077/userservices/v2/user/login',
              json={
                  "device_type": "app"
              },
              headers={
                  "bundle_id": "com.paopaotalk.im"
              },
              pkcs12_filename='Client.p12',
              pkcs12_password='111111',
              verify=False
          )
          print(res.text)
      • 方式2:
        • 使用requests模块(亲测不推荐
          • 默认requests不支持直接使用p12格式的证书,所以需要将p12转换成pem才可以
            openssl pkcs12 -in Client.p12 -out demo.pem -nodes -passin 'pass:111111'
          • 代码示例:
            from requests import post
            
            res = post(
                url='https://39.108.102.14:46077/userservices/v2/user/login',
                json={
                    "device_type": "app"
                },
                headers={
                    "bundle_id": "com.paopaotalk.im"
                },
                cert='demo.pem',
                verify=False
            )
            print(res.text)

             openssl转换证书格式貌似不太好使,不推荐这种方案

四、代码混淆

  • 关于混淆
    • 第三方的包是可以进行混淆的,例如:OKHttp3.Http.Cert.check 被混淆后可以是a.f.c.b 形式
    • 系统包不会被混淆,比如:java.lang.String
    • 因此:代码混淆针对的是【客户端证书的校验】
  • 解决思路:
    • Hook系统底层必走的核心方法,获取调用栈
    • 根据调用栈向上寻找到客户端证书校验的代码位置,通过GDA/Jadx等工具分析源码,对代码混淆前后的类名和方法名进行比对
    • 编写frida Hook脚本
  • 客户端证书校验顺序
    • 注意:内部按照顺序对这个3个过程进行校验,只要有一个无法通过,后续的校验就不会再触发执行。上述三个校验的触发位置是在:okhttp3.internal.connection.RealConnection类中的connectTls方法

  • Hook流程
    • 1、Hook系统方法connectTls位置【同时也能过证书校验】
      Java.perform(function () {
          var Platform = Java.use('com.android.org.conscrypt.Platform');
          Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = function (x509tm, chain, authType, socket) {
              console.log('\n[+] checkServer  ',x509tm,JSON.stringify(x509tm) );
              console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
              // 这里会去调用客户端证书校验的方法,不执行,就是不去校验(直接通过)。
              // return this.checkServerTrusted(x509tm, chain, authType, socket);
          };
      });
      
      // frida -U -f cn.ticktick.task -l hook_system.js

      根据调用栈向上找到证书校验的位置,即com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake的上一级调用栈(日志直接看它的下一行),然后通过GDA等查看源码进行比对

    • 2、Hook【主机校验:verify方法】----需要分两步
      • 第一步
        • Hook connectTls方法,读取内部的hostNameVerifier值,从而获取混淆后的类名【需要使用反射
          Java.perform(function () {
              // 反射获取对象属性
              function getFieldValue(obj, fieldName) {
                  var cls = obj.getClass();
                  var field = cls.getDeclaredField(fieldName);
                  field.setAccessible(true);
                  var value = field.get(obj);
                  return value;
              }var RealConnection = Java.use('uk.c');
              RealConnection.f.implementation = function (a, b, c, d) {
                  try {
                      // 源码:【!a1.j.verify()】  =>  【this.c.a.j.verify()】
                      var route = getFieldValue(this, "c");
                      console.log('route=', route);
                      var address = getFieldValue(route, 'a');
                      console.log('address=', address);
                      var hostnameVerifier = getFieldValue(address, 'j');
                      console.log('\n[+++] hostnameVerifier', JSON.stringify(hostnameVerifier));
          
                  } catch (e) {
                      console.log(e);
                  }
                  return this.f(a, b, c, d);
              };
          });
      • 第二步
        • 根据上述Hook结果,进行Hook
          Java.perform(function () {
              var d = Java.use('al.d');
              d.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b){
                  console.log('2、主机名校验:verify。。。')
                  return true;
              }
          });
    • 3、Hook 【Pinner公钥校验:check方法】
      • 可以参照frida_multiple_unpinning脚本中的写法,但是具体的类和方法名需要替换成混淆后的【即原check方法】
        Java.perform(function () {
            var pinner = Java.use('rk.f');
            pinner.a.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
                console.log('[+] pinner check ' + a);
                return;
            };
        });
        
        // frida -UF -l hook_check.js
  • 示例:滴答清单v6.3.3.0【需要同时hook过校验】
    Java.perform(function () {
    
        // 1、证书校验
        var Platform = Java.use('com.android.org.conscrypt.Platform');
        Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = function (x509tm, chain, authType, socket) {
            // console.log('\n[+] checkServer  ',x509tm,JSON.stringify(x509tm) );
            // console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            // 这里会去调用客户端证书校验的方法,不执行,就是不去校验(直接通过)。
            // return this.checkServerTrusted(x509tm, chain, authType, socket);
            console.log('1、证书校验。。。')
        };
    
        // 2、主机名校验
        var d = Java.use('al.d');
        d.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b){
            console.log('2、主机名校验:verify。。。')
            return true;
        }
    
        // 3、pinner公钥校验
        var pinner = Java.use('rk.f');
        pinner.a.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
            console.log('3、pinner公钥check。。。');
            return;
        };
    });

五、quic协议降级

  • 抖音系使用的QUIC协议,想要抓包,可以基于此Hook脚本实现。
  • 本质:强制降级不要走QUIC协议,只能走HTTP协议。
    function hookso_ssl(){
        Java.perform(function(){
            var class_ = Java.use("org.chromium.CronetClient");
            console.log(class_);
            class_.tryCreateCronetEngine.implementation = function(a,b,c,d,e,f,g,h){
                console.log(a,b,c,d,e,f,g,h);
                //var result = this.tryCreateCronetEngine(a,b,c,d,e,f,g,h);
                var result;
                console.log("result==>",result);
                return result;
            }
        })
    }
    
    setImmediate(hookso_ssl)
    //frida -U -f com.dragon.read -l dy.js
    //frida -U -f com.ss.android.ugc.aweme -l dy.js

    例如:抖音v25.9.0 或 番茄免费小说 v5.8.1.33

posted @ 2024-03-07 22:53  eliwang  阅读(42)  评论(0编辑  收藏  举报