HTTP協議發展至今已經有二十多年的歷史,整個發展的趨勢主要是兩個方向:效率和安全。效率方面,從HTTP1.0的一次請求一個連線,到HTTP1.1的連線複用,到SPDY/HTTP2的多路複用,到QUIC/HTTP3的基於UDP傳輸,在效率方面越來越高效。安全方面,從HTTP的明文,到HTTP2強制使用TLSv1.2,到QUIC/HTTP3強制使用TLSv1.3,越來越注重資料傳輸的安全性。總而言之,HTTP協議的發展對使用者是友好的,但是對開發者而言卻不那麼友善。
抓包是每個程式設計師的必修技能之一,尤其是在介面除錯和程式逆向方面具有廣闊的用途。但是,隨著越來越多的通訊協議使用加密的HTTPS,而且系統層面也開始強制規定使用HTTPS,抓包似乎是顯得越來越難了。
本篇部落格,主要詳解Android平臺下,HTTPS抓包的常見問題以及解決辦法。工欲善其事必先利其器,部落格中以HttpCanary作為抓包工具進行講解。更多HttpCanary的資料,請見:github.com/MegatronKin…
1. 抓包原理
幾乎所有網路資料的抓包都是採用中間人的方式(MITM),包括大家常用的Fiddler、Charles等知名抓包工具,HttpCanary同樣是使用中間人的方式進行抓包。
從上面這個原理圖,可以看出抓包的核心問題主要是兩個:
- MITM Server如何偽裝成真正的Server;
- MITM Client如何偽裝成真正的Client。
第一個問題,MITM Server要成為真正的Server,必須能夠給指定域名簽發公鑰證照,且公鑰證照能夠通過系統的安全校驗。比如Client傳送了一條https://www.baidu.com/的網路請求,MITM Server要偽裝成百度的Server,必須持有www.baidu.com域名的公鑰證照併發給Client,同時還要有與公鑰相匹配的私鑰。
MITM Server的處理方式是從第一個SSL/TLS握手包Client Hello中提取出域名www.baidu.com,利用應用內建的CA證照建立www.baidu.com域名的公鑰證照和私鑰。建立的公鑰證照在SSL/TLS握手的過程中發給Client,Client收到公鑰證照後會由系統會對此證照進行校驗,判斷是否是百度公司持有的證照,但很明顯這個證照是抓包工具偽造的。為了能夠讓系統校驗公鑰證照時認為證照是真實有效的,我們需要將抓包應用內建的CA證照手動安裝到系統中,作為真正的證照發行商(CA),即洗白。這就是為什麼,HTTPS抓包一定要先安裝CA證照。
第二個問題,MITM Client偽裝成Client。由於伺服器並不會校驗Client(絕大部分情況),所以這個問題一般不會存在。比如Server一般不會關心Client到底是Chrome瀏覽器還是IE瀏覽器,是Android App還是iOS App。當然,Server也是可以校驗Client的,這個後面分析。
2. 安裝CA證照
抓包應用內建的CA證照要洗白,必須安裝到系統中。而Android系統將CA證照又分為兩種:使用者CA證照和系統CA證照。顧明思議,使用者CA證照是由使用者自行安裝的,系統CA證照是由系統內建的,很明顯後者更加真實有效。
系統CA證照存放在/etc/security/cacerts/目錄下,名稱是CA證照subjectDN的Md5值前四位移位取或,字尾名是.0,比如00673b5b.0。考慮到安全原因,系統CA證照需要有Root許可權才能進行新增和刪除。
對於非Root的Android裝置,使用者只能安裝使用者CA證照。
無論是系統CA證照還是使用者CA證照,都可以在設定->系統安全->加密與憑據->信任的憑據中檢視:
3. Android 7.0的使用者CA證照限制
Android從7.0開始系統不再信任使用者CA證照(應用targetSdkVersion >= 24時生效,如果targetSdkVersion < 24即使系統是7.0+依然會信任)。也就是說即使安裝了使用者CA證照,在Android 7.0+的機器上,targetSdkVersion >= 24的應用的HTTPS包就抓不到了。
比如上面的例子,抓包工具用內建的CA證照,建立了www.baidu.com域名的公鑰證照發給Client,系統校驗此證照時發現是使用者CA證照籤發的,sorry。。。
那麼,我們如果繞過這種限制呢?已知有以下四種方式(低於7.0的系統請忽略):
3.1 AndroidManifest中配置networkSecurityConfig
如果我們想抓自己的App,只需要在AndroidManifest中配置networkSecurityConfig即可:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
複製程式碼
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
複製程式碼
這樣即表示,App信任使用者CA證照,讓系統對使用者CA證照的校驗給予通過。更多相關資訊,詳見Network security configuration。
3.2 調低targetSdkVersion < 24
如果想抓一個App的包,可以找個歷史版本,只需要其targetSdkVersion < 24即可。然而,隨著GooglePlay開始限制targetSdkVersion,現在要求其必須>=26,2019年8月1日後必須>=28,國內應用市場也開始逐步響應這種限制。絕大多數App的targetSdkVersion都將大於24了,也就意味著抓HTTPS的包越來越難操作了。
3.3 平行空間抓包
如果我們希望抓targetSdkVersion >= 24的應用的包,那又該怎麼辦呢?我們可以使用平行空間或者VirtualApp來曲線救國。平行空間和VirtualApp這種多開應用可以作為宿主系統來執行其它應用,如果平行空間和VirtualApp的targetSdkVersion < 24,那麼問題也就解決了。
在此,我推薦使用平行空間,相比部分開源的VirtualApp,平行空間執行得更加穩定。但必須注意平行空間的版本4.0.8625以下才是targetSdkVersion < 24,別安裝錯了。當然,HttpCanary的設定中是可以直接安裝平行空間的。
3.4 安裝到系統CA證照目錄
對於Root的機器,這是最完美最佳的解決方案。如果把CA證照安裝到系統CA證照目錄中,那這個假CA證照就是真正洗白了,不是真的也是真的了。由於系統CA證照格式都是特殊的.0格式,我們必須將抓包工具內建的CA證照以這種格式匯出,HttpCanary直接提供了這種匯出選項。
操作路徑:設定 -> SSL證照設定 -> 匯出HttpCanary根證照 -> System Trusted(.0)
PS. 很不幸的HttpCanary v2.8.0前匯出的證照名稱可能不正確,建議升級到v2.8.0以上版本操作。
匯出.0格式的證照後,可以使用MT管理器將.0檔案複製到/etc/security/cacerts/目錄下,或者通過adb remount然後push也可(這裡稍微提一下,別在sdcard裡找這個目錄)。
4. Firefox證照安裝
火狐瀏覽器Firefox自行搞了一套CA證照管理,無論是系統CA證照還是使用者CA證照,Firefox通通都不認可。這種情況,我們需要將CA證照通過特殊方式匯入到Firefox中,否則Firefox瀏覽網頁就無法工作了。
HttpCanary v2.8.0版本提供了Firefox證照匯入選項。在設定 -> SSL證照設定 -> 新增HttpCanary根證照至Firefox 中:
點選右上角複製按鈕將url複製到貼上板,然後保持此頁面不動,開啟Firefox貼上輸入複製的url。
出現下載證照彈框後,一定要手動勾上:信任用來標誌網站和信任用來標誌電子郵件使用者。然後確定即可。
5. 公鑰證照固定
證照固定(Certificate Pinning)是指Client端內建Server端真正的公鑰證照。在HTTPS請求時,Server端發給客戶端的公鑰證照必須與Client端內建的公鑰證照一致,請求才會成功。
在這種情況下,由於MITM Server建立的公鑰證照和Client端內建的公鑰證照不一致,MITM Server就無法偽裝成真正的Server了。這時,抓包就表現為App網路錯誤。已知的知名應用,比如餓了麼,就採用了證照固定。
另外,有些伺服器採用的自簽證照(證照不是由真正CA發行商簽發的),這種情況App請求時必須使用證照固定。
證照固定的一般做法是,將公鑰證照(.crt或者.cer等格式)內建到App中,然後建立TrustManager時將公鑰證照加進去。很多應用還會將內建的公鑰證照偽裝起來或者加密,防止逆向提取,比如餓了麼就偽裝成了png,當然對公鑰證照偽裝或者加密沒什麼太大必要,純粹自欺欺人罷了。
證照固定對抓包是個非常麻煩的阻礙,不過我們總是有辦法繞過的,就是麻煩了點。
5.1 JustTrustMe破解證照固定
Xposed和Magisk都有相應的模組,用來破解證照固定,實現正常抓包。
破解的原理大致是,Hook建立SSLContext等涉及TrustManager相關的方法,將固定的證照移除。
5.2 基於VirtualApp的Hook機制破解證照固定
Xposed和Magisk需要刷機等特殊處理,但是如果不想刷機折騰,我們還可以在VirtualApp中加入Hook程式碼,然後利用VirtualApp開啟目標應用進行抓包。當然,有開發者已經實現了相關的功能。詳見:
不過,這裡CertUnpinning外掛的程式碼有點問題,要改改。
5.3 匯入真正的公鑰證照和私鑰
如果Client固定了公鑰證照,那麼MITM Server必須持有真正的公鑰證照和匹配的私鑰。如果開發者具有真正服務端的公鑰證照和私鑰,(比如百度的公鑰證照和私鑰百度的後端開發肯定有),如果真有的話,可以將其匯入HttpCanary中,也可以完成正常抓包。
在設定 -> SSL證照設定 -> 管理SSL匯入證照 中,切換到服務端,然後匯入公鑰證照+私鑰,支援.p12和.bks格式檔案。
6. 雙向認證
SSL/TLS協議提供了雙向認證的功能,即除了Client需要校驗Server的真實性,Server也需要校驗Client的真實性。這種情況,一般比較少,但是還是有部分應用是開啟了雙向認證的。比如匿名社交應用Soul部分介面就使用了雙向認證。使用了雙向認證的HTTPS請求,同樣無法直接抓包。
關於雙向認證的原理。
首先,雙向認證需要Server支援,Client必須內建一套公鑰證照 + 私鑰。在SSL/TLS握手過程中,Server端會向Client端請求證照,Client端必須將內建的公鑰證照發給Server,Server驗證公鑰證照的真實性。
注意,這裡的內建的公鑰證照有區別於前面第5點的公鑰證照固定,雙向認證內建的公鑰證照+私鑰是額外的一套,不同於證照固定內建的公鑰證照。
如果一個Client既使用證照固定,又使用雙向認證,那麼Client端應該內建一套公鑰證照 + 一套公鑰證照和私鑰。第一套與Server端的公鑰證照相同,用於Client端系統校驗與Server發來的證照是否相同,即證照固定;第二套SSL/TLS握手時公鑰證照發給Server端,Server端進行簽名校驗,即雙向認證。
用於雙向認證的公鑰證照和私鑰代表了Client端身份,所以其是隱祕的,一般都是用.p12或者.bks檔案+金鑰進行存放。由於是內建在Client中,儲存的金鑰一般也是寫死在Client程式碼中,有些App為了防反編譯會將金鑰寫到so庫中,比如S匿名社交App,但是隻要存在於Client端中都是有辦法提取出來的。
6.1 雙向認證抓包
這裡以S匿名社交App為例,講解下如何抓取使用了雙向認證的App的HTTPS包。
如果伺服器使用了Nginx且開啟了雙向認證,抓包時會出現400 Bad Request的錯誤,如下:
有些伺服器可能不會返回404,直接請求失敗。
接下來看,如何使用HttpCanary配置雙向認證抓包。
首先,解壓APK,提取出.p12或者.bks檔案,二進位制的檔案一般存放都在raw或者assets目錄。
將client.p12檔案匯入手機,然後在HttpCanary的設定 -> SSL證照設定 -> 管理SSL匯入證照中,切換到客戶端(因為需要配給MITM Client),然後匯入.p12檔案。
由於雙向認證的公鑰證照和私鑰是受金鑰保護的,所以需要輸入密碼:
一般通過逆向可以從APK中提取出金鑰,具體操作這裡略過。輸入金鑰後,需要輸入對映域名,這裡使用萬用字元*對映所有相關域名:
匯入完成後如下:
可以點進證照詳情檢視細節,這個client.p12檔案包含公鑰證照和私鑰,是用於雙向認證的。
配置完成後,重新進行抓包,看看效果。
可以看到,之前400 Bad Request的兩個要求雙向認證的請求成功了!
7. SSL重協商
有些伺服器可能會開啟SSL重協商,即SSL/TLS握手成功後傳送請求時伺服器會要求重新握手。這種情況一般比較少,但是也不排除,已知的應用比如 10000社群 就使用了SSL重協商。
由於Android系統對SSL重協商是有限支援,所以部分系統版本抓包會失敗,表現為網路異常。在Android 8.1以下,SslSocket是完全支援SSL重協商的,但是SSLEngine卻是不支援SSL重協商的,而HttpCanary解析SSL/TLS使用的是SSLEngine。在Android 8.1及以上,SSLEngine和SslSocket統一了實現,故是支援SSL重協商的。
所以,如果確認伺服器使用了SSL重協商,請使用8.1及以上版本系統進行抓包。
8. 非HTTP協議抓包
如果確認了以上幾點,HttpCanary仍然抓包失敗,那麼極有可能使用的並非是HTTP協議。比如像微信聊天,視訊直播等,使用的就不是HTTP協議,這種情況需要使用其它的抓包工具,比如Packet Capture這種直接解析TCP/UDP協議的,但是往往非HTTP協議的資料包即使抓到了也無法解析出來,因為大概率都是二進位制而非文字格式的。
9. 結語
抓包是個技術活兒,需要對網路協議有大致的瞭解,對抓包感興趣的同學可以多查閱TCP、UDP、SSL/TLS、HTTP等相關資料。
HttpCanary是專業的HTTP協議抓包工具,專注HTTP協議三十年(吹過頭了),不過目前還不支援QUIC/HTTP3這種新協議,等QUIC/HTTP3正式應用起來再說吧。