lsposed运行流程分析
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进程中运行自己的模块,同时zygisk是magisk的附加功能。首先magisk会在初始化的过程中启动magisk服务,当magisk启用了zygisk功能时magisk服务会调用mount_zygisk
mount_zygisk
- 将原始的/system/bin/app_process(32/64)的文件句柄保存
- 将/sbin/magisk(32/64)另存为/sbin/.magisk/zygisk/app_process(32/64)
- 将/sbin/.magisk/zygisk/app_process(32/64)挂载到/system/bin/app_process(32/64),实际就是将/sbin/magisk(32/64)挂载到/system/bin/app_process(32/64)
app_process_main
然后在启动zygote(/system/bin/app_process)
进程的时候实际上就是执行的magisk
,magisk
入口会执行app_process_main
函数
app_process_main
会与magiskd
建立socket连接- 将
libzygisk-ld.so(32/64)
的名称发送给magiskd
,magiskd
会调用setup_files
- 从
magiskd
获取原始/system/bin/app_process
的文件句柄 - 设置
LD_PRELOAD
环境变量,32位新增加/system/bin/bu
,64位新增加/system/bin/appwidget
- 通过调用
fexecve
执行原始的zygote(/system/bin/app_process)
进程,因为设置了LD_PRELOAD
环境变量的缘故,/system/bin/bu
或者/system/bin/appwidget
会被注入到zygote
进程中
setup_files
- 创建
/sbin/.magisk/zygisk/loader32/64.so
- 从socket中读取
libzygisk-ld.so
,并将libzygisk-ld.so
的文件内容copy到loader32/64.so
- 将
/system/bin/ru
挂载到loader32.so
,将/system/bin/appwidget
挂载到loader64.so
- 将原始
/system/bin/app_process
文件句柄进行回传
所以通过LD_PRELOAD
环境变量注入/system/bin/bu
或者/system/bin/appwidget
到zygote
进程实际就是将/sbin/.magisk/zygisk/loader32/64.so
注入到zygote
进程中
zygisk_inject_entry
查看libzygisk-ld.so
的代码,其会设置一个.init
函数,其会调用magisk
的zygisk_inject_entry
函数,调用完``会调用unload_first_stage
卸载自己并取消之前的挂载。
- 对于
LD_PRELOAD
环境变量有多个路径的情况zygisk_inject_entry
会保留第一个路径,而对于只包含一个路径的则直接调用unsetenv将LD_PRELOAD
环境变量直接删除 - 调用unsetenv删除
MAGISKTMP
环境变量 - 调用
sanitize_environ
将被删除的环境变量留下的内存空洞进行补齐 - 调用
hook_functions
去hook一些重要的函数
hook_functions
此函数会hook libandroid_runtime.so
的fork
,unshare
,androidSetCreateThreadFunc
,android_log_close
其中最重要的就是androidSetCreateThreadFunc
,过滤函数中会先调用hook_jni_env
,之后在调用原始的androidSetCreateThreadFunc
hook_jni_env
主要就是替换JNINativeInterface
函数列表中RegisterNative
的地址为env_RegisterNative
,实际上就是hook RegisterNative
函数
env_RegisterNative
函数会调用hookAndSaveJNIMethods
hookAndSaveJNIMethods
会去hook nativeForkAndSpecialize, nativeSpecializeAppProcess, nativeForkSystemServer
这三个函数(和riru一样)
这样每当app被fork的时候zygisk
都会获得执行时机并加载对应的zygisk
模块并进行初始化
run_modules_pre
加载zygisk
模块时(也就是liblspd.so)的时候会讲其名称设置为jit-cache
,这样的话so的内存段在maps中就是/memfd:/jit-cache (deleted)
lsposed运行流程分析
由riru运行流程分析可知,其在加载模块so之后会先后进行如下几步操作(模块so就是liblspd.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的函数进行初始化。
检测lsposed指纹
检查栈回溯
可以通过主动抛出一个异常并检查栈回溯信息看是否存在一些特殊方法调用,例如:de.robv.android.xposed
内存漫游获取classloader
getInstancesOfClasses可以获取某个类的所有实例,可以通过调用此函数获取所有的ClassLoader实例并进一步查看此ClassLoader加载的所有类信息,看是否存在特殊的类名称。但是此函数是hide api,调用的话需要先绕过hide api限制。
检查/proc/pid/maps
无论lsposed是基于riru还是zigisk,其so模块都会被隐藏。虽然隐藏后虽然内存块是匿名的,但是内存块还是包含可执行属性,正常情况下是很少出现匿名的可执行内存的。可以通过检测map表是否存在匿名的并且具有可执行属性的内存判断是否存在lsposed模块。
检查riru
riru会将libriruloader.so放在/system/lib64
目录下并注入到zygote进程中,通过fopen("/system/lib64/libriruloader.so", "r");
判断是否存在此文件。
检查zygisk
zygisk通过修改zygote进程的环境变量LD_PRELOAD注入zygisk的so文件,在应用程序中可以检测环境变量LD_PRELOAD
和MAGISKTMP
。同时可以看到环境变量后面紧跟着/dev/fd/4
,这证明zygote是通过fexecve
创建的,正常应该是/system/bin/app_process64
。(不过shamiko项目可以对这些变化进行抹去https://github.com/LSPosed/LSPosed.github.io/releases/tag/shamiko-126
)
还可以检测zygisk
的so文件:/system/bin/appwidget
,或者直接检测magisk
参考:
https://bbs.kanxue.com/thread-269094.htm
https://bbs.kanxue.com/thread-263018.htm
https://liwugang.github.io/2022/01/01/android_env_detection.html
以上均属个人观点,仅供参考。