type
status
date
slug
summary
tags
category
icon
password
接上文:
Magisk源码分析一、日志
二、Zygisk
1. 概述
每个应用进程都从一个名为 Zygote 的现有进程进行fork
得到的。系统启动并加载通用框架代码和资源时,Zygote 进程随之启动。为启动新的应用进程,系统会fork
Zygote进程
,然后在新进程中加载并运行应用代码。Zygote 是 Android 所有其他应用进程的父进程。Zygisk 的得名非常直白 —— Magisk注入Zygote
2. Zygisk-SETUP
在Magisk启动过程中,通过挂载实现了/system/bin/app_process(32/64)
到magisk(32/64)
的替换,因此当启动app_process(32/64)
时实际是执行了magisk(32/64)
,而magisk本身入口位于native/src/core/applets.cpp
的main
,内部会校验当前是app_process(32/64)
被执行还是其他,如果是app_process(32/64)
,则进入app_process_main
。通过zygisk_request(ZygiskRequest::SETUP)
发送SETUP请求,由守护进程处理,处理完成后设置LD_PRELOAD
和MAGISKTMP_ENV
环境变量,LD_PRELOAD
在Linux系统中用于指定要在程序运行前加载的共享库。当应用程序启动时,动态链接器会优先加载指定的共享库,然后再加载其他库。这通常用于劫持库的加载,即用自己的库替换原来系统的库,或者在运行时注入代码,实际上这里添加的是后面提到的loader32.so
和loader64.so
。而MAGISKTMP_ENV
实际就是/debug_ramdisk/.magisk
。设置环境变量后fexecve(app_proc_fd, argv, environ);
,这里的文件描述符实际是原来的app_process_##bit
因此会执行原本的app_process
守护进程接收到请求后调用setup_files
进行处理 ,setup_files
获取到两个loader的二进制内容后输出到/.magisk/zygisk/
目录下,然后挂载到/system/bin/
对应位置(23:/system/bin/bu)(64:/system/bin/appwidget)
loader
源码如下,实际就是在加载时拿到/system/bin/app_process
的zygisk_inject_entry
地址进行调用(这里的app_process
实际是经过替换的magisk
),卸载时unload_first_stage
这里需要明确一点:
app_process(32/64)
在实际运行过程中已经被替换为了magisk
,理论依据就是Android会在init.rc
设置各个阶段的具体时机,而trigger zygote-start
也就是触发zygote-start
是在post-fs-data
被触发之后的,而在post-fs-data
被触发后,之前注入init.rc
的内容决定了在post-fs-data
被触发后magisk启动监听进程并且对app_process(32/64)
进行替换,所以之后zygote-start
时实际启动的是被替换后的3. zygisk_inject_entry
上面提到LD_PRELOAD
先于其他动态链接库加载了loader
,先分析zygisk_inject_entry
,首先就是恢复LD_PRELOAD
为原本的环境变量,然后恢复MAGISKTMP_ENV
。通过sanitize_environ
确保该文件中的环境变量是经过清理的、符合预期的。重点分析hook_functions
,设置了对fork
unshare
androidSetCreateThreadFunc
android_log_close
的PLT Hook
重点关注对androidSetCreateThreadFunc
和fork
的Hook,在原本的androidSetCreateThreadFunc
被执行前调用hook_jni_env
,该函数Replace the function table in JNIEnv to hook RegisterNatives
即通过替换function table实现了对RegisterNatives
的Hookenv_RegisterNatives
则对注册的jni函数进行了Hook,并调用了hookAndSaveJNIMethods
对nativeForkAndSpecialize
、nativeSpecializeAppProcess
和nativeForkSystemServer
这三个方法进行了Hook。
4. nativeForkAndSpecialize
无论是哪个安卓版本Magisk对于nativeForkAndSpecialize
的Hook都是三步策略
ctx.nativeForkAndSpecialize_pre
-> nativeForkAndSpecializ ->
ctx.nativeForkAndSpecialize_post
,而Android源码中forkAndSpecialize
也是三步走,ZygoteHooks.preFork ->
nativeForkAndSpecialize
-> ZygoteHooks.postForkCommon
,其中第二步才调用到了nativeForkAndSpecialize
ZygoteHooks.preFork()
是一个钩子方法,它会在Zygote fork新进程之前被调用,进行一些fork
前操作例如通过在Java层记录fork事件来跟踪进程创建
预加载新进程可能需要的类或资源
申请和预热新进程可能需要的内存
nativeForkAndSpecialize
在底层执行实际的process fork
操作创建新进程并处理针对新进程的native堆初始化等操作。ZygoteHooks.postForkCommon
在native层的fork之后在新进程中调用,负责新进程Java层环境的初始化。在Hook的nativeForkAndSpecialize
函数中首先是nativeForkAndSpecialize_pre
, 重点是fork_pre
,在fork_pre
中pid = old_fork();
预先进行了fork
,而到原本的nativeForkAndSpecialize
进行fork
时实际是调用的被Hook 的fork
,这次fork
不会实际进行fork
,而是返回pid
。run_modules_pre
将之前Magisk
在post_fs_data
阶段安装模块时的内存描述符进行处理,对动态链接库进行加载并删除描述符,然后对加载的模块进行初始化。sanitize_fds
则用于清理/限制进程文件描述符的,这可以防止app传入不需要的FD,从而限制app可访问的资源,同时又保留app需要的FD不被关闭,实现最小特权的FD访问控制。实际上提前进行fork是为了能够控制在denylist
中的APP不加载模块,如果需要加载模块在nativeForkAndSpecialize_pre
结尾调用了app_specialize_pre
,该函数调用run_modules_pre
将之前Magisk
在post_fs_data
阶段安装模块时的内存描述符进行处理,对动态链接库进行加载并删除描述符,然后对加载的模块进行初始化。zygisk注入原理 | d0nuts (d0nuts33.github.io):zygisk 这种在 fork 后加载模块的方式使得它可以在不重启的情况下更新 lsposed 代码,而且能够选择性加载模块。对比 xposed 的方式,xposed 在的 XposedInit 里就把模块加载进来,再进行正常的 ZygoteInit 启动流程,使得 zygote 之后 fork 的每一个进程都带有模块。
参考
- 作者:LLeaves
- 链接:https://lleavesg.top//article/zygisk-src
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章