首页
社区
课程
招聘
[原创]Xposed的新打开方式--Xpatch工作流程分析
发表于: 2019-5-19 11:47 9088

[原创]Xposed的新打开方式--Xpatch工作流程分析

2019-5-19 11:47
9088

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的,能力有限,没能展开。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-5-20 15:16 被luoyesiqiu编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 15003
活跃值: (6213)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
楼主应该研究太极,类似Xpatch。
不知道太极是不是也有你说的问题?“Apk如果没有手动继承Application类并在AndroidManifest.xml中指定,那么Xpatch就注入不了代码“”
2019-5-19 12:39
0
雪    币: 9753
活跃值: (3828)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
厉害了,Android Hook 都被你们玩出花了
2019-5-21 11:20
0
雪    币: 200
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
4
不要玩Android了,谷歌已断供安卓,要变天了。逆向安卓没有前途了
2019-5-21 11:59
0
雪    币: 1380
活跃值: (116)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
5
没有Application类可以自己填进去一个啊
2019-5-22 20:52
0
雪    币: 1446
活跃值: (1536)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
tDasm 楼主应该研究太极,类似Xpatch。 不知道太极是不是也有你说的问题?“Apk如果没有手动继承Application类并在AndroidManifest.xml中指定,那么Xpatch就注入不了代码 ...
这个东西,没有 Application 类,可以动态生成字节码,然后设置到AndroidManifest.xml 就可以了吧
2019-5-23 10:26
0
雪    币: 218
活跃值: (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
部分加固会在so文件中对application子类进行检查校验。
2019-5-23 22:59
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
一键生成一个Application 执行完代码再反射设置回原Application
2021-8-22 03:53
0
游客
登录 | 注册 方可回帖
返回
//