xposed适用的最高版本为android 8.0,针对高版本的ART HOOK框架可以使用比较有名的lsposed。它使用了lsplant ART HOOK框架(早期使用YAHFA)并提供了和xposed一样的接口API与其进行了兼容,同时lsposed本身是一个基于magisk的riru/zygisk插件,所以在分析lsposed运行流程之前先分别分析一下riru和zygisk的运行流程。
riru运行流程分析
riru的目的就是为了能够将插件so在zygote刚开始启动的时候注入到此进程中,首先riru自己需要先注入到zygote进程中。不同版本的riru使用了不同的方法将自己注入到zygote进程中。
- 早期:通过替换系统so库:libmemtrack.so来实现劫持注入。
- 中期:使用public.libraries.txt,在zygote启动时会加载此文件中的所有so库。
- 现在:修改系统属性
ro.dalvik.vm.native.bridge
进行注入,zygote会加载此系统属性值对应的so库。
libriruloader.so的.initarray
目前最新的riru版本(V26)通过修改系统属性ro.dalvik.vm.native.bridge
将libriruloader.so注入到zygote进程中,然后查看此so的.initarray其会先调用dlopen将libriru.so加载,最后调用libriru.so的init函数。
libriru.so的init
libriru的init函数分别调用了PrepareMapsHideLibrary,InstallHooks和Load。
PrepareMapsHideLibrary
PrepareMapsHideLibrary加载libriruhide.so并获取其导出函数riru_hide
InstallHooks
XHOOK_REGISTER是一个宏,其通过GOT表hook libandroid_runtime.so的jniRegisterNativeMethods,因为libandroid_runtime.so的jni函数都是通过jniRegisterNativeMethods注册的,所以hook后可以主动调用原jniRegisterNativeMethods为libandroid_runtime.so注册回调函数并将nativeForkAndSpecialize ,nativeSpecializeAppProcess, nativeForkSystemServer
这三个函数指针修改。这三个jni函数会在Zygote进程java层fork应用进程和系统进程时被调用,通过修改这三个函数的指针就可以在zygote fork新进程的时候得到执行时机。
Load
- 调用LoadModule函数,通过dlopen加载所有的riru模块so并调用其init函数。
- 调用HideFromMaps函数通过之前获取的libriruhide.so的导出函数riru_hide隐藏所有加载的riru模块so和libriru.so本身。
- 调用所有加载的riru模块so的onModuleLoaded函数。
libriruhide.so的导出函数riru_hide,此函数会调用do_hide隐藏指定内存块,通过备份后再重新map会原地址的方法欺骗map表。
zygisk运行流程分析
zygisk目的和riru一样都是为了在zygote进程中运行自己的模块,其通过修改app_process程序的入口,通过设置环境变量LD_PRELOAD后运行原来的app_process程序从而注入zigisk自己的so。
zygisk自己的so注入到zygote进程中后和riru一样也会加载所有的模块so,同时也会利用相同的方式隐藏这些so模块。
整体流程大致和riru相似。
lsposed运行流程分析
由riru运行流程分析可知,其在加载模块so之后会先后进行如下几步操作:
- 加载完模块so后调用so中的init函数
- 隐藏模块so
- 调用模块so的onModuleLoaded函数
- 当zygote fork生成新apk时会调用模块so中设置的回调函数
nativeForkAndSpecialize(pre/post) ,nativeSpecializeAppProcess(pre/post) , nativeForkSystemServer(pre/post)
以nativeForkAndSpecialize(pre/post)为例,lsposed设置这两个回调函数后,当zygote fork一个新的app进程时会分别调用这两个回调函数。lsposed设置nativeForkAndSpecializepost回调函数会调用MagiskLoader::OnNativeForkAndSpecializePost,此函数会调用一系列函数在zygote fork的apk运行前进行一些初始化。
LoadDex
先调用PreloadedDex将lspd.dex文件从磁盘map到内存中。
LoadDex实例化一个InMemoryClassLoader并设置parent为系统类加载器systemClassLoader,同时加载了lspd.dex。
InitArtHooker and InitHooks
进行一些初始化,其中InitHooks内部会获取前面实例化的InMemoryClassLoader类加载器中加载的所有dex文件(实际就是lspd.dex)的DexFile对象,然后调用DexFile_setTrusted使此dex文件中的类能够绕过android 9.0开始的对私有系统frameword API的限制访问,但是查看DexFile_setTrusted源码发现,此函数只有在apk处于调试状态下才能生效。
SetupEntryClass
相当于找到lspd.dex的入口类org.lsposed.lspd.core.Main
forkCommon
forkCommon会调用initXposed和bootstrapXposed
initXposed
initXposed进行一些初始化,通过前面加载的lspd.dex中提供的xposed API进行一些hook操作。
bootstrapXposed
bootstrapXposed调用lspd.dex的loadModules。
loadModules调用getModulesList加载并所有的xposed模块,调用重载的loadModule(内部调用InitModule)初始化xposed模块中需要hook的函数。
loadModules调用getModulesList,getModulesList内部经过层层调用最后会调用LoadModule,此函数会读取xposed模块apk文件中的assets/xposed_init中注册的类名称。
loadModules调用重载的loadModule,此函数通过一个自定义的类LspModuleClassLoader继承于ByteBufferDexClassLoader ,ByteBufferDexClassLoader继承于BaseDexClassLoader,BaseDexClassLoader继承于ClassLoader,定义一个classloader设置parent为之前创建的InMemoryClassLoader并加载对应的xposed模块apk。最后还会调用InitModule对xposed模块中需要hook的函数进行初始化。