From 9bcbec91aa60f2faa7ff6b4e96f87ad4b5f1405d Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sun, 24 Nov 2024 18:15:31 +0100 Subject: [PATCH 1/3] improve: turn on CMAKE_EXPORT_COMPILE_COMMANDS To make `clangd` work properly, one needs to make symbolic links ``` ln -sf $PWD/loader/build/tools/debug/arm64-v8a/compile_commands.json loader/build ``` --- loader/build.gradle.kts | 1 + loader/src/CMakeLists.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index 144ffba..442f3a2 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -49,6 +49,7 @@ android { externalNativeBuild.cmake { path("src/CMakeLists.txt") + buildStagingDirectory = layout.buildDirectory.get().asFile } defaultConfig { diff --git a/loader/src/CMakeLists.txt b/loader/src/CMakeLists.txt index c8dc73a..84ba7b9 100644 --- a/loader/src/CMakeLists.txt +++ b/loader/src/CMakeLists.txt @@ -3,6 +3,8 @@ project("loader") find_package(cxx REQUIRED CONFIG) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\") aux_source_directory(common COMMON_SRC_LIST) From 3d79939d7bb30841c5f85013dfab9515fd1a4145 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 29 Nov 2024 08:16:54 +0100 Subject: [PATCH 2/3] fix: linker64 static symbols lookup with suffix On Android 15+ in some devices, the symbol names of some static variables such as `solist`, has a `llvm` suffix in its exported name. Current commit handles this case and close #63 as fixed. --- loader/src/common/elf_util.cpp | 28 ++++++ loader/src/include/elf_util.h | 6 ++ loader/src/include/solist.hpp | 171 +++++++++++++++++++-------------- 3 files changed, 134 insertions(+), 71 deletions(-) diff --git a/loader/src/common/elf_util.cpp b/loader/src/common/elf_util.cpp index 4ea0732..d265b3c 100644 --- a/loader/src/common/elf_util.cpp +++ b/loader/src/common/elf_util.cpp @@ -179,6 +179,7 @@ ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const { } } } + if (auto i = symtabs_.find(name); i != symtabs_.end()) { return i->second->st_value; } else { @@ -186,6 +187,33 @@ ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const { } } +std::string_view ElfImg::LinearLookupByPrefix(std::string_view name) const { + if (symtabs_.empty()) { + symtabs_.reserve(symtab_count); + if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { + for (ElfW(Off) i = 0; i < symtab_count; i++) { + unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info); + const char *st_name = offsetOf(header, symstr_offset_for_symtab + + symtab_start[i].st_name); + if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) { + symtabs_.emplace(st_name, &symtab_start[i]); + } + } + } + } + + auto size = name.size(); + for (auto symtab : symtabs_) { + if (symtab.first.size() < size) continue; + + if (symtab.first.substr(0, size) == name) { + return symtab.first; + } + } + + return ""; +} + ElfImg::~ElfImg() { //open elf file local diff --git a/loader/src/include/elf_util.h b/loader/src/include/elf_util.h index 95559f8..1b8c0b7 100644 --- a/loader/src/include/elf_util.h +++ b/loader/src/include/elf_util.h @@ -48,6 +48,10 @@ namespace SandHook { } } + std::string_view findSymbolNameByPrefix(std::string_view prefix) const { + return LinearLookupByPrefix(prefix); + } + template constexpr T getSymbAddress(std::string_view name) const { return reinterpret_cast(getSymbAddress(name)); @@ -72,6 +76,8 @@ namespace SandHook { ElfW(Addr) LinearLookup(std::string_view name) const; + std::string_view LinearLookupByPrefix(std::string_view name) const; + constexpr static uint32_t ElfHash(std::string_view name); constexpr static uint32_t GnuHash(std::string_view name); diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp index 5feb94d..73ff917 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -6,98 +6,127 @@ #include #include "elf_util.h" -namespace SoList -{ - class SoInfo { - public: -#ifdef __LP64__ +namespace SoList { + class SoInfo { + public: + #ifdef __LP64__ inline static size_t solist_next_offset = 0x30; constexpr static size_t solist_realpath_offset = 0x1a8; -#else + #else inline static size_t solist_next_offset = 0xa4; constexpr static size_t solist_realpath_offset = 0x174; -#endif + #endif - inline static const char *(*get_realpath_sym)(SoInfo *) = nullptr; - inline static const char *(*get_soname_sym)(SoInfo *) = nullptr; + inline static const char *(*get_realpath_sym)(SoInfo *) = NULL; + inline static const char *(*get_soname_sym)(SoInfo *) = NULL; - inline SoInfo *get_next() { - return *(SoInfo **) ((uintptr_t) this + solist_next_offset); - } + inline SoInfo *get_next() { + return *(SoInfo **) ((uintptr_t) this + solist_next_offset); + } - inline const char *get_path() { - return get_realpath_sym ? get_realpath_sym(this) : ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str(); - } + inline const char *get_path() { + if (get_realpath_sym) return get_realpath_sym(this); - inline const char *get_name() { - return get_soname_sym ? get_soname_sym(this) : *((const char **) ((uintptr_t) this + solist_realpath_offset - sizeof(void *))); - } + return ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str(); + } - void nullify_name() { - const char** name = (const char**)get_soname_sym(this); + inline const char *get_name() { + if (get_soname_sym) return get_soname_sym(this); - static const char* empty_string = ""; - *name = reinterpret_cast(&empty_string); - } + return ((std::string *) ((uintptr_t) this + solist_realpath_offset - sizeof(void *)))->c_str(); + } - void nullify_path() { - const char** name = (const char**)get_realpath_sym(this); + void nullify_name() { + const char **name = (const char**)get_soname_sym(this); - static const char* empty_string = ""; - *name = reinterpret_cast(&empty_string); - } - }; + static const char *empty_string = ""; + *name = reinterpret_cast(&empty_string); + } - static SoInfo *solist = nullptr; - static SoInfo *somain = nullptr; + void nullify_path() { + const char **name = (const char**)get_realpath_sym(this); - template - inline T *getStaticPointer(const SandHook::ElfImg &linker, const char* name) - { - auto *addr = reinterpret_cast(linker.getSymbAddress(name)); - return addr == nullptr ? nullptr : *addr; + static const char *empty_string = ""; + *name = reinterpret_cast(&empty_string); + } + }; + + static SoInfo *solist = NULL; + static SoInfo *somain = NULL; + + template + inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) { + auto *addr = reinterpret_cast(linker.getSymbAddress(name)); + + return addr == NULL ? NULL : *addr; + } + + static void NullifySoName(const char* target_name) { + for (auto *iter = solist; iter; iter = iter->get_next()) { + if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { + iter->nullify_path(); + LOGI("Cleared SOList entry for %s", target_name); + } } - static void NullifySoName(const char* target_name) { - for (auto *iter = solist; iter; iter = iter->get_next()) { - if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { - iter->nullify_path(); - LOGI("Cleared SOList entry for %s", target_name); - } - } + for (auto *iter = somain; iter; iter = iter->get_next()) { + if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { + iter->nullify_path(); - for (auto *iter = somain; iter; iter = iter->get_next()) { - if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { - iter->nullify_path(); - break; - } - } + break; + } + } + } + + static bool Initialize() { + SandHook::ElfImg linker("/linker"); + + /* INFO: Since Android 15, the symbol names for the linker have a suffix, + this makes it impossible to hardcode the symbol names. To allow + this to work on all versions, we need to iterate over the loaded + symbols and find the correct ones. + + See #63 for more information. + */ + + std::string_view solist_sym_name = linker.findSymbolNameByPrefix("__dl__ZL6solist"); + if (solist_sym_name.empty()) return false; + + /* INFO: The size isn't a magic number, it's the size for the string: .llvm.7690929523238822858 */ + char llvm_sufix[25 + 1]; + + if (solist_sym_name.length() != strlen("__dl__ZL6solist")) { + strncpy(llvm_sufix, solist_sym_name.data() + strlen("__dl__ZL6solist"), sizeof(llvm_sufix)); + } else { + llvm_sufix[0] = '\0'; } - static bool Initialize() { - SandHook::ElfImg linker("/linker"); - solist = getStaticPointer(linker, "__dl__ZL6solist"); - somain = getStaticPointer(linker, "__dl__ZL6somain"); + solist = getStaticPointer(linker, solist_sym_name.data()); + if (solist == NULL) return false; - if (solist != nullptr && somain != nullptr) - { - SoInfo::get_realpath_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); - SoInfo::get_soname_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv")); - auto vsdo = getStaticPointer(linker, "__dl__ZL4vdso"); + char somain_sym_name[sizeof("__dl__ZL6somain") + sizeof(llvm_sufix)]; + snprintf(somain_sym_name, sizeof(somain_sym_name), "__dl__ZL6somain%s", llvm_sufix); - 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 != nullptr && possible_next == vsdo)) - { - SoInfo::solist_next_offset = i * sizeof(void *); - break; - } - } + char vsdo_sym_name[sizeof("__dl__ZL4vdso") + sizeof(llvm_sufix)]; + snprintf(vsdo_sym_name, sizeof(vsdo_sym_name), "__dl__ZL4vdso%s", llvm_sufix); - return (SoInfo::get_realpath_sym != nullptr && SoInfo::get_soname_sym != nullptr); - } + somain = getStaticPointer(linker, somain_sym_name); + if (somain == NULL) return false; - return false; + auto vsdo = getStaticPointer(linker, vsdo_sym_name); + + SoInfo::get_realpath_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); + SoInfo::get_soname_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv")); + + 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)) { + SoInfo::solist_next_offset = i * sizeof(void *); + + break; + } } -} \ No newline at end of file + + return (SoInfo::get_realpath_sym != NULL && SoInfo::get_soname_sym != NULL); + } +} From 6c41a8188d1911d76793c07b390d30693f7a14d7 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 29 Nov 2024 11:25:45 +0100 Subject: [PATCH 3/3] improve: drop soinfo records of loaded modules In Bionic linker, the `soinfo` structure has a field `next`, which points to the next loaded library in a linked list consisting of all loaded libraries. Hence, an injected process can easily find all loaded libraries. Previously in ReZygisk, module library records are hidden by setting the `pathname` field to be empty, which is futile but easier to detect. Current idea of dropping record can be found in the following commit: https://github.com/RikkaApps/Riru/commit/5d635e8c66a018b5086f28e083b18b0d0656022b --- loader/src/include/solist.hpp | 97 ++++++++++++++++++++++++++--------- loader/src/injector/hook.cpp | 8 +-- 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp index 73ff917..c65798b 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -5,6 +5,7 @@ #include #include "elf_util.h" +#include "logging.h" namespace SoList { class SoInfo { @@ -36,23 +37,59 @@ namespace SoList { return ((std::string *) ((uintptr_t) this + solist_realpath_offset - sizeof(void *)))->c_str(); } - void nullify_name() { - const char **name = (const char**)get_soname_sym(this); - - static const char *empty_string = ""; - *name = reinterpret_cast(&empty_string); - } - - void nullify_path() { - const char **name = (const char**)get_realpath_sym(this); - - static const char *empty_string = ""; - *name = reinterpret_cast(&empty_string); + void set_next(SoInfo *si) { + *(SoInfo **) ((uintptr_t) this + solist_next_offset) = si; } }; + class ProtectedDataGuard { + public: + ProtectedDataGuard() { + if (ctor != nullptr) + (this->*ctor)(); + } + + ~ProtectedDataGuard() { + if (dtor != nullptr) + (this->*dtor)(); + } + + static bool setup(const SandHook::ElfImg &linker) { + ctor = MemFunc{.data = {.p = reinterpret_cast(linker.getSymbAddress( + "__dl__ZN18ProtectedDataGuardC2Ev")), .adj = 0}}.f; + dtor = MemFunc{.data = {.p = reinterpret_cast(linker.getSymbAddress( + "__dl__ZN18ProtectedDataGuardD2Ev")), .adj = 0}}.f; + return ctor != nullptr && dtor != nullptr; + } + + ProtectedDataGuard(const ProtectedDataGuard &) = delete; + + void operator=(const ProtectedDataGuard &) = delete; + + private: + using FuncType = void (ProtectedDataGuard::*)(); + + static FuncType ctor; + static FuncType dtor; + + union MemFunc { + FuncType f; + + struct { + void *p; + std::ptrdiff_t adj; + } data; + }; + }; + + 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(); template inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) { @@ -61,25 +98,28 @@ namespace SoList { return addr == NULL ? NULL : *addr; } - static void NullifySoName(const char* target_name) { - for (auto *iter = solist; iter; iter = iter->get_next()) { - if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { - iter->nullify_path(); - LOGI("Cleared SOList entry for %s", target_name); - } + static void DropSoPath(const char* target_path) { + if (solist == NULL && !Initialize()) { + LOGE("Failed to initialize solist"); + return; } - - for (auto *iter = somain; iter; iter = iter->get_next()) { - if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { - iter->nullify_path(); - - break; + 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)) { + SoList::ProtectedDataGuard guard; + prev->set_next(iter->get_next()); + if (iter == *sonext) { + *sonext = prev; + } + 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; /* INFO: Since Android 15, the symbol names for the linker have a suffix, this makes it impossible to hardcode the symbol names. To allow @@ -107,13 +147,20 @@ namespace SoList { char somain_sym_name[sizeof("__dl__ZL6somain") + sizeof(llvm_sufix)]; snprintf(somain_sym_name, sizeof(somain_sym_name), "__dl__ZL6somain%s", llvm_sufix); + 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); somain = getStaticPointer(linker, somain_sym_name); if (somain == NULL) return false; - auto vsdo = getStaticPointer(linker, vsdo_sym_name); + sonext = linker.getSymbAddress(sonext_sym_name); + if (sonext == NULL) return false; + + SoInfo *vsdo = getStaticPointer(linker, vsdo_sym_name); + if (vsdo == NULL) return false; SoInfo::get_realpath_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); SoInfo::get_soname_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv")); diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 4f02f6b..a1b715c 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -582,13 +582,7 @@ void ZygiskContext::run_modules_post() { m.tryUnload(); } - // Remove from SoList to avoid detection - bool solist_res = SoList::Initialize(); - if (!solist_res) { - LOGE("Failed to initialize SoList"); - } else { - SoList::NullifySoName("jit-cache"); - } + SoList::DropSoPath("jit-cache"); // Remap as well to avoid checking of /memfd:jit-cache for (auto &info : lsplt::MapInfo::Scan()) {