From 5e43e4a71b5231cb8f822651e23228bca34ea87a Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Thu, 12 Dec 2024 15:57:41 +0100 Subject: [PATCH 1/5] improve: hook pthread_attr_setstacksize Relying on dlclose to unload libzygisk.so will block us to clean its trace in the solist. This commit allows us to unmap libzygisk.so without using dlclose. To call munmap, we use the function pthread_attr_setstacksize instead of pthread_attr_destroy, so that tail-call can still be applied here since it has the same signature as munmap. --- loader/src/injector/entry.cpp | 11 ++++++----- loader/src/injector/hook.cpp | 18 ++++++++++-------- loader/src/injector/zygisk.hpp | 5 ++--- loader/src/ptracer/ptracer.cpp | 18 ++++++++++++++++-- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp index 1cabc27..40a71b6 100644 --- a/loader/src/injector/entry.cpp +++ b/loader/src/injector/entry.cpp @@ -1,16 +1,17 @@ #include "daemon.h" #include "logging.h" #include "zygisk.hpp" -#include "module.hpp" using namespace std; -void *self_handle = nullptr; +void *start_addr = nullptr; +size_t block_size = 0; extern "C" [[gnu::visibility("default")]] -void entry(void* handle, const char* path) { +void entry(void* addr, size_t size, const char* path) { LOGI("Zygisk library injected, version %s", ZKSU_VERSION); - self_handle = handle; + start_addr = addr; + block_size = size; zygiskd::Init(path); if (!zygiskd::PingHeartbeat()) { @@ -22,6 +23,6 @@ void entry(void* handle, const char* path) { logging::setfd(zygiskd::RequestLogcatFd()); #endif - LOGI("Start hooking"); + LOGI("Start hooking, call %p", hook_functions); hook_functions(); } diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index a1b715c..16bb8d1 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -176,22 +176,24 @@ DCL_HOOK_FUNC(void, android_log_close) { // We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns, // it will return to our code which has been unmapped, causing segmentation fault. -// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start. -DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { - int res = old_pthread_attr_destroy((pthread_attr_t *)target); +// Instead, we hook `pthread_attr_setstacksize` which will be called when VM daemon threads start. +DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) { + int res = old_pthread_attr_setstacksize((pthread_attr_t *)target, size); + LOGV("Call pthread_attr_setstacksize in [tid, pid]: %d, %d", gettid(), getpid()); // Only perform unloading on the main thread if (gettid() != getpid()) return res; - LOGV("pthread_attr_destroy"); + LOGV("Clean zygisk reminders"); if (should_unmap_zygisk) { unhook_functions(); if (should_unmap_zygisk) { - // Because both `pthread_attr_destroy` and `dlclose` have the same function signature, + // Because both `pthread_attr_setstacksize` and `dlclose` have the same function signature, // we can use `musttail` to let the compiler reuse our stack frame and thus - // `dlclose` will directly return to the caller of `pthread_attr_destroy`. - [[clang::musttail]] return dlclose(self_handle); + // `dlclose` will directly return to the caller of `pthread_attr_setstacksize`. + LOGV("Unmap libzygisk.so"); + [[clang::musttail]] return munmap(start_addr, block_size); } } @@ -858,7 +860,7 @@ static void hook_unloader() { } } - PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy); + PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_setstacksize); hook_commit(); } diff --git a/loader/src/injector/zygisk.hpp b/loader/src/injector/zygisk.hpp index 5c06f8b..636367b 100644 --- a/loader/src/injector/zygisk.hpp +++ b/loader/src/injector/zygisk.hpp @@ -1,10 +1,9 @@ #pragma once -#include #include -#include -extern void *self_handle; +extern void *start_addr; +extern size_t block_size; void hook_functions(); diff --git a/loader/src/ptracer/ptracer.cpp b/loader/src/ptracer/ptracer.cpp index 9182ddd..f790f98 100644 --- a/loader/src/ptracer/ptracer.cpp +++ b/loader/src/ptracer/ptracer.cpp @@ -222,10 +222,24 @@ bool inject_on_main(int pid, const char *lib_path) { return false; } - /* call injector entry(handle, path) */ + /* record the address range of libzygisk.so */ + map = MapInfo::Scan(std::to_string(pid)); + void *start_addr = nullptr; + size_t block_size = 0; + for (auto &info : map) { + if (strstr(info.path.c_str(), "libzygisk.so")) { + void *addr = (void *)info.start; + if (start_addr == nullptr) start_addr = addr; + size_t size = info.end - info.start; + block_size += size; + LOGD("found block %s: [%p-%p] with size %zu", info.path.c_str(), addr, (void *)info.end, size); + } + } + /* call injector entry(start_addr, block_size, path) */ args.clear(); - args.push_back(remote_handle); + args.push_back((uintptr_t) start_addr); + args.push_back(block_size); str = push_string(pid, regs, zygiskd::GetTmpPath().c_str()); args.push_back((long) str); From b7138d635347f9d7848cdcd85710444b243370a2 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sun, 15 Dec 2024 15:40:40 +0100 Subject: [PATCH 2/5] add: drop soinfo for libzygisk.so 1. fix typo vsdo -> vdso and allow this symbol to be not found, which is the case for the 32bit linkers on some devices 2. use soinfo_free to fully remove the soinfo record of libzygisk.so 3. set `soinfo.size = 0` to avoid the library being unmapped while removing its soinfo record 4. add more debug logs for troubleshooting --- loader/src/include/solist.hpp | 75 +++++++++++++++++++++++++--------- loader/src/injector/entry.cpp | 3 +- loader/src/injector/hook.cpp | 10 +++-- loader/src/injector/zygisk.hpp | 2 + 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp index c65798b..77ab4a5 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -11,20 +11,27 @@ namespace SoList { class SoInfo { public: #ifdef __LP64__ - inline static size_t solist_next_offset = 0x30; + inline static size_t solist_size_offset = 0x18; + inline static size_t solist_next_offset = 0x28; constexpr static size_t solist_realpath_offset = 0x1a8; #else + inline static size_t solist_size_offset = 0x90; inline static size_t solist_next_offset = 0xa4; constexpr static size_t solist_realpath_offset = 0x174; #endif inline static const char *(*get_realpath_sym)(SoInfo *) = NULL; inline static const char *(*get_soname_sym)(SoInfo *) = NULL; + inline static void (*soinfo_free)(SoInfo *) = NULL; inline SoInfo *get_next() { return *(SoInfo **) ((uintptr_t) this + solist_next_offset); } + inline size_t get_size() { + return *(size_t *) ((uintptr_t) this + solist_size_offset); + } + inline const char *get_path() { if (get_realpath_sym) return get_realpath_sym(this); @@ -40,6 +47,10 @@ namespace SoList { void set_next(SoInfo *si) { *(SoInfo **) ((uintptr_t) this + solist_next_offset) = si; } + + void set_size(size_t size) { + *(size_t *) ((uintptr_t) this + solist_size_offset) = size; + } }; class ProtectedDataGuard { @@ -69,8 +80,8 @@ namespace SoList { private: using FuncType = void (ProtectedDataGuard::*)(); - static FuncType ctor; - static FuncType dtor; + inline static FuncType ctor = NULL; + inline static FuncType dtor = NULL; union MemFunc { FuncType f; @@ -86,8 +97,6 @@ namespace SoList { static SoInfo *solist = NULL; static SoInfo *somain = NULL; static SoInfo **sonext = NULL; - ProtectedDataGuard::FuncType ProtectedDataGuard::ctor = NULL; - ProtectedDataGuard::FuncType ProtectedDataGuard::dtor = NULL; static bool Initialize(); @@ -103,23 +112,22 @@ namespace SoList { LOGE("Failed to initialize solist"); return; } - SoInfo *prev = NULL; for (auto iter = solist; iter; iter = iter->get_next()) { - if (prev != NULL && iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_path)) { + if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_path)) { SoList::ProtectedDataGuard guard; - prev->set_next(iter->get_next()); - if (iter == *sonext) { - *sonext = prev; + LOGI("dropping solist record for %s loaded at %s with size %zu", iter->get_name(), iter->get_path(), iter->get_size()); + if (iter->get_size() > 0) { + iter->set_size(0); + SoInfo::soinfo_free(iter); } - LOGI("Dropped solist record for %s loaded at %s", iter->get_name(), iter->get_path()); } - prev = iter; } } static bool Initialize() { SandHook::ElfImg linker("/linker"); if (!ProtectedDataGuard::setup(linker)) return false; + LOGD("found symbol ProtectedDataGuard"); /* INFO: Since Android 15, the symbol names for the linker have a suffix, this makes it impossible to hardcode the symbol names. To allow @@ -131,6 +139,11 @@ namespace SoList { std::string_view solist_sym_name = linker.findSymbolNameByPrefix("__dl__ZL6solist"); if (solist_sym_name.empty()) return false; + LOGD("found symbol name %s", solist_sym_name.data()); + + std::string_view soinfo_free_name = linker.findSymbolNameByPrefix("__dl__ZL11soinfo_freeP6soinfo"); + if (soinfo_free_name.empty()) return false; + LOGD("found symbol name %s", soinfo_free_name.data()); /* INFO: The size isn't a magic number, it's the size for the string: .llvm.7690929523238822858 */ char llvm_sufix[25 + 1]; @@ -143,6 +156,7 @@ namespace SoList { solist = getStaticPointer(linker, solist_sym_name.data()); if (solist == NULL) return false; + LOGD("found symbol solist"); char somain_sym_name[sizeof("__dl__ZL6somain") + sizeof(llvm_sufix)]; snprintf(somain_sym_name, sizeof(somain_sym_name), "__dl__ZL6somain%s", llvm_sufix); @@ -150,30 +164,51 @@ namespace SoList { char sonext_sym_name[sizeof("__dl__ZL6sonext") + sizeof(llvm_sufix)]; snprintf(sonext_sym_name, sizeof(somain_sym_name), "__dl__ZL6sonext%s", llvm_sufix); - char vsdo_sym_name[sizeof("__dl__ZL4vdso") + sizeof(llvm_sufix)]; - snprintf(vsdo_sym_name, sizeof(vsdo_sym_name), "__dl__ZL4vdso%s", llvm_sufix); + char vdso_sym_name[sizeof("__dl__ZL4vdso") + sizeof(llvm_sufix)]; + snprintf(vdso_sym_name, sizeof(vdso_sym_name), "__dl__ZL4vdso%s", llvm_sufix); somain = getStaticPointer(linker, somain_sym_name); if (somain == NULL) return false; + LOGD("found symbol somain"); sonext = linker.getSymbAddress(sonext_sym_name); if (sonext == NULL) return false; + LOGD("found symbol sonext"); - SoInfo *vsdo = getStaticPointer(linker, vsdo_sym_name); - if (vsdo == NULL) return false; + SoInfo *vdso = getStaticPointer(linker, vdso_sym_name); + if (vdso != NULL) { + LOGD("found symbol vdso"); + } else { + LOGD("symbol vdso is missing"); + } SoInfo::get_realpath_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); + if (SoInfo::get_realpath_sym == NULL) return false; + LOGD("found symbol get_realpath_sym"); + SoInfo::get_soname_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv")); + if (SoInfo::get_soname_sym == NULL) return false; + LOGD("found symbol get_soname_sym"); + + SoInfo::soinfo_free = reinterpret_cast(linker.getSymbAddress(soinfo_free_name)); + if (SoInfo::soinfo_free == NULL) return false; + LOGD("found symbol soinfo_free"); + for (size_t i = 0; i < 1024 / sizeof(void *); i++) { - auto *possible_next = *(void **) ((uintptr_t) solist + i * sizeof(void *)); - if (possible_next == somain || (vsdo != NULL && possible_next == vsdo)) { + auto possible_field = (uintptr_t) solist + i * sizeof(void *); + auto possible_size_of_somain = *(size_t *)((uintptr_t) somain + i * sizeof(void *)); + if (possible_size_of_somain < 0x100000 && possible_size_of_somain > 0x100) { + SoInfo::solist_size_offset = i * sizeof(void *); + LOGD("solist_size_offset is %zu * %zu = %p", i, sizeof(void *), (void*) SoInfo::solist_size_offset); + } + if (*(void **)possible_field == somain || (vdso != NULL && *(void **)possible_field == vdso)) { SoInfo::solist_next_offset = i * sizeof(void *); - + LOGD("solist_next_offset is %zu * %zu = %p", i, sizeof(void *), (void*) SoInfo::solist_next_offset); break; } } - return (SoInfo::get_realpath_sym != NULL && SoInfo::get_soname_sym != NULL); + return true; } } diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp index 40a71b6..36409aa 100644 --- a/loader/src/injector/entry.cpp +++ b/loader/src/injector/entry.cpp @@ -23,6 +23,7 @@ void entry(void* addr, size_t size, const char* path) { logging::setfd(zygiskd::RequestLogcatFd()); #endif - LOGI("Start hooking, call %p", hook_functions); + LOGI("start plt hooking"); hook_functions(); + clean_trace(path); } diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 16bb8d1..4d4f2a8 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -185,14 +185,13 @@ DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) { if (gettid() != getpid()) return res; - LOGV("Clean zygisk reminders"); if (should_unmap_zygisk) { unhook_functions(); if (should_unmap_zygisk) { // Because both `pthread_attr_setstacksize` and `dlclose` have the same function signature, // we can use `musttail` to let the compiler reuse our stack frame and thus // `dlclose` will directly return to the caller of `pthread_attr_setstacksize`. - LOGV("Unmap libzygisk.so"); + LOGI("unmap libzygisk.so loaded at %p with size %zu", start_addr, block_size); [[clang::musttail]] return munmap(start_addr, block_size); } } @@ -584,7 +583,7 @@ void ZygiskContext::run_modules_post() { m.tryUnload(); } - SoList::DropSoPath("jit-cache"); + clean_trace("jit-cache"); // Remap as well to avoid checking of /memfd:jit-cache for (auto &info : lsplt::MapInfo::Scan()) { @@ -816,6 +815,11 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ #define PLT_HOOK_REGISTER(DEV, INODE, NAME) \ PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) +void clean_trace(const char* path) { + LOGD("clean solist trace for path %s", path); + SoList::DropSoPath(path); +} + void hook_functions() { default_new(plt_hook_list); default_new(jni_hook_list); diff --git a/loader/src/injector/zygisk.hpp b/loader/src/injector/zygisk.hpp index 636367b..f704181 100644 --- a/loader/src/injector/zygisk.hpp +++ b/loader/src/injector/zygisk.hpp @@ -7,6 +7,8 @@ extern size_t block_size; void hook_functions(); +void clean_trace(const char* path); + void revert_unmount_ksu(); void revert_unmount_magisk(); From 2814aaf67fc3993394e5b3801a4cf9a2606b63a8 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sat, 14 Dec 2024 13:11:03 +0100 Subject: [PATCH 3/5] remove: unnecessary memory mapping renaming Note that it is impossible to hide injecting trace of virtual memory maps from the hooked target process. ReZygisk will only focus on removing its trace for approcess that are not hooked by modules. --- loader/src/include/solist.hpp | 7 ++- loader/src/injector/hook.cpp | 107 +++++++++------------------------ loader/src/injector/zygisk.hpp | 2 +- 3 files changed, 36 insertions(+), 80 deletions(-) diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp index 77ab4a5..51a4f2e 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -107,10 +107,11 @@ namespace SoList { return addr == NULL ? NULL : *addr; } - static void DropSoPath(const char* target_path) { + static bool DropSoPath(const char* target_path) { + bool path_found = false; if (solist == NULL && !Initialize()) { LOGE("Failed to initialize solist"); - return; + return path_found; } for (auto iter = solist; iter; iter = iter->get_next()) { if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_path)) { @@ -119,9 +120,11 @@ namespace SoList { if (iter->get_size() > 0) { iter->set_size(0); SoInfo::soinfo_free(iter); + path_found = true; } } } + return path_found; } static bool Initialize() { diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 4d4f2a8..0c86588 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -583,80 +583,7 @@ void ZygiskContext::run_modules_post() { m.tryUnload(); } - clean_trace("jit-cache"); - - // Remap as well to avoid checking of /memfd:jit-cache - for (auto &info : lsplt::MapInfo::Scan()) { - if (strstr(info.path.c_str(), "jit-cache-zygisk")) - { - void *addr = (void *)info.start; - size_t size = info.end - info.start; - // MAP_SHARED should fix the suspicious mapping. - void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); - if (copy == MAP_FAILED) { - LOGE("Failed to mmap jit-cache-zygisk"); - continue; - } - - 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); - } - } - - // Don't know if there's a header for things like this - // so I just put it into a lambda - auto generateRandomString = [](char *str, int length) { - const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - srand(time(NULL)); - - for (int i = 0; i < length; i++) { - int key = rand() % (sizeof(charset) - 1); - str[i] = charset[key]; - } - - str[length] = '\0'; - }; - - // Randomize name of anonymous mappings - // We don't run this in the previous loop because LSPosed might also add - // mappings that are not related to /memfd:jit-zygisk-cache - // - // Since we changed to MAP_SHARED, I don't think this is still needed but let's - // leave it here just in case. - for (auto info : lsplt::MapInfo::Scan()) { - // I had some problems with info.perms & PROT_EXEC so I had to change lsplt source a bit. - // If that problem occurs here, do strchr(info.perms_str.c_str(), 'x') instead and add perms_str - // to the lsplt MapInfo struct and set it to the raw perms string in Scan(); - if (info.perms & PROT_EXEC && info.path.empty()) { - // Generate Random Name - char randomString[11]; - generateRandomString(randomString, 10); - LOGI("Randomized Memory map name: %s", randomString); - - // Memory address of random string - uintptr_t strAddr = (uintptr_t)&randomString; - - // https://lore.kernel.org/lkml/1383170047-21074-2-git-send-email-ccross@android.com/ - prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, info.start, info.end - info.start, strAddr); - } - - // Remap as MAP_SHARED - if (info.perms & PROT_EXEC && info.dev == 0 && info.path.find("anon") != std::string::npos) { - void *addr = reinterpret_cast(info.start); - size_t size = info.end - info.start; - void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -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); - } - } + clean_trace("jit-cache-zygisk", true); } /* Zygisksu changed: Load module fds */ @@ -815,9 +742,35 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ #define PLT_HOOK_REGISTER(DEV, INODE, NAME) \ PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) -void clean_trace(const char* path) { - LOGD("clean solist trace for path %s", path); - SoList::DropSoPath(path); +void clean_trace(const char* path, bool spoof_maps) { + LOGD("cleaning trace for path %s", path); + + if (!SoList::DropSoPath(path) || !spoof_maps) { + return; + } + + LOGD("spoofing virtual maps for %s", path); + // spoofing map names is futile in Android, we do it simply + // to avoid Zygisk detections based on string comparison + for (auto &info : lsplt::MapInfo::Scan()) { + if (strstr(info.path.c_str(), path)) + { + void *addr = (void *)info.start; + size_t size = info.end - info.start; + void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (copy == MAP_FAILED) { + LOGE("failed to backup block %s [%p, %p]", info.path.c_str(), addr, (void*)info.end); + continue; + } + + 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); + } + } } void hook_functions() { diff --git a/loader/src/injector/zygisk.hpp b/loader/src/injector/zygisk.hpp index f704181..744755e 100644 --- a/loader/src/injector/zygisk.hpp +++ b/loader/src/injector/zygisk.hpp @@ -7,7 +7,7 @@ extern size_t block_size; void hook_functions(); -void clean_trace(const char* path); +void clean_trace(const char* path, bool spoof_maps = false); void revert_unmount_ksu(); From 5e072bd919d6c2b38d31359762c500a05c48f87b Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sun, 15 Dec 2024 01:05:23 +0100 Subject: [PATCH 4/5] improve: cache scanned virtual maps Reading the file `/proc/self/maps` is detectable by the target process. Hence, we should cache scanned virtual maps after `libart.so` is loaded for later plt hooks in the target process. --- .gitmodules | 2 +- loader/src/external/lsplt | 2 +- loader/src/injector/hook.cpp | 41 +++++++++++++++++++++++------------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.gitmodules b/.gitmodules index 340c732..e46b92c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "loader/src/external/lsplt"] path = loader/src/external/lsplt - url = https://github.com/LSPosed/lsplt + url = https://github.com/JingMatrix/LSPlt diff --git a/loader/src/external/lsplt b/loader/src/external/lsplt index 5d2b820..9848042 160000 --- a/loader/src/external/lsplt +++ b/loader/src/external/lsplt @@ -1 +1 @@ -Subproject commit 5d2b820cf968fcd8162697d208ad406805b6db25 +Subproject commit 984804293e6f7f87604479fefd57976a2c222a89 diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 0c86588..ef0ad6a 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -122,6 +122,7 @@ struct ZygiskContext { vector> *plt_hook_list; map, StringCmp> *jni_hook_list; bool should_unmap_zygisk = false; +std::vector cached_map_infos = {}; } // namespace @@ -187,6 +188,7 @@ DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) { if (should_unmap_zygisk) { unhook_functions(); + cached_map_infos.clear(); if (should_unmap_zygisk) { // Because both `pthread_attr_setstacksize` and `dlclose` have the same function signature, // we can use `musttail` to let the compiler reuse our stack frame and thus @@ -205,6 +207,8 @@ DCL_HOOK_FUNC(char *, strdup, const char *s) { if (strcmp(s, "com.android.internal.os.ZygoteInit") == 0) { LOGV("strdup %s", s); initialize_jni_hook(); + cached_map_infos = lsplt::MapInfo::Scan(); + LOGD("cached_map_infos updated"); } return old_strdup(s); @@ -268,7 +272,7 @@ void initialize_jni_hook() { auto get_created_java_vms = reinterpret_cast( dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); if (!get_created_java_vms) { - for (auto &map: lsplt::MapInfo::Scan()) { + for (auto &map: cached_map_infos) { if (!map.path.ends_with("/libnativehelper.so")) continue; void *h = dlopen(map.path.data(), RTLD_LAZY); if (!h) { @@ -349,7 +353,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; } if (api_version >= 4) { - api->v4.pltHookCommit = lsplt::CommitHook; + api->v4.pltHookCommit = []() { return lsplt::CommitHook(cached_map_infos); }; api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) { if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr) return; @@ -383,7 +387,7 @@ void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) { void ZygiskContext::plt_hook_process_regex() { if (register_info.empty()) return; - for (auto &map : lsplt::MapInfo::Scan()) { + for (auto &map : cached_map_infos) { if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue; for (auto ®: register_info) { if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0) @@ -411,7 +415,7 @@ bool ZygiskContext::plt_hook_commit() { register_info.clear(); ignore_info.clear(); } - return lsplt::CommitHook(); + return lsplt::CommitHook(cached_map_infos); } @@ -719,8 +723,8 @@ ZygiskContext::~ZygiskContext() { } // namespace -static bool hook_commit() { - if (lsplt::CommitHook()) { +static bool hook_commit(std::vector &map_infos = cached_map_infos) { + if (lsplt::CommitHook(map_infos)) { return true; } else { LOGE("plt_hook failed"); @@ -752,23 +756,23 @@ void clean_trace(const char* path, bool spoof_maps) { LOGD("spoofing virtual maps for %s", path); // spoofing map names is futile in Android, we do it simply // to avoid Zygisk detections based on string comparison - for (auto &info : lsplt::MapInfo::Scan()) { - if (strstr(info.path.c_str(), path)) + for (auto &map : lsplt::MapInfo::Scan()) { + if (strstr(map.path.c_str(), path)) { - void *addr = (void *)info.start; - size_t size = info.end - info.start; + void *addr = (void *)map.start; + size_t size = map.end - map.start; void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); if (copy == MAP_FAILED) { - LOGE("failed to backup block %s [%p, %p]", info.path.c_str(), addr, (void*)info.end); + LOGE("failed to backup block %s [%p, %p]", map.path.c_str(), addr, (void*)map.end); continue; } - if ((info.perms & PROT_READ) == 0) { + if ((map.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); + mprotect(addr, size, map.perms); } } } @@ -783,7 +787,8 @@ void hook_functions() { // ino_t native_bridge_inode = 0; // dev_t native_bridge_dev = 0; - for (auto &map : lsplt::MapInfo::Scan()) { + cached_map_infos = lsplt::MapInfo::Scan(); + for (auto &map : cached_map_infos) { if (map.path.ends_with("libandroid_runtime.so")) { android_runtime_inode = map.inode; android_runtime_dev = map.dev; @@ -809,7 +814,7 @@ static void hook_unloader() { ino_t art_inode = 0; dev_t art_dev = 0; - for (auto &map : lsplt::MapInfo::Scan()) { + for (auto &map : cached_map_infos) { if (map.path.ends_with("/libart.so")) { art_inode = map.inode; art_dev = map.dev; @@ -817,6 +822,12 @@ static void hook_unloader() { } } + if (art_dev == 0 || art_inode == 0) { + LOGE("virtual map for libart.so is not cached"); + return; + } else { + LOGD("hook_unloader called with libart.so [%zu:%lu]", art_dev, art_inode); + } PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_setstacksize); hook_commit(); } From 0671f2e497140e40071f67e67481541def4258bb Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Mon, 16 Dec 2024 10:08:09 +0100 Subject: [PATCH 5/5] improve: reset linker module counters Two counters for module loading and unloading are introduced in the commit https://cs.android.com/android/_/android/platform/bionic/+/a2e83ab34845759f0999d0ec88f4cdf558c0a9f5. To remove linker traces of libzygisk.so and Zygisk modules, we should reset them properly. --- loader/src/include/solist.hpp | 35 +++++++++++++++++++++++++++++----- loader/src/injector/entry.cpp | 2 +- loader/src/injector/hook.cpp | 17 +++++++++++------ loader/src/injector/module.hpp | 5 +++-- loader/src/injector/zygisk.hpp | 2 +- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp index 51a4f2e..f55d4b1 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -98,6 +98,9 @@ namespace SoList { static SoInfo *somain = NULL; static SoInfo **sonext = NULL; + static uint64_t *g_module_load_counter = NULL; + static uint64_t *g_module_unload_counter = NULL; + static bool Initialize(); template @@ -127,6 +130,27 @@ namespace SoList { return path_found; } + static void ResetCounters(size_t load, size_t unload) { + if (solist == NULL && !Initialize()) { + LOGE("Failed to initialize solist"); + return; + } + if (g_module_load_counter == NULL || g_module_unload_counter == NULL) { + LOGI("g_module counters not defined, skip reseting them"); + return; + } + auto loaded_modules = *g_module_load_counter; + auto unloaded_modules = *g_module_unload_counter; + if (loaded_modules >= load) { + *g_module_load_counter = loaded_modules - load; + LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter); + } + if (unloaded_modules >= unload) { + *g_module_unload_counter = unloaded_modules - unload; + LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter); + } + } + static bool Initialize() { SandHook::ElfImg linker("/linker"); if (!ProtectedDataGuard::setup(linker)) return false; @@ -179,11 +203,7 @@ namespace SoList { LOGD("found symbol sonext"); SoInfo *vdso = getStaticPointer(linker, vdso_sym_name); - if (vdso != NULL) { - LOGD("found symbol vdso"); - } else { - LOGD("symbol vdso is missing"); - } + if (vdso != NULL) LOGD("found symbol vdso"); SoInfo::get_realpath_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); if (SoInfo::get_realpath_sym == NULL) return false; @@ -197,6 +217,11 @@ namespace SoList { if (SoInfo::soinfo_free == NULL) return false; LOGD("found symbol soinfo_free"); + g_module_load_counter = reinterpret_cast(linker.getSymbAddress("__dl__ZL21g_module_load_counter")); + if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter"); + + g_module_unload_counter = reinterpret_cast(linker.getSymbAddress("__dl__ZL23g_module_unload_counter")); + if (g_module_unload_counter != NULL) LOGD("found symbol g_module_unload_counter"); for (size_t i = 0; i < 1024 / sizeof(void *); i++) { auto possible_field = (uintptr_t) solist + i * sizeof(void *); diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp index 36409aa..a927042 100644 --- a/loader/src/injector/entry.cpp +++ b/loader/src/injector/entry.cpp @@ -25,5 +25,5 @@ void entry(void* addr, size_t size, const char* path) { LOGI("start plt hooking"); hook_functions(); - clean_trace(path); + clean_trace(path, 1, 0, false); } diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index ef0ad6a..ae41b6a 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -578,16 +578,21 @@ void ZygiskContext::run_modules_pre() { void ZygiskContext::run_modules_post() { flags[POST_SPECIALIZE] = true; + + size_t modules_unloaded = 0; for (const auto &m : modules) { if (flags[APP_SPECIALIZE]) { m.postAppSpecialize(args.app); } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { m.postServerSpecialize(args.server); } - m.tryUnload(); + if (m.tryUnload()) modules_unloaded++; } - clean_trace("jit-cache-zygisk", true); + if (modules.size() > 0) { + LOGD("modules unloaded: %zu/%zu", modules_unloaded, modules.size()); + clean_trace("jit-cache-zygisk", modules.size(), modules_unloaded, true); + } } /* Zygisksu changed: Load module fds */ @@ -746,12 +751,12 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ #define PLT_HOOK_REGISTER(DEV, INODE, NAME) \ PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) -void clean_trace(const char* path, bool spoof_maps) { +void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps) { LOGD("cleaning trace for path %s", path); - if (!SoList::DropSoPath(path) || !spoof_maps) { - return; - } + if (load > 0 || unload >0) SoList::ResetCounters(load, unload); + bool path_found = SoList::DropSoPath(path); + if (!path_found || !spoof_maps) return; LOGD("spoofing virtual maps for %s", path); // spoofing map names is futile in Android, we do it simply diff --git a/loader/src/injector/module.hpp b/loader/src/injector/module.hpp index dfbde57..24111fc 100644 --- a/loader/src/injector/module.hpp +++ b/loader/src/injector/module.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "api.hpp" @@ -209,7 +210,7 @@ case 5: \ int getModuleDir() const; void setOption(zygisk::Option opt); static uint32_t getFlags(); - void tryUnload() const { if (unload) dlclose(handle); } + bool tryUnload() const { return unload && dlclose(handle) == 0; }; void clearApi() { memset(&api, 0, sizeof(api)); } int getId() const { return id; } @@ -235,4 +236,4 @@ case 5: \ } mod; }; -} // namespace \ No newline at end of file +} // namespace diff --git a/loader/src/injector/zygisk.hpp b/loader/src/injector/zygisk.hpp index 744755e..793ba78 100644 --- a/loader/src/injector/zygisk.hpp +++ b/loader/src/injector/zygisk.hpp @@ -7,7 +7,7 @@ extern size_t block_size; void hook_functions(); -void clean_trace(const char* path, bool spoof_maps = false); +void clean_trace(const char* path, size_t load = 1, size_t unload = 0, bool spoof_maps = false); void revert_unmount_ksu();