在 Android 7.0 以上的系统中,Google 引入了一种名为网络安全配置(Network Security Configuration)的功能。据官方文档所说,这个功能可以让开发者在一个安全的声明性 XML 配置文件中自定义应用的网络安全设置,而无需修改应用代码。也可以针对特定域和特定应用配置这些设置。
可以参考官方文档原文
https://developer.android.google.cn/training/articles/security-config.html
当然这篇文章并不是介绍 Network Security Configuration 的具体用法的,本篇文章主要讲如何绕过这种在 Android 7.0+ 的默认行为。
如果了解过相关的知识,对于这个新增的功能,最直观的感觉可能就是,在运行着 Android 7.0 的手机上无法使用 Fiddler 或类似工具抓到 https 连接的包了。只有一些 https 的握手请求,无法查看到实际的数据,根本原因就是应用不再信任用户导入的 Fiddler 证书了。
想要研究如何绕过这项功能,就必须先了解如何正常使用。据官方文档描述,需要在 res/xml 下创建一个 XML 文件来自定义网络配置:
下面给出几个样例,可参照注释
配置该应用的所有 HTTPS 链接
1 2 3 4 5 6 7 8 9 | <?xml version="1.0"encoding="utf-8"?> <network-security-config> <base-config> <trust-anchors> <!-- 信任锚点集合 --> <certificates src="system"/> <!-- 信任系统自带的证书 --> <certificates src="user"/> <!-- 信任用户导入的证书 --> </trust-anchors> </base-config> </network-security-config> |
配置该应用的自定义 CA
1 2 3 4 5 6 7 8 | <?xml version="1.0"encoding="utf-8"?> <network-security-config> <base-config> <trust-anchors> <certificates src="@raw/my_custom_ca"/> <!-- 放在 res/raw 下的自定义 CA 文件 --> </trust-anchors> </base-config> </network-security-config> |
根据域名配置 HTTPS 可信域
1 2 3 4 5 6 7 8 9 10 | <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <!-- 与上文的 base-config 不同 --> <domain includeSubdomains="true">example1.iacn.me</domain> <!-- 过滤域名,可配置多个 --> <domain includeSubdomains="true">example2.iacn.me</domain> <!-- 一般会将 CDN 配置在此 --> <trust-anchors> <certificates src="system"/> </trust-anchors> </domain-config> </network-security-config> |
开发阶段的配置
仅在 android:debuggable=”true” 时生效
1 2 3 4 5 6 7 8 | <?xml version="1.0"encoding="utf-8"?> <network-security-config> <debug-overrides> <trust-anchors> <certificates src="system"/> </trust-anchors> </debug-overrides> </network-security-config> |
除此之外,还需要在 AndroidManifest.xml 中引用自定义的网络安全配置
1 2 3 4 5 6 7 | <?xml version="1.0"encoding="utf-8"?> <manifest> <application android:networkSecurityConfig="@xml/network_security_config" ...> </application> </manifest> |
上面是 Network Security Configuration 的使用简介,那么如何绕过该功能呢?
重编译 APK 文件
如上文介绍,需要将目标 APK 文件反编译,然后修改 XML 配置文件,在 trust-anchors 中信任用户导入的证书,之后重新打包即可。
运行时 Hook
在某些情况下,第一种方法也许是不可行的。比如说,需要保留目标应用原始的签名文件。这是无法做到的,因为你不可能拿到开发者的原始证书去给重编译后的应用签名。这里就需要用到 Hook 技术,可以在不修改应用代码的前提下修改应用的行为。
查看 Android 7.1.2_r36 源码,android.security.net.config.ManifestConfigSource 类 用于加载和处理网络安全配置 XML 文件的相关信息
getConfigSource() 方法下,如果应用未配置网络信息,它将会加载默认配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public final class NetworkSecurityConfig { ... private ConfigSource getConfigSource() { ... ConfigSource source; if (mConfigResourceId != 0) { ... source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild, mTargetSdkVersion, mTargetSandboxVesrsion); } else { ... source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion, mTargetSandboxVesrsion); } mConfigSource = source; return mConfigSource; } } |
DefaultConfigSource 类是 ManifestConfigSource 类中一个内部类,即上文代码中返回的默认网络配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public final class NetworkSecurityConfig { ... private static final class DefaultConfigSource implements ConfigSource { ... public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion, int targetSandboxVesrsion) { mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion, targetSandboxVesrsion) .setCleartextTrafficPermitted(usesCleartextTraffic) .build(); } } } |
在其构造方法中,调用了 android.security.net.config.NetworkSecurityConfig 类中的 getDefaultBuilder() 去构造一个默认配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public final class NetworkSecurityConfig { ... public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) { Builder builder = new Builder() .setHstsEnforced(DEFAULT_HSTS_ENFORCED) .addCertificatesEntryRef( new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); ... if (targetSdkVersion <= Build.VERSION_CODES.M) { builder.addCertificatesEntryRef( new CertificatesEntryRef(UserCertificateSource.getInstance(), false)); } return builder; } } |
可以看到,在应用的 targetSdkVersion <= M 时(Android 6.0 及以下),有一个 addCertificatesEntryRef(UserCertificateSource),系统将默认信任用户导入的证书。
那么以 Xposed 为例,Hook getDefaultBuilder(),在调用前将第一个参数 targetSdkVersion 改为 Android N 以下就可以了。这里给出核心代码:
1 2 3 4 5 6 7 8 9 10 11 | public void initZygote(StartupParam startupParam) throws Throwable { Class<?> targetClass = findClass("android.security.net.config.NetworkSecurityConfig", null); if (targetClass != null) { XposedBridge.hookAllMethods(targetClass, "getDefaultBuilder", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.args[0] = Build.VERSION_CODES.M; } }); } } |
添加系统 CA 证书
在上文中可以发现,应用是会默认信任系统证书的。那么我们也可以将自己的 CA 文件添加至系统。
从 Charles 里导出二进制格式证书,但要使 Android 能够识别,还需要做一些转换,我们可以很方便的使用 openssl 工具来做到。
这里以导出的 cert.cer 证书文件为例。
1 | $ openssl x509 -inform DER -subject_hash_old -in cert.cer |
执行该命令,并复制输出结果的第一行哈希字符串,后面会用。如下图所示。
1 | $ openssl x509 -inform DER -text -in cert.cer -out 7ef3ba8a.0 |
以刚才复制的哈希字符串为文件名,.0 结尾,如 7ef3ba8a.0,当做输出的文件名。
执行后就会输出 7ef3ba8a.0 这个文件。
复制输出的文件到 Android 上 /system/etc/security/cacerts/ 这个目录下,644 权限。
然后重启手机,你的证书就被添加到系统里了。
转载自https://iacn.me/2018/03/14/network-security-configuration-newer-than-android-n/