修改 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 抓包了!
终于不是满屏红叉了!!