背景
从Android7.0之后系统不再信任用户CA证书。主要限制在应用的targetSdkVersion >= 24时生效,如果targetSdkVersion < 24即使系统是7.0+依然会信任(用户证书)。也就是说即使安装了用户CA证书,在Android 7.0+的机器上,targetSdkVersion >= 24的应用的HTTPS包就抓不到了。对于普通的HTTP请求,可以使用一些抓包工具进行抓包,对于targetSdkVersion >= 24的Https请求,只需要信任相关的证书,也可以抓取Https请求(抓包相关的配置不是本文的重点)。
可行性
常见的解决方案
1.官方的方案
网络安全配置功能使用一个 XML 文件,您可以在该文件中指定应用的设置。您必须在应用的清单中添加一个指向该文件的条目。以下代码摘自一个清单文件,演示了如何创建此条目:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
自定义可信 CA
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
</domain-config>
</network-security-config>
其中可配置自定义CA,限制可信CA,信任其他CA等。连接地址(需要翻墙)
但是这种方案仅适用于有源码了进行修改,但是在开发阶段还需要注意存在这些配置的包不能流传到外部,否则造成泄露。这种方案仅适用于开发和测试人员或者内部使用。
- 修改targetSdkVersion
将APP的targetSdkVersion修改在24以下就行。但是这样对APP的影响太大,兼容性、上架等因素,所以这种方案不可取。 - ROOT
首先这个方案需要ROOT,当然,既然Root了,有很多方案可以抓包。
(1).复制证书到系统证书层面
这个方案网上方法很多,就不一一赘述(因为我也没试过,搞起来麻烦,我都是用第二种方案),举个例子:例子
(2).Xposed
有个大佬写了一个JustTrustMe,当然这个框架是基于Xposed(ROOT版的)。安装这个软件可以直接抓取Https的包。
不常见的方案
以上的方案不是需要ROOT就是需要改源码,这个操作对于普通线下用户来说还是有点难度的,所以针对上面的方案对一下拓展,如何实现免ROOT的抓包。
- 官方的方法
(1)热修复
官方要求在manifest和xml中都进行修改,原本准备以热修复的形式将代码插入APP中,但是很多热修复框架不支持Manifest文件的修复,所以热修复的方案放弃
(2)重打包
这个方案就比较简单了,将APP进行重打包,思路:XPatch
将APP解包,对manifest,resource.arsc和资源目录进行修改,最后重新签名的过程。实现起来唯一有难度的地方就是resource.arsc文件的修改,不过网上也有比较成熟的方案。
这个方案比较麻烦,因为Xpatch是给Xpoased用的,所以用在这个地方有点杀鸡焉用牛刀的感觉。
2.修改targetSdkVersion
官方说只有targetSdkVersion的限制且经过修改manifest后就可以进行抓包,说明源码肯定有过修改,通过官方的配置可知:
android:networkSecurityConfig="@xml/network_security_config"
相关配置必定和networkSecurityConfig相关,不出意外,搜到一个类:NetworkSecurityConfig文件(同名,谷歌还是个讲究人),果然在里面发现了关于Android M版本的相关校验:
/** 基于Android 7.1.1_r6的源码
* Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
*
* <p>
* The default configuration has the following properties:
* <ol>
* <li>Cleartext traffic is permitted.</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.</li>
* </ol>
*
* @hide
*/
public static final Builder getDefaultBuilder(int targetSdkVersion) {
Builder builder = new Builder()
.setCleartextTrafficPermitted(DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
if (targetSdkVersion <= Build.VERSION_CODES.M) {
// User certificate store, does not bypass static pins.
builder.addCertificatesEntryRef(
new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
}
return builder;
}
这样看起来我们只需要hook这个方法就可以实现了修改targetSdkVersion的值,下面开始动手实现:
try {
findAndHookMethod("android.security.net.config.NetworkSecurityConfig",
lpparam.classLoader,"getDefaultBuilder",Integer.class,Integer.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(TAG + " >> getDefaultBuilder:2:" + param.args.length);
}
});
} catch (Exception e) {
e.printStackTrace();
}
很遗憾,方法没有走进来,我有一个合理的猜测是:
无ROOT的Xposed方案采用的是XPatch的方案:也就是解包->修改源码->重新签名的过程,但是在这一步与完整版的Xposed有一个非常大的区别在于HOOK的时机。
完整版的Xposed完整,不需要对APP做任何修改,因为他的插桩点在ActivityThread上,当进程创建的时候就已经开始拦截方法,所以理论上可以拦截APP上所有的方法。
但是Xpatch的方案无法修改ActivityThread的方法(这个APP中不存在这个方法),他的插桩点在Application方法上,attach方法后,所以针对APP启动流程的相关方法当然就拦截不到了,所以有可能这个方法被初始化一遍之后就被缓存了起来(这也是很常见的,常用的配置不用每次用的时候都去初始化),所以只会初始化一遍,且在manifest解析的时候,所以上面的Hook行为也就拦截不上了。
那么问题来了,这样我们就没有办法了吗?当然不是,我们从这个方法向上找,总能找到蛛丝马迹或者说一个好的Hook的地方,让我们放手去做吧:
//frameworks/base/core/java/android/security/net/config/ManifestConfigSource$DefaultConfigSource.java
private static final class DefaultConfigSource implements ConfigSource {
private final NetworkSecurityConfig mDefaultConfig;
public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion) {
mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion)
.setCleartextTrafficPermitted(usesCleartextTraffic)
.build();
}
@Override
public NetworkSecurityConfig getDefaultConfig() {
return mDefaultConfig;
}
@Override
public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
return null;
}
}
//frameworks/base/core/java/android/security/net/config/ManifestConfigSource.java
@Override
public NetworkSecurityConfig getDefaultConfig() {
return getConfigSource().getDefaultConfig();
}
private ConfigSource getConfigSource() {
synchronized (mLock) {
if (mConfigSource != null) {
return mConfigSource;
}
ConfigSource source;
if (mConfigResourceId != 0) {
boolean debugBuild = (mApplicationInfoFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
if (DBG) {
Log.d(LOG_TAG, "Using Network Security Config from resource "
+ mContext.getResources().getResourceEntryName(mConfigResourceId)
+ " debugBuild: " + debugBuild);
}
source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild,
mTargetSdkVersion);
} else {
if (DBG) {
Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
}
boolean usesCleartextTraffic =
(mApplicationInfoFlags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0;
source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion);
}
mConfigSource = source;
return mConfigSource;
}
}
从上向下看,一个内部类初始化了NetworkSecurityConfig并传入了targetSdkVersion,而内部类在getConfigSource里面被初始化的,而targetSdkVersion则是ManifestConfigSource在初始化的时候从ApplicationInfo中取出来的,那么,事情简单了,看情况只需要修改targetSdkVersion就可以了,现在我们决定在getConfigSource这个方法的前面将targetSdkVersion修改掉,不过很遗憾,方法并未执行,不用气馁,我们继续修改他的上层函数:getDefaultConfig。可喜可贺,这个方法执行了,我们在这里做一个HOOK:
findAndHookMethod("android.security.net.config.ManifestConfigSource",
lpparam.classLoader, "getDefaultConfig", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedHelpers.setIntField(param.thisObject, "mTargetSdkVersion", 23);
}
});
来源:oschina
链接:https://my.oschina.net/u/4382384/blog/4303679