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) 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..c65798b 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -5,99 +5,175 @@ #include #include "elf_util.h" +#include "logging.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() { + if (get_realpath_sym) return get_realpath_sym(this); + + return ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str(); + } + + inline const char *get_name() { + if (get_soname_sym) return get_soname_sym(this); + + return ((std::string *) ((uintptr_t) this + solist_realpath_offset - sizeof(void *)))->c_str(); + } + + 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) { + auto *addr = reinterpret_cast(linker.getSymbAddress(name)); + + return addr == NULL ? NULL : *addr; + } + + static void DropSoPath(const char* target_path) { + if (solist == NULL && !Initialize()) { + 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)) { + 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; + } + } - inline const char *get_path() { - return get_realpath_sym ? get_realpath_sym(this) : ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str(); - } + static bool Initialize() { + SandHook::ElfImg linker("/linker"); + if (!ProtectedDataGuard::setup(linker)) return false; - inline const char *get_name() { - return get_soname_sym ? get_soname_sym(this) : *((const char **) ((uintptr_t) this + solist_realpath_offset - sizeof(void *))); - } + /* 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. - void nullify_name() { - const char** name = (const char**)get_soname_sym(this); + See #63 for more information. + */ - static const char* empty_string = ""; - *name = reinterpret_cast(&empty_string); - } + std::string_view solist_sym_name = linker.findSymbolNameByPrefix("__dl__ZL6solist"); + if (solist_sym_name.empty()) return false; - void nullify_path() { - const char** name = (const char**)get_realpath_sym(this); + /* INFO: The size isn't a magic number, it's the size for the string: .llvm.7690929523238822858 */ + char llvm_sufix[25 + 1]; - static const char* empty_string = ""; - *name = reinterpret_cast(&empty_string); - } - }; - - static SoInfo *solist = nullptr; - static SoInfo *somain = nullptr; - - template - inline T *getStaticPointer(const SandHook::ElfImg &linker, const char* name) - { - auto *addr = reinterpret_cast(linker.getSymbAddress(name)); - return addr == nullptr ? nullptr : *addr; + 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 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); - } - } + solist = getStaticPointer(linker, solist_sym_name.data()); + if (solist == NULL) return false; - 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; - } - } + 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; + + 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")); + + 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; + } } - static bool Initialize() { - SandHook::ElfImg linker("/linker"); - solist = getStaticPointer(linker, "__dl__ZL6solist"); - somain = getStaticPointer(linker, "__dl__ZL6somain"); - - 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"); - - 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; - } - } - - return (SoInfo::get_realpath_sym != nullptr && SoInfo::get_soname_sym != nullptr); - } - - return false; - } -} \ No newline at end of file + return (SoInfo::get_realpath_sym != NULL && SoInfo::get_soname_sym != NULL); + } +} 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()) {