Zygisk源码阅读

警告
本文最后更新于 2023-06-11,文中内容可能已过时。

基于Magisk v25.0

magic_mount的原理是挂载tmpfs作为目录,并bind_mount原有的和修改后的文件,而zygisk的处理逻辑也在这个函数当中,整个过程是在magiskd这个系统守护进程中处理的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// native/jni/core/module.cpp

void magic_mount() {
    // bind_mount的过程
    ......

    // Mount on top of modules to enable zygisk
    if (zygisk_enabled) {
        string zygisk_bin = MAGISKTMP + "/" ZYGISKBIN;
        // zygisk_bin对应的是/dev/xxxx/zygisk
        mkdir(zygisk_bin.data(), 0);
        mount_zygisk(32)
        mount_zygisk(64)
    }
}

// native/jni/core/module.cpp

int app_process_32 = -1;
int app_process_64 = -1;

#define mount_zygisk(bit)                                                               \
if (access("/system/bin/app_process" #bit, F_OK) == 0) {                                \
    app_process_##bit = xopen("/system/bin/app_process" #bit, O_RDONLY | O_CLOEXEC);    \
    string zbin = zygisk_bin + "/app_process" #bit;                                     \
    string dbin = zygisk_bin + "/magisk" #bit;                                          \
    string mbin = MAGISKTMP + "/magisk" #bit;                                           \
    int src = xopen(mbin.data(), O_RDONLY | O_CLOEXEC);                                 \
    int out = xopen(zbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0);                    \
    xsendfile(out, src, nullptr, INT_MAX);                                              \
    close(out);                                                                         \
    out = xopen(dbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0);                        \
    lseek(src, 0, SEEK_SET);                                                            \
    xsendfile(out, src, nullptr, INT_MAX);                                              \
    close(out);                                                                         \
    close(src);                                                                         \
    clone_attr("/system/bin/app_process" #bit, zbin.data());                            \
    clone_attr("/system/bin/app_process" #bit, dbin.data());                            \
    bind_mount(zbin.data(), "/system/bin/app_process" #bit);                            \
}

从mount_zygisk的过程中可以看出做了三件事

  1. 打开原先的app_process(32|64)文件,fd保存到app_process_(32|64)中
  2. 把magisk自己的可执行文件magisk(32|64)(mbin)复制到zygisk目录下的app_process(32|64)(zbin),此处用了sendfile直接在内核中复制文件

    sendfile函数在两个文件描写叙述符之间直接传递数据(完全在内核中操作,传送),从而避免了内核缓冲区数据和用户缓冲区数据之间的拷贝,操作效率非常高,被称之为零拷贝

  3. 把zygisk目录下app_process(实际上是magisk文件)通过bind_mount的方式挂载到到原先的/system/bin/app_process(32|64)上

那么这样一来,/system/bin下的app_process就变成了magisk文件,而原先的app_process的fd被magiskd持有。执行app_process的时候就是执行了magisk

首先magisk可执行文件的入口main在native/jni/core/applets.cpp里面,app_process实际上可以看作是它的一个applet(类似su、resetprop这些,不过被隐藏了,因为这是个内部功能)

main启动会判断自己的文件名(argv0),如果是app_process就会调用app_process_main,如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// native\jni\core\applets.cpp

int main(int argc, char *argv[]) {
    enable_selinux();
    cmdline_logging();
    init_argv0(argc, argv);

    string_view base = basename(argv[0]);

    // app_process is actually not an applet
    if (str_starts(base, "app_process")) {
        return app_process_main(argc, argv);
    }

    umask(0);
    if (base == "magisk" || base == "magisk32" || base == "magisk64") {
        if (argc > 1 && argv[1][0] != '-') {
            // Calling applet via magisk [applet] args
            --argc;
            ++argv;
        } else {
            return magisk_main(argc, argv);
        }
    }

    return call_applet(argc, argv);
}

// native/jni/zygisk/main.cpp
// Entrypoint for app_process overlay
int app_process_main(int argc, char *argv[]) {
    android_logging();
    char buf[256];

    bool zygote = false;
    if (auto fp = open_file("/proc/self/attr/current", "r")) {
        fscanf(fp.get(), "%s", buf);
        zygote = (buf == "u:r:zygote:s0"sv);
    }

    if (!zygote) {
        // ...
    }

    if (int socket = connect_daemon(); socket >= 0) {
        do {
            write_int(socket, ZYGISK_REQUEST);
            write_int(socket, ZYGISK_SETUP);

            if (read_int(socket) != 0)
                break;

            int app_proc_fd = recv_fd(socket);
            if (app_proc_fd < 0)
                break;

            string tmp = read_string(socket);
#if defined(__LP64__)
            string lib = tmp + "/" ZYGISKBIN "/zygisk.app_process64.1.so";
#else
            string lib = tmp + "/" ZYGISKBIN "/zygisk.app_process32.1.so";
#endif
            if (char *ld = getenv("LD_PRELOAD")) {
                char env[256];
                sprintf(env, "%s:%s", ld, lib.data());
                setenv("LD_PRELOAD", env, 1);
            } else {
                setenv("LD_PRELOAD", lib.data(), 1);
            }
            setenv(INJECT_ENV_1, "1", 1);
            setenv("MAGISKTMP", tmp.data(), 1);

            close(socket);

            snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd);
            fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
            execve(buf, argv, environ);
        } while (false);

        close(socket);
    }

    // If encountering any errors, unmount and execute the original app_process
    xreadlink("/proc/self/exe", buf, sizeof(buf));
    xumount2("/proc/self/exe", MNT_DETACH);
    execve(buf, argv, environ);
    return 1;
}

逻辑中会区分是否是zygote的情况,这里我们只关注zygote

首先连接到magiskd,然后发送ZYGISK_SETUP,会得到一个fd和一个字符串,观察对应的处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// native/jni/zygisk/entry.cpp
void zygisk_handler(int client, const sock_cred *cred) {
    int code = read_int(client);
    char buf[256];
    switch (code) {
    case ZYGISK_SETUP:
        setup_files(client, cred);
        break;
        // ...
    }
  // ...
}

static void setup_files(int client, const sock_cred *cred) {
    LOGD("zygisk: setup files for pid=[%d]\n", cred->pid);

    char buf[256]; 
    // 请求者的可执行程序路径 (/proc/pid/exec) ,一般是/system/bin/app_process[32|64]
    if (!get_exe(cred->pid, buf, sizeof(buf))) {
        write_int(client, 1);
        return;
    }

    bool is_64_bit = str_ends(buf, "64");
    write_int(client, 0);
    // 发送持有的真正的 app_process文件fd
    send_fd(client, is_64_bit ? app_process_64 : app_process_32); 

    string path = MAGISKTMP + "/" ZYGISKBIN "/zygisk." + basename(buf);
    // 复制buf路径的文件到MAGISKTMP/zygisk/zygisk.app_process[32|64].1.so
    cp_afc(buf, (path + ".1.so").data()); 
    cp_afc(buf, (path + ".2.so").data());
    // 发送MAGISKTMP路径
    write_string(client, MAGISKTMP); 
}

从代码中可以看到

  • fd对应的是原始app_process文件的fd,可以用来exec
  • string对应的是magisktmp路径

其中还有一个操作是将/system/bin/app_process文件复制到MAGISKTMP/zygisk/zygisk.app_process[32|64].1.so上

回到app_process_main,后面做了几件事

  1. 把MAGISKTMP/zygisk/zygisk.app_process(32|64).1.so、MAGISKTMP/zygisk/zygisk.app_process(32|64).2.so写入环境变量LD_PRELOAD里

    LD_PRELOAD是Linux系统中的一个环境变量,它可以影响程序的运行时的链接(Runtimelinker),它允许你定义在程序运行前优先加载的动态链接库,这样,在程序运行时会优先使用动态库中的符号而不是系统默认的符号

  2. 设置INJECT_ENV_1、MAGISKTMP_ENV到系统变量中
  3. fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC)表示当执行exec时关闭fd,这里的app_proc_fd是原始的fd
  4. fexecve开始执行原生的app_process,此时LD_PRELOAD已经被替换

目前为止,可以看到zygisk的注入思路,通过类似hook的方式hook app_process,用LD_PRELOAD的方式完成自身注入,而不是像riru那样通过修改native_bridge或者xposed直接修改app_process

再细看下LD_PRELOAD的执行逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// linker_main.cpp

static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
  ProtectedDataGuard guard;

  // These should have been sanitized by __libc_init_AT_SECURE, but the test
  // doesn't cost us anything.
  const char* ldpath_env = nullptr;
  const char* ldpreload_env = nullptr;
  if (!getauxval(AT_SECURE)) {
    ldpath_env = getenv("LD_LIBRARY_PATH");
    if (ldpath_env != nullptr) {
      INFO("[ LD_LIBRARY_PATH set to \"%s\" ]", ldpath_env);
    }
    ldpreload_env = getenv("LD_PRELOAD");
    if (ldpreload_env != nullptr) {
      INFO("[ LD_PRELOAD set to \"%s\" ]", ldpreload_env);
    }
  }
  ......

  // Use LD_LIBRARY_PATH and LD_PRELOAD (but only if we aren't setuid/setgid).
  parse_LD_LIBRARY_PATH(ldpath_env);
  parse_LD_PRELOAD(ldpreload_env);

  std::vector<android_namespace_t*> namespaces = init_default_namespaces(exe_info.path.c_str());

  if (!si->prelink_image()) __linker_cannot_link(g_argv[0]);

  // add somain to global group
  si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL);
  // ... and add it to all other linked namespaces
  for (auto linked_ns : namespaces) {
    if (linked_ns != &g_default_namespace) {
      linked_ns->add_soinfo(somain);
      somain->add_secondary_namespace(linked_ns);
    }
  }

  linker_setup_exe_static_tls(g_argv[0]);

  // Load ld_preloads and dependencies.
  std::vector<const char*> needed_library_name_list;
  size_t ld_preloads_count = 0;

  for (const auto& ld_preload_name : g_ld_preload_names) {
    needed_library_name_list.push_back(ld_preload_name.c_str());
    ++ld_preloads_count;
  }
  ......

  if (needed_libraries_count > 0 &&
      !find_libraries(&g_default_namespace,
                      si,
                      needed_library_names,
                      needed_libraries_count,
                      nullptr,
                      &g_ld_preloads,
                      ld_preloads_count,
                      RTLD_GLOBAL,
                      nullptr,
                      true /* add_as_children */,
                      true /* search_linked_namespaces */,
                      &namespaces)) {
    __linker_cannot_link(g_argv[0]);
  } else if (needed_libraries_count == 0) {
    if (!si->link_image(SymbolLookupList(si), si, nullptr, nullptr)) {
      __linker_cannot_link(g_argv[0]);
    }
    si->increment_ref_count();
  }

  linker_finalize_static_tls();
  __libc_init_main_thread_final();

  if (!get_cfi_shadow()->InitialLinkDone(solist)) __linker_cannot_link(g_argv[0]);

  si->call_pre_init_constructors();
  si->call_constructors();
  ......
  return entry;
}

static void parse_LD_PRELOAD(const char* path) {
  g_ld_preload_names.clear();
  if (path != nullptr) {
    // We have historically supported ':' as well as ' ' in LD_PRELOAD.
    g_ld_preload_names = android::base::Split(path, " :");
    g_ld_preload_names.erase(std::remove_if(g_ld_preload_names.begin(), g_ld_preload_names.end(),
                                            [](const std::string& s) { return s.empty(); }),
                             g_ld_preload_names.end());
  }
}

linker_main这里关于LD_PRELOAD做了三件事

  1. 获取了环境变量中的值ldpreload_env = getenv(“LD_PRELOAD”);
  2. parse_LD_PRELOAD(ldpreload_env);解析LD_PRELOAD的值写入全局变量g_ld_preload_names
  3. 遍历g_ld_preload_names添加至needed_library_name_list
  4. so加载流程:find_library、call_pre_init_constructors、call_constructors调用DT_INIT、DT_INIT_ARRAY段的函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// native/jni/zygisk/entry.cpp

__attribute__((constructor))
static void zygisk_init() {
    if (getenv(INJECT_ENV_2)) {
        // Return function pointer to first stage
        char buf[128];
        snprintf(buf, sizeof(buf), "%p", &second_stage_entry);
        setenv(SECOND_STAGE_PTR, buf, 1);
    } else if (getenv(INJECT_ENV_1)) {
        first_stage_entry();
    }
}

这里根据系统变量决定不同阶段处理方式

正常加载时会加载第一阶段,这是在写入LD_PRELOAD时完成环境变量设置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void first_stage_entry() {
    android_logging();
    ZLOGD("inject 1st stage\n");

    char *ld = getenv("LD_PRELOAD");
    char tmp[128];
    strlcpy(tmp, getenv("MAGISKTMP"), sizeof(tmp));
    char *path;
    if (char *c = strrchr(ld, ':')) {
        *c = '\0';
        setenv("LD_PRELOAD", ld, 1);  // Restore original LD_PRELOAD
        path = strdup(c + 1);
    } else {
        unsetenv("LD_PRELOAD");
        path = strdup(ld);
    }
    unsetenv(INJECT_ENV_1);
    unsetenv("MAGISKTMP");
    sanitize_environ();

    char *num = strrchr(path, '.') - 1;

    // Update path to 2nd stage lib
    *num = '2';

    // Load second stage
    setenv(INJECT_ENV_2, "1", 1);
    void *handle = dlopen(path, RTLD_LAZY);
    remap_all(path);

    // Revert path to 1st stage lib
    *num = '1';

    // Run second stage entry
    char *env = getenv(SECOND_STAGE_PTR);
    decltype(&second_stage_entry) second_stage;
    sscanf(env, "%p", &second_stage);
    second_stage(handle, tmp, path);
}

一阶段的作用,环境清理(重置env)、获取LD_PRELOAD路径并dlopen、开启二阶段,这种方式和riru很类似,native_bridge加载的libriruloader.so只是一个loader的作用,负责load libriru.so后被处理

dlopen的path对应的是zygisk.app_process.[32|64].2.so,加载完成后调用remap_all

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void remap_all(const char *name) {
    vector<map_info> maps = find_maps(name);
    for (map_info &info : maps) { 
        // 遍历 maps 中指定文件名的映射信息
        void *addr = reinterpret_cast<void *>(info.start);
        size_t size = info.end - info.start;
        // 映射和目标同样大小的可写内存
        void *copy = xmmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 
        if ((info.perms & PROT_READ) == 0) {
            // 如果目标不可读,让其可读
            mprotect(addr, size, PROT_READ); 
        }
        // 复制目标的内存到新的映射
        memcpy(copy, addr, size); 
        // 用新的匿名映射覆盖到原先目标的位置
        mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); 
        // 恢复权限使其和目标一致
        mprotect(addr, size, info.perms); 
    }
}

maps文件处理,把path对应的映射全部重新处理成匿名,目的应该是为了隐藏

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static void second_stage_entry(void *handle, const char *tmp, char *path) {
    self_handle = handle;
    MAGISKTMP = tmp;
    unsetenv(INJECT_ENV_2);
    unsetenv(SECOND_STAGE_PTR);

    zygisk_logging();
    ZLOGD("inject 2nd stage\n");
    hook_functions();

    // First stage will be unloaded before the first fork
    first_stage_path = path;
}

核心函数hook_functions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// native\jni\zygisk\hook.cpp

#define XHOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \
    hook_register(PATH_REGEX, SYM, (void*) new_##NAME, (void **) &old_##NAME)

#define XHOOK_REGISTER(PATH_REGEX, NAME) \
    XHOOK_REGISTER_SYM(PATH_REGEX, #NAME, NAME)

#define ANDROID_RUNTIME ".*/libandroid_runtime.so$"
#define APP_PROCESS     "^/system/bin/app_process.*"

void hook_functions() {
#if MAGISK_DEBUG
    // xhook_enable_debug(1);
    xhook_enable_sigsegv_protection(0);
#endif
    default_new(xhook_list);
    default_new(jni_hook_list);
    default_new(jni_method_map);

    XHOOK_REGISTER(ANDROID_RUNTIME, fork);
    XHOOK_REGISTER(ANDROID_RUNTIME, unshare);
    XHOOK_REGISTER(ANDROID_RUNTIME, jniRegisterNativeMethods);
    XHOOK_REGISTER(ANDROID_RUNTIME, selinux_android_setcontext);
    XHOOK_REGISTER_SYM(ANDROID_RUNTIME, "__android_log_close", android_log_close);
    hook_refresh();

    // Remove unhooked methods
    xhook_list->erase(
            std::remove_if(xhook_list->begin(), xhook_list->end(),
            [](auto &t) { return *std::get<2>(t) == nullptr;}),
            xhook_list->end());

    if (old_jniRegisterNativeMethods == nullptr) {
        ZLOGD("jniRegisterNativeMethods not hooked, using fallback\n");

        // android::AndroidRuntime::setArgv0(const char*, bool)
        XHOOK_REGISTER_SYM(APP_PROCESS, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0);
        hook_refresh();

        // We still need old_jniRegisterNativeMethods as other code uses it
        // android::AndroidRuntime::registerNativeMethods(_JNIEnv*, const char*, const JNINativeMethod*, int)
        constexpr char sig[] = "_ZN7android14AndroidRuntime21registerNativeMethodsEP7_JNIEnvPKcPK15JNINativeMethodi";
        *(void **) &old_jniRegisterNativeMethods = dlsym(RTLD_DEFAULT, sig);
    }
}

我们知道,Zygisk和Riru最大的不同在于它有一个「排除列表」,被排除的进程一定不会注入,而如果像Riru那样直接在zygote进程中加载所有模块显然是做不到的,一旦Zygote加载了模块,它想干什么就不是Zygisk能管的了。但是又要让模块有修改forkAndSpecialize参数的能力,而这个方法调用fork前是在zygote进程中执行的,因此好像必须在zygote中执行模块的代码。这么看来Zygisk一定用了一些巧妙的手段处理

注意到Zygisk hook了很多关键的函数,我们先看看fork

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Skip actual fork and return cached result if applicable
// Also unload first stage zygisk if necessary
DCL_HOOK_FUNC(int, fork) {
    unload_first_stage();
    return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
}

#define DCL_HOOK_FUNC(ret, func, ...) \
ret (*old_##func)(__VA_ARGS__);       \
ret new_##func(__VA_ARGS__)

如果g_ctx存在且pid大于0返回pid,否则则调用原始fork,那这是什么含义呢?

1
2
// Current context
HookContext *g_ctx;

这是一个HookContext类型的全局变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// native\jni\zygisk\hook.cpp

struct HookContext {
    JNIEnv *env;
    union {
        AppSpecializeArgsImpl *args;
        ServerSpecializeArgsImpl *server_args;
        void *raw_args;
    };
    const char *process;
    int pid;
    bitset<FLAG_MAX> flags;
    AppInfo info;
    vector<ZygiskModule> modules;

    HookContext() : pid(-1), info{} {}

    static void close_fds();
    void unload_zygisk();

    DCL_PRE_POST(fork)
    void run_modules_pre(const vector<int> &fds);
    void run_modules_post();
    DCL_PRE_POST(nativeForkAndSpecialize)
    DCL_PRE_POST(nativeSpecializeAppProcess)
    DCL_PRE_POST(nativeForkSystemServer)
};

#define DCL_PRE_POST(name) \
void name##_pre();         \
void name##_post();

DCL_PRE_POST声明了fork的两个函数

  • fork_pre
  • fork_post
1
2
3
4
5
6
7
8
// Do our own fork before loading any 3rd party code
// First block SIGCHLD, unblock after original fork is done
void HookContext::fork_pre() {
    g_ctx = this;
    sigmask(SIG_BLOCK, SIGCHLD);
    // this->pid, 即 g_ctx->pid
    pid = old_fork(); 
}

可见,fork_pre中修改了全局变量g_ctx为this,屏蔽SIGCHLD信号,并主动调用了原先的fork函数。注释中说,这是要在加载第三方代码(模块)前先进行fork,看看fork_pre的调用场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void HookContext::nativeForkSystemServer_pre() {
    fork_pre();
    flags[SERVER_SPECIALIZE] = true;
    if (pid == 0) {
        ZLOGV("pre  forkSystemServer\n");
        run_modules_pre(remote_get_info(1000, "system_server", &info));
        close_fds();
        android_logging();
    }
}

void HookContext::nativeForkAndSpecialize_pre() {
    fork_pre();
    flags[FORK_AND_SPECIALIZE] = true;
    if (pid == 0) {
        nativeSpecializeAppProcess_pre();
    }
}

梳理下流程,根据Android应用启动流程,zygote在接收到启动新进程的socket通信请求后会调用nativeForkAndSpecialize从而调用fork启动新进程,而被hook之后先调用了nativeForkAndSpecialize_pre,在这个函数中做了fork,再调用nativeForkAndSpecialize,也就是说fork被提前了

这里存在一个问题,nativeForkAndSpecialize本身调用的fork会发生什么呢?看看上面的fork函数

1
(g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();

hook后的fork会先判断g_ctx->pid的值是否为0,若为0表示它没做过fork,进而调用原本的fork函数,若不为0就直接返回pid,这个是子进程的pid值,说明nativeForkAndSpecialize的第二次fork不会调用真正的fork函数

提前fork的作用是什么呢?看下nativeForkAndSpecialize_pre

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void HookContext::nativeSpecializeAppProcess_pre() {
    g_ctx = this;
    state[APP_SPECIALIZE] = true;
    process = env->GetStringUTFChars(args->nice_name, nullptr);
    if (state[FORK_AND_SPECIALIZE]) {
        ZLOGV("pre  forkAndSpecialize [%s]\n", process);
    } else {
        ZLOGV("pre  specialize [%s]\n", process);
    }

    vector<int> module_fds;
    int fd = remote_get_info(args->uid, process, &flags, module_fds);
    if ((flags & UNMOUNT_MASK) == UNMOUNT_MASK) {
        ZLOGI("[%s] is on the denylist\n", process);
        state[DO_UNMOUNT] = true;
    } else if (fd >= 0) {
        // 加载模块
        run_modules_pre(module_fds);
    }
    close(fd);

    close_fds();
    android_logging();
}

它调用的nativeSpecializeAppProcess_pre会做zygisk模块的加载,也就是说zygisk为了让fork后的进程加载模块,就得提前fork,这样还在它自己的代码空间,它可以根据denylist决定是否加载模块

zygisk这种在fork后加载模块的方式使得它可以在不重启的情况下更新lsposed代码

fork_post没有特殊的处理逻辑,zygisk环境清理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void HookContext::fork_post() {
    sigmask(SIG_UNBLOCK, SIGCHLD);
    g_ctx = nullptr;
    unload_zygisk();
}

void HookContext::unload_zygisk() {
    if (state[CAN_DLCLOSE]) {
        // Do NOT call the destructor
        operator delete(jni_method_map);
        // Directly unmap the whole memory block
        jni_hook::memory_block::release();

        // Strip out all API function pointers
        for (auto &m : modules) {
            memset(&m.api, 0, sizeof(m.api));
        }

        new_daemon_thread(reinterpret_cast<thread_entry>(&dlclose), self_handle);
    }
}

相关内容