前言

Xpatch是一款免Root实现App加载Xposed插件的工具,可以非常方便地实现App的逆向破解(再也不用改smali代码了),源码也已经上传到Github上,欢迎各位Fork and Star。

本文主要介绍Xpatch的实现原理。由于其原理比较复杂,所以分二篇文章来详细讲解。

由于Xpatch处理Xposed module的方法参考了Xposed框架部分源码,所以本文先介绍Xposed框架加载Xposed模块原理,再详细讲解Xpatch如何兼容Xposed模块。

Xposed框架加载Xposed Module的原理

Xposed是github上rovo89大神设计的一个针对Android平台的动态劫持项目,其主要原理是通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的app进程的劫持。

XposedBridge.jar的入口方法是main(),其主要逻辑如下:

//de.robv.android.xposed.XposedBridge.javaprotected static void main(String[] args) {// Initialize the Xposed framework and modulestry {if (!hadInitErrors()) {initXResources();SELinuxHelper.initOnce();SELinuxHelper.initForProcess(null);runtime = getRuntime();XPOSED_BRIDGE_VERSION = getXposedVersion();if (isZygote) {XposedInit.hookResources();XposedInit.initForZygote();}XposedInit.loadModules();} else {Log.e(TAG, "Not initializing Xposed because of previous errors");}} catch (Throwable t) {Log.e(TAG, "Errors during Xposed initialization", t);disableHooks = true;}// Call the original startup codeif (isZygote) {ZygoteInit.main(args);} else {RuntimeInit.main(args);}}

这里最核心的一行代码是:

XposedInit.loadModules();

在这个方法里,通过读取/data/data/de.robv.android.xposed.installer/conf/modules.list这个文件,找到需要加载的Xposed插件(APK)路径。而这些路径都是通过Xposed Installer这个App里的开关控制的。在Xposed Installer App里,有一个已安装的Xposed插件列表,用户选定某个插件后,就会将该插件APK路径写到modules.list文件里,从而实现插件开关的控制。

在modules.list文件里查找到所有插件APK路径后,根据Apk的绝对路径构造一个PathClassLoader(),然后用此Classloader加载全类名写在资源文件assets/xposed_init里的入口类,其核心逻辑代码如下:

//de.robv.android.xposed.XposedInit.java
...
...
ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER);BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));try {String moduleClassName;while ((moduleClassName = moduleClassesReader.readLine()) != null) {moduleClassName = moduleClassName.trim();if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))continue;try {Log.i(TAG, "  Loading class " + moduleClassName);Class<?> moduleClass = mcl.loadClass(moduleClassName);if (!IXposedMod.class.isAssignableFrom(moduleClass)) {Log.e(TAG, "    This class doesn't implement any sub-interface of IXposedMod, skipping it");continue;} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {Log.e(TAG, "    This class requires resource-related hooks (which are disabled), skipping it.");continue;}......

加载到这些类之后,将这些类使用全局变量保存起来:

if (moduleInstance instanceof IXposedHookLoadPackage)XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));......//保存在全局变量sLoadedPackageCallbacks里
public static void hookLoadPackage(XC_LoadPackage callback) {synchronized (sLoadedPackageCallbacks) {sLoadedPackageCallbacks.add(callback);}}

保存起来后,何时执行这些类里的入口方法呢?
下面一段代码,给出了答案:

// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) {...XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);lpparam.packageName = reportedPackageName;lpparam.processName = (String) getObjectField(param.args[0], "processName");lpparam.classLoader = loadedApk.getClassLoader();lpparam.appInfo = appInfo;lpparam.isFirstApplication = true;XC_LoadPackage.callAll(lpparam);...});

通过上面代码可知,是在main入口处拦截了ActivityThreadhandleBindApplication方法,在这个方法执行之前,加载了Xposed插件里的Hook代码(入口代码)。而ActivityThreadhandleBindApplication方法的主要功能就是创建Application,并调用其attachBaseContextonCreate等方法。因此,在App的Application创建之前就实现了Xposed Hook。

至此,Xposed框架加载Xposed module的流程就非常清晰了。

免Root实现Xposed的探索

由于Xposed框架是修改了/system/bin/app_process程序,控制的zygote进程的启动,从而在app进程启动之前执行了加载xposed模块,实现了App代码的Hook。因此,只有Root的手机才能使用Xposed。

那有没有办法实现免Root下也能让App加载Xposed模块呢?

其中一种已经被探索过的方法是使用App双开工具,让其他App运行在自己的App里面。比如,利用大名鼎鼎的开源双开工具VirtualApp,让其他App运行其中,这样VirtualApp就可以控制其他App的进程启动了,当然也就可以实现免Root加载Xposed模块了。

不过,这样做也有一些问题,其兼容性和稳定性比较差。而且,由于VirtualApp的开源版本已经很少维护,bug会比较多,有些App在里面启动非常卡顿,甚至无法启动

那有没有更好的方案呢?

有,那就是基于Apk二次打包的Xpatch方案。

为了实现免Root Xposed,我们可以修改App入口代码,在App的Application初始化时,插入我们加载Xposed模块的代码,并对App进行重新打包签名即可。

Xpatch加载Xposed模块的方法

通过以上分析,Xposed框架执行Xposed模块的入口位置是通过Hook ActivityThreadhandleBindApplication方法,从而使在创建应用的Application之前就执行Xposed模块里的入口方法(执行Hook流程)。

由于我们是修改应用代码,因此入口只能在应用的Application里,可以是Application的静态方法块,也可以是attachBaseContext方法或onCreate方法。

那到底应该选择哪个作为加载Xposed模块的入口呢?

首先,肯定是越早Hook越好,否则可能会出现有些方法调用之后才执行Hook方法,导致方法没有被Hook到。而且加载Xposed插件需要用到Applicatin的context参数,所以笔者选了在attachBaseContext方法的第一行代码执行加载Xposed模块:

    // MyApplication.java@Overrideprotected void attachBaseContext(Context base) {XposedModuleEntry.init(base);...//App其他业务代码...super.attachBaseContext(base);}

通过对一些app进行测试,发现大多数应用走这个流程都没问题,唯一有问题的是微信,修改后的微信一启动就奔溃,具体原因暂时不清楚。

因此,我尝试将加载Xposed模块的入口代码放在Application的静态代码块里,静态代码块在类创建的时候就会执行,比attachBaseContext方法执行的时机更早。

通过测试,修改后的微信的确能够成功启动!!

    // MyApplication.java中的静态代码块static {XposedModuleEntry.init();}

但是,在Application的静态代码块中,并没有Application的context参数,而加载Xposed模块时,需要传递参数,参数中的applicationInfo和应用的classLoader都是需要从Context中取得,没有Context怎么办?总不能传个空过去吧。

既然没有Context,我们就自己构造一个Context。

创建App Context流程

Android sdk并没有提供应用自己创建context的方法,为了找到构建一个Application Context的方法,我们先来了解android Framework里是如何创建Application的Context。

Application里最早出现Context的地方是attachBaseContext方法:

// Application的父类android.content.ContextWrapper.javaprotected void attachBaseContext(Context base) {if (mBase != null) {throw new IllegalStateException("Base context already set");}mBase = base;}

这个方法唯一调用的地方是:

// android.app.Application.java/*** @hide*//* package */ final void attach(Context context) {attachBaseContext(context);mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;}

Application里的attach方法是在new Application之后立即调用的,具体是在android.app.Instrumentation.java里:

// android.app.Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)throws InstantiationException, IllegalAccessException, ClassNotFoundException {return newApplication(cl.loadClass(className), context);}static public Application newApplication(Class<?> clazz, Context context)throws InstantiationException, IllegalAccessException, ClassNotFoundException {Application app = (Application)clazz.newInstance();app.attach(context);return app;
}

Instrumentation类的newApplication方法最终又是在android.app.LoadedApk.java类里的makeApplication方法调用的:

// android.app.LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {...//代码省略...String appClass = mApplicationInfo.className;if (forceDefaultAppClass || (appClass == null)) {appClass = "android.app.Application";}...//代码省略...ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);appContext.setOuterContext(app);...//代码省略...
}

终于看到构造context的方法了

ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

这个方法的实现是:

// android.app.ContextImpl.java
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {if (packageInfo == null) throw new IllegalArgumentException("packageInfo");ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,null);context.setResources(packageInfo.getResources());return context;}

createAppContext方法需要传两个参数,一个是mActivityThread,另一个是this,也就是LoadedApk对象。mActivityThread这个对象比较容易找到,因为一个进程只有一个ActivityThread对象,只用通过反射调用ActivityThread的静态方法currentActivityThread即可:

// android.app.ActivityThread.java
public static ActivityThread currentActivityThread() {return sCurrentActivityThread;
}

反射:

//反射调用ActivityThread.java的静态方法currentActivityThread()
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThreadObj = currentActivityThreadMethod.invoke(null);

另外一个对象LoadedApk该如何获取呢?

LoadedApk对象的获取

上面代码分析过,App启动时最先调用 ActivityTheadhandleBindApplication(AppBindData data)方法,并在其这个方法里创建Application,而创建Application的唯一方法是

// android.app.LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation){}

查看handleBindApplication方法具体实现过程,发现makeApplication确实被调用到:

// android.app.ActivityThread.java
private void handleBindApplication(AppBindData data) {...//其他代码省略...mBoundApplication = data;mConfiguration = new Configuration(data.config);mCompatConfiguration = new Configuration(data.config);...//其他代码省略...data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);...//其他代码省略...// 创建ApplicationApplication app = data.info.makeApplication(data.restrictedBackupMode, null);mInitialApplication = app;...//其他代码省略...
}

根据以上代码可知,LoadedApk的实例就是data.info,而data.info是通过方法getPackageInfoNoCheck来获取的,而且data.info对象存到了全局变量mBoundApplication里,因此,mBoundApplication对象里的info变量就是我们要找的LoadedApk实例。

我们可以通过反射来获取它:

// 获取ActivityThread的mBoundApplication变量
Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
boundApplicationField.setAccessible(true);
Object mBoundApplicationObj = boundApplicationField.get(activityThreadObj);   // mBoundApplication: AppBindData// 获取mBoundApplication的info变量(LoadedApk)
Field infoField = mBoundApplicationObj.getClass().getDeclaredField("info");   // info: LoadedApk
infoField.setAccessible(true);
Object loadedApkObj = infoField.get(mBoundApplicationObj);  // LoadedApk

获取到ActivityThreadLoadedApk后,通过反射调用ContextImpl的静态方法createAppContext就可以构造一个context对象:

Class contextImplClass = Class.forName("android.app.ContextImpl");
//Get createAppContext method
Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass());
createAppContextMethod.setAccessible(true);// call method: ContextImpl.createAppContext()
Object context = createAppContextMethod.invoke(null, activityThreadObj, loadedApkObj);

至此,我们在Application的静态代码块中成功得到Application context。

Comming soon…

下一篇Xpatch源码解析中,我们将接着这部分内容介绍XposedModuleEntry.init();这个方法的具体实现逻辑,然后再介绍如何利用dex2jar工具修改Apk中dex文件。

欢迎扫二维码,关注我的技术公众号Android葵花宝典 ,获取高质量的Android干货分享:

免Root 实现App加载Xposed插件的工具Xpatch源码解析(一)相关推荐

  1. Xpatch:免Root实现App加载Xposed插件的一种方法

    Xpatch概述 Xpatch用来重新签名打包Apk文件,使重打包后的Apk能加载安装在系统里的任意Xposed插件,从而实现免Root Hook任意App. 源码 github.com/WindyS ...

  2. react.lazy 路由懒加载_React lazy/Suspense使用及源码解析

    React v16.6.0已经发布快一年了,为保障项目迭代发布,没有及时更新react版本,最近由于开启了新项目,于是使用新的react版本进行了项目开发.项目工程如何搭建,如何满足兼容性要求,如何规 ...

  3. Android 插件化开发——宿主APP加载APK插件

    本篇博客说一下我们的宿主APP怎样加载别的APK文件. 首先需要说一些知识点,我们的Java文件要想在Android环境运行,需要将.java文件通过转为class文件,然后为了能在DVM上面运行,再 ...

  4. iOS自定义弹出视图、收音机APP、图片涂鸦、加载刷新、文件缓存等源码

    iOS精选源码 一款优秀的 聆听夜空FM 源码 zhPopupController 简单快捷弹出自定义视图 WHStoryMaker搭建美图(贴纸,涂鸦,文字,滤镜) iOS cell高度自适应 有加 ...

  5. iOS自定义弹出视图、收音机APP、图片涂鸦、加载刷新、文件缓存等源码 1

    iOS精选源码 一款优秀的 聆听夜空FM 源码 zhPopupController 简单快捷弹出自定义视图 WHStoryMaker搭建美图(贴纸,涂鸦,文字,滤镜) iOS cell高度自适应 有加 ...

  6. Android一行代码实现网络加载GIF闪图(附源码)

    最近项目有个需求是要从网络加载GIF闪图, 但是Android原生的ImageView并不支持Gif... 于是从网上看了些Dome, 发现总是有些这样那样的问题, 譬如: ☹ 没有缓存,还要自己写一 ...

  7. Java是如何加载资源文件的?(源码解毒)

    上文提到应老板要求开发一个测试工具能方便的加载存于文件中的测试参数,当时考虑既然是测试,把测试参数文件和测试类放在一起岂不是很方便,但是老板说:我的需求是你把测试参数文件放到统一文件夹下比如resou ...

  8. 网页中加载二次元3D虚拟主播源码(1:项目介绍和源码)

    vrm格式的二次元3D虚拟主播在日本实际上已经盛行多年,由于文化和差异的原因,在我们这只有年轻人比较喜爱.今天我们讲的是如何加载这种模型,然后实现一些动画. 别的不说,我们先上效果视频: 3D二次元虚 ...

  9. iOS高仿微信、仪表盘、图片标注图片滤镜、高斯模糊、上拉加载、下拉刷新等源码

    iOS精选源码 Swift-图片画框标注 Swift版的上拉加载, 下拉刷新控件(一句话集成, 超级易用) iOS tabbar上的提示框 Swift图片浏览器,经过一年多维护,已基本稳定 图片滤镜 ...

最新文章

  1. 理解OpenCV中的宏定义 CVAPI(函数返回类型)
  2. FreeBSD挂截U盘和光盘
  3. 1分钟了解协同过滤,pm都懂了
  4. java输入框1-100_Java开发笔记(一百三十九)JavaFX的输入框
  5. 太原理工大学这两年程序设计等竞赛奖牌统计
  6. Java实例分析:宠物商店
  7. 深度探索C++对象模型-Data语义学
  8. 《彩虹屁》快夸夸我!彩虹屁生成器
  9. hadoop-bigdata-v2.2 版本shell脚本一键部署 master、slaver1、slaver2三个节点源代码.
  10. Vanishing point detection
  11. 基于微信小程序的单词记忆系统(Java+SSM+MySQL)
  12. 八爪鱼爬取列表数据和详情页数据(国内网址)
  13. Java SE - 10 - 多线程
  14. c++多态之 运行时多态与编译时多态
  15. 公众号榜单 | 2020·6月公众号地区排行榜重磅发布
  16. matlab如何分类汇总,excel表格按照数据作图-excel怎么按分类汇总后的汇总数据绘制图表...
  17. 计算机技术在模具设计中的应用,南通模具设计培训 冲压模具设计和制造中的数字化技术应用...
  18. 【转载】XML轻松学习手册
  19. 回归分析|变量重要性选择
  20. java final类为什么不能继承_浅谈Java之终止继承:Final类和Fianl方法

热门文章

  1. 切片和切块 钻取 旋转(转)
  2. pgsql 筛选中文字符正则_匹配中文字符的正则表达式
  3. 教你如何拿到华为offer!华为面试流程及面试题解析
  4. 安卓在子类中调用父类的方法
  5. CSS的简单学习(6)
  6. 山中无富途,“老虎”称大王?
  7. dlopen函数使用示例
  8. 将时间戳转日期和对应星期几
  9. Halcon入门--提取图片对象个数
  10. js+html实现买机票方法