前言
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(),其主要逻辑如下:
1 | //de.robv.android.xposed.XposedBridge.java |
这里最核心的一行代码是:
1 | 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
里的入口类,其核心逻辑代码如下:
1 | //de.robv.android.xposed.XposedInit.java |
加载到这些类之后,将这些类使用全局变量保存起来:
1 | if (moduleInstance instanceof IXposedHookLoadPackage) |
保存起来后,何时执行这些类里的入口方法呢?
下面一段代码,给出了答案:
1 | // normal process initialization (for new Activity, Service, BroadcastReceiver etc.) |
通过上面代码可知,是在main入口处拦截了ActivityThread
的handleBindApplication
方法,在这个方法执行之前,加载了Xposed插件里的Hook代码(入口代码)。而ActivityThread
的handleBindApplication
方法的主要功能就是创建Application
,并调用其attachBaseContext
,onCreate
等方法。因此,在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 ActivityThread
的handleBindApplication
方法,从而使在创建应用的Application之前就执行Xposed模块里的入口方法(执行Hook流程)。
由于我们是修改应用代码,因此入口只能在应用的Application
里,可以是Application
的静态方法块,也可以是attachBaseContext
方法或onCreate
方法。
那到底应该选择哪个作为加载Xposed模块的入口呢?
首先,肯定是越早Hook越好,否则可能会出现有些方法调用之后才执行Hook方法,导致方法没有被Hook到。而且加载Xposed插件需要用到Applicatin的context参数,所以笔者选了在attachBaseContext
方法的第一行代码执行加载Xposed模块:
1 | // MyApplication.java |
通过对一些app进行测试,发现大多数应用走这个流程都没问题,唯一有问题的是微信,修改后的微信一启动就奔溃,具体原因暂时不清楚。
因此,我尝试将加载Xposed模块的入口代码放在Application的静态代码块里,静态代码块在类创建的时候就会执行,比attachBaseContext
方法执行的时机更早。
通过测试,修改后的微信的确能够成功启动!!
1 | // MyApplication.java中的静态代码块 |
但是,在Application的静态代码块中,并没有Application的context参数,而加载Xposed模块时,需要传递参数,参数中的applicationInfo和应用的classLoader都是需要从Context中取得,没有Context怎么办?总不能传个空过去吧。
既然没有Context,我们就自己构造一个Context。
创建App Context流程
Android sdk并没有提供应用自己创建context的方法,为了找到构建一个Application Context的方法,我们先来了解android Framework里是如何创建Application的Context。
Application里最早出现Context的地方是attachBaseContext
方法:
1 | // Application的父类android.content.ContextWrapper.java |
这个方法唯一调用的地方是:
1 | // android.app.Application.java |
Application里的attach
方法是在new Application之后立即调用的,具体是在android.app.Instrumentation.java
里:
1 | // android.app.Instrumentation.java |
Instrumentation类的newApplication
方法最终又是在android.app.LoadedApk.java
类里的makeApplication
方法调用的:
1 | // android.app.LoadedApk.java |
终于看到构造context的方法了
1 | ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); |
这个方法的实现是:
1 | // android.app.ContextImpl.java |
createAppContext
方法需要传两个参数,一个是mActivityThread,另一个是this,也就是LoadedApk对象。mActivityThread这个对象比较容易找到,因为一个进程只有一个ActivityThread对象,只用通过反射调用ActivityThread的静态方法currentActivityThread
即可:
1 | // android.app.ActivityThread.java |
反射:
1 | //反射调用ActivityThread.java的静态方法currentActivityThread() |
另外一个对象LoadedApk该如何获取呢?
LoadedApk对象的获取
上面代码分析过,App启动时最先调用 ActivityThead
的handleBindApplication(AppBindData data)
方法,并在其这个方法里创建Application,而创建Application的唯一方法是
1 | // android.app.LoadedApk.java |
查看handleBindApplication
方法具体实现过程,发现makeApplication
确实被调用到:
1 | // android.app.ActivityThread.java |
根据以上代码可知,LoadedApk
的实例就是data.info
,而data.info
是通过方法getPackageInfoNoCheck
来获取的,而且data.info
对象存到了全局变量mBoundApplication
里,因此,mBoundApplication
对象里的info
变量就是我们要找的LoadedApk实例。
我们可以通过反射来获取它:
1 | // 获取ActivityThread的mBoundApplication变量 |
获取到ActivityThread
和LoadedApk
后,通过反射调用ContextImpl
的静态方法createAppContext
就可以构造一个context对象:
1 | Class contextImplClass = Class.forName("android.app.ContextImpl"); |
至此,我们在Application的静态代码块中成功得到Application context。
Comming soon…
下一篇Xpatch源码解析中,我们将接着这部分内容介绍XposedModuleEntry.init();
这个方法的具体实现逻辑,然后再介绍如何利用dex2jar工具修改Apk中dex文件。
欢迎扫二维码,关注我的技术公众号Android葵花宝典 ,获取高质量的Android干货分享: