27

关于Android 抓包 与 反抓包

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ%3D%3D&%3Bmid=2650831957&%3Bidx=1&%3Bsn=41099f951f7a9f085759631a589d47b3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文作者

作者: 迪迪迪迪迪

链接:

https://blog.csdn.net/alcoholdi/article/details/106455192

本文由作者授权发布。

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: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

而此时的 charles 显示如下:

3QNbqa2.png!web

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

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

JVBVnyJ.png!web

nmqEJvv.png!web

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” 属性。

yUvymq7.png!web

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 官方配置信息证书

比如你可以像这样把你信任的网站的证书给搞下来.

步骤①: 点击域名旁边锁的图标,弹出框里面点「证书」

IvARrye.png!web

mac系统,对着证书那个图标拖动到某个文件夹里。这样你就能得到一个HTTPS的里面的SSL里面的非对称加密算法的公钥。

QNzama3.png!web

步骤②: 放进 res/raw 文件夹里,在network_security_config.xml 里写上相关配置

NfaMbey.png!web

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

附赠一份看过的比较好的 HTTPS 文章《从0到1讲解HTTPS设计流程》,帮助解决95%关于 HTTPS 方面的疑问。

也许,这样理解HTTPS更容易

推荐阅读

直面底层:你真的了解 View.post() 原理吗?

吹爆系列:深入实战Android卡顿优化

Android UI 渲染机制的演进,你需要了解什么?

vEVjqa7.jpg!web

扫一扫  关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~

┏(^0^)┛明天见!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK