android|Magisk注入Zygisk的过程

admin 2024年5月17日21:45:26评论22 views字数 14325阅读47分45秒阅读模式

magisk 版本:https://github.com/topjohnwu/Magisk/tree/366dd524197d207be76322cb4f45044f9b466e93

Magisk开启zygisk 最终会进入mount_zygisk

0x1 mount_zygisk
#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 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);                                                                         
    close(src);                                                                         
    clone_attr("/system/bin/app_process" #bit, zbin.data());                            
    bind_mount("zygisk", zbin.data(), "/system/bin/app_process" #bit);                  
}

主要做了以下工作

  • 保存应位数的/system/bin/app_process文件 ("/system/bin/app_process" #bit)
  • 将 /sbin/magisk(32/64) 复制到 /sbin/.magisk/zygisk/app_process(32/64) (android 11 以下是 /sbin,11之上在/dev下随机创建一个文件夹)
  • 将系统 /system/bin/app_process(32/64) 的属性复制给/sbin/.magisk/zygisk/app_process(32/64)
  • 最后将 /sbin/.magisk/zygisk/app_process(32/64) 挂载到 /system/bin/app_process(32/64)
  • 执行 /system/bin/app_process(32/64) 就是 执行 /sbin/.magisk/zygisk/app_process(32/64)

最终/system/bin 下的 app_process 就变成了 magisk ,而原先的 app_process 的 fd 被 magiskd持有。执行 app_process 的时候就是执行了 magisk 。

0x2 magisk 的 main
// native/src/core/applets.cpp
int main(int argc, char *argv[]) {
    if (argc < 1)
        return 1;

enable_selinux();
cmdline_logging();
init_argv0(argc, argv);

string_view argv0 = basename(argv[0]);

// app_process is actually not an applet
if (argv0.starts_with("app_process")) {
return app_process_main(argc, argv); // 入口
}
// ...
return 1;
}

2-1 app_process_main
// Magisk/native/src/zygisk/main.cpp
// Entrypoint for app_process overlay
int app_process_main(int argc, char *argv[]) {
    android_logging();
    char buf[PATH_MAX];

//...
if (int socket = zygisk_request(ZygiskRequest::SETUP); socket >= 0) {
// 和 magisk 进行通信 通信成功
do {
if (read_int(socket) != 0)
break;

// Send over zygisk loader
// zygisk_ld 在 native/out/generated/arm64-v8a_binaries.h
write_int(socket, sizeof(zygisk_ld));
xwrite(socket, zygisk_ld, sizeof(zygisk_ld)); // zygisk_ld 来自python 脚本组装的, 保存内容是 libzygisk-ld.so -》 zygisk/loader.c -> zygisk_inject_entry 注入入口

int app_proc_fd = recv_fd(socket); // 接收 系统的/system/bin/app_process(32/64) 的文件符
if (app_proc_fd < 0)
break;

string tmp = read_string(socket); // 获取 MAGISKTMP 路径
if (char *ld = getenv("LD_PRELOAD")) {
string env = ld;
env += ':';
env += HIJACK_BIN;
setenv("LD_PRELOAD", env.data(), 1); // 设置 LD_PRELOAD 是已经被magisk接收并挂载到HIJACK_BIN上的 zygisk_ld.so
} else {
setenv("LD_PRELOAD", HIJACK_BIN, 1); // 设置 LD_PRELOAD 预加载so 启动进程前设置LD_PRELOAD变量(https://blog.csdn.net/whatday/article/details/108890018)
}
setenv(MAGISKTMP_ENV, tmp.data(), 1);

close(socket);

// 执行一个新程序,同时确保在执行过程中关闭了不需要的文件描述符(FD_CLOEXEC)
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
fexecve(app_proc_fd, argv, environ); // environ 是全局变量 设置了LD_PRELOAD 会预先脚在 环境变量设置的so
} 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;
}

2-2 zygisk 的处理函数zygisk_handler(client, &cred)

zygisk_handler 有多个信号处理对应的函数

void zygisk_handler(int client, const sock_cred *cred) {
    int code = read_int(client);
    char buf[256];
    switch (code) {
    case ZygiskRequest::SETUP:
        setup_files(client, cred); //  处理 SETUP 信号
        break;
    case ZygiskRequest::PASSTHROUGH:
        magiskd_passthrough(client);
        break;
    case ZygiskRequest::GET_INFO:
        get_process_info(client, cred);
        break;
    case ZygiskRequest::GET_LOG_PIPE:
        send_log_pipe(client);
        break;
    case ZygiskRequest::CONNECT_COMPANION:
        if (get_exe(cred->pid, buf, sizeof(buf))) {
            connect_companion(client, str_ends(buf, "64"));
        } else {
            LOGW("zygisk: remote process %d probably died, abortn", cred->pid);
        }
        break;
    case ZygiskRequest::GET_MODDIR:
        get_moddir(client);
        break;
    default:
        // Unknown code
        break;
    }
    close(client);
}
2-3 setup_files
// #define HIJACK_BIN64   "/system/bin/appwidget"
// #define HIJACK_BIN32   "/system/bin/bu"

// #define SEPOL_FILE_TYPE "magisk_file"
// native/src/zygisk/entry.cpp
static void setup_files(int client, const sock_cred *cred) {
LOGD("zygisk: setup files for pid=[%d]n", cred->pid);

// ...
// Hijack some binary in /system/bin to host loader
const char *hbin;
string mbin;
int app_fd;
bool is_64_bit = str_ends(buf, "64");
if (is_64_bit) {
hbin = HIJACK_BIN64;
mbin = MAGISKTMP + "/" ZYGISKBIN "/loader64.so";
app_fd = app_process_64;
} else {
hbin = HIJACK_BIN32;
mbin = MAGISKTMP + "/" ZYGISKBIN "/loader32.so";
app_fd = app_process_32;
}

// ...

// Ack
write_int(client, 0); // 写入0 继续执行

// Receive and bind mount loader // 接受传过来的二进制(libzygisk-ld.so)
int ld_fd = xopen(mbin.data(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0755);
string ld_data = read_string(client); // 先读取大小再读取内容
xwrite(ld_fd, ld_data.data(), ld_data.size()); // 写入mbin文件(/sbin/.magisk/zygisk/loader64.so)内
close(ld_fd);
// 设置文件的安全上下文(SELinux 上下文)
setfilecon(mbin.data(), "u:object_r:" SEPOL_FILE_TYPE ":s0");
xmount(mbin.data(), hbin, nullptr, MS_BIND, nullptr); // mbin(/sbin/.magisk/zygisk/loader64.so) 文件挂在到hbin(/system/bin/appwidget)文件上

send_fd(client, app_fd); // 发送系统原来的 /system/bin/app_process(32/64) 文件符号
write_string(client, MAGISKTMP); // 写入 路径"/sbin"
}

完成在/system/bin/app_process zygote 上注入在 libzygisk-ld.so

0x3 注入zygisk流程图
android|Magisk注入Zygisk的过程
zygisk注入

接下来分析libzygisk-ld.so做了些什么

0x4 llibzygisk-ld.so 的初始化函数

根据编译文件定位文件loader.c

LOCAL_MODULE := zygisk-ld
LOCAL_SRC_FILES := zygisk/loader.c
// native/src/zygisk/loader.c
#if defined(__LP64__)
// Use symlink to workaround linker bug on old broken Android
// https://issuetracker.google.com/issues/36914295
#define SECOND_STAGE_PATH "/system/bin/app_process"
#else
#define SECOND_STAGE_PATH "/system/bin/app_process32"
#endif

__attribute__((constructor))
static void zygisk_loader() {
android_dlextinfo info = {
.flags = ANDROID_DLEXT_FORCE_LOAD
};
// Android 5.x doesn't support ANDROID_DLEXT_FORCE_LOAD
void *handle =
android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info) ?:
dlopen(SECOND_STAGE_PATH, RTLD_LAZY);
if (handle) {
void(*entry)(void*) = dlsym(handle, "zygisk_inject_entry"); // 找到符号zygisk_inject_entry
if (entry) {
entry(handle); // 执行 zygisk_inject_entry 方法
}
}
}

加载/system/bin/app_process(已经被替换为magisk)找到符号zygisk_inject_entr并执行。

4-1 zygisk_inject_entr
// native/src/zygisk/entry.cpp
extern "C" void zygisk_inject_entry(void *handle) {  // 注入入口
    zygisk_logging();
    ZLOGD("load successn");

// 如果包含多个路径,代码会截取第一个路径,并将其设置回 LD_PRELOAD 环境变量中。如果只有一个路径或没有路径,它会将 LD_PRELOAD 环境变量删除。
char *ld = getenv("LD_PRELOAD");
if (char *c = strrchr(ld, ':')) {
*c = '&#x0;';
setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD
} else {
unsetenv("LD_PRELOAD");
}

MAGISKTMP = getenv(MAGISKTMP_ENV);
self_handle = handle;

unsetenv(MAGISKTMP_ENV);
sanitize_environ(); // 让环境变量对齐,避免检测到被删除的环境变量留下的空白
hook_functions(); // 核心函数
new_daemon_thread(&unload_first_stage, nullptr);
}

4-2 hook_functions

这部分才是 zygisk 的核心 plt hook fork unshare 等函数 androidSetCreateThreadFunc (重点函数)

// native/src/zygisk/hook.cpp
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
// template<class T>
// static inline void default_new(T *&p) { p = new T(); }
// template<class T>
// static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }

void hook_functions() {
default_new(plt_hook_list);
default_new(jni_hook_list);
default_new(jni_method_map);

ino_t android_runtime_inode = 0;
dev_t android_runtime_dev = 0;
for (auto &map : lsplt::MapInfo::Scan()) {
if (map.path.ends_with("libandroid_runtime.so")) {
android_runtime_inode = map.inode;
android_runtime_dev = map.dev;
break;
}
}

PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc);
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
hook_commit();
// Remove unhooked methods
plt_hook_list->erase(
std::remove_if(plt_hook_list->begin(), plt_hook_list->end(),
[](auto &t) { return *std::get<3>(t) == nullptr;}),
plt_hook_list->end());
}

首先看几个定义的宏,最终通过lsplt::RegisterHook进行hook,发现 hook_register symbol 和 new_func 是拼接的,如androidSetCreateThreadFunc 该位置是

(void **) &old_androidSetCreateThreadFunc

(void*)new_androidSetCreateThreadFunc

// native/src/zygisk/hook.cpp
static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {
    if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {
        ZLOGE("Failed to register plt_hook "%s"n", symbol);
        return;
    }
    plt_hook_list->emplace_back(dev, inode, symbol, old_func);
}

#define PLT_HOOK_REGISTER_SYM(DEV, INODE, SYM, NAME)
hook_register(DEV, INODE, SYM, (void*) new_##NAME, (void **) &old_##NAME)

#define PLT_HOOK_REGISTER(DEV, INODE, NAME)
PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME)

4-3 跟踪androidSetCreateThreadFunc

定位函数指针定义的位置,找到了一个DCL_HOOK_FUNC是用于声明 hook 函数和备份函数的。

// native/src/zygisk/hook.cpp
// Current context
HookContext *g_ctx;
const JNINativeInterface *old_functions = nullptr;
JNINativeInterface *new_functions = nullptr;

} // namespace
#define DCL_HOOK_FUNC(ret, func, ...)
ret (*old_##func)(__VA_ARGS__);
ret new_##func(__VA_ARGS__)

jint env_RegisterNatives(
JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods)
{
auto className = get_class_name(env, clazz);
ZLOGV("JNIEnv->RegisterNatives [%s]n", className.data());
// 调用hookAndSaveJNIMethods
auto newMethods = hookAndSaveJNIMethods(className.data(), methods, numMethods);
// 调用原来的jni方法 有newMethods 就使用newMethods(替换后的函数)
return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods);
}

DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void *func) {
//new_androidSetCreateThreadFunc 函数体
ZLOGD("androidSetCreateThreadFuncn");
using method_sig = jint(*)(JavaVM **, jsize, jsize *);
do {
auto get_created_vms = reinterpret_cast<method_sig>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); // RTLD_DEFAULT 是一个特殊的句柄,用于指示 dlsym 在所有已加载的共享库中搜索符号
if (!get_created_vms) {
for (auto &map: lsplt::MapInfo::Scan()) {
if (!map.path.ends_with("/libnativehelper.so")) continue;
void *h = dlopen(map.path.data(), RTLD_LAZY);
if (!h) {
LOGW("cannot dlopen libnativehelper.so: %sn", dlerror());
break;
}
// 从 libnativehelper.so 获取JNI_GetCreatedJavaVMs 方法
get_created_vms = reinterpret_cast<method_sig>(dlsym(h, "JNI_GetCreatedJavaVMs"));
dlclose(h);
break;
}
if (!get_created_vms) {
LOGW("JNI_GetCreatedJavaVMs not foundn");
break;
}
}
JavaVM *vm = nullptr;
jsize num = 0;
jint res = get_created_vms(&vm, 1, &num);
if (res != JNI_OK || vm == nullptr) break;
JNIEnv *env = nullptr;
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (res != JNI_OK || env == nullptr) break;
default_new(new_functions); // new一个JNINativeInterface空函数
memcpy(new_functions, env->functions, sizeof(*new_functions)); // 复制env->functions
new_functions->RegisterNatives = &env_RegisterNatives; // 替换RegisterNatives方法

// Replace the function table in JNIEnv to hook RegisterNatives
old_functions = env->functions; // 将原来的jni函数进行保存
env->functions = new_functions; // 替换env->functions 修改了RegisterNatives
} while (false);
old_androidSetCreateThreadFunc(func); // 执行原来的 androidSetCreateThreadFunc 函数
}

  • 当系统调用androidSetCreateThreadFunc方法时先替换jni中的RegisterNatives方法再执行原来的androidSetCreateThreadFunc方法

  • 执行当jni执行RegisterNatives会先执行hookAndSaveJNIMethods再执行原的RegisterNatives

  • 为什么选择hookRegisterNatives,是因为zyogte 启动过程过程中会调用androidSetCreateThreadFunc->RegisterNatives,时机比较早

4-4 hookAndSaveJNIMethods
  • 只有 className 是 com/android/internal/os/Zygote才进行hook
// native/src/zygisk/jni_hooks.hpp
unique_ptr<JNINativeMethod[]> hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {
    unique_ptr<JNINativeMethod[]> newMethods;
    int clz_id = -1;
    int hook_cnt = 0;
    do {
        if (className == "com/android/internal/os/Zygote"sv) {
            // "com/android/internal/os/Zygote" 是一个 Android 系统中的类,它是 Android 启动过程中的第一个特定于 Android 的进程,它负责预加载所有系统资源和类,并为每个应用程序创建新的进程
            clz_id = 0;
            hook_cnt = 3;
            break;
        }
    } while (false);
    if (hook_cnt) {
        newMethods = make_unique<JNINativeMethod[]>(numMethods);
        memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);
    }
    auto &class_map = (*jni_method_map)[className]; // 返回这个新创建的 tree_map 的引用
    for (int i = 0; i < numMethods; ++i) {
        if (hook_cnt && clz_id == 0) {// 重点hook的三个函数
            HOOK_JNI(nativeForkAndSpecialize)
            HOOK_JNI(nativeSpecializeAppProcess)
            HOOK_JNI(nativeForkSystemServer)
        }
        class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;
    }
    return newMethods;
}
4-4 HOOK_JNI
// 用zygisk的方法(method##_methods[j])替换, 之后执行的方法就是zygisk中定义的函数
// method##_orig = methods[i].fnPtr;  保存原来的函数指针
// native/src/zygisk/hook.cpp  
#define HOOK_JNI(method)                                                                     
if (methods[i].name == #method##sv) {                                                        
    int j = 0;                                                                               
    for (; j < method##_methods_num; ++j) {                                                  
        if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) {              
            jni_hook_list->try_emplace(className).first->second.push_back(methods[i]);       
            method##_orig = methods[i].fnPtr;                                                
            newMethods[i] = method##_methods[j];                                             
            ZLOGI("replaced %s#" #method "n", className);                                   
            --hook_cnt;                                                                      
            break;                                                                           
        }                                                                                    
    }                                                                                        
    if (j == method##_methods_num) {                                                         
        ZLOGE("unknown signature of %s#" #method ": %sn", className, methods[i].signature); 
    }                                                                                        
    continue;                                                                                
}

三个函数的逻辑基本一致,这里挑选一个函数进行跟踪

nativeForkSystemServer

// native/src/zygisk/jni_hooks.hpp
// 收集了android不同版本同一个函数不同的签名 比如 nativeForkSystemServer
// newMethods[i] = method##_methods[j]; => newMethods[i] = nativeForkSystemServer_methods[j];   替换函数
const JNINativeMethod nativeForkSystemServer_methods[] = {
    {
        "nativeForkSystemServer",
        "(II[II[[IJJ)I",
        (void *) &nativeForkSystemServer_l
    },
    {
        "nativeForkSystemServer",
        "(II[IIII[[IJJ)I",
        (void *) &nativeForkSystemServer_samsung_q
    },
};
constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods);

void *nativeForkSystemServer_orig = nullptr;
[[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ctx.nativeForkSystemServer_pre(); // nativeForkSystemServer 执行前
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
); // 执行原函数
ctx.nativeForkSystemServer_post();// nativeForkSystemServer 执行后
return ctx.pid;
}

nativeForkSystemServer_pre

void HookContext::nativeForkSystemServer_pre() {
    ZLOGV("pre  forkSystemServern");
    flags[SERVER_FORK_AND_SPECIALIZE] = true;

fork_pre();
if (pid != 0)
return;

vector<int> module_fds;
int fd = remote_get_info(1000, "system_server", &info_flags, module_fds);
if (fd >= 0) {
if (module_fds.empty()) {
write_int(fd, 0);
} else {
run_modules_pre(module_fds); // 运行modules插入的hook代码

// Send the bitset of module status back to magiskd from system_server
dynamic_bitset bits;
for (const auto &m : modules)
bits[m.getId()] = true;
write_int(fd, static_cast<int>(bits.slots()));
for (int i = 0; i < bits.slots(); ++i) {
auto l = bits.get_slot(i);
xwrite(fd, &l, sizeof(l));
}
}
close(fd);
}

sanitize_fds();
}
void HookContext::nativeForkSystemServer_post() {
if (pid == 0) {
ZLOGV("post forkSystemServern");
run_modules_post();
}
fork_post();
}

  • Zygisk 加载是通过替换 app_process ,修改 LD_PRELOAD ,再执行原 app_process 实现
  • LD_PRELOAD 执行的so,hook三个和创建进程开辟进程空间有关系的关键函数

参考文章:

https://gist.github.com/5ec1cff/bfe06429f5bf1da262c40d0145e9f190#file-zygisk-md

原文始发于微信公众号(逆向与采集):android|Magisk注入Zygisk的过程

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月17日21:45:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   android|Magisk注入Zygisk的过程http://cn-sec.com/archives/2048989.html

发表评论

匿名网友 填写信息