本文主要讨论如何在 xposed 模块中调用app中的方法,以及如何 hook 加壳的 app。
主要依靠 xposed 提供的几个 api,使用起来类似反射,不过比反射要简单的多。
public?static?java.lang.Object?callStaticMethod(java.lang.Class<?>?clazz,?java.lang.String?methodName,?java.lang.Object...?args)
public?static?java.lang.Object?callStaticMethod(java.lang.Class<?>?clazz,?java.lang.String?methodName,?java.lang.Class<?>[]?parameterTypes,?java.lang.Object...?args)
需要的参数:
类名
方法名
方法参数类型,是数组,比如有两个参数,形式应为:java.lang.Class<?>[] parameterTypes = {String.class, int.class};
?,也可以不传,xposed 会自动匹配参数类型。
方法参数值
public?static?java.lang.Object?callMethod(java.lang.Object?obj,?java.lang.String?methodName,?java.lang.Object...?args)
public?static?java.lang.Object?callMethod(java.lang.Object?obj,?java.lang.String?methodName,?java.lang.Class<?>[]?parameterTypes,?java.lang.Object...?args)
参数与调用静态方法就第一个参数不一样,显然实例方法需要对象,静态方法需要类对象。
public?static?java.lang.Object?newInstance(java.lang.Class<?>?clazz,?java.lang.Object...?args)
public?static?java.lang.Object?newInstance(java.lang.Class<?>?clazz,?java.lang.Class<?>[]?parameterTypes,?java.lang.Object...?args)
需要的参数:
类名
方法参数类型,是数组,比如有两个参数,形式应为:java.lang.Class<?>[] parameterTypes = {String.class, int.class};
?,也可以不传,xposed 会自动匹配参数类型。
方法参数值
想要hook加壳app中的方法,需要先知道两个基础知识:
加壳原理
脱壳
脱壳现在有很多的开源项目与脚本,fart,dexdump 都可以完成,只不过可能有些app做了 frida 特征识别,导致脱不下来,这个后面会说到。
加壳的原理其实也不难,简单来说就是,开发者编写的app被藏起来了,展示给我们看的只是包着app的一个壳,所以我们无法看到里面的app内容。但是一个重要的点是,app始终是要运行的,既然要运行,那么壳就会将它包着的app内容给拿出来,所以只要抓住这个点就能很好的理解脱壳。
至于加壳,就是相当于先将 app 给加密,然后在某个时机再解密出来,让其运行。具体可以看下面这些文章:
https://bbs.kanxue.com/thread-254555.htm
https://bbs.kanxue.com/thread-252630.htm
https://bbs.kanxue.com/thread-254028.htm
看一个例子,材料已上传到 p23 (lebo.apk)。
使用jadx打开,发现里面没有 Activity 等类:
?
看AndroidManifest.xml里面,有很多类都找不到:
?
这说明该 apk 被加壳了。
那么,如果我们想hook?com.zw.lebo.SplashView
?这个类,在它的 onCreate 方法里面弹一个 toast,该如何做呢?
我们先尝试按照之前的方法,直接使用 findAndHookMethod 看看效果:
if?(loadPackageParam.packageName.equals("com.f0208.lebo"))?{
????XposedHelpers.findAndHookMethod(
????????????"com.zw.lebo.SplashView",
????????????loadPackageParam.classLoader,
????????????"onCreate",
????????????Bundle.class,
????????????new?XC_MethodHook()?{
????????????????@Override
????????????????protected?void?afterHookedMethod(MethodHookParam?param)?throws?Throwable?{
????????????????????super.afterHookedMethod(param);
????????????????????Toast.makeText((Context)?param.thisObject,?"a5right",?Toast.LENGTH_SHORT).show();
????????????????}
????????????});
}
打开 app,运行,发现会提示错误:
de.robv.android.xposed.XposedHelpers$ClassNotFoundError:?java.lang.ClassNotFoundException:?com.zw.lebo.MainActivity
?at?de.robv.android.xposed.XposedHelpers.findClass(XposedHelpers.java:71)
?at?de.robv.android.xposed.XposedHelpers.findAndHookMethod(XposedHelpers.java:260)
?at?com.example.edxposedtest.Xposed04.handleLoadPackage(Xposed04.java:17)
?at?de.robv.android.xposed.IXposedHookLoadPackage$Wrapper.handleLoadPackage(IXposedHookLoadPackage.java:37)
?at?de.robv.android.xposed.callbacks.XC_LoadPackage.call(XC_LoadPackage.java:61)
?at?de.robv.android.xposed.callbacks.XCallback.callAll(XCallback.java:117)
?at?com.elderdrivers.riru.edxp._hooker.impl.LoadedApkGetCL.afterHookedMethod(LoadedApkGetCL.java:61)
?at?de.robv.android.xposed.MethodHooker.handleHookedMethod(MethodHooker.java:94)
?at?EdHooker_d80f73e34da17c301b40feab7995d5a6a0b56621.hook(Unknown?Source:45)
?at?android.app.LoadedApk.getResources(LoadedApk.java:954)
?at?android.app.ContextImpl.createAppContext(ContextImpl.java:2270)
这就是我们之前提到过的,需要传递对应的类加载器才能正确地找到类。
我们可以看看这个类加载器加载的 dex 里面有哪些类:
public?void?getClassLoaderClassList(ClassLoader?classLoader)?{
????//private?final?DexPathList?pathList;
????XposedBridge.log("start?deal?with?classloader:"?+?classLoader);
????Object?pathListObj?=?XposedHelpers.getObjectField(classLoader,?"pathList");
????//private?final?Element[]?dexElements;
????Object[]?dexElementsObj?=?(Object[])?XposedHelpers.getObjectField(pathListObj,?"dexElements");
????for?(Object?i?:?dexElementsObj)?{
????????//private?final?DexFile?dexFile;
????????Object?dexFileObj?=?XposedHelpers.getObjectField(i,?"dexFile");
????????//private?Object?mCookie;
????????Object?mCookieObj?=?XposedHelpers.getObjectField(dexFileObj,?"mCookie");
????????//private?static?native?String[]?getClassNameList(Object?cookie);
????????Class?DexFileClass?=?XposedHelpers.findClass("dalvik.system.DexFile",?classLoader);
????????String[]?classList?=?(String[])?XposedHelpers.callStaticMethod(DexFileClass,?"getClassNameList",?mCookieObj);
????????for?(String?classname?:?classList)?{
????????????XposedBridge.log(dexFileObj?+?"---"?+?classname);
????????}
????}
????XposedBridge.log("end?deal?with?classloader:"?+?classLoader);
}
这个代码就不细说了,与Android的类加载机制有关,做过Android开发的应该会比较清楚。
具体的分析过程,可以自己去寻找源码。按照自己测试机的版本去看,因为不同的版本有些逻辑或者字段可能会不一样。
贴一个source查看网站:
http://aospxref.com/
打印一下,发现里面的类确实没有 Activity 相关的类,都是壳的类:
I??start?deal?with?classloader:dalvik.system.PathClassLoader[DexPathList[[zip?file?"/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk"],nativeLibraryDirectories=[/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/lib/arm,?/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk!/lib/armeabi-v7a,?/system/lib,?/vendor/lib]]]
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.BuildConfig
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$attr
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$dimen
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$drawable
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$id
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$menu
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$string
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R$style
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.example.helloworld.R
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.f0208.lebo.MyWrapperProxyApplication
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.StubShell.TxAppEntry
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.Bugly
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.BuglyStrategy$a
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.BuglyStrategy
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.CrashModule
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.a
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.b
I??/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk---com.tencent.bugly.yaq.crashreport.BuglyHintException
这就说明app壳只负责加载壳代码,那么app代码什么时候加载呢?自然是当app要运行的时候,几乎所有壳都是处理的 application,所以当 application 的 onCreate 方法执行完之后,下一步要展示app界面的时候,壳肯定要将app的代码释放出来。
那么,是如何释放的呢?这就又与Android的机制相关了,做过插件化开发的都知道有一个类叫做 LoadedApk,它描述的是一个已经加载过的 apk 文件,它里面有一个类加载器,就是用来加载 apk 里面的代码的。当壳释放完app代码后,类加载器也需要修正回来,不然就加载不到app代码了。
所以,当 Application 的 onCreate 方法执行完之后,我们就可以去 ActivityThread 里面的字段里面去拿 LoadedApk 对象,再去获取其类加载即可。
public?static?ClassLoader?getLoadedApkClassloader(XC_MethodHook.MethodHookParam?param)?{
????ClassLoader?currentClassLoader?=?param.thisObject.getClass().getClassLoader();
????Object?currentActivityThread?=?XposedHelpers.callStaticMethod(
????????????XposedHelpers.findClass("android.app.ActivityThread",?currentClassLoader),
????????????"currentActivityThread",
????????????new?Class[]{},
????????????new?Object[]{});
????Object?mBoundApplication?=?XposedHelpers.getObjectField(
????????????currentActivityThread,
????????????"mBoundApplication");
????Application?mInitialApplication?=?(Application)?XposedHelpers.getObjectField(
????????????currentActivityThread,
????????????"mInitialApplication");
????Object?loadedApkInfo?=?XposedHelpers.getObjectField(
????????????mBoundApplication,?"info");
????Application?mApplication?=?(Application)?XposedHelpers.getObjectField(loadedApkInfo,?"mApplication");
????return?mApplication.getClassLoader();
}
我们可以看一下这个 classLoader 里面加载的类,与加载类路径:
dalvik.system.PathClassLoader[DexPathList[[dex?file?"/data/user/0/com.f0208.lebo/files/prodexdir/00O000ll111l_0.dex",?dex?file?"/data/user/0/com.f0208.lebo/files/prodexdir/00O000ll111l_1.dex",?zip?file?"/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk"],nativeLibraryDirectories=[/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/lib/arm,?/data/app/com.f0208.lebo-3LhDNfPVIAqO1p49M_Rvww==/base.apk!/lib/armeabi-v7a,?/system/lib,?/vendor/lib]]]
可以看到dex的路径是释放在了 files 文件夹里面。
重新启动app运行,就可以正常弹出 toast 了,这样我们就做到了 hook 加壳的 app。
上面说的这个方法还是比较通用的。只要找准classLoader,对加壳app的hook也不难。