從原理到實戰,全面總結 Android HTTPS 抓包

彭旭銳發表於2022-11-23

你的支援對我意義重大!

? Hi,我是小彭。本文已收錄到 GitHub · Android-NoteBook 中。這裡有 Android 進階成長路線筆記 & 部落格,有志同道合的朋友,歡迎跟著我一起成長。(聯絡方式 & 入群方式在 GitHub)

前言

  • 網路請求抓包是研發過程中常見問題,無論是開發時的介面除錯,還是測試時的資料檢驗,都有網路抓包的需求。隨著 HTTPS 協議的推廣以及手機系統安全性的升級,抓包的門檻可能會逐漸變高;
  • 在這篇文章裡,我將帶你從原理到實戰全面認識 HTTPS 抓包,既理解 HTTPS 抓包背後的實現原理,又掌握市面上已有的抓包方案。對於一些方案中存在的坑點我也一一列舉並給出解決方法。如果能幫上忙,請務必點贊加關注,這真的對我非常重要。

1. HTTPS 工作原理

要說清楚 HTTPS 抓包的原理,首先需要先說清楚 HTTPS 實現資料安全傳輸的工作原理,主要分為三要素和三階段。如果你對 HTTPS 安全傳輸的模型還比較模糊,記得先回顧下我們之前的討論:加密、摘要、簽名、證書,一次說明白!

三要素分別是:

  • 加密: 透過對稱加密演算法實現
  • 認證: 透過數字簽名實現(因為私鑰只有 “合法的傳送方” 持有,其他人偽造的數字簽名無法透過驗證)
  • 報文完整性: 透過數字簽名實現(因為數字簽名中使用了訊息摘要,其他人篡改的訊息無法透過驗證)

三階段分別是:

  • CA 證書校驗: CA 證書校驗發生在 TLS 的前兩次握手,客戶端和服務端透過 Client Hello、Server Hello 等報文獲得服務端 CA 證書,客戶端驗證 CA 證書合法性,從而確認 CA 證書中的公鑰合法性(大多數場景不會做雙向認證,即服務端不會認證客戶端合法性,這裡先不考慮);
  • 金鑰協商: 金鑰協商發生在 TLS 的後兩次握手,客戶端和服務端分別基於公鑰和私鑰進行非對稱加密通訊,協商獲得 Master Secret 對稱加密私鑰(不同演算法的協商過程細節略有不同);
  • 資料傳輸: 資料傳輸發生在 TLS 握手之後,客戶端和服務端基於協商的對稱金鑰進行對稱加密通訊。

—— 圖片引用自 https 原理模板

2. HTTPS 抓包原理 - 中間人攻擊

我們熟悉的 Fiddler、Charles 和 HttpCanary App 等抓包工具,其實都是採用了中間人攻擊(Man-in-the-MiddleAttack,MITM)的方案: 將客戶端的網路流量代理到 MITM 主機,再透過一系列的皮膚或工具將網路請求結構化地呈現出來。

如果攔截的是 HTTP 請求還好說,要是攔截的是 HTTPS 請求,首先就遇到第一個問題 —— 加密:

  • 加密: 由於 HTTPS 通訊中對稱金鑰 Master Secret 只有通訊雙方才持有,MITM 無法解密密文,導致在抓包工具上也只能看到一堆無意義的亂碼。

要解決這個問題,只能想辦法讓 MITM 也獲得這個對稱金鑰。此時,MITM 不僅要做流量攔截,還需要偽裝成真實的客戶端和服務端,與真實的通訊雙方分別建立獨立的連線。我們來看下在中間人攻擊下,HTTPS 的三階段:

連線 1:客戶端與中間人的 HTTPS 連線:

  • CA 證書校驗: 客戶端與 MITM 握手,MITM 返回一個 “調包” 的 CA 證書(為了讓客戶端驗證 CA 證書透過,需要提前在系統上安裝 MITM 的證書);
  • 金鑰協商: 客戶端和 MITM 基於 “調包” 的公鑰和私鑰進行非對稱加密通訊,協商獲得對稱金鑰;
  • 資料傳輸: 客戶端和 MITM 基於協商的對稱金鑰進行對稱加密通訊,此時 MITM 就可以解密出明文。

連線 2:中間人與服務端的 HTTPS 連線:

  • CA 證書校驗: MITM 與 服務端握手,服務端返回 CA 證書,由於服務端證書本來就是合法的,因此 MITM 可以拿到服務端公鑰;
  • 金鑰協商: MITM 和服務端分別基於公鑰和私鑰進行非對稱加密通訊,協商獲得 Master Secret 對稱加密私鑰;
  • 資料傳輸: MITM 和服務端基於協商的對稱金鑰進行對稱加密通訊。

到這裡,MITM 就成功與真實的客戶端和服務端建立了獨立的連線,傳送的密文在 MITM 上就可以成功解密出來了。

既然 HTTPS 可以被抓包,是否說明 HTTPS 不安全的?

這需要區分不同使用場景下安全的口徑,在使用者不知情的場景 HTTPS 完全可以保證資料安全傳輸,而對於使用者主動授權的場景,使用者需要為這個主動的行為承擔相應的安全風險。

總結一下實現 HTTPS 抓包的基本步驟:

  • 部署 MITM 代理伺服器;
  • 透過代理等方式將網路流量歸集到 MITM 主機;
  • 客戶端信任 MITM CA 證書;
  • MITM 分別與客戶端和服務端建立獨立連線;
  • MITM 解密客戶端和服務端的通訊,並結構化地呈現出來。

3. 在 Android 上安裝 CA 證書

在 Android 上安裝 CA 證書,可以總結為三種,其中系統證書和使用者證書都可以在系統設定中 信任的憑據 中檢視:

  • 系統證書: 系統 CA 證書安裝在 /system/etc/security/cacerts/ 目錄,只允許 Root 許可權才能進行新增和刪除。需要注意系統 CA 證書有特殊的命名格式:雜湊值.0 ,轉換方法可以參考:Android 內建證書檔案
  • 使用者證書: 使用者 CA 證書安裝在 /data/misc/user/0/cacerts-added/ 目錄,由使用者自行安裝,可以在系統設定 安裝證書 中進行安裝。由於 Android 7.0 行為變更,對於 targetSdkVersion ≥ 24 的應用,系統不再預設信任使用者證書,需要在配置中額外宣告:

應用級 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
       ... >
        ...
    </application>
</manifest>

network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <!-- Trust preinstalled CAs -->
            <certificates src="system" />
            <!-- HERE: Additionaly trus user added CAs -->
            <certificates src="user" />
        </trust-anchors>
    </base-config>
</network-security-config>
  • 應用固定證書(Certificate Pinner): 客戶端可以直接內建信任的服務端證書來限制其接受的證書集,用自定義的 TrustManager 代替系統預設的 TrustManager。在 HTTPS 請求的證書校驗階段,只有其接受的證書集合可以校驗透過,這也是規避中間人攻擊的一種方案。例如:
protected static SSLSocketFactory getSSLSocketFactory(Context context, @ResId int[] certificates) {
    if (context == null) {
        throw new NullPointerException("context == null");
    }
    CertificateFactory certificateFactory;
    try {
        certificateFactory = CertificateFactory.getInstance("X.509");
        // Create a KeyStore containing our trusted CAs
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        for (int i = 0; i < certificates.length; i++) {
            // 載入內建證書
            InputStream is = context.getResources().openRawResource(certificates[i]);
            keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(is));
            if (is != null) {
                is.close();
            }
        }
        // Create a TrustManager that trusts the CAs in our keyStore
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
        return sslContext.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
// 透過 OkHttp 的 API 設定證書
OkHttpClient.Builder builder = new OkHttpClient.Builder();

int[] certficates = new int[]{R.raw.media};
builder.socketFactory(getSSLSocketFactory(context, certficates));
...

突破 Android 7.0 使用者 CA 證書限制

由於 Android 7.0 行為變更,對於 targetSdkVersion ≥ 24 的應用,系統不再預設信任使用者證書,需要在應用的 AndroidManifest.xml 中增加 android:networkSecurityConfig 配置。如果你要抓包第三方應用,並且該應用沒有配置,就需要採用一些手段來突破限制了:

  • 方法 1 - 使用 Android 7.0 以下系統: 從源頭抹平使用者證書限制,這個最簡單直接;
  • 方法 2 - 使用平行空間等虛擬系統: 使用 HttpCanary 平行空間或 VMOS App 等虛擬系統,在手機上虛擬出 Android 7.0 以下系統環境;
  • 方法 3 - 安裝證書到系統證書目錄: 需要 Root 許可權,把 CA 證書直接安裝到系統證書目錄下。

4. Fiddler 使用技巧總結

Fiddler 目前主要是用在 Window 系統上的網路除錯工具,最新版本的 Fiddler Everywhere 開始支援全平臺了,但我體驗下來覺得不少功能是缺失的,期待官方更新。下面的介紹是採用新版 Fiddler Everywhere,操作與舊版本上是類似的。

4.1 使用 Fiddler 進行 HTTPS 抓包

這裡總結一下使用 Fiddler 進行抓包的主要步驟,其實就是按照 第 2 節 提到的 實現 HTTPS 抓包的基本步驟 的思路進行配置:

  • 1、部署 MITM 代理伺服器: 在電腦上啟動 Fiddler,預設會在這臺電腦的 8866 埠部署 Fiddler 的 Web 伺服器,可以透過下圖設定頁面修改埠。因為我們是在手機上抓包,所以還必須勾選 Allow remote computers to connect

  • 2、透過代理等方式將網路流量歸集到 MITM 主機: 在電腦命令列執行 ipconfig 獲得本地 IP 地址,然後將手機和電腦連線到同一個區域網下,再修改手機連線 Wifi 的高階設定,設定代理到電腦的 IP 地址和 8866 埠號。至此,你就可以在 Fiddler 上看到抓取的網路請求了,但還看不到 HTTPS 協議傳輸的資料,介面上會有一個 “加鎖” 的圖示提示:

  • 3、客戶端信任 MITM CA 證書: 首先需要在 Fiddler 上開啟 Https 抓包功能,才能匯出證書,在下圖設定頁面中,先點選 Trust root certificate,再勾選 Capture HTTPS traffic 選項。

然後,你需要把 CA 證書安裝到手機上,有兩種方式:先使用 Export root certificate 匯出證書檔案(預設匯出到桌面),再自行將檔案傳送到手機上;也可以在手機瀏覽器上訪問 ipv4.fiddler:8866/,點選 FiddlerRoot certificate 直接下載證書。

現在你已經成功把 CA 證書下載到手機上了,還需要手動安裝證書。在系統設定中搜尋 安裝證書,找到剛才下載的 CA 證書並安裝(不同手機系統介面不同):

到這裡,你已經順利地在 Fiddler 上抓取到 HTTPS 請求了。最好在篩選欄上過濾干擾項,比如不重要的域名,CONNECT 握手驗證請求:

  • 過濾域名:contains juejin
  • 過濾 Method:is not equals to CONNECT、HEAD
  • 過濾 Content-Type:does not contains image/

提示: 事實上,Fiddler 能做的不止是 HTTP 抓包這麼簡單,它還支援非常多高階功能。如果你需要深入研究 Fiddler,建議你直接購買肖佳寫的《HTTP抓包實戰》,下面我總結一些我玩過的一些技巧。

4.2 Fiddler 報文重放測試

重放攻擊(Replay Attacks)是指攻擊者透過抓包的方式,得到一個客戶端向服務端傳送的真實請求報文,並重復傳送給服務端的攻擊行為。比如攻擊者抓取到一個點贊、投票或領獎的請求報文,並重復傳送給伺服器。因為這個請求本身就是合法的,而且攻擊者並未篡改請求內容,所以服務端會誤以為是一個真實的有效請求。

在 Fiddler 上重放請求很簡單,有兩種方式:右鍵點選一個請求,選擇 Replay→Reissue Requests 直接重放;或者選擇 Edit in Composer 先編輯再重放。

我在專案中實際遇到過重放攻擊,一個比較聰明的使用者抓取到了 App 類似領金幣的請求報文,然後用重放攻擊薅了一波羊毛。防範重放方法是在請求中增加標識引數和數字簽名(防篡改):

  • 時間戳: 服務端將當前請求的時間戳與伺服器時間對比,如果超過了閾值(如 60s),則判定為過時請求。缺點是 60s 內的重放請求依然會被判定為有效;
  • 流水號: 服務端將當前請求的流水號與服務端記錄的流水號對比,如果收到一個非升序(相等或小於)則判定為過時請求。缺點是需要保證報文順序。
  • 一次性口令: 服務端用當前請求的一次性口令在服務端維護的口令表中查詢,如果已經使用過該口令則判斷為過時請求。缺點是需要維護口令表,實踐中可以綜合使用時間戳 + 一次性口令的方案,這樣既避免了短時間內的重放攻擊,服務端也只需要維護一小段時間視窗內的口令表。

4.3 Fiddler 弱網模擬

目前最新版本的 Fiddler Everywhere 還不支援弱網模擬,需要使用舊版本的 Fiddler Classic,配置路徑為:Rules→Performances→Simulate Modem Speeds

4.4 Fiddler 修改 HTTP 請求

Fiddler 本身是一個代理伺服器,可以攔截 HTTP 請求 / 響應進行修改後再放行。在 Fiddler 上配置 Rule:

5. Charles 使用技巧總結

5.1 使用 Charles 進行 HTTPS 抓包

這裡總結一下使用 Charles 進行抓包的主要步驟,其實就是按照 第 2 節 提到的 實現 HTTPS 抓包的基本步驟 的思路進行配置:

  • 1、部署 MITM 代理伺服器: 在電腦上啟動 Charles,預設會在這臺電腦的 8888 埠部署 Charles 的 Web 伺服器,可以透過下圖設定頁面修改埠,這裡最好不要使用預設埠號。

  • 2、透過代理等方式將網路流量歸集到 MITM 主機: 在電腦命令列執行 ipconfig 獲得本地 IP 地址(也可以透過 Charles 的 Help→Local Ip Address 檢視),然後將手機和電腦連線到同一個區域網下,再修改手機連線 Wifi 的高階設定,設定代理到電腦的 IP 地址和 8888 埠號。
  • 3、客戶端信任 MITM CA 證書: Charles 抓包也需要在手機上信任 Charles CA 證書。根據指引,會讓你在手機瀏覽器訪問 chls.pro/ssl 下載證書,安裝證書的方法與 Fiddler 相同(踩坑記錄:使用預設埠號 8888 時,訪問 chls.pro/ssl 一直不會自動下載,修改埠號就可以了)。

到這裡,你已經順利地在 Charles 上抓取到 HTTPS 請求了。最好在篩選欄中過濾掉干擾項:

5.2 Charles 報文重放測試

在 Charles 上重放請求很簡單,有兩種方式:右鍵點選一個請求,選擇 RepeatRepeat Advanced 直接重放;或者選擇 Compose 先編輯再重放。其中 Repeat Advanced 適合做壓力測試。

5.3 Charles 弱網模擬

開啟 Proxy→Throttle Settings 進入弱網配置頁面,其中 Throttle preset 提供了多種預置的網路環境模擬配置,可以直接在此基礎上修改。各個選項的含義如下:

  • Bandwidth 頻寬: 單位時間內透過通訊鏈路的資料量,即每秒傳輸的資料量;
  • Utilisation 利用率: 頻寬利用率;
  • Round-trip latency 往返時延: 傳輸層概念,指報文在客戶端和服務端之間一次往返通訊的時延;
  • MTU 最大傳輸單元: 資料鏈路層概念,指資料幀可攜帶最大的有效載荷,在乙太網中一般是 1500 位元組。如果一個 IP 包大小超過了 MTU,則會進行 IP 分片;
  • Reliability 可靠性 / 丟包率: 指資料傳輸失敗的機率;
  • Stability 穩定性 / 抖動率: 指網路環境的穩定性,如果網路不穩定,則資料傳輸的成功率也會不穩定。這個引數非常適合模擬行動網路;
  • Unstable quality range 不穩定質量範圍: 指網路環境的不穩定範圍。

5.4 Charles 修改 HTTP 請求

Charles 本身是一個代理伺服器,可以攔截 HTTP 請求 / 響應進行修改後再放行。在 Charles 上配置 Rewrite:

6. 手機本地抓包方案

前面提到的 Fiddler、Charles 或者 Wireshark 等抓包方案的整個配置過程是比較繁瑣的,比如需要配置手機代理、安裝證書等。最大的缺點是都依賴於一臺部署代理伺服器的電腦,不能滿足隨時隨地抓包的需求。實踐中可以採用綜合的抓包方案:在手機上使用本地抓包方案,無法滿足需求時再使用 Fiddler 等方案補齊。

6.1 VPNService API

VPNService 是 Android 4.0 引入的 API,能夠對系統流量進行擷取且不需要 root 許可權。我們熟悉的科學上網 Ap,其實就是執行了一個 VPNService 服務。在 VPN 執行時,通常在通知欄上會有相關的提示,比如 “已啟用 VPN” 等。在系統設定中搜尋 VPN,可以檢視當前手機中提供 VPN 服務的應用,例如:

  • HttpCanary App

HttpCanary 是一款強大的針對安卓手機的網路分析工具,它的工作原理是基於 VPNService 部署了一個 MITM 代理伺服器來抓取網路資料包。另外,HttpCanary 還支援破解雙向認證抓包,具體操作參考:Android 平臺 HTTPS 抓包全方案

不過,HttpCanary 在 Android 11 系統上抓取 HTTPS 請求會有問題,由於系統只允許從系統設定中手動選擇安裝證書,而目前 App 還沒有適配這個問題,導致安裝證書這一步就涼了。我提供兩個解決方法:

  • 1、使用低版本的手機(極其舒適);
  • 2、使用有 Root 許可權的手機,手動把從 HttpCanary 內部儲存空間中把證書撈出來,再轉化為系統證書並安裝。這個方案順便還解決了第三方應用未配置 android:networkSecurityConfig 的問題。具體操作參考:安卓 11 httpcanary 小黃鳥系統證書的安裝

  • 有贊移動助手 App

有贊技術團隊是我經常關注的團隊之一,有贊移動助手 App 本地抓包方案 是他們 19 年分享的一個手機本地抓包方案。有贊助手 App 的原理也是基於 Android VPNService 或 IOS NetworkExtension 搭建的代理伺服器,由助手 App 與真實伺服器完成 HTTP 請求,相當於是自實現的 HttpCanary。目前以小彭的積累還不能完全消化實現這個方案,就記錄在這裡提供個思路吧。

6.2 OkHttp 攔截器

對於基於 OkHttp 實現網路請求的應用,可以透過攔截器監控應用內的網路資料,再透過通知欄、桌面小部件等入口檢視抓取的資料。目前業內已經有開源的實現可直接使用:

  • Chunk 整合 Chunk 後,通知欄訊息會顯示監控到的網路請求,點選後可以進入分析頁面。不過這個專案已經多年沒有維護了,而且也不支援資料篩選,有些雞肋。

  • 滴滴 DoraemonKit

滴滴的 哆啦A夢 大家都比較熟悉了,是一款面向大前端產品的效率平臺,目前已經發展成一個相對完整的生態,支援 Android 和 iOS 等多個平臺。DoraemonKit 也提供了網路監聽的能力,其原理同樣是基於 OkHttp 攔截器。區別在於 DoraemonKit 是透過 AOP 注入的方式新增攔截器,所以初始化時不需要我們手動新增攔截器,這也很好地解決不同元件存在多個 OkHttpClient 的問題。相關原始碼:

HttpUrlConnectionProxyUtil

private static void addInterceptor(OkHttpClient.Builder builder) {
    // 判斷當前是否已經新增了攔截器,如果已新增則返回
    for (Interceptor interceptor : builder.interceptors()) {
        if (interceptor instanceof DokitMockInterceptor) {
            return;
        }
    }

    builder
        //新增mock攔截器
        .addInterceptor(new DokitMockInterceptor())
        //新增大圖檢測攔截器
        .addInterceptor(new DokitLargePicInterceptor())
        //新增dokit攔截器
        .addInterceptor(new DokitCapInterceptor())
        //新增弱網 攔截器
        .addNetworkInterceptor(new DokitWeakNetworkInterceptor())
        // 新增擴充套件攔截器
        .addInterceptor(new DokitExtInterceptor());
}

  • 優酷 Ribut

Ribut 是小彭交流群中的同學分享的,看了下目前這個專案還處於比較初期的階段,期待後續的發展。它是阿里巴巴優酷技術團隊研發的視覺化除錯架構,目標是透過工具化手段解決研發日常痛點問題,如網路抓包。其中網路抓包的大概思路分為兩步:1、透過掃碼建立與 PC 端連線;2、透過 OkHttp 攔截器抓取網路請求資料並轉發到 PC 端。雖然嚴格來說還是依賴 PC 端,但是整體的體驗是 OK 的。

7. 總結

說了這麼多抓包的方案,讓我們換個視角,App 反抓包有哪些方案,你知道嗎?關注我,帶你瞭解更多,我們下次見。

參考資料
你的點贊對我意義重大!微信搜尋公眾號 [彭旭銳],希望大家可以一起討論技術,找到志同道合的朋友,我們下次見!

相關文章