修改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 | Caused by: java.security.cert.CertificateException: xxx. |
通常,Android开发者会将NetworkConfig按照以下XML文件进行配置:
1 |
|
然而对于开发者来说,通常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,就会发现有以下相关的文件
其中NetworkSucurityConfig.java
和XmlConfigSource.java
就和证书判断逻辑相关
仔细分析一波NetworkSucurityConfig
类,可以发现这个类使用了Android中常见的Builder模式来进行创建,其中包括一个返回默认配置Builder的静态方法
public static final Builder getDefaultBuilder(ApplicationInfo info)
在Android 9以前,这个方法的参数为两个int型字段:int targetSdkVersion, int targetSandboxVesrsion
这里列出这个方法的详细代码
1 | /** |
可以看出以下几个特点:
Android 9(假设Target和系统版本一样)以下默认允许明文传输(非HTTPS)
默认只信任系统内置的根证书,Android 7以下添加对用户配置的证书信任
HSTS不强制开启
于是再次搜索这个方法的调用
其中,第一个调用的地方是重写了XmlConfigSource
实现的ConfigSource
类中的方法getDefaultConfig
而第二个地方则是在private void parseNetworkSecurityConfig(XmlResourceParser parser)
这个方法当中,根据方法名,就知道这是在解析XML配置中的网络安全配置
那么再分别查看它们的调用
那么,系统读取App网络安全配置的基本流程就可以猜想出来了:
首先是解析
AndroidMainfest.xml
文件中间会初始化一个
XmlConfigSource
实例用来解析NetworkConfig当需要用到网络配置的具体内容时,会调用
XmlConfigSource
实例的getDefaultConfig
方法,其中ensureInitialized
会具体负责解析XML文件中的证书、域名、是否允许明文等配置parseNetworkSecurityConfig
方法会首先利用NetworkSecurityConfig
类的getDefaultBuilder
方法获取一个通用的Builder,构建一个默认配置,再根据XML中的实际情况进行补充
修改相关源码
了解了网络安全配置加载的流程,那么从理论上来说,我们可以在任一环境进行修改,即可去掉HTTPS的抓包限制,其中,修改的几个点如下:
首先修改
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;
}其次我们需要忽略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挂上代理抓包,很多手机刷完以后时间会还原,所以先去设置中把时间调成正确的,否则无论如何证书都是过期的……
调整好时间后,连上Charles或者Fiddler的代理,装上它们的证书,即可对任意的App进行HTTPS抓包了!
终于不是满屏红叉了!!