type
status
date
slug
summary
tags
category
icon
password
接上文:
Magisk源码分析一、日志
二、Zygisk
1. 概述
每个应用进程都从一个名为 Zygote 的现有进程进行fork得到的。系统启动并加载通用框架代码和资源时,Zygote 进程随之启动。为启动新的应用进程,系统会forkZygote进程,然后在新进程中加载并运行应用代码。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/对应位置(32:/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,设置了对forkunshareandroidSetCreateThreadFuncandroid_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,其中第二步才调用到了nativeForkAndSpecializeZygoteHooks.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 许可协议,转载请注明出处。
相关文章






