在 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" /> </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 > <domain includeSubdomains ="true" > example1.iacn.me</domain > <domain includeSubdomains ="true" > example2.iacn.me</domain > <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 权限。 然后重启手机,你的证书就被添加到系统里了。