關於Android 抓包 與 反抓包

現象與原因

Android 對於 Http 和 Https 兩類網絡請求

Http

因爲沒有加密,屬於明文傳輸,是可以抓包的。
但是從 Android 9.0 開始,默認是禁止 App 使用 Http 這種使用所有未加密的連接,使用 Http 會導致程序報錯。

java.net.UnknownServiceException: CLEARTEXT communication

但還是可以通過寫一段關於網絡安全的配置 network_security_config ,讓系統允許繼續使用 Http 協議。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

這裏先跳過,下面會詳細講怎麼讓它生效。
可以認爲 Android9.0 以後不推薦使用 Http,要求開發者轉移到 Https。


Https

Https 是一種使用了加密傳輸的協議,防止了 App 和 服務器之間的中間人進來攔截、僞造、篡改問題。
但是如果是手機持有人,主動在手機裏安裝 charles 的根證書,實現了認證環節,是可以實現抓包的。
然而 Android 也發現了這種漏洞,爲了保護應用開發公司的通訊安全,在 Android7.0 以後,只信任 Android 設備的系統根證書。也就是如果你安裝的 charles 根證書這類屬於「用戶證書」分類的證書,Android 系統是不認的,照樣不讓 Https 請求正常通過。
程序會報錯:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

而此時的 charles 顯示如下:charles 顯示紅色X圖標

ps:一般在 Android 手機的這個打開路徑下「設置 - 某個“安全”設置子頁面 - 加密與憑證 - 信任的憑證」,可以看到該設備的所有根證書。
分爲「系統」和「用戶」兩個分類。





——————————————————————————————

解決辦法

一、開發者抓第三方的 Https 包

如果第三方只是使用默認的 Android Https 配置,那麼可以使用這兩種方法可以抓到它們的請求包。

1. 使用 Android7.0 以下的手機安裝應用,然後抓包

很好理解,上面講過了只有 Android7.0 以後,纔開始不信任用戶根證書。

2. 想辦法把 你的 charles 證書或者其他證書,變成設備的根證書

比如你是手機設備廠家,或者你可以編輯一套ROM出來,當然可以把任何個人證書給搞成是系統證書。
另一種方法是,需要一部有Root權限的手機,安裝 Xposed 的 JustTrustMe 模塊來信任所有的證書。




二、開發者抓自己 App 的 Https 包

0.應該不會有人爲了抓包,把 targetSdkVersion 強行改成低於24(Android 7.0)的版本吧。孩子睡覺老是踢被子,幸好被我及時發現打斷了腿,否則肯定感冒。

1. 使用 Android 提供的「網絡安全配置(Network security configuration)」

官方講解文檔:Android 開發者官網 網絡安全配置
步驟一:在 manifest 文件中配置一個 android:networkSecurityConfig 屬性,填寫一個 xml 文件。

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

步驟二:在 res/xml 文件夾裏創建一個 network_security_config.xml 文件,裏面配置如下。
配置的意思是在 debug 模式下,信任用戶證書。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <debug-overrides>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>

一個很重要的知識點是,區分是不是 debug 包是通過 module 的 build.gralde 文件,在 buildType 裏面的 debuggable 字段來決定的。

    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //debuggable true  如果打生產包時候忘了關,就玩脫了
        }
        preRelease {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            debuggable true //推薦這種做法。創建一個預發佈的 buildType,打開 debuggable=true 專門給測試人員能在正式域名環境裏抓包檢查用。
        }
        debug {
            minifyEnabled false
            shrinkResources false
        }
    }

在 buildTypes 裏配置這個 debuggable 屬性,最終會被合併到 manifest 文件裏面的 <application 結點下,增加一個 android:debuggable=“true” 屬性。
合併後的manifest文件

2. 配置 OkHttp 信任所有證書

在使用 builder 模式構建 OkHttpClient 的時候,增加 sslSocketFactory 和 hostnameVerifier 配置項。下面的演示代碼裏這兩個配置項裏面會信任所有證書。爲了避免玩脫也記得只在 BuildConfig.DEBUG 條件下才使用這個配置。這個 BuildConfig.DEBUG 的值跟前面講的 debuggable 是一致的。

public class ZhihuHttp {

    public static final String ZHIHU_BASE_URL = "https://news-at.zhihu.com/api/";

    private static final ZhihuHttp zhihuHttp = new ZhihuHttp();

    private OkHttpClient okHttpClient;

    private static SSLSocketFactory createSSLSocketFactory() {
        SSLSocketFactory sSLSocketFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()},
                    new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sSLSocketFactory;
    }

    private static class TrustAllManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    private ZhihuHttp() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(10, TimeUnit.SECONDS);

        if (BuildConfig.DEBUG) {
            builder.sslSocketFactory(createSSLSocketFactory(), new TrustAllManager());
            builder.hostnameVerifier(new TrustAllHostnameVerifier());
        }

        okHttpClient = builder.build();
    }

    public static ZhihuHttp getZhihuHttp() {
        return zhihuHttp;
    }

    public void getDailiesWithCallback() {
        Request request = new Request.Builder()
                .url(ZHIHU_BASE_URL + "4/news/latest")
                .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.e("YAO", "ZhihuHttp.java - onFailure() ----- e:" + e.toString());
                e.printStackTrace();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.e("YAO", "ZhihuHttp.java - onResponse() ----- :" + response.toString());
            }
        });
    }

}




三、怎麼防止被其他開發者抓包

前面講到,默認的 Android Https 配置下,只要使用 Android7.0 以下的手機、或者找個 Root 設備安裝把用戶證書(比如charles證書)想辦法搞進系統證書那部分,就可以抓包了。這對於黑產來說也忒忒忒簡單了。那麼怎麼防止呢?
答案是配置你信任的網站證書或者配置信任的認證鏈

1.Android 官方配置信息證書

比如你可以像這樣把你信任的網站的證書給搞下來
步驟①:點擊域名旁邊鎖的圖標,彈出框裏面點「證書」
查看網站證書
mac系統,對着證書那個圖標拖動到某個文件夾裏。這樣你就能得到一個HTTPS的裏面的SSL裏面的非對稱加密算法的公鑰。
拿到網站證書
步驟②:放進 res/raw 文件夾裏,在network_security_config.xml 裏寫上相關配置
配置信任某幾個證書

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

    <!-- 這個是全局的基礎的配置 -->
    <base-config>
        <trust-anchors>
            <!-- 如果整個base-config都不寫,就等於是<certificates src="system" /> -->
            <!-- 這裏寫全局基礎配置,只信任下面某幾個證書 -->
            <certificates src="@raw/zhihu" />
            <certificates src="@raw/baidu" />
        </trust-anchors>
    </base-config>

    <!-- 如果只對某些涉及數據安全的私密域名進行保護,可以針對某個域名,只信任某幾個證書 -->
    <domain-config>
        <domain includeSubdomains="true">zhihu.com</domain>
        <trust-anchors>
            <certificates src="@raw/zhihu" />
            <certificates src="@raw/tencent" />
        </trust-anchors>
    </domain-config>

    <debug-overrides>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>

</network-security-config>

以上就是 Android 官方推薦的做法。





——————————————————————————————

2. OkHttp 配置信任認證鏈

參照這部分的 OkHttp官方介紹 以及 stack overflow,可以學到這部分的使用方法


步驟①:寫一個 CertificatePinner 的配置

其中的 add方法兩個參數。第一個參數是網址的域名host,第二個是sha256的證書。證書我們目前不清楚,先輸入這麼一段字符串。使用這麼一段錯誤的配置運行後將會報錯,然後在日誌裏得到正確的配置信息。

注意第一個參數不要包含協議,也不要省略部分域名,錯誤示例 「https://news-at.zhihu.com」、「zhihu.com」。
第二個參數是個假證書識別串,但是有效的,我測試時候亂輸入了一串「sha256/wrong」沒有觸發到搜想要的結果。

public class ZhihuHttp {

    public static final String ZHIHU_BASE_URL = "https://news-at.zhihu.com/api/";

    private static final ZhihuHttp zhihuHttp = new ZhihuHttp();

    private OkHttpClient okHttpClient;

    private ZhihuHttp() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(10, TimeUnit.SECONDS);

        // 只信任網站對應的證書
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
                .add("news-at.zhihu.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
                .build();
        builder.certificatePinner(certificatePinner);
        okHttpClient = builder.build();
    }

    public static ZhihuHttp getZhihuHttp() {
        return zhihuHttp;
    }

    public void getDailiesWithCallback() {
        Request request = new Request.Builder()
                .url(ZHIHU_BASE_URL + "4/news/latest")
                .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.e("YAO", "ZhihuHttp.java - onFailure() ----- e:" + e.toString());
                e.printStackTrace();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.e("YAO", "ZhihuHttp.java - onResponse() ----- :" + response.toString());
            }
        });
    }

}

步驟②:執行代碼後報錯。搜索關鍵字,我們能得到這麼一串報錯
Subscriber onError() : javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain:
    sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=: CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
    sha256/zUIraRNo+4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=: CN=GeoTrust RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US
    sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=: CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
  Pinned certificates for news-at.zhihu.com:
    sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=

這報錯的意思是。現在訪問這個鏈接的認證鏈是 Peer certificate chain 下面的3個sha256。
「sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=」
「sha256/zUIraRNo+4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=」
「sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=」
鏈的含義是,第一個sha256對應的證書由第二個sha256對應的證書認證,第二個sha256對應的證書又第三個sha256對應的證書認證。

在代碼裏配置的用於「news-at.zhihu.com」域名的認證 sha256 期望是
「sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=」

因爲兩者對應不上,所以請求失敗。然而通過這個方法,我們得到正確的sha256,下面就拿這幾個正確的sha256來配置。


步驟③:配置正確 sha256

CertificatePinner certificatePinner = new CertificatePinner.Builder()
        //正常請求下的證書驗證鏈路
        .add("news-at.zhihu.com", "sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=")//CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
        .add("news-at.zhihu.com", "sha256/zUIraRNo+4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=")//CN=GeoTrust RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US
        .add("news-at.zhihu.com", "sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=")//CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
        .build();

配置後,這個域名的請求就不能被抓包了。因爲開啓抓包後,根證書是 charles 的公鑰,跟期望的 sha256 匹配不上。
一個很重要的點是,其實我們可以不用把3個 sha256 都加上。匹配邏輯是任意一個 sha256 匹配上請求就可以通過了。所以其實可以這麼寫。

CertificatePinner certificatePinner = new CertificatePinner.Builder()
        //正常請求下的證書驗證鏈路
        .add("news-at.zhihu.com", "sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=")//CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
        .build();

域名hostname 支持通配符,可以參考 OkHttp CertificatePinner 裏面的「Wildcard pattern rules」部分



驗證結果
按照 OkHttp 官方指導配置完後,使用 charles 抓包看看還能不能在 Android 7.0 以下系統抓到包。
驗證結果,不能抓包,會出現一個報錯:

Subscriber onError() : javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
  Peer certificate chain:
    sha256/dVUJFtUhQtJki5t0/j+hMYzTgtVkETqjsogUuyquPPo=: CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
    sha256/54ZQa+M6vq6DhdR7DLkc1X6fWmVEZ6wLZaaYwoR4Uvw=: C=NZ,ST=Auckland,L=Auckland,O=XK72 Ltd,OU=https://charlesproxy.com/ssl,CN=Charles Proxy CA (2 十月 2017\, YaodeMacBook-Pro.local)
  Pinned certificates for news-at.zhihu.com:
    sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=
    sha256/zUIraRNo+4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=
    sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=

可以看到現在訪問這個鏈接的認證鏈是 Peer certificate chain 下面的兩個sha256。
「sha256/dVUJFtUhQtJki5t0/j+hMYzTgtVkETqjsogUuyquPPo=」
「sha256/54ZQa+M6vq6DhdR7DLkc1X6fWmVEZ6wLZaaYwoR4Uvw=」
報錯的信息裏,第二個 sha256 冒號後面,顯示這串字符來自我的 charles 公鑰。


對比 未使用 和 使用 抓包的報錯信息,發現第一個 sha256 都是來自於知乎的公鑰,但兩個的 sha256 是不一樣的。
//未開啓抓包
sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=: CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
//開啓抓包
sha256/dVUJFtUhQtJki5t0/j+hMYzTgtVkETqjsogUuyquPPo=: CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN

一開始我一直以爲這一串東西是SSL公鑰進行一次hash算法得到的字符串。後面觀察後發現不是。
這是兩個公鑰在不同 「中間證書認證」-「中間證書認證」……「根證書認證」這種認證體系下的一個hash串。
所以如果開啓抓包,那麼他對應的根證書是 charles 的公鑰,得出的第一個來自知乎證書的 sha256 就會有所不同。


warning: 配置信任某個具體證書一定要與服務器開發或運維溝通好,因爲如果服務器進行了證書替換而App沒有更新到最新證書,App的請求將會失效。如果開啓的是全部域名的證書配置,意味着你連應用內升級或者熱更新都用不了,絕對是重大事故。




四、還有什麼騷操作

上面講到我們配置到工程代碼裏的是 網站的公鑰(任何人都可以隨意下載)
根據HTTPS的原理,公鑰和私鑰的原理,其實完全可以在代碼裏配置上開發者的 charles 公鑰(只針對某臺具體的筆記本,charles 爲它生成的一對公鑰私鑰)。因爲沒人能根據公鑰能破解出對應的私鑰。
所以如果在在 app 裏配上我們某部電腦 charles 公鑰,以後就可以用那個電腦抓正式環境正式域名的請求了。比如工程加上公司的公用開發機的 charles 公鑰,或者核心App測試大佬的 charles 公鑰。

使用 Android 官方配置信任證書可以這些寫

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

    <!-- 這個是全局的基礎的配置 -->
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <!-- 如果整個base-config都不寫,就等於是<certificates src="system" /> -->
            <!-- 這裏寫全局基礎配置,只信任下面某幾個證書 -->
            <certificates src="@raw/zhihu" />
            <certificates src="@raw/yao_charles" />
        </trust-anchors>
    </base-config>

    <debug-overrides>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>

</network-security-config>

使用OkHttp的配置的方法,這麼寫

// 只信任網站對應的證書
CertificatePinner certificatePinner = new CertificatePinner.Builder()
        //正常請求下的證書驗證鏈路
        .add("news-at.zhihu.com", "sha256/f5fNYvDJUKFsO51UowKkyKAlWXZXpaGK6Bah4yX9zmI=")//CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
        .add("news-at.zhihu.com", "sha256/zUIraRNo+4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=")//CN=GeoTrust RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US
        .add("news-at.zhihu.com", "sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=")//CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US

        //charles 抓包下的配置
        .add("news-at.zhihu.com", "sha256/dVUJFtUhQtJki5t0/j+hMYzTgtVkETqjsogUuyquPPo=")//CN=*.zhihu.com,OU=IT,O=智者四海(北京)技術有限公司,L=北京市,C=CN
        .add("news-at.zhihu.com", "sha256/54ZQa+M6vq6DhdR7DLkc1X6fWmVEZ6wLZaaYwoR4Uvw=")//C=NZ,ST=Auckland,L=Auckland,O=XK72 Ltd,OU=https://charlesproxy.com/ssl,CN=Charles Proxy CA (2 十月 2017\, YaodeMacBook-Pro.local)
        .build();





——————————————————————————————
附贈一份看過的比較好的 HTTPS 文章《從0到1講解HTTPS設計流程》,幫助解決95%關於 HTTPS 方面的疑問。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章