写点什么

解决 APP 抓包问题【网络安全】

  • 2022-11-09
    湖南
  • 本文字数:15497 字

    阅读完需:约 51 分钟

1.前言

在日常渗透过程中我们经常会遇到瓶颈无处下手,这个时候如果攻击者从 APP 进行突破,往往会有很多惊喜。但是目前市场上的 APP 都会为防止别人恶意盗取和恶意篡改进行一些保护措施,比如模拟器检测、root检测、APK加固、代码混淆、代码反调试、反脱壳、签名校验等等对抗机制。


而测试人员对 APP 进行渗透的首步操作通常就是上 burp 或者 Charles 这类抓包工具进行抓包,查看请求记录里的域名及链接地址是否可以进一步利用,但是如果遇到一些 APP 出现证书报错或者抓不到包的情况该怎么办,读过本篇文章之后,相信你会拥有一些新的解决方案和思考。

2.数字证书

我们都知道 http 协议传输的是明文信息,是可以直接捕获的,从而造成了数据泄露。为了防止中间人的拦截,出现了 HTTPS 加密机制。在 HTTPS 中,使用了证书+数字签名解决了这个问题。


此篇的重点在于如何应对 APP 的抓包对抗。


总结的 HTTPS 加密机制如下:


  • 数字签名是发送方的明文经历了两次加密得到的两个东西组成,一个是 hash ,一个是经过私钥加密。

  • 数字证书就是明文+数字签名。但是数字证书中的内容远不止这俩,还包括了权威机构的信息,服务器的域名,最重要的是有签名的计算方法,不然用公钥进行解密之后的 hash,如何与加密明文进行对比呢,还有证书中还包括公钥,公钥用于发放给请求证书的客户端。

  • HTTPS 就是使用 SSL/TLS 协议进行加密传输,让客户端拿到服务器的公钥,然后客户端随机生成一个对称加密的秘钥,使用公钥加密,传输给服务端,后续的所有信息都通过该对称秘钥进行加密解密,完成整个 HTTPS 的流程。

3.https 抓包

【一一帮助安全学习一一】

①网络安全学习路线

②20 份渗透测试电子书

③安全攻防 357 页笔记

④50 份安全攻防面试指南

⑤安全红队渗透工具包

⑥网络安全必备书籍

⑦100 个漏洞实战案例

⑧安全大厂内部教程

导入用户证书

在第一次使用 burp 时,都会有这么一步,将 burp 的证书导出,添加进浏览器 【受信任的根证书颁发机构】中去,这样就会信任 burp 发来的请求包,也就可以请求数据进行修改。我们对 APP 抓包,也同样要将 burp 证书安装到系统证书中去,一般从【SD 卡安装】的证书会存放在用户信任的凭据下




但是,在 Android 7.0 以前,应用默认会信任系统证书和用户证书,Android 7.0 开始,默认只信任系统证书。


所以如果你的手机是处于 Android7.0 以上版本的话,并且在没有绑定 SSL 证书的情况下,也会抓不到包,从安卓开发的角度可以很清楚的看到这一点。


下图是我将 burp 证书安装到 Android7.1.2 的用户证书下,使用 okhttp 对https://ttt.com进行请求的结果。由于 ttt.com 的 SSL 证书是自签名证书,而自签名证书是不被系统默认信任的,所以需要先将 ttt.com 的自签名证书添加到系统证书中才可以访问。



自签名证书的生成如下图所示:



  • 系统证书路径:/system/etc/security/cacerts/

  • 用户证书路径:/data/misc/user/0/cacerts-added/


移动到系统根证书路径的方法:


1、导出 burp.der


2、使用 openssl 更改证书格式,先将 burp 证书的 der 格式转成 pem,再获取证书的 hash


openssl x509 -inform DER -in burp.der -out burp.pemopenssl x509 -inform PEM -subject_hash_old -in burp.pem 
复制代码



3.移动到系统根证书目录路径下


Android 根证书目录都是以 pem 证书的 hash 值+.0 格式,所以要将刚才生成的 pem 改名为 xxxx.0


mv burp.pem a5ba575.0
复制代码


由于系统读写权限问题,不一定能直接上传到 system 目录


adb push 9a5ba575.0 /sdcard
adb shell mount -o remount,rw /systemcp /sdcard/9a5ba575.0 /system/etc/security/cacerts/chmod 644 /system/etc/security/cacerts/9a5ba575.0
复制代码


移动完成之后,再打开【设置】-【安全】-【信任的凭据】验证一下



这时可以在 Android7.0 以上版本正常访问https://ttt.com了,其他抓包工具同理即可。

证书有效期过长

还有一种情况是,导入到系统证书仍抓不到包,并且浏览器会报 NET::ERR_CERT_VALIDITY_TOO_LONG 错误。


原因是 chrome 从 2018 年开始只信任有效期少于 825 天(27 个月)的证书,而 burp 证书有效期过长。


解决方案是自己做一个低于 27 个月的 root 证书导入 burp,再通过 burp 重新导出证书并放入到系统证书路径下。


openssl genrsa -out key.pem 3072 -nodesopenssl req -new -x509 -key key.pem -sha256 -config openssl.cnf -out cert.pem -days 730 -subj "/C=JP/ST=/L=/O=m4bln/CN=MY CA"openssl pkcs12 -export -inkey key.pem -in cert.pem -out cert_and_key.pfx把cert_and_key.pfx导入burp
复制代码


目前还没遇到过这种情况,但是如果遇到了这种问题要知道怎么解决。


以上两种方法都是仅依靠了系统校验证书的方式进行抓包,APP 在整个请求 HTTPS 的请求过程时还并未进行证书校验,和在普通的浏览器中访问并无区别,只是要将想要被信任的证书放入系统证书路径内。

4.SSLPinning

对于像 ttt.com 这种自签名的免费证书,不需要 CA 权威认证的证书,大多数 APP 开发商都会使用。那么如果在安卓开发的过程中,将证书的验证逻辑放在 APP 内部,与系统和浏览器毫无相关,这时再想将 burp 证书导入系统受信任路径下也于事无补了。


APP 自己校验证书,分为两种,一种是将验证逻辑也在代码中,一种是写在安卓 7.0 之后才有的network-security-config中。


验证是方式也有两种,一种是验证证书公钥的 hash 值,一种是直接验证证书的公钥文件。


这种通过 APP 自身的验证方式就叫做证书绑定(也叫Certificate PinningSSL Pinning)。


那么如何去判断一个 APP 是否使用了证书绑定呢?首先拿到 apk 文件,用 apktool 工具进行反编译,查看敏感文件


apktool d -s <file.apk> -o <outdir>
复制代码


开发人员经常会将网络配置的相关文件保存到指定位置,如下图就指定在了 xml 目录下。



所以在反编译后的 res/xml 目录下会有一个network_security_config.xml文件,打开看到<domain-config>标签,说明使用了证书绑定机制。


在配置文件中检验的两种方法

<network-security-config xmlns:tools="http://schemas.android.com/tools">    <!--允许http访问-->    <base-config cleartextTrafficPermitted="true"        tools:ignore="InsecureBaseConfiguration" />
<!--证书校验--> <domain-config> <domain includeSubdomains="true">www.ttt.com</domain> <trust-anchors> <certificates src="@raw/ttt"/> </trust-anchors> </domain-config>
<!--公钥校验--> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">ttt.com</domain> <!--利用xml校验证书公钥的hash值--> <pin-set expiration="2099-01-01" tools:ignore="MissingBackupPin"> <pin digest="SHA-256">7VMdvZE3PGbxb0Pgf1PlCp+MI8KZ2ZC5psM8TIylNDA=</pin> </pin-set> <!--利用xml校验证书的公钥文件--> <trust-anchors> <certificates src="@raw/ttt"/> </trust-anchors> </domain-config>
</network-security-config>
复制代码


这两种校验机制出现一种即可,从代码中可以看出,ttt.com 就是安卓自己要校验绑定的域名。


如果只是在这个文件进行校验,有两种解决方案:一是直接将文件中校验的部分<trust-anchors>或<pin-set>注释掉,再重新打包和签名即可,但是这过程又有些麻烦,并不是上上策,如果遇到了不能重打包的 apk 就尴尬了。。。二是最常用的也是最好用的 frida 来 hook 关键函数进行绕过,后面会讲解。当然有些人会直接在真机或者模拟器上安装 xposed 模块,但是我个人觉得每次使用都要软重启,可能还会造成卡机,所以感觉还是使用 frida 最方便。

在代码中检验的两种方法

1.利用代码校验证书的公钥 hash


String hostname = "www.ttt.com";CertificatePinner certificatePinner = new CertificatePinner.Builder()    .add(hostname, "sha256/7VMdvZE3PGbxb0Pgf1PlCp+MI8KZ2ZC5psM8TIylNDA=")    .build();OkHttpClient client = new OkHttpClient.Builder()    .certificatePinner(certificatePinner)    .hostnameVerifier(new HostnameVerifier() {        @Override        public boolean verify(String hostname, SSLSession session) {            return true;        }    }).build();
复制代码


2.利用代码校验证书的公钥证书文件


// 获取证书输入流InputStream openRawResource = getApplicationContext().getResources().openRawResource(R.raw.ttt); Certificate ca = CertificateFactory.getInstance("X.509").generateCertificate(openRawResource);// 创建 Keystore 包含我们的证书KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());keyStore.load(null, null);keyStore.setCertificateEntry("ca", ca);// 创建一个 TrustManager 仅把 Keystore 中的证书 作为信任的锚点TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // 建议不要使用自己实现的X509TrustManager,而是使用默认的X509TrustManagertrustManagerFactory.init(keyStore);// 用 TrustManager 初始化一个 SSLContextsslContext = SSLContext.getInstance("TLS");  //定义:public static SSLContext sslContext = null;sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagerFactory.getTrustManagers()[0] ) .hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }).build();
复制代码


通过 frida 进行 hook,这种绕过的脚本也很多,比较熟悉的有 JustTrustMe 和DroidSSLUnpinning,他们的底层原理都是一样的,通过 hook 关键的验证函数,进行逻辑绕过。


frida 的安装过程就不详细讲解了,网上很多教程。这里我使用的是frida 12.8.0 + frida-tools=5.3.0


这里我使用的 hook.js 的脚本如下:


/*  Android ssl certificate pinning bypass script for various methodsby Maurizio Siddu modify by Ch3nYe
Run with:frida -U -f [APP_ID] -l frida_multiple_unpinning.js --no-pause*/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 TrustManagername: '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 classvar SSLContext_init = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');try {// Override the init method, specifying the custom TrustManagerSSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {console.log('[+] Bypassing Trustmanager (Android < 7) request');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 true;};} catch (err) {console.log('[-] OkHTTPv3 {1} pinner not found');//console.log(err);}try {// Bypass OkHTTPv3 {2}// This method of CertificatePinner.check could be found in some old Android appvar 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 true;};} 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 true;};} 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[''].implementation = function (a, b) {console.log('[+] Bypassing OkHTTPv3 {4}: ' + a);};} 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.implementation = function () {console.log('[+] Bypassing Trustkit {3}');};} catch (err) {console.log('[-] Trustkit {3} pinner not found');//console.log(err);}
// TrustManagerImpl (Android > 7) //////////////////////////////////////try {var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {console.log('[+] Bypassing TrustManagerImpl (Android > 7): ' + host);return untrustedChain;};} catch (err) {console.log('[-] TrustManagerImpl (Android > 7) pinner not found');//console.log(err);}
// Appcelerator Titanium /////////////////////////////try {var appcelerator_PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');appcelerator_PinningTrustManager.checkServerTrusted.implementation = function () {console.log('[+] Bypassing Appcelerator PinningTrustManager');};} catch (err) {console.log('[-] Appcelerator PinningTrustManager pinner not found');//console.log(err);}
// OpenSSLSocketImpl Conscrypt ///////////////////////////////////try {var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, JavaObject, authMethod) {console.log('[+] Bypassing OpenSSLSocketImpl Conscrypt');};} catch (err) {console.log('[-] OpenSSLSocketImpl Conscrypt pinner not found');//console.log(err);}
// OpenSSLEngineSocketImpl Conscrypt /////////////////////////////////////////try {var OpenSSLEngineSocketImpl_Activity = Java.use('com.android.org.conscrypt.OpenSSLEngineSocketImpl');OpenSSLSocketImpl_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 (https://github.com/EddyVerbruggen/SSLCertificateChecker-PhoneGap-Plugin) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////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.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) {console.log('[+] Bypassing Conscrypt CertPinManager: ' + a);return true;};} catch (err) {console.log('[-] Conscrypt CertPinManager 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 pinner 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 pinner not found');//console.log(err);}
// Android WebViewClient (double 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 {1}');};} catch (err) {console.log('[-] Android WebViewClient {1} pinner 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 {2}');};} catch (err) {console.log('[-] Android WebViewClient {2} pinner 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');obj3.proceed();};} catch (err) {console.log('[-] Apache Cordova WebViewClient pinner 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: ' + host);};} catch (err) {console.log('[-] Boye AbstractVerifier pinner not found');//console.log(err);}
});
}, 0);
复制代码


启动 frida 进行 hook 指定 APP 的包名


frida -U -f com.example.safehttps -l hook.js --no-pause
复制代码



可以看到开启 burp 抓包成功。

5.开启双向校验

双向校验顾名思义也就是服务器也要对客户端进行证书校验,在刚才客户端校验服务端的基础上添加一直被校验的逻辑在里面。


首先 ttt.com 所在的 nginx 服务器要开启双向认证



开启客户端的校验后,在浏览器进行访问,会发现返回 400,没有被请求的 SSL 证书发送,是因为浏览器正常请求不会携带证书信息去请求 ttt.com



那么如何携带客户端的证书,就要利用 burp 来操作,将 ttt.com 的证书添加到 TLS 客户证书



这时再访问



在 APP 中进行绑定客户端的证书文件,一般是 p12 格式文件,会放在 assets 目录下或者 raw 目录下,client.p12 会有一个密钥内置在代码中,需要找到才能添加进 burp 中。



这个在反编译后的目录下也能找到,通常在 assets 或者 res/raw 目录下查找,但是再导入 burp 这一步是需要证书密码的,比如上图能明显看到密码是 123456,但是找不到证书密码怎么办,可以看一下目录下是否存在 lib 文件夹,如果存在的话极大可能是将密码写进 so 层了,这就需要你会 IDA 反汇编获取证书密钥了,这部分在此不详细阐述,感兴趣的朋友可以先去研究一下。


APP 双向校验验证

服务器校验客户端的证书 ClientSSLSocketFactory,服务端将客户端的证书进行绑定。


TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());trustManagerFactory.init((KeyStore) null);TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));}trustManager = (X509TrustManager) trustManagers[0];
OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(Objects.requireNonNull(ClientSSLSocketFactory.getSocketFactory(getApplicationContext())), Objects.requireNonNull(trustManager)) .hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { //强行返回true 即验证成功 return true; }}).build();
复制代码


public class ClientSSLSocketFactory  {    private static final String KEY_STORE_PASSWORD = "123456"; // 证书密码    private static InputStream client_input;
public static SSLSocketFactory getSocketFactory(Context context) { try { //客户端证书 client_input = context.getResources().getAssets().open("client.p12"); SSLContext sslContext = SSLContext.getInstance("TLS"); KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(client_input, KEY_STORE_PASSWORD.toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray()); sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception e) { e.printStackTrace(); } finally { try { client_input.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }}
复制代码


双向校验时,要将 SSLPinning 与服务器校验客户端证书的模块同时开启。


对其就行绕过,需要先启动 Frida 进行 hook,然后勾选上客户端证书,才可以请求成功。如下图所示:



没勾选就会请求失败,出现 400 Bad Request,和浏览器无代理时请求的结果一样,如下图:



此时 burp 代理日志显示如下,SSL 请求失败


WebView 证书校验

webview 也是一种请求方式,相当于页面的跳转或嵌入,请求的结果会显示在主屏幕上。


对于 webview 证书校验,有些可以找到的脚本不一定有绕过,所以在使用的过程中要查看是否含有 webview 的关键信息


这里我使用的是DroidSSLUnpinning


使用效果如下:




此时的 APP 绑定场景为:OKhttp 请求为双向校验,webview 为 SSLPinning 校验。所以在图中的上面一行是 okhttp 的请求结果,下面的是 webview 的请求结果,此脚本双向校验仍然可以绕过。


如果说 webview 再添加了客户端校验,那么在反编译 apk 后,需要找到 webview 访问域名的证书密钥,再安装进 burp 中即可。

6.ssl_logger 通杀

至此,我们可以绕过证书绑定,抓 APP 发出的 https 包了,然而上述的证书解绑 hook 工具仅仅是通过 hook 了几种绑定证书的 API,不适用于新出现或者非主流的证书绑定技术。这时,就需要神器 ssl_logger


ssl_logger 是用来解密 SSL 流量的工具,也是一款基于 frida 的 hook 工具,通过 hook libssl 库中的 SSL_read、SSL_write 等函数来实现流量解密,由于底层的实现会调用这几个函数来封装,所以可以直接解出流量数据。



r0capture 是 r0ysue 大佬在其基础上进行改进的一款工具。



由于 ssl_logger 是适用于 MAC 和 linux 操作系统,所以我选择在 kali 上进行 hook 实现双向校验的 app



打开抓到的 1.pcap 包




从图中可以看出成功的获取到了信息,是不是感觉这个工具特别神,但是看 pcap 的包总归不如看 burp 的一目了然,这个工具就这一点不太友好。所以用哪个看你心情

7.eBPF hook 免 CA 证书

ecapture:eBPF HOOK uprobe 实现的各种用户态进程的数据捕获,无需改动原程序。这个工具也是通过 hook 了libssl库中SSL_writeSSL_read这两个关键的 SSL 加密函数的返回值,拿到明文信息,通过 ebpf map 传递给用户进程。在 APP 中,如果遇到场景是 burp 抓包时出现证书报错,我觉得可以尝试一下用这个工具直接 curl 访问进行抓包。eBPF hook 也是最近才发现的 hook 方法,值得我们去深入探索。


我这里用的是作者 v0.1.3 版本发布的工具,使用效果如下:


8.总结

在依靠系统或默认浏览器校验证书的情况下,导入 burp 证书为用户证书是可以抓 https 包的


当 app 支持的最小 API 为 24(Android 7.0)或以上时,默认情况下 app 只信任系统级别的证书,需要把 burp 变为系统证书


自签名证书作为系统证书时,有效期最长不超过 825 天,用户证书则没有限制


开启证书校验的 APP 在使用 burp 抓包时会报 certtificate_unknown 等错误


使用 frida hook 绕过双向证书校验时,必须要将客户端的 p12 文件导入 burp 中


一些脚本仍绕不过可以使用 ssl_logger 或者逆向代码进行分析验证逻辑,再有针对性的绕过


p12 文件的密钥如果在 so 层,需要会用 IDA 进行静态分析 lib 下的 so 文件获取关键密钥


看完本篇文章之后,相信你再面对抓不到包的 APP 或者是 https 请求也不会手足无措了。


用户头像

我是一名网络安全渗透师 2021-06-18 加入

关注我,后续将会带来更多精选作品,需要资料+wx:mengmengji08

评论

发布
暂无评论
解决APP抓包问题【网络安全】_网络安全_网络安全学海_InfoQ写作社区