我们团队在开发过程中,测试接口时,常使用fiddler抓包查看请求报文和响应报文快速定位问题所在,这可比在代码中打断点看数据高效一万倍……
然而fiddler只能抓http包,如果是https,有可能因为证书问题抓不到包。
而这种场景一般都出现在,需要黑别家app,查看一些小秘密。
没错,这次我们由于业务需要,得分析某竞品,自然不能倒在这抓包的第一步,所以下面记录下使用Xpose绕过自定义证书验证的全过程。
反编译APK
常见的套路,apk后缀改zip解压,拿到dex文件,这里一般会有多个dex,全部拷到dex2jar目录下。
接下来我们要使用d2j-dex2jar.bat 把dex反编译成jar。
最简单的方式,把d2j-dex2jar.bat和dex文件都拖到终端命令框,回车。
把反编译的jar丢到gui,然后:

得到java源文件,到此为止我们就拿到了混淆后的java文件。
接下来就要考验大家对代码的敏感度了,因为核心代码都是abc这样的字母,我们要靠它们找到代码中疑似设置httpClinet的地方。
定位https
这里推荐使用EditPlus,可以全局搜索字符串,快捷键是Ctrl + Shift + F5。
我们直接全局搜httpClinet,找到对应代码段:
private static DefaultHttpClient a(boolean paramBoolean)
{
……
KeyStore ks= KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
b localb = new b(ks, paramBoolean);
localb.setHostnameVerifier(b.ALLOW_ALL_HOSTNAME_VERIFIER);
BasicHttpParams localBasicHttpParams = new BasicHttpParams();
localBasicHttpParams.setParameter("http.protocol.cookie-policy", "netscape");
HttpConnectionParams.setSocketBufferSize(localBasicHttpParams, 8192);
HttpConnectionParams.setStaleCheckingEnabled(localBasicHttpParams, false);
HttpConnectionParams.setConnectionTimeout(localBasicHttpParams, 20000);
HttpConnectionParams.setSoTimeout(localBasicHttpParams, 20000);
HttpProtocolParams.setUseExpectContinue(localBasicHttpParams, false);
SchemeRegistry localSchemeRegistry = new SchemeRegistry();
//注册http协议
localSchemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
//注册https协议
localSchemeRegistry.register(new Scheme("https", localb, 443));
DefaultHttpClient localDefaultHttpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(localBasicHttpParams, localSchemeRegistry), localBasicHttpParams);
localDefaultHttpClient.setRedirectHandler(new f());
……
}
可以看到https协议用了自己的证书,却信任全部证书……那就很简单了,只要勾住SchemeRegistry中的register方法,把其第二个参数(证书)替换成http证书即可。
Hook
直接上代码:
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("目标包名"))
return;
classLoader = lpparam.classLoader;
//勾住android入口函数
XposedHelpers.findAndHookMethod("android.app.Instrumentation", lpparam.classLoader, "newActivity", ClassLoader.class, String.class, Intent.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader realClassLoader = lpparam.classLoader;
final Class<?> acpv = XposedHelpers.findClass("org.apache.http.conn.scheme.SchemeRegistry", realClassLoader);
XposedBridge.hookAllMethods(acpv, "register", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Scheme s = (Scheme) param.args[0];
if (s.getDefaultPort() == 443) {
//这里把证书替换成http证书
param.args[0] = new Scheme("https", PlainSocketFactory.getSocketFactory(), 443);
}
super.beforeHookedMethod(param);
}
});
super.afterHookedMethod(param);
}
});
}
由于目标代码信任全部证书,所以这里只是简单的替换证书。
对于Xpose再提一点,每次更新了hook代码,都要在Xpose框架里重启手机让hook代码生效。
对于更严格的,验证了自定义证书的目标,可以使用现成的轮子: JustTrustMe
轮子将APK中所有用于校验SSL证书的API都进行了Hook,真狠呐……
虽然有现成的轮子,不过对于简单的东西,能自己动手实现一番,何乐而不为呢?