Xpatch是一款利用重打包的方式,使得被处理的Apk启动时自动加载Xposed模块,来实现应用内Hook的工具。
项目地址:https://github.com/WindySha/Xpatch
Xpatch修改apk,主要有三个步骤,代码在MainCommand类的doCommandLine方法:
(1) 第一步
在Xpatch的源码中,第一步对应的是ApkModifyTask类,实现的是Runnable接口,它的任务是修改Dex文件,使得被处理的apk在启动时能够执行指定的代码。
如果反编译被Xpatch处理过的Apk,查看App中Application的子类,会发现其中多了以下的代码:
我们大胆的猜测,这就是Xpatch给注入进去的入口代码。我们回到Xpatch的源码,来看看它是如何注入的。查看ApkModifyTask类,一步步进行跟踪:
ApkModifyTask类的run方法,在任务被启动时调用,它的代码:
dumpJarFile方法:
继续跟踪到dex2JarCmd方法:
看到了它创建了一个com.googlecode.dex2jar.tools.Dex2jarCmd
类实例,这个类在名为dex-tools的外部库里,并调用了Dex2jarCmd的doMain方法,给他传进去一些类似于命令行参数的东西,令我们比较提得起精神的是-app
参数,它传进去一个applicationName,这个applicationName的值来自MainCommand类的doCommandLine方法,逻辑是从解压的apk中读取AndroidManifest.xml,并读取application
节点下的name属性的值,最后将值赋予applicatioName
exm?就这样?并没有发现任何注入的代码啊,不急,继续跟踪,看到applicationName传进去了,一定能跟踪到有用的信息。接下来就是进入dex-tools外部库了,代码都是反编译出来的
com.googlecode.dex2jar.tools.BaseCmd的doMain方法:
主要看doCommandLine方法,doCommandLine是个抽象方法,它的真正实现是在Dex2jarCmd类里
跳转到com.googlecode.d2j.dex.Dex2jar类的to方法
to方法调用doTranslate方法
doTranslate方法很长,但是我们很容易就能看到了很敏感的字符串:com/wind/xposed/entry/XposedModuleEntry
,这就是Xpatch插入自己初始化的代码的地方。visitMethodInsn方法用于在函数内插入一条指令,看到两处调用visitMethodInsn来插入调用 com.wind.xposed.entry.XposedModuleEntry类的init方法的指令。
注:上面的操作码,184代表invoke-static
,177代表return-void
。这些操作码定义在org.objectweb.asm.Opcodes类中。
到这里,第一步我们已经搞清楚了。
(2) 第二步
对应的是SoAndDexCopyTask类,从名字可以看出它的任务是复制so和dex的,具体是怎样的,我们看代码。
SoAndDexCopyTask类,它也实现了Runnable接口,run方法在任务被启动时调用:
这个类主要就做这三个动作:复制so文件,复制dex文件,删除Meta信息。
我们先看copySoFile代码:
看代码可以知道它的任务是把Xpatch.jar中assets目录下的libxpatch_wl.so复制到apk解压目录的lib/<架构文件夹>
下。这个libxpatch_wl.so是whale框架提供so文件,为Hook提供可能。
除了复制so,如果我们在用Xpatch时使用-xm参数来将Xposed模块集成到apk中,那么模块会被就会被重命名成:以libxpatch_xpmodule为前缀,后面接着模块序号,最后再以so为后缀。最终这个模块被复制到apk的lib目录下。
copyDexFile方法:
逻辑也很明了,把assets下的classes.dex复制到apk解压目录下,根据原来apk中的dex个数来给复制进去的dex重命名。
deleteMetaInfo方法:
没什么好说的,就是删除<apk解压目录>/META-INF
下的指定文件。
(3) 第三步
对应的是BuildAndSignApkTask类,从名字可以看出它的任务是构建和对apk签名的。
这个BuildAndSignApkTask类也是实现Runnable接口,我们来看run方法:
这个方法做了两件重要的事,把apk解压目录给压缩成zip,并给压缩成的文件签名,这里就不细讲了。
我们在上面提到过,Xpatch把assets目录下的classes.dex文件复制进了目标apk里,这个dex是不开源的,那么这个dex里面究竟有什么呢,我们把dex解压出来,拖进jadx中反编译。
既然Xpatch将初始化代码注入到应用的Application类,初始化代码调用com.wind.xposed.entry.XposedModuleEntry
类的init
方法,那么我们从init方法开始看起。
init方法代码比较多,上面标注释的地方是比较值得关注的,根据这些地方展开
注释1: 这里主要通过反射来创建Context,作为这么早执行的代码,作者也通过很巧妙的方式创建了Context,有了Context后,很多事就好办多了,XpatchUtils.createAppContext()的代码如下:
注释2:调用com.wind.xposed.entry.b类的a方法,并将当前App的ApplicationInfo和ClassLoader传过去,从这里开始就开始碰到XposedBridge的代码了
方法第一行把a.a()传给了Wrapper的构造函数,a类完整类名是com.wind.xposed.entry.a,该类实现IXposedHookLoadPackage接口,a静态方法返回a类实例,那么Wrapper的构造函数得到的就是IXposedHookLoadPackage接口的类实例。接着Wrapper类实例被添加到一个CopyOnWriteSortedSet中,这个CopyOnWriteSortedSet类是一个操作Object数组的类,CopyOnWriteSortedSet被传到LoadPackageParam类的构造函数中,调用这个构造函数就是在给它父类(Param类)中的callbacks字段赋值。
接下来就是给LoadPackageParam的字段赋值,这些字段存储着当前应用包名,进程名,ApplicationInfo,ClassLoader等等信息。
在com.wind.xposed.entry.b.a(ApplicationInfo applicationInfo, ClassLoader classLoader)
方法的最后,调用XCallback类的callAll方法
callAll方法遍历Param类中的所有callback,调用它们的call方法
饶了半天,就是调用传进Wrapper类构造函数的类的handleLoadPackage方法,那就是调用com.wind.xposed.entry.a
类的handleLoadPackage方法,而com.wind.xposed.entry.a
类的handleLoadPackage方法又去调用com.wind.xposed.entry.a.a
类的handleLoadPackage方法,那我们去看com.wind.xposed.entry.a.a
类的handleLoadPackage的实现
这个方法的作用是Hook相关的函数,将被处理的apk的签名替换成原来的,防止某些App检测到自己的Apk被修改。apk在被Xpatch处理之前,签名的信息的被保存了下来,对应的任务类是SaveApkSignatureTask,上文没有讲到,感兴趣可以去看一下。
注释3:调用本类中的a方法,这个方法的参数只有一个参数Context
这个函数是读取设备中已安装的Apk,根据meta信息判断它们是否属于Xposed模块,如果是并且外部存储不存在xposed_config/modules.list
把它们的安装位置添加到列表中。并且开启一个线程,如果xposed_config/modules.list
存在则读取,xposed_config/modules.list
文件记录着模块加载规则,具体可以去查看Xpatch项目的README。最后,将读取到的Xposed模块安装位置列表返回
注释4:调用本类中的a方法,这个方法的参数是一个Context和List
这个方法的目的是获取所有打包进apk中的Xposed模块的路径添加到传进来的List中
注释5:调用com.wind.xposed.entry.b
类的a(String str, String str2, ApplicationInfo applicationInfo, ClassLoader classLoader)
方法
这个函数读取传进来的Xposed模块的信息,获取DexClassLoader,读取模块assets下的xposed_init
文件,得到其中的类名并根据实例类型(IXposedHookZygoteInit或者IXposedHookLoadPackage)分别实例化它,是IXposedHookZygoteInit实例就callInitZygote,是IXposedHookLoadPackage实例就像上面的注释2
所讲的一样调用模块的handleLoadPackage方法。
讲到这里好像并没有涉及到whale框架,我们编写模块的时候,Hook的代码都是写在handleLoadPackage方法中,比如我们在handleLoadPackage方法内,写个findAndHookMethod,最终就会调用WhaleRuntime.hookMethodNative
本地方法,来实现应用内的Hook
Xpatch思路很好,不需要ROOT,不用担心Xposed在某些设备上的兼容性,不用每次调试Xposed模块都重启手机,很方便的就可以使用Xposed模块,实现应用内的Hook。但是在使用的过程中也发现了一个小问题,要处理的Apk如果没有手动继承Application类并在AndroidManifest.xml中指定,那么Xpatch就注入不了代码,也就无法正常使用。本文也只讲了Xpatch的基本流程,具体whale是怎么Hook的,能力有限,没能展开。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-5-20 15:16
被luoyesiqiu编辑
,原因: