修改Android源码,解放HTTPS抓包

为什么HTTPS不能抓包了?

Google在Android 7.0以后的版本中,添加了“网络安全配置(Network security configuration)”的相关配置项。其旨在增强App的安全性,可以避免TargetSDK版本>=N的App内部网络请求在非测试环境下被恶意抓包。

Network Sercurity Configuration对安全性的保证,主要是通过以下几个途径:是否允许明文HTTP请求(非HTTPS)、HTTPS证书(区分系统、用户)信任设置、域名以及App的Debug或Release配置,只有符合Manifest中配置的NetworkConfig字段内容下的条件,才可以进行正常的HTTP请求,如果需要使用Charles、Fiddler等工具进行抓包,也需要利用NetworkConfig来配置可以信任的证书,否则HTTPS请求在CONNECT阶段,就会返回错误,同时LogCat会打印出类似以下的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Caused by: java.security.cert.CertificateException: xxx.
at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:661)
at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:539)
at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:605)
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:495)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:321)
at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:113)
at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:87)
at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:116)
at com.huawei.secure.android.common.ssl.SecureX509TrustManager.checkServerTrusted(:105)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:212)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSocket.java:404)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:375)

通常,Android开发者会将NetworkConfig按照以下XML文件进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 允许明文HTTP(非HTTPS)请求 -->
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!-- 其他情况只允许信任系统内置证书 -->
<certificates src="system"/>
</trust-anchors>
</base-config>
<!-- 仅Debug包允许用户证书 -->
<debug-overrides>
<trust-anchors>
<certificates src="user"/>
</trust-anchors>
</debug-overrides>
</network-security-config>

然而对于开发者来说,通常App发版上线会选择使用Release版本的安装包,如果希望对Release包抓包,只能使用Android 6.0及以下的手机,这样一来,有时候出现线上问题,便无法进行HTTPS抓包,并不利于测试和修正

如何解决这一问题?

基本的几个思路

  • 反编译APK,修改NetworkConfig相关的XML文件,让App可以信任用户配置的证书

    这个方案局限性比较大,现在很多App都对反编译技术(主要是apktool)进行了防御,有的App使用apktool解包以后,就无法成功打包了,还有的用上了加固技术,通用的反编译工具无法直接获取真正Apk内部的文件。不过,如果能够使用apktool成功获得解码后的XML文件,且进行修改后能够重新打包成可以运行的Apk,这样做也是可行的

  • 使用Hook

    使用Xposed框架在App请求过程中进行Hook,依赖的基础仍然是反编译,且Xposed框架需要建立在Root后的基础上,成功率比较低,且成本还比较高

  • 修改Android源码

    这个应该是最成功且一劳永逸的办法了,但成本不小,需要有一台性能不错的电脑(至少硬盘空间够大)和一台Google的手机,具体如何实现,下面详细来说

寻求一个适合自己的办法

这里并不是说修改源码就是最好的,举例来讲,能使用6.0手机,是最简单的办法,但随着现在最低SDK版本的提升,在不久的将来,一些App或许就不会再支持6.0了,所以我只是在寻找一个更为靠谱的方案

如何改Android源码

准备工作

首先需要将AOSP的源码准备好,这里就不在赘述了,跟着Google官方的文档下载即可:https://source.android.com/setup/build/downloading

如果没法访问Google,可以使用清华大学的镜像:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

分析源码

AOSP的源码非常的庞大,如果是初次接触,可能会完全摸不着头脑,当然,我们也不可能完全把它的结构和内容摸索清楚再进行修改,学会用文本查找很重要

这里安利VSCode,我们不需要使用Android Studio这样的工具,因为太慢!

首先去搜索一下NetworkSecurityConfig,就会发现有以下相关的文件

Search

其中NetworkSucurityConfig.javaXmlConfigSource.java就和证书判断逻辑相关

仔细分析一波NetworkSucurityConfig类,可以发现这个类使用了Android中常见的Builder模式来进行创建,其中包括一个返回默认配置Builder的静态方法

public static final Builder getDefaultBuilder(ApplicationInfo info)

在Android 9以前,这个方法的参数为两个int型字段:int targetSdkVersion, int targetSandboxVesrsion

这里列出这个方法的详细代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
*
* <p>
* The default configuration has the following properties:
* <ol>
* <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic
* is allowed by default.</li>
* <li>Cleartext traffic is not permitted for ephemeral apps.</li>
* <li>HSTS is not enforced.</li>
* <li>No certificate pinning is used.</li>
* <li>The system certificate store is trusted for connections.</li>
* <li>If the application targets API level 23 (Android M) or lower then the user certificate
* store is trusted by default as well for non-privileged applications.</li>
* <li>Privileged applications do not trust the user certificate store on Android P and higher.
* </li>
* </ol>
*
* @hide
*/
public static Builder getDefaultBuilder(ApplicationInfo info) {
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
&& info.targetSandboxVersion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
// User certificate store, does not bypass static pins.
builder.addCertificatesEntryRef(
new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
}
return builder;
}

可以看出以下几个特点:

  1. Android 9(假设Target和系统版本一样)以下默认允许明文传输(非HTTPS)

  2. 默认只信任系统内置的根证书,Android 7以下添加对用户配置的证书信任

  3. HSTS不强制开启

于是再次搜索这个方法的调用

NetoworkConfig

其中,第一个调用的地方是重写了XmlConfigSource实现的ConfigSource类中的方法getDefaultConfig

而第二个地方则是在private void parseNetworkSecurityConfig(XmlResourceParser parser)这个方法当中,根据方法名,就知道这是在解析XML配置中的网络安全配置

那么再分别查看它们的调用

getDefaultConfig
XmlConfig

那么,系统读取App网络安全配置的基本流程就可以猜想出来了:

  1. 首先是解析AndroidMainfest.xml文件

  2. 中间会初始化一个XmlConfigSource实例用来解析NetworkConfig

  3. 当需要用到网络配置的具体内容时,会调用XmlConfigSource实例的getDefaultConfig方法,其中ensureInitialized会具体负责解析XML文件中的证书、域名、是否允许明文等配置

  4. parseNetworkSecurityConfig方法会首先利用NetworkSecurityConfig类的getDefaultBuilder方法获取一个通用的Builder,构建一个默认配置,再根据XML中的实际情况进行补充

修改相关源码

了解了网络安全配置加载的流程,那么从理论上来说,我们可以在任一环境进行修改,即可去掉HTTPS的抓包限制,其中,修改的几个点如下:

  1. 首先修改NetworkSecurityConfig的默认Builder,让它能够和Android 6.0以下的配置一样,默认添加对用户配置的证书信任,具体修改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // NetworkSecurityConfig.java

    // 修改前
    public static Builder getDefaultBuilder(ApplicationInfo info) {
    Builder builder = new Builder()
    .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
    // System certificate store, does not bypass static pins.
    .addCertificatesEntryRef(
    new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
    final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
    && info.targetSandboxVersion < 2;
    builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
    // Applications targeting N and above must opt in into trusting the user added certificate
    // store.
    if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
    // User certificate store, does not bypass static pins.
    builder.addCertificatesEntryRef(
    new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
    }
    return builder;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // NetworkSecurityConfig.java

    // 修改后
    public static final Builder getDefaultBuilder(ApplicationInfo info) {
    Builder builder = new Builder()
    .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
    // System certificate store, does not bypass static pins.
    .addCertificatesEntryRef(
    new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
    final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
    && info.targetSandboxVersion < 2;
    builder.setCleartextTrafficPermitted(true);
    // 修改点1:忽略targetSdkVersion的判断,直接添加UserCertificateSource
    builder.addCertificatesEntryRef(new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
    return builder;
    }
  2. 其次我们需要忽略App内置的配置,直接忽略XmlConfigSource中的解析结果,getDefaultConfig方法直接返回我们改过的DefaultConfig:

    1
    2
    3
    4
    5
    6
    // XmlConfigSource.java
    // 修改前
    public NetworkSecurityConfig getDefaultConfig() {
    ensureInitialized();
    return mDefaultConfig;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // XmlConfigSource.java
    // 修改后
    public NetworkSecurityConfig getDefaultConfig() {
    // 防止出现其他异常,其他初始化操作不进行修改(保留ensureInitialized方法的调用)
    ensureInitialized();
    // 修改点2:不使用解析后的mDefaultConfig,重新获取一个DefaultConfig
    NetworkSecurityConfig.Builder builder = NetworkSecurityConfig.getDefaultBuilder(null);
    return builder.build();
    }

刷机验证

确认没有基本的Java语法问题以后,lunch、make一波走起!!

如果还不会编译AOSP,戳这里:https://source.android.com/setup/build/building

稍等片刻,刷机试验……

注意:刷完机后,先不要着急连上WiFi挂上代理抓包,很多手机刷完以后时间会还原,所以先去设置中把时间调成正确的,否则无论如何证书都是过期的……

Time

调整好时间后,连上Charles或者Fiddler的代理,装上它们的证书,即可对任意的App进行HTTPS抓包了!

终于不是满屏红叉了!!