type
status
date
slug
summary
tags
category
icon
password
 
 
接上文:
Magisk源码分析
Magisk源码分析
 

一、日志

 

二、Zygisk

 

1. 概述

每个应用进程都从一个名为 Zygote 的现有进程进行fork得到的。系统启动并加载通用框架代码和资源时,Zygote 进程随之启动。为启动新的应用进程,系统会fork Zygote进程,然后在新进程中加载并运行应用代码。Zygote 是 Android 所有其他应用进程的父进程。
notion image
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.cppmain ,内部会校验当前是app_process(32/64)被执行还是其他,如果是app_process(32/64),则进入app_process_main
通过zygisk_request(ZygiskRequest::SETUP) 发送SETUP请求,由守护进程处理,处理完成后设置LD_PRELOADMAGISKTMP_ENV环境变量,LD_PRELOAD在Linux系统中用于指定要在程序运行前加载的共享库。当应用程序启动时,动态链接器会优先加载指定的共享库,然后再加载其他库。这通常用于劫持库的加载,即用自己的库替换原来系统的库,或者在运行时注入代码,实际上这里添加的是后面提到的loader32.soloader64.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_processzygisk_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_closePLT Hook
重点关注对androidSetCreateThreadFuncfork的Hook,在原本的androidSetCreateThreadFunc 被执行前调用hook_jni_env ,该函数Replace the function table in JNIEnv to hook RegisterNatives 即通过替换function table实现了对RegisterNatives 的Hook
env_RegisterNatives 则对注册的jni函数进行了Hook,并调用了hookAndSaveJNIMethodsnativeForkAndSpecializenativeSpecializeAppProcess 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 将之前Magiskpost_fs_data 阶段安装模块时的内存描述符进行处理,对动态链接库进行加载并删除描述符,然后对加载的模块进行初始化。
sanitize_fds 则用于清理/限制进程文件描述符的,这可以防止app传入不需要的FD,从而限制app可访问的资源,同时又保留app需要的FD不被关闭,实现最小特权的FD访问控制。
实际上提前进行fork是为了能够控制在denylist中的APP不加载模块,如果需要加载模块在nativeForkAndSpecialize_pre结尾调用了app_specialize_pre ,该函数调用run_modules_pre 将之前Magiskpost_fs_data 阶段安装模块时的内存描述符进行处理,对动态链接库进行加载并删除描述符,然后对加载的模块进行初始化。
zygisk注入原理 | d0nuts (d0nuts33.github.io)zygisk 这种在 fork 后加载模块的方式使得它可以在不重启的情况下更新 lsposed 代码,而且能够选择性加载模块。对比 xposed 的方式,xposed 在的 XposedInit 里就把模块加载进来,再进行正常的 ZygoteInit 启动流程,使得 zygote 之后 fork 的每一个进程都带有模块。
 
 

参考

  1. zygisk注入原理 | d0nuts (d0nuts33.github.io)
  1. gist.github.com/5ec1cff/bfe06429f5bf1da262c40d0145e9f190#file-zygisk-md
IDA动调SO流程及问题Magisk V26.1源码分析
  • Twikoo