解決APP抓包問題「網路安全」

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卡安裝】的證書會存放在使用者信任的憑據下

解決APP抓包問題「網路安全」

解決APP抓包問題「網路安全」

但是,在Android 7。0以前,應用預設會信任系統證書和使用者證書,Android 7。0開始,預設只信任系統證書。

所以如果你的手機是處於Android7。0以上版本的話,並且在沒有繫結SSL證書的情況下,也會抓不到包,從安卓開發的角度可以很清楚的看到這一點。

下圖是我將burp證書安裝到Android7。1。2的使用者證書下,使用okhttp對

https://ttt。com

進行請求的結果。由於ttt。com的SSL證書是自簽名證書,而自簽名證書是不被系統預設信任的,所以需要先將ttt。com的自簽名證書新增到系統證書中才可以訪問。

解決APP抓包問題「網路安全」

自簽名證書的生成如下圖所示:

解決APP抓包問題「網路安全」

系統證書路徑:

/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

解決APP抓包問題「網路安全」

3。移動到系統根證書目錄路徑下

Android根證書目錄都是以pem證書的hash值+。0格式,所以要將剛才生成的pem改名為xxxx。0

mv burp。pem a5ba575。0

由於系統讀寫許可權問題,不一定能直接上傳到system目錄

adb push 9a5ba575。0 /sdcardadb shell mount -o remount,rw /systemcp /sdcard/9a5ba575。0 /system/etc/security/cacerts/chmod 644 /system/etc/security/cacerts/9a5ba575。0

移動完成之後,再開啟【設定】-【安全】-【信任的憑據】驗證一下

解決APP抓包問題「網路安全」

這時可以在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 Pinning

SSL Pinning

)。

那麼如何去判斷一個APP是否使用了證書繫結呢?首先拿到apk檔案,用apktool工具進行反編譯,檢視敏感檔案

apktool d -s -o

開發人員經常會將網路配置的相關檔案儲存到指定位置,如下圖就指定在了xml目錄下。

解決APP抓包問題「網路安全」

所以在反編譯後的res/xml目錄下會有一個

network_security_config。xml

檔案,開啟看到標籤,說明使用了證書繫結機制。

解決APP抓包問題「網路安全」

在配置檔案中檢驗的兩種方法

<!——允許http訪問——> <!——證書校驗——> www。ttt。com <!——公鑰校驗——> ttt。com <!——利用xml校驗證書公鑰的hash值——> 7VMdvZE3PGbxb0Pgf1PlCp+MI8KZ2ZC5psM8TIylNDA= <!——利用xml校驗證書的公鑰檔案——>

這兩種校驗機制出現一種即可,從程式碼中可以看出,ttt。com就是安卓自己要校驗繫結的域名。

如果只是在這個檔案進行校驗,有兩種解決方案:一是直接將檔案中校驗的部分或註釋掉,再重新打包和簽名即可,但是這過程又有些麻煩,並不是上上策,如果遇到了不能重打包的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 Ch3nYeRun 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

啟動frida進行hook指定APP的包名

frida -U -f com。example。safehttps -l hook。js ——no-pause

解決APP抓包問題「網路安全」

可以看到開啟burp抓包成功。

5。開啟雙向校驗

雙向校驗顧名思義也就是伺服器也要對客戶端進行證書校驗,在剛才客戶端校驗服務端的基礎上新增一直被校驗的邏輯在裡面。

首先ttt。com所在的nginx伺服器要開啟雙向認證

解決APP抓包問題「網路安全」

開啟客戶端的校驗後,在瀏覽器進行訪問,會發現返回400,沒有被請求的SSL證書傳送,是因為瀏覽器正常請求不會攜帶證書資訊去請求ttt。com

解決APP抓包問題「網路安全」

那麼如何攜帶客戶端的證書,就要利用burp來操作,將ttt。com的證書新增到TLS客戶證書

解決APP抓包問題「網路安全」

這時再訪問

解決APP抓包問題「網路安全」

在APP中進行繫結客戶端的證書檔案,一般是p12格式檔案,會放在assets目錄下或者raw目錄下,client。p12會有一個金鑰內建在程式碼中,需要找到才能新增進burp中。

解決APP抓包問題「網路安全」

這個在反編譯後的目錄下也能找到,通常在assets或者res/raw目錄下查詢,但是再匯入burp這一步是需要證書密碼的,比如上圖能明顯看到密碼是123456,但是找不到證書密碼怎麼辦,可以看一下目錄下是否存在lib資料夾,如果存在的話極大可能是將密碼寫進so層了,這就需要你會IDA反彙編獲取證書金鑰了,這部分在此不詳細闡述,感興趣的朋友可以先去研究一下。

解決APP抓包問題「網路安全」

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,然後勾選上客戶端證書,才可以請求成功。如下圖所示:

解決APP抓包問題「網路安全」

沒勾選就會請求失敗,出現400 Bad Request,和瀏覽器無代理時請求的結果一樣,如下圖:

解決APP抓包問題「網路安全」

此時burp代理日誌顯示如下,SSL請求失敗

解決APP抓包問題「網路安全」

WebView證書校驗

webview也是一種請求方式,相當於頁面的跳轉或嵌入,請求的結果會顯示在主螢幕上。

對於webview證書校驗,有些可以找到的指令碼不一定有繞過,所以在使用的過程中要檢視是否含有webview的關鍵資訊

這裡我使用的是

DroidSSLUnpinning

使用效果如下:

解決APP抓包問題「網路安全」

解決APP抓包問題「網路安全」

此時的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等函式來實現流量解密,由於底層的實現會呼叫這幾個函式來封裝,所以可以直接解出流量資料。

解決APP抓包問題「網路安全」

r0capture

是r0ysue大佬在其基礎上進行改進的一款工具。

解決APP抓包問題「網路安全」

由於ssl_logger是適用於MAC和linux作業系統,所以我選擇在kali上進行hook實現雙向校驗的app

解決APP抓包問題「網路安全」

開啟抓到的1。pcap包

解決APP抓包問題「網路安全」

解決APP抓包問題「網路安全」

從圖中可以看出成功的獲取到了資訊,是不是感覺這個工具特別神,但是看pcap的包總歸不如看burp的一目瞭然,這個工具就這一點不太友好。所以用哪個看你心情

7。eBPF hook 免CA證書

ecapture

:eBPF HOOK uprobe實現的各種使用者態程序的資料捕獲,無需改動原程式。這個工具也是透過hook了

libssl

庫中

SSL_write

SSL_read

這兩個關鍵的SSL加密函式的返回值,拿到明文資訊,透過ebpf map傳遞給使用者程序。在APP中,如果遇到場景是burp抓包時出現證書報錯,我覺得可以嘗試一下用這個工具直接curl訪問進行抓包。eBPF hook也是最近才發現的hook方法,值得我們去深入探索。

我這裡用的是作者v0。1。3版本釋出的工具,使用效果如下:

解決APP抓包問題「網路安全」

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請求也不會手足無措了。

解決APP抓包問題「網路安全」