关于Android 抓包 与 反抓包

作者: ea14cffb33a4 | 来源:发表于2020-07-17 17:49 被阅读0次

    1现象与原因

    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: 
    

    而此时的 charles 显示如下:

    ps:一般在 Android 手机的这个打开路径下「设置 - 某个“安全”设置子页面 - 加密与凭证 - 信任的凭证」,可以看到该设备的所有根证书。

    分为「系统」和「用户」两个分类。

    2开发者抓第三方的 Https 包 解决办法

    如果第三方只是使用默认的 Android Https 配置,那么可以使用这两种方法可以抓到它们的请求包。

    1. 使用 Android7.0 以下的手机安装应用,然后抓包

    很好理解,上面讲过了只有 Android7.0 以后,才开始不信任用户根证书。

    2. 想办法把 你的 charles 证书或者其他证书,变成设备的根证书

    比如你是手机设备厂家,或者你可以编辑一套ROM出来,当然可以把任何个人证书给搞成是系统证书。

    另一种方法是,需要一部有Root权限的手机,安装 Xposed 的 JustTrustMe 模块来信任所有的证书。

    3开发者抓自己 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” 属性。

    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());
                }
            });
        }
    
    }
    

    4怎么防止被其他开发者抓包

    前面讲到,默认的 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,可以学到这部分的使用方法

    https://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html

    https://stackoverflow.com/questions/24006545/how-can-i-pin-a-certificate-with-square-okhttp

    步骤①:写一个 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」部分

    https://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html

    验证结果

    按照 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的请求将会失效。如果开启的是全部域名的证书配置,意味着你连应用内升级或者热更新都用不了,绝对是重大事故。

    5还有什么骚操作

    上面讲到我们配置到工程代码里的是 网站的公钥(任何人都可以随意下载)

    根据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();
    

    文章最后 分享

    移动架构师笔记+阿里P6P7【安卓】进阶资料分享+加薪跳槽必备面试题+安卓程序员简历模板

    相关文章

      网友评论

        本文标题:关于Android 抓包 与 反抓包

        本文链接:https://www.haomeiwen.com/subject/bgcnhktx.html