一、前言
在上篇文章源码编译(2)——Xopsed源码编译详解中详细介绍了Xposed源码编译的完整过程,本文将从Android编译过程到Xposed运行机制,最后进行Xposed框架的详细定制。其中Xposed的定制主要参考世界美景大佬的定制Xposed框架和肉丝大佬的来自高纬的对抗:魔改XPOSED过框架检测(下)。
致谢:
首先感谢世界美景大佬的定制Xposed框架,从里面学习到对Xposed框架特征的修改,但是由于个人水平有限,大佬的贴子不够详细,不能完整复现,经过搜索发现肉丝大佬的基于此的两篇详细的贴子讲解:来自高纬的对抗:魔改XPOSED过框架检测(上)和来自高纬的对抗:魔改XPOSED过框架检测(下),本文的Android系统运行参考老罗的博客
二、Android运行机制
我们在了解Xposed的运行机制前,不得不需要了解Android系统的基本结构和运行机制,这样我们才能进一步学习如何进行Xposed定制,才能减少更多的错误
1. Android平台架构
Android的平台架构如下图所示:
下面我们依次介绍各层之间的功能和作用:
(1)Linux内核
Android平台的基础是linux内核,Android Runtime(ART)依靠Linux内核来执行底层功能,使用Linux内核可让Android利用主要安全功能,并且运行设备制造商为著名的内核开发硬件驱动程序,可以理解基于linux内核让Android更安全并且可以拥有很多设备驱动
(2)硬件抽象层(HAL)
HAL提供标准界面,向更高级别Java API框架显示设备硬件功能,HAL包含多个模块,其中每个模块都为特定类型的硬件组件实现一个界面,例如相机和蓝牙模块,当框架API要访问设备硬件时,Android系统为该硬件组件加载库模块。
(3)Android Runtime
Android 5.0之前Android Runtime为Dalvik,Android 5.0之后Android Runtime为ART
首先我们先了解一些文件的含义:
1 |
(1)dex文件:Android将所有的class文件打包形成一个dex文件,是Dalvik运行的程序 |
下面我们从Android系统的发展过程中详细介绍二者的区别:
版本 | 虚拟机类型 | 特性 |
---|---|---|
2.1-4.4 | Dalvik | JIT+解释器 |
5.0-7.0 | ART | AOT |
7.0-11 | ART | AOT+JIT+解释器 |
下面部分参考博客:博客地址
Android 2.2
Dalvik
1 |
支持已转换成dex格式的android应用,基于寄存器,指令执行更快,加载的是odex文件,采用JIT运行时编译 |
JIT:
1 |
JIT即运行时编译策略,可以理解成一种运行时编译器,此时Android的虚拟机使用的是Dalvik,为了加快Dalvik虚拟机解释dex速度,运行时动态地将执行频率很高的dex字节码翻译成本地机器码 |
1 |
基于Dalvik的虚拟机,在APK安装时会对dex文件进行优化,产生odex文件,然后在启动APP后,运行时会利用JIT即时编译,处理执行频率高的一部分dex,将其翻译成机器码,这样在再次调用的时候就可以直接运行机器码,从而提高了Dalvik翻译的速率,提高运行速度 |
Android 4.4——ART和AOT
此时引入全新的虚拟机运行环境ART和全新的编译策略AOT,此时ART和Dalvik是共存的,用户可以在两者之间选择
Android 5.0——ART全面取代Dalvik
AOT:
1 |
AOT是一种运行前编译的策略 |
AOT与JIT区别:
1 |
JIT 是在运行时进行编译,是动态编译,并且每次运行程序的时候都需要对 odex 重新进行编译 |
JVM、Dalvik和ART区别:
1 |
JVM:传统的Java虚拟机、基于栈、运行class文件 |
1 |
基于ART的虚拟机,会在APK第一次安装时,将dex进行AOT(预编译),通过dex2oat生成oat文件,即Android可执行ELF文件,包括原dex文件和翻译后的机器码,然后启动程序后,直接运行 |
Android 7.0——JIT回归
考虑上面AOT的缺点,dex2oat过程比较耗时且会占用额外的存储空间,Android 7.0 再次加入JIT形成AOT+JIT+解释器
模式
特点:
1 |
(1)应用在安装的时候 dex 不会被编译 |
混合编译模式综合了 AOT 和 JIT 的各种优点,使得应用在安装速度加快的同时,运行速度、存储空间和耗电量等指标都得到了优化
最后我们可以看下Android各版本ClassLoader加载dex时的dexopt过程:
(4)原生C/C++库
许多核心 Android 系统组件和服务(例如 ART 和 HAL)构建自原生代码,需要以 C 和 C++ 编写的原生库。Android 平台提供 Java 框架 API 以向应用显示其中部分原生库的功能,我们可以通过NDK开发Android中的C/C++库
(5)Java API框架
通过以 Java 语言编写的 API 使用 Android OS 的整个功能集。这些 API 形成创建 Android 应用所需的构建块,它们可简化核心模块化系统组件和服务的重复使用,包括以下组件和服务
2. Android启动流程
Android启动流程如下图所示:
(1)Loader
1 |
Boot ROM: 当手机处于关机状态时,长按Power键开机,引导芯片开始从固化在ROM里的预设代码开始执行,然后加载引导程序到RAM |
我们长按电源键后,手机就会在Loader层加载引导程序,并启动引导程序,初始化参数
(2)Linux内核
1 |
(1)启动Kernel的swapper进程(pid=0):该进程又称为idle进程, 系统初始化过程Kernel由无到有开创的第一个进程, 用于初始化进程管理、内存管理,加载Display,Camera Driver,Binder Driver等相关工作,这些模块驱动都会封装到对应的HAL层中 |
(3)执行init进程
init 进程是Linux系统中用户空间的第一个进程,进程号为1,是所以用户进程的祖先
Linux Kernel完成系统设置后,会首先在系统中寻找init.rc文件,并启动init进程,init.rc脚本存放路径: /system/core/rootdir/init.rc
,init进程:/system/core/init
init进程的启动可以分为三个部分:
1 |
(1)init进程会孵化出ueventd、logd、healthd、installd、adbd、lm这里写代码片kd等用户守护进程 |
创建Zygote过程:
1 |
(1)解析 init.zygote.rc //parse_service() |
(4)Zygote
Zygote为孵化器,即所有Android应用的祖先,Zygote 让 VM 共享代码、低内存占用以及最小的启动时间成为可能, Zygote 是一个虚拟机进程,Zygote是由init进程通过解析init.zygote.rc
文件而创建的,zygote所对应的可执行程序app_process
,所对应的源文件是App_main.cpp
,进程名为zygote
Zygote作用过程:
1 |
(1)解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法; |
Android系统流程总结:
1 |
(1) 手机开机后,引导芯片启动,引导芯片开始从固化在ROM里的预设代码执行,加载引导程序到到RAM,BootLoader检查RAM,初始化硬件参数等功能; |
三、Xposed框架运行机制
我们从上文中已经详细介绍了Android的启动流程,如下图所示:
下面我们来详细介绍Xposed框架的实现原理:
1. Xposed实现原理
我们先进入Xposed官网,可以发现Xposed工程的5个文件夹:
具体的模块功能和作用,我们在上文源码编译(2)——Xopsed源码编译详解已经介绍了,大家可以去参考
上文中我们知道,Xposed集成到Android源码中主要是:
1 |
(1)替换了Android中的虚拟机art |
具体如下图所示
我们从上文中可以得知Android所有的用户进程都是通过Zygote孵化出来的,而Zygote的执行程序是app_process,安装Xposed,将app_process替换,然后替换对应的虚拟机,这样Zygote孵化器就变成了Xposed孵化器
我们先来分析一下替换后的app_process
:
1 |
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) |
程序通过判断SDK
的版本选择加载文件,这里我们是在Android 6.0上运行,SDK版本为23,因此app_process
会执行app_main2.cpp
和ART.mk
为了方便我们分析,这里我画了一个思维导图方便大家结合后面源码分析:
我们再分析app_main2.cpp中main函数:
1 |
int main(int argc, char* const argv[]) |
1 |
main函数主要做了两件事: |
初始化xposed:
1 |
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) { |
1 |
(1)初始化xposed内相关变量 |
创建虚拟机:
1 |
void AndroidRuntime::start(const char* className, const Vector<String8>& options) |
1 |
(1)创建虚拟机 |
这里我们可以参考老罗的源码分析:
我们可以发现,我们在初始化虚拟机后,xposed会对虚拟机修改,函数onVmCreated(env)
onVmCreated(env):
1 |
void onVmCreated(JNIEnv* env) { |
我们来分析xposedInitLib
libxposed_art.cpp#xposedInitLib
libxposed_common.cpp#onVmCreatedCommon
libxposed_common.cpp#initXposedBridge
libxposed_art.cpp#onVmCreated
onVmCreated总结:
1 |
(1)通过dlopen加载libxposed_art.so |
de.robv.android.xposed.XposedBridge#main
1 |
protected static void main(String[] args) { |
源码分析:
1 |
虚拟机初始化完成后,会调用传入的de.robv.android.xposed.XposedBridge类,初始化java层XposedBridge.jar,调用main函数 |
到此Xposed初始化结束
2. Xposed hook原理
通过上文的详细分析,我们可以得出Xposed的hook原理:
1 |
Xposed的基本原理是修改了ART/Davilk虚拟机,将需要hook的函数注册为Native层函数。当执行到这一函数是虚拟机会优先执行Native层函数,然后再去执行Java层函数,这样完成函数的hook |
启动过程总结:
1 |
(1)手机启动时init进程会启动zygote这个进程。由于zygote进程文件app_process已被替换,所以启动的时Xposed版的zygote进程 |
我们对Xposed的基本原理和hook原理就基本掌握了,大家都知道我们这使用Xposed时,需要不断的去重启手机和勾选我们安装的模块,为了方便使用,这里补充两个技巧,我们了解Xposed源码后,就可以很方便实现了
(1)取消重启手机
我们先观察上文XposedBridge中main
上面的XposedInit.loadModules()这个函数,这个函数的作用就是load hook模块到进程中,因为zygote启动时先跑到java层XposeBridge.main中,在main里面有一步操作是将hook模块load进来,模块加载到zygote进程中,zygote fork所有的app进程里面也有这个hook模块,所以这个模块可以hook任意app。
编写hook模块的第一步就是判断当前的进程名字,如果是要hook的进程就hook,不是则返回,所以修改模块后,要将模块重新load zygote里面必须重启zygote,要想zygote重启就要重启手机了。
解决办法:
1 |
所以修改的逻辑是不把模块load到zygote里面,而是load到自己想要hook的进程里面,这样修改模块后只需重启该进程即可 |
步骤:
1 |
(1)将上面XposedInit.loadModules()注释掉即可 |
1 |
if (isZygote) { |
我们只需要将我们的模块进程指定,这样就不用每次开机都重启那
(2)取消操作Installer APP
通过阅读源码,我们发现读Install App的源码发现其实勾选hook模块其实app就是把模块的apk位置写到一个文件里,等load模块时会读取这个文件,从这个文件中的apk路径下把apk load到进程中
loadmodules的源码:
apk配置文件就是installer app文件路径下的conf/modules.list
这个文件data/data/de.robv.android.xposed.installer/conf/modules.list
或者data/user_de/0/de.robv.android.xposed.installer/conf/modules.list
所以我们勾选一个文件,实际是将其写到conf/modules.list
文件下,此时我们发现Xposed中还有一个方法loadModule
这个方法可以根据具体的路径和类加载器,直接导入模块,所以只要我们在上面代码中修改一些,就可以直接导入,不需要勾选那,我们确定apk路径:pathclass = "/data/local/tmp/module.apk"
和类加载器为根类加载器
1 |
if (isZygote) { |
四、Xposed框架特征
本节参考世界美景大佬定制Xposed框架和肉丝大佬来自高纬的对抗:魔改XPOSED过框架检测(下),经过我们上文的Android运行机制和Xposed框架运行机制讲解,相信大家对Xposed的框架已经有了进一步的认识,这样我们再来看这篇帖子里的修改的特征,就显得十分清晰了
五、Xposed特征修改
1. XposedInstaller
我们下载XposedInstaller的工程代码加载到AndroidStudio中,让XposedInstaller配置环境,错误提示解决见上文源码编译(2)——Xopsed源码编译详解
修改点:
1 |
(1)修改整体包名 |
(1)修改整体包名
先来改下整体的包名,首先将目录折叠给取消掉
然后我们在包名路径中,将xposed改成xppsed,这样可以保证包名长度是一样,同时xposed特征消失不见,选择Refactor→Rename
我们直接点击Refactor,就可以替换成功,点击Preview,则需要再点击 Do Refactor
这时候我们可以发现程序下的包名都改变了
接下来就是在整个项目的根文件夹下,进行整体的包名替换,因为还有很多编译配置、或者路径配置等等,需要进行包名的更换
在app文件夹右击,选择Replace in Path
把所有的de.robv.android.xposed.installe
都改成de.robv.android.xpsed.installer
搜出来匹配的地方只有5个文件中合计的7处地方,并不多,直接replace All替换即可
(2)xposed.prop
改成xpsed.prop
就是把如下图处的xposed.prop
改成xppsed.prop
即可
接下来就是编译了。编译时先Build→Clean一下,然后再Build→Make Project,这样就直接编译通过了。可以连接到手机,刷到手机上去,App会被装在手机上,但是无法自动启动,得手动点开
2. XposedBridge
(1)修改整体包名
首先是改包名,方法与上文一模一样,也是首先将xposed进行重构,改成xppsed
注:我们发现很多类型需要手动修改包名,我们依次将包名修改,这里我们发现重构后没反应,很可能是Android Studio的问题,换一个版本或重启
然后也是一样的在项目根目录下,执行Replace in Path,将所有的de.robv.android.xposed.installer
都改成de.robv.android.xppsed.installer
这里就修改完成
(2)生成文件
XposedBridge.jar:
先Make Clean一下,然后编译,将编译出来的文件复制一份,命名为XppsedBridge.jar即可
api.jar:
然后我们在Gradle->others->generate API中生成api.jar,保存在build/api下
3. Xposed
Xposed中的文件需要修改的地方不少:
(1)libxposed_common.h
修改之前:
修改之后:
(2)Xposed.h
修改之前:
1 |
#define XPOSED_PROP_FILE "/system/xposed.prop" |
修改之后:
1 |
#define XPOSED_PROP_FILE "/system/xppsed.prop" |
(3)xposed_service.cpp
修改之前:
1 |
IMPLEMENT_META_INTERFACE(XposedService, "de.robv.android.xposed.IXposedService"); |
修改之后:
1 |
IMPLEMENT_META_INTERFACE(XposedService, "de.robv.android.xppsed.IXposedService"); |
(4)xposed_shared.h
修改之前:
1 |
#define XPOSED_DIR "/data/user_de/0/de.robv.android.xposed.installer/" |
修改之后:
1 |
#define XPOSED_DIR "/data/user_de/0/de.robv.android.xppsed.installer/" |
(5)ART.mk
修改之前:
1 |
libxposed_art.cpp |
修改之后:
1 |
libxppsed_art.cpp |
(6)libxposed_art.cpp
1 |
将文件夹下的libxposed_art.cpp文件,重命名为libxppsed_art.cpp |
4. XposedTools
我们在XposedTools中将build.pl
和zipstatic/_all/META-INF/com/google/android/flash-script.sh
的字符替换就可以了
1 |
xposed.prop--->xppsed.prop |
记得不要有遗漏,可以在修改完之后,到根目录下运行下述grep命令试试看,找不到相应的字符串即为全部替换完成
1 |
grep -ril xposed.prop |
可是明明这里我是替换了的,我进入文件中也查找不到
经过分析,我们发现这里会将xposed_prop识别为xposed.prop,说明我们是替换完成了
5.源码编译
源码编译流程,详细的参考上文
1 |
(1)这里我们已经替换了art |
我们再次输入编译指令:
1 |
./build.pl -t arm:23 |
编译成功,生成文件:
我们重新移动生成文件到源码文件夹下,具体参考上文:
1 |
cp /home/tom/SourceCode/XposedBridge/sdk23/arm/files/system/bin/* . |
记得将xposed编译生成的app_process32_xposed替换system/bin文件夹下的app_process32
然后我们进入源码目录下,再次编译镜像:
1 |
source build/envsetup.sh |
6.结果与验证
(1)结果
我们将镜像刷入手机:
1 |
fastboot flash system system.img |
然后重启,发现Xposed安装成功
(2)验证
我们下载XposedCheck,这里我们从官网下载源码,用Android Studio打开,然后编译安装,发现我们定制Xposed框架成功
7.错误
(1)错误1
问题分析:
这是我们没有替换xposed导致的
问题解决:
我们将修改的Xposed替换原来/SourceCode/Android-6.0.1_r1/frameworks/base/cmds/
文件夹下的xposed文件夹
(2)错误2
问题分析:
这是我们的Xposed文件夹首字母为大写导致
问题解决:
我们将其首字母改为小写
(3)错误3
错误分析:
这是xposedinstaller和XposedBridge版本不一致导致
问题解决:
匹配详细参考上文
(4)错误4
错误分析:
错误分析:
经过反复的检查,最后原来是XposedBridge.jar编译的问题
问题解决:
因为我们编译过程中,将这里给注释掉了 所以导致并没导入Android6.0的环境支持,我们需要加入支持,详细见上文
六、实验总结
经过几天的学习,处理完许许多多的bug,从源码分析到源码定制,花了一周终于将这几篇文章写完了,从中收获了很多,这中间参考了很多大佬的文章,在文中和参考文献中会一一列出来,如果其中还存在一些问题,就请各位大佬指正了。
七、参考文献
Android源码分析:
1 |
https://juejin.cn/post/6844903748058218509、 |
Xposed:
1 |
https://zhuanlan.zhihu.com/p/389889716 |
- source:security-kitchen.com
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论