Android 系统各个版本上https的抓包

一、本文侧重点在哪?

  1. https 的客户端和服务器端的请求流程,加了Charles之后对请求有什么影响(中间人攻击)
  2. 我们原来怎么抓https包的
  3. Android 7.0 (api 24 ) 和 targetSdkVersion 对抓包的影响
    开发者对自己app的抓包,
    逆向工程师对别人app的抓包
  4. 有 CA 签名的证书访问https的app和自签名证书app
    微博和我们自己的app
  5. 设置 VirtualApp 为debug模式,并且设置VA 的targetSdkVersion <24

二、测试环境

  • Android EVA-AL00 7.0 API 24 华为,安装了Charles根证书,并且设置了代理
  • Android EVA-AL00 6.0 API 23 华为,安装了Charles根证书,并且设置了代理
  • 微博客户端,微博热搜榜接口这个接口当时是用oppo测试机抓的不用关心里面的参数,能用就行
  • 网络请求框架 okhttp3

三、https 通信过程和中间人攻击

下图是https 客户端和服务器端通信的基本流程

https 通信过程.png

那么如何抓包呢,原理其实说起来也很简单,就是在客户端给服务器端发消息的时候,中间人(Charles)截取客户端发送给服务器的请求,然后伪装成客户端与服务器进行通信;将服务器返回给客户端的内容发送给客户端,伪装成服务器与客户端进行通信。

其实Charles就是这么做的,当配置了Charles之后,理论上所有的http/https请求数据都被拦截到了。看下面一张简化的中间人抓包的图:

中间人抓包图.png

四、我们原来怎么抓https包的

分为三步

  1. 手机上导入Charles根证书,导入方式见Charles官网

    Charles导入根证书到Android设备上.png

  2. 电脑端Charles设置https抓包配置
    菜单栏 Proxy -->ProxySetting


    电脑端Charles设置.png
  3. 手机端配置代理


    手机端配置代理.png

理论上来说按照上面的配置,配置之后,然后手机上安装新浪微博客户端之后就能抓到微博的数据了。我们分别用Android6.0 和 Android7.0 的手机打开微博客户端看下效果:

发现在6.x系统上,微博打开正常,并且Charles显示列出了已经抓到的微博的接口api。

但是在Android 7.x操作系统上,微博显示网络出错啦,请点击按钮重新加载如下图:

Android 7.X系统微博抓包失败

而且Charles上显示确实抓到了包,但是报错You may need to configure your browser or application to trust the Charles Root Certificate. See SSL Proxying in the Help menu.,Charles说手机端没有信任Charles的根证书,但是我们手机上已经安装了Charles根证书了,为什么会这样?

原来在Android 7.0(API 24 ) ,有一个名为“Network Security Configuration”的新安全功能。这个新功能的目标是允许开发人员在不修改应用程序代码的情况下自定义他们的网络安全设置。如果应用程序运行的系统版本高于或等于24,并且targetSdkVersion>=24,则只有系统(system)证书才会被信任。所以用户(user)导入的Charles根证书是不被信任的。具体说明看官方文档在这个官方文档里面说了,如何能指定信任用户安装的根证书从而可以实现抓包。

五、Android 7.0 (api 24 ) 和 targetSdkVersion 对抓包的影响

这里要分两种情况:

  • 抓自己开发的app的网络包
  • 抓第三方app的网络包,比如微博客户端

这两种情况有什么区别的,第一种app是我们自己开发的,我们手里有源码,能够修改,能够做到像官方文档里面说的一样进行配置。第二种我们没有源码,要想做到像官方文档里面配置的话,只能反编译后,把配置文件添加进去然后重新打包,但是重新打包就会遇到很多坑,并不一定能成功,所以需要使用其他方式达到抓包目的。

引用官方文档一句话:默认情况下,来自所有应用的安全连接(使用 TLS 和 HTTPS 之类的协议)均信任预装的系统 CA,而面向 Android 6.0(API 级别 23)及更低版本的应用默认情况下还会信任用户添加的 CA 存储。应用可以使用 base-config(应用范围的自定义)或 domain-config(按域自定义)自定义自己的连接。

如何能在Android 7.0 上成功的抓自己开发的app的https的包

目前我常用的有三种方式:

  1. 操作系统,通过apk里面的配置文件控制app的证书信任机制

Android 官方文档里的配置方式进行配置,文档里面讲的很详细,7.0之后对于自己app可选择的可信任的证书链控制很细。我这里只添加一种方式,让我们自己开发的app能够信任Android手机上用户导入的根证书。

如下图所示,手机的根证有两种,一种是系统预装的,一种是用户自己导入的:


手机上的证书分为两种.png

配置方式:

添加如下文件 res/xml/network_security_config.xml 到你的代码里面,然后就能实现debug模式下,信任用户自己安装的根证书,比如Charles的证书:

<network-security-config> 
  <!-- Trust user added CAs while debuggable only -->
  <debug-overrides> 
    <trust-anchors> 
      <!--信任用户安装的证书-->
      <certificates src="user" /> 
    </trust-anchors> 
  </debug-overrides> 
</network-security-config>

然后在你 app的 manifest 文件中引入上面的文件, 如下所示:

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

然后配置好之后,这样就可以直接访问,https请求,并且通过Charles抓包了。

  1. 降低 targetSdkVersion 的版本来绕过Android 7.0(api 24)上网络的安全机制
    如果我们不想像上面一样配置这么复杂的东西,可以通过降低targetSdkVersion的方式来达到一样的效果,在gradle文件中配置 targetSdkVersion < 24 就可以了,但是这玩意在以后开发规范中有可能是不允许的。
defaultConfig {
        applicationId "me.febsky.okhttp.test"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

网络请求测试代码如下,别忘了配置manifest文件中申请网络请求权限:

package me.febsky.okhttp.test;

import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread() {
            @Override
            public void run() {
                super.run();

                String url = "https://api.weibo.cn/2/guest/cardlist?" +
                        "networktype=wifi&uicode=10000327&moduleID=708&checktoken=" +
                        "&c=android&i=a52f1e4&s=c1108e87&ua=OPPO-OPPO%20R9m__weibo__6.8.2__android__android5.1" +
                        "&wm=9847_0002&aid=01AilAKZLB81znjKciZxofmqIMYg52EReWuEaQL7hIDXj6IR4." +
                        "&did=b87cc255f19b91ff8e202968adab0eb9fc159a2e&&v_f=2&v_p=33" +
                        "&from=1068295010&gsid=_2AkMg6ZSSf8NhqwJRmP0QzGPgb4l_wgjEieLBAH7sJRM3HRl-3T9jqnUstRUyD-wT6lM3A4HWHM1fFXBWuOYnxg.." +
                        "&lang=zh_CN&page=1&skin=default&count=20&oldwm=9893_0044" +
                        "&sflag=1&containerid=1087030002_2982_2_50&need_head_cards=0";

                OkHttpClient okHttpClient = new OkHttpClient.Builder()
                        .connectTimeout(10, TimeUnit.SECONDS)
                        .writeTimeout(10, TimeUnit.SECONDS)
                        .readTimeout(10, TimeUnit.SECONDS)
                        //.sslSocketFactory()
                        .build();

                Request request = new Request.Builder()
                        .url(url)
                        .build();

                okHttpClient.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        Log.e("Q_M", "GET -->" + e.toString());

                    }

                    @Override
                    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                        Log.d("Q_M", "GET -->" + response.body().string());
                    }
                });
            }
        }.start();
    }
}
  1. 修改http请求框架的协议栈,让框架不验证证书

这里我们用的是okhttp的网络请求框架,如何添加信任证书或者,不验证证书,可以参考官网文档,或者自行搜索。这会因为你使用的框架不同而不同。现在的开发者一般没人这么用吧,这么用了,还特么用啥https 啊??!

如何在Android 7.X 以上系统中抓第三方app的https包

一般情况下第三方我们都是抓第三方app的包,是为了分析别人的数据。但是像上面所说的前两种方式一般在第三方app上不会存在的,谁也不想让自己的app被抓包不是。

  1. 我们可以通过重打包的方式强行修改配置,或者强行降低 targetSdkVersion,或者强行修改别人源码里面的信任证书的代码,然后再重打包就好了(分别针对上面1,2,3里面所说的方法,只不过通过逆向的方式添加)。

  2. 通过使用Xposed的 JustTrustMe 模块来信任所有的证书,Xposed不会用的看这里

  3. 有点伪~~,使用Android 7.0 以下的系统安装应用,并抓包

  4. 使用双开沙箱应用,比如VirtualApp,让被抓包的应用运行到VA里面,并且修改VA的targetSdkVersion < 24,或者通过Manifest文件里面配置networkSecurityConfig属性,让他信息Charles证书。

  5. 把 Charles 证书,打入到Android 系统信任证书里面去

六、使用自签名证书的应用和双向验证的应用

在抓取一些第三方应用的包的时候,他们比较顽固:其一呢,客户端通过指定的方式只信任某一个证书;其二,我们一般来说只有客户端验证服务端公钥证书是不是合法,但是某些app,比如支付宝,采用双向验证的方式,在通信过程中,服务器会验证app的公钥证书,这时候,就没办法使用Charles(中间人攻击的方式)抓包了。

自签名证书的https和CA签名的https 区别在哪里?对https请求的影响又在哪里?关于这些问题建议了解些证书和CA相关的概念以及证书分发的流程。

从前面的https 客户端和服务器端通信的时序图中可以看到,在握手过程中客户端会验证服务器的公钥证书。如果证书验证不通过会终止请求。那么问题来了,证书合不合法谁说了算?一般来说,在Android 手机出厂的时候会预装一些证书,这些证书是CA的根证书,理论上来说默认是可信任的。如上图:手机上的证书分为两种.png 里面的系统列表的证书就是。对于网站来说,这个验证过程是由浏览器来做的,浏览器通过系统里面预装的CA的根证书来验证这个服务器的证书是不是由系统已经有的这些CA签发的。如果是那么就认为这个https的请求中的服务器证书是可信的。

但是话说回来,我们在开发app的时候,请求网络一般用的是网络框架,这灵活性就比较好了,大多数的网络请求框架(HttpClient,OkHttp等)都支持修改这个证书验证过程,就是我不用上面所说的浏览器验证证书的过程,修改框架里面这个验证过程,(因为CA签发证书是要花钱的好多公司都是用的自签名的证书)指定框架在验证的时候只会认为和我们公司一样的证书才认为验证成功,才能进行接下来的通信。

其实上面 通过使用Xposed的 JustTrustMe 模块来信任所有的证书,Xposed不会用的看这里 就是针对这些网络请求框架进行攻击的 通过hook的方式强制让这些网络框架信任所有的证书。

推荐阅读更多精彩内容