From 4625587ea9ff6d5af6c4212e8ad30aa8d67e6eab Mon Sep 17 00:00:00 2001 From: ThePedroo Date: Mon, 31 Mar 2025 19:30:21 -0300 Subject: [PATCH] improve: ELF utils and SoList code This commit improves the code related to ELF and SoList, porting them to C. --- loader/src/common/elf_util.c | 396 +++++++++++++++++++++++++++++++++ loader/src/common/elf_util.cpp | 263 ---------------------- loader/src/include/elf_util.h | 184 +++++---------- loader/src/include/solist.h | 66 ++++++ loader/src/include/solist.hpp | 211 ------------------ loader/src/injector/hook.cpp | 11 +- loader/src/injector/solist.c | 236 ++++++++++++++++++++ 7 files changed, 759 insertions(+), 608 deletions(-) create mode 100644 loader/src/common/elf_util.c delete mode 100644 loader/src/common/elf_util.cpp create mode 100644 loader/src/include/solist.h delete mode 100644 loader/src/include/solist.hpp create mode 100644 loader/src/injector/solist.c diff --git a/loader/src/common/elf_util.c b/loader/src/common/elf_util.c new file mode 100644 index 0000000..bfe89ba --- /dev/null +++ b/loader/src/common/elf_util.c @@ -0,0 +1,396 @@ +/* INFO: This file is written in C99 */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "logging.h" + +#include "elf_util.h" + +#define SHT_GNU_HASH 0x6ffffff6 + +uint32_t ElfHash(const char *name) { + uint32_t h = 0, g = 0; + + while (*name) { + h = (h << 4) + (unsigned char)*name++; + g = h & 0xf0000000; + + if (g) { + h ^= g >> 24; + } + + h &= ~g; + } + + return h; +} + +uint32_t GnuHash(const char *name) { + uint32_t h = 5381; + + while (*name) { + h = (h << 5) + h + (unsigned char)(*name++); + } + + return h; +} + +ElfW(Shdr) *offsetOf_Shdr(ElfW(Ehdr) * head, ElfW(Off) off) { + return (ElfW(Shdr) *)(((uintptr_t)head) + off); +} + +char *offsetOf_char(ElfW(Ehdr) * head, ElfW(Off) off) { + return (char *)(((uintptr_t)head) + off); +} + +ElfW(Sym) *offsetOf_Sym(ElfW(Ehdr) * head, ElfW(Off) off) { + return (ElfW(Sym) *)(((uintptr_t)head) + off); +} + +ElfW(Word) *offsetOf_Word(ElfW(Ehdr) * head, ElfW(Off) off) { + return (ElfW(Word) *)(((uintptr_t)head) + off); +} + +int dl_cb(struct dl_phdr_info *info, size_t size, void *data) { + (void) size; + + if ((info)->dlpi_name == NULL) return 0; + + ElfImg *img = (ElfImg *)data; + + if (strstr(info->dlpi_name, img->elf)) { + img->elf = strdup(info->dlpi_name); + img->base = (void *)info->dlpi_addr; + + return 1; + } + + return 0; +} + +bool find_module_base(ElfImg *img) { + dl_iterate_phdr(dl_cb, img); + + return img->base != NULL; +} + +size_t calculate_valid_symtabs_amount(ElfImg *img) { + size_t count = 0; + + if (img->symtab_start == NULL || img->symstr_offset_for_symtab == 0) return count; + + for (ElfW(Off) i = 0; i < img->symtab_count; i++) { + unsigned int st_type = ELF_ST_TYPE(img->symtab_start[i].st_info); + + if ((st_type == STT_FUNC || st_type == STT_OBJECT) && img->symtab_start[i].st_size) + count++; + } + + return count; +} + +void ElfImg_destroy(ElfImg *img) { + if (img->elf) { + free(img->elf); + img->elf = NULL; + } + + if (img->symtabs_) { + size_t valid_symtabs_amount = calculate_valid_symtabs_amount(img); + if (valid_symtabs_amount == 0) goto finalize; + + for (size_t i = 0; i < valid_symtabs_amount; i++) { + free(img->symtabs_[i].name); + } + + free(img->symtabs_); + img->symtabs_ = NULL; + } + + if (img->header) { + munmap(img->header, img->size); + img->header = NULL; + } + + finalize: + free(img); + img = NULL; +} + +ElfImg *ElfImg_create(const char *elf) { + ElfImg *img = (ElfImg *)calloc(1, sizeof(ElfImg)); + if (!img) { + LOGE("Failed to allocate memory for ElfImg"); + + return NULL; + } + + img->bias = -4396; + img->elf = strdup(elf); + img->base = NULL; + + if (!find_module_base(img)) { + LOGE("Failed to find module base for %s", img->elf); + + ElfImg_destroy(img); + + return NULL; + } + + int fd = open(img->elf, O_RDONLY); + if (fd < 0) { + LOGE("failed to open %s", img->elf); + + ElfImg_destroy(img); + + return NULL; + } + + img->size = lseek(fd, 0, SEEK_END); + if (img->size <= 0) { + LOGE("lseek() failed for %s", img->elf); + + ElfImg_destroy(img); + + return NULL; + } + + img->header = (ElfW(Ehdr) *)mmap(NULL, img->size, PROT_READ, MAP_SHARED, fd, 0); + + close(fd); + + img->section_header = offsetOf_Shdr(img->header, img->header->e_shoff); + + uintptr_t shoff = (uintptr_t)img->section_header; + char *section_str = offsetOf_char(img->header, img->section_header[img->header->e_shstrndx].sh_offset); + + for (int i = 0; i < img->header->e_shnum; i++, shoff += img->header->e_shentsize) { + ElfW(Shdr) *section_h = (ElfW(Shdr *))shoff; + + char *sname = section_h->sh_name + section_str; + size_t entsize = section_h->sh_entsize; + + switch (section_h->sh_type) { + case SHT_DYNSYM: { + if (img->bias == -4396) { + img->dynsym = section_h; + img->dynsym_offset = section_h->sh_offset; + img->dynsym_start = offsetOf_Sym(img->header, img->dynsym_offset); + } + + break; + } + case SHT_SYMTAB: { + if (strcmp(sname, ".symtab") == 0) { + img->symtab = section_h; + img->symtab_offset = section_h->sh_offset; + img->symtab_size = section_h->sh_size; + img->symtab_count = img->symtab_size / entsize; + img->symtab_start = offsetOf_Sym(img->header, img->symtab_offset); + } + + break; + } + case SHT_STRTAB: { + if (img->bias == -4396) { + img->strtab = section_h; + img->symstr_offset = section_h->sh_offset; + img->strtab_start = offsetOf_Sym(img->header, img->symstr_offset); + } + + if (strcmp(sname, ".strtab") == 0) { + img->symstr_offset_for_symtab = section_h->sh_offset; + } + + break; + } + case SHT_PROGBITS: { + if (img->strtab == NULL || img->dynsym == NULL) + break; + + if (img->bias == -4396) { + img->bias = (off_t)section_h->sh_addr - (off_t)section_h->sh_offset; + } + + break; + } + case SHT_HASH: { + ElfW(Word) *d_un = offsetOf_Word(img->header, section_h->sh_offset); + img->nbucket_ = d_un[0]; + img->bucket_ = d_un + 2; + img->chain_ = img->bucket_ + img->nbucket_; + + break; + } + case SHT_GNU_HASH: { + ElfW(Word) *d_buf = (ElfW(Word) *)(((size_t)img->header) + section_h->sh_offset); + img->gnu_nbucket_ = d_buf[0]; + img->gnu_symndx_ = d_buf[1]; + img->gnu_bloom_size_ = d_buf[2]; + img->gnu_shift2_ = d_buf[3]; + img->gnu_bloom_filter_ = (uintptr_t *)(d_buf + 4); + img->gnu_bucket_ = (uint32_t *)(img->gnu_bloom_filter_ + img->gnu_bloom_size_); + img->gnu_chain_ = img->gnu_bucket_ + img->gnu_nbucket_ - img->gnu_symndx_; + + break; + } + } + } + + return img; +} + +ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash) { + if (img->nbucket_ == 0) + return 0; + + char *strings = (char *)img->strtab_start; + + for (size_t n = img->bucket_[hash % img->nbucket_]; n != 0; n = img->chain_[n]) { + ElfW(Sym) *sym = img->dynsym_start + n; + + if (strncmp(name, strings + sym->st_name, strlen(name)) == 0) + return sym->st_value; + } + return 0; +} + +ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) { + static size_t bloom_mask_bits = sizeof(ElfW(Addr)) * 8; + + if (img->gnu_nbucket_ == 0 || img->gnu_bloom_size_ == 0) + return 0; + + size_t bloom_word = + img->gnu_bloom_filter_[(hash / bloom_mask_bits) % img->gnu_bloom_size_]; + uintptr_t mask = 0 | (uintptr_t)1 << (hash % bloom_mask_bits) | + (uintptr_t)1 << ((hash >> img->gnu_shift2_) % bloom_mask_bits); + if ((mask & bloom_word) == mask) { + size_t sym_index = img->gnu_bucket_[hash % img->gnu_nbucket_]; + if (sym_index >= img->gnu_symndx_) { + char *strings = (char *)img->strtab_start; + do { + ElfW(Sym) *sym = img->dynsym_start + sym_index; + + if (((img->gnu_chain_[sym_index] ^ hash) >> 1) == 0 && + name == strings + sym->st_name) { + return sym->st_value; + } + } while ((img->gnu_chain_[sym_index++] & 1) == 0); + } + } + + return 0; +} + +ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) { + size_t valid_symtabs_amount = calculate_valid_symtabs_amount(img); + if (valid_symtabs_amount == 0) return 0; + + if (!img->symtabs_) { + img->symtabs_ = (struct symtabs *)calloc(1, sizeof(struct symtabs) * valid_symtabs_amount); + if (!img->symtabs_) return 0; + + + if (img->symtab_start != NULL && img->symstr_offset_for_symtab != 0) { + ElfW(Off) i = 0; + for (ElfW(Off) pos = 0; pos < img->symtab_count; pos++) { + unsigned int st_type = ELF_ST_TYPE(img->symtab_start[pos].st_info); + const char *st_name = offsetOf_char(img->header, img->symstr_offset_for_symtab + img->symtab_start[pos].st_name); + + if ((st_type == STT_FUNC || st_type == STT_OBJECT) && img->symtab_start[pos].st_size) { + img->symtabs_[i].name = strdup(st_name); + img->symtabs_[i].sym = &img->symtab_start[pos]; + + i++; + } + } + } + } + + for (size_t i = 0; i < valid_symtabs_amount; i++) { + if (strcmp(name, img->symtabs_[i].name) != 0) continue; + + return img->symtabs_[i].sym->st_value; + } + + return 0; +} + +ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *name) { + size_t valid_symtabs_amount = calculate_valid_symtabs_amount(img); + if (valid_symtabs_amount == 0) return 0; + + if (!img->symtabs_) { + img->symtabs_ = (struct symtabs *)malloc(sizeof(struct symtabs) * valid_symtabs_amount); + if (!img->symtabs_) return 0; + + if (img->symtab_start != NULL && img->symstr_offset_for_symtab != 0) { + ElfW(Off) i = 0; + for (ElfW(Off) pos = 0; pos < img->symtab_count; pos++) { + unsigned int st_type = ELF_ST_TYPE(img->symtab_start[pos].st_info); + const char *st_name = offsetOf_char(img->header, img->symstr_offset_for_symtab + img->symtab_start[pos].st_name); + + if ((st_type == STT_FUNC || st_type == STT_OBJECT) && img->symtab_start[pos].st_size) { + img->symtabs_[i].name = strdup(st_name); + img->symtabs_[i].sym = &img->symtab_start[pos]; + + i++; + } + } + } + } + + for (size_t i = 0; i < valid_symtabs_amount; i++) { + if (strlen(img->symtabs_[i].name) < strlen(name)) + continue; + + if (strncmp(img->symtabs_[i].name, name, strlen(name)) == 0) + return img->symtabs_[i].sym->st_value; + } + + return 0; +} + +ElfW(Addr) getSymbOffset(ElfImg *img, const char *name) { + ElfW(Addr) offset = GnuLookup(img, name, GnuHash(name)); + if (offset > 0) return offset; + + offset = ElfLookup(img, name, ElfHash(name)); + if (offset > 0) return offset; + + offset = LinearLookup(img, name); + if (offset > 0) return offset; + + return 0; +} + +ElfW(Addr) getSymbAddress(ElfImg *img, const char *name) { + ElfW(Addr) offset = getSymbOffset(img, name); + + if (offset < 0 || !img->base) return 0; + + return ((uintptr_t)img->base + offset - img->bias); +} + +ElfW(Addr) getSymbAddressByPrefix(ElfImg *img, const char *prefix) { + ElfW(Addr) offset = LinearLookupByPrefix(img, prefix); + + if (offset < 0 || !img->base) return 0; + + return (ElfW(Addr))((uintptr_t)img->base + offset - img->bias); +} + +void *getSymbValueByPrefix(ElfImg *img, const char *prefix) { + ElfW(Addr) address = getSymbAddressByPrefix(img, prefix); + + return address == 0 ? NULL : *((void **)address); +} diff --git a/loader/src/common/elf_util.cpp b/loader/src/common/elf_util.cpp deleted file mode 100644 index 2ce61b8..0000000 --- a/loader/src/common/elf_util.cpp +++ /dev/null @@ -1,263 +0,0 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2019 Swift Gan - * Copyright (C) 2021 LSPosed Contributors - */ -#include -#include -#include -#include -#include -#include -#include -#include "elf_util.h" - -using namespace SandHook; - -template -inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) { - return reinterpret_cast, T, T *>>( - reinterpret_cast(head) + off); -} - -ElfImg::ElfImg(std::string_view base_name) : elf(base_name) { - if (!findModuleBase()) { - base = nullptr; - return; - } - - //load elf - int fd = open(elf.data(), O_RDONLY); - if (fd < 0) { - // LOGE("failed to open %s", elf.data()); - return; - } - - size = lseek(fd, 0, SEEK_END); - if (size <= 0) { - // LOGE("lseek() failed for %s", elf.data()); - } - - header = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); - - close(fd); - - section_header = offsetOf(header, header->e_shoff); - - auto shoff = reinterpret_cast(section_header); - char *section_str = offsetOf(header, section_header[header->e_shstrndx].sh_offset); - - for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) { - auto *section_h = (ElfW(Shdr) *) shoff; - char *sname = section_h->sh_name + section_str; - auto entsize = section_h->sh_entsize; - switch (section_h->sh_type) { - case SHT_DYNSYM: { - if (bias == -4396) { - dynsym = section_h; - dynsym_offset = section_h->sh_offset; - dynsym_start = offsetOf(header, dynsym_offset); - } - break; - } - case SHT_SYMTAB: { - if (strcmp(sname, ".symtab") == 0) { - symtab = section_h; - symtab_offset = section_h->sh_offset; - symtab_size = section_h->sh_size; - symtab_count = symtab_size / entsize; - symtab_start = offsetOf(header, symtab_offset); - } - break; - } - case SHT_STRTAB: { - if (bias == -4396) { - strtab = section_h; - symstr_offset = section_h->sh_offset; - strtab_start = offsetOf(header, symstr_offset); - } - if (strcmp(sname, ".strtab") == 0) { - symstr_offset_for_symtab = section_h->sh_offset; - } - break; - } - case SHT_PROGBITS: { - if (strtab == nullptr || dynsym == nullptr) break; - if (bias == -4396) { - bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset; - } - break; - } - case SHT_HASH: { - auto *d_un = offsetOf(header, section_h->sh_offset); - nbucket_ = d_un[0]; - bucket_ = d_un + 2; - chain_ = bucket_ + nbucket_; - break; - } - case SHT_GNU_HASH: { - auto *d_buf = reinterpret_cast(((size_t) header) + - section_h->sh_offset); - gnu_nbucket_ = d_buf[0]; - gnu_symndx_ = d_buf[1]; - gnu_bloom_size_ = d_buf[2]; - gnu_shift2_ = d_buf[3]; - gnu_bloom_filter_ = reinterpret_cast(d_buf + 4); - gnu_bucket_ = reinterpret_cast(gnu_bloom_filter_ + - gnu_bloom_size_); - gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_; - break; - } - } - } -} - -ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const { - if (nbucket_ == 0) return 0; - - char *strings = (char *) strtab_start; - - for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { - auto *sym = dynsym_start + n; - if (name == strings + sym->st_name) { - return sym->st_value; - } - } - return 0; -} - -ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const { - static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8; - - if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0; - - auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_]; - uintptr_t mask = 0 - | (uintptr_t) 1 << (hash % bloom_mask_bits) - | (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits); - if ((mask & bloom_word) == mask) { - auto sym_index = gnu_bucket_[hash % gnu_nbucket_]; - if (sym_index >= gnu_symndx_) { - char *strings = (char *) strtab_start; - do { - auto *sym = dynsym_start + sym_index; - if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0 - && name == strings + sym->st_name) { - return sym->st_value; - } - } while ((gnu_chain_[sym_index++] & 1) == 0); - } - } - return 0; -} - -ElfW(Addr) ElfImg::LinearLookup(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]); - } - } - } - } - - if (auto i = symtabs_.find(name); i != symtabs_.end()) { - return i->second->st_value; - } else { - return 0; - } -} - -ElfW(Addr) 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.second->st_value; - } - } - - return 0; -} - - -ElfImg::~ElfImg() { - //open elf file local - if (buffer) { - free(buffer); - buffer = nullptr; - } - //use mmap - if (header) { - munmap(header, size); - } -} - -ElfW(Addr) ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const { - if (auto offset = GnuLookup(name, gnu_hash); offset > 0) { - // LOGD("found %s %p in %s in dynsym by gnuhash", name.data(), reinterpret_cast(offset), elf.data()); - return offset; - } else if (offset = ElfLookup(name, elf_hash); offset > 0) { - // LOGD("found %s %p in %s in dynsym by elfhash", name.data(), reinterpret_cast(offset), elf.data()); - return offset; - } else if (offset = LinearLookup(name); offset > 0) { - // LOGD("found %s %p in %s in symtab by linear lookup", name.data(), reinterpret_cast(offset), elf.data()); - return offset; - } else { - return 0; - } - -} - -bool ElfImg::findModuleBase() { - dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data) -> int { - (void) size; - - if ((info)->dlpi_name == nullptr) { - return 0; - } - - auto *self = reinterpret_cast(data); - if (strstr(info->dlpi_name, self->elf.data())) { - self->elf = info->dlpi_name; - self->base = reinterpret_cast(info->dlpi_addr); - return 1; - } - return 0; - }, this); - return base != 0; -} diff --git a/loader/src/include/elf_util.h b/loader/src/include/elf_util.h index 42a08c6..fd24ecd 100644 --- a/loader/src/include/elf_util.h +++ b/loader/src/include/elf_util.h @@ -1,152 +1,76 @@ -/* - * This file is part of LSPosed. - * - * LSPosed is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LSPosed is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LSPosed. If not, see . - * - * Copyright (C) 2019 Swift Gan - * Copyright (C) 2021 LSPosed Contributors - */ -#ifndef SANDHOOK_ELF_UTIL_H -#define SANDHOOK_ELF_UTIL_H +#ifndef ELF_UTIL_H +#define ELF_UTIL_H -#include -#include +#include +#include #include #include -#include -#include + +#define restrict /* INFO: Temporary measure */ #define SHT_GNU_HASH 0x6ffffff6 -namespace SandHook { - class ElfImg { - public: +struct symtabs { + char *name; + ElfW(Sym) *sym; +}; - ElfImg(std::string_view elf); +typedef struct { + char *elf; + void *base; + char *buffer; + off_t size; + off_t bias; + ElfW(Ehdr) *header; + ElfW(Shdr) *section_header; + ElfW(Shdr) *symtab; + ElfW(Shdr) *strtab; + ElfW(Shdr) *dynsym; + ElfW(Sym) *symtab_start; + ElfW(Sym) *dynsym_start; + ElfW(Sym) *strtab_start; + ElfW(Off) symtab_count; + ElfW(Off) symstr_offset; + ElfW(Off) symstr_offset_for_symtab; + ElfW(Off) symtab_offset; + ElfW(Off) dynsym_offset; + ElfW(Off) symtab_size; - constexpr ElfW(Addr) getSymbOffset(std::string_view name) const { - return getSymbOffset(name, GnuHash(name), ElfHash(name)); - } + uint32_t nbucket_; + uint32_t *bucket_; + uint32_t *chain_; - constexpr ElfW(Addr) getSymbAddress(std::string_view name) const { - ElfW(Addr) offset = getSymbOffset(name); - if (offset > 0 && base != nullptr) { - return static_cast((uintptr_t) base + offset - bias); - } else { - return 0; - } - } + uint32_t gnu_nbucket_; + uint32_t gnu_symndx_; + uint32_t gnu_bloom_size_; + uint32_t gnu_shift2_; + uintptr_t *gnu_bloom_filter_; + uint32_t *gnu_bucket_; + uint32_t *gnu_chain_; - constexpr ElfW(Addr) getSymbAddressByPrefix(std::string_view prefix) const { - ElfW(Addr) offset = LinearLookupByPrefix(prefix); - if (offset > 0 && base != nullptr) { - return static_cast((uintptr_t) base + offset - bias); - } else { - return 0; - } - } + struct symtabs *symtabs_; +} ElfImg; - template - constexpr T getSymbAddress(std::string_view name) const { - return reinterpret_cast(getSymbAddress(name)); - } +void ElfImg_destroy(ElfImg *img); - template - constexpr T getSymbAddressByPrefix(std::string_view prefix) const { - return reinterpret_cast(getSymbAddressByPrefix(prefix)); - } +ElfImg *ElfImg_create(const char *elf); - bool isValid() const { - return base != nullptr; - } +ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash); - const std::string name() const { - return elf; - } +ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash); - ~ElfImg(); +ElfW(Addr) LinearLookup(ElfImg *restrict img, const char *restrict name); - private: - ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const; +ElfW(Addr) LinearLookupByPrefix(ElfImg *restrict img, const char *name); - ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const; +int dl_cb(struct dl_phdr_info *info, size_t size, void *data); - ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const; +ElfW(Addr) getSymbOffset(ElfImg *img, const char *name); - ElfW(Addr) LinearLookup(std::string_view name) const; +ElfW(Addr) getSymbAddress(ElfImg *img, const char *name); - ElfW(Addr) LinearLookupByPrefix(std::string_view name) const; +ElfW(Addr) getSymbAddressByPrefix(ElfImg *img, const char *prefix); - constexpr static uint32_t ElfHash(std::string_view name); +void *getSymbValueByPrefix(ElfImg *img, const char *prefix); - constexpr static uint32_t GnuHash(std::string_view name); - - bool findModuleBase(); - - std::string elf; - void *base = nullptr; - char *buffer = nullptr; - off_t size = 0; - off_t bias = -4396; - ElfW(Ehdr) *header = nullptr; - ElfW(Shdr) *section_header = nullptr; - ElfW(Shdr) *symtab = nullptr; - ElfW(Shdr) *strtab = nullptr; - ElfW(Shdr) *dynsym = nullptr; - ElfW(Sym) *symtab_start = nullptr; - ElfW(Sym) *dynsym_start = nullptr; - ElfW(Sym) *strtab_start = nullptr; - ElfW(Off) symtab_count = 0; - ElfW(Off) symstr_offset = 0; - ElfW(Off) symstr_offset_for_symtab = 0; - ElfW(Off) symtab_offset = 0; - ElfW(Off) dynsym_offset = 0; - ElfW(Off) symtab_size = 0; - - uint32_t nbucket_{}; - uint32_t *bucket_ = nullptr; - uint32_t *chain_ = nullptr; - - uint32_t gnu_nbucket_{}; - uint32_t gnu_symndx_{}; - uint32_t gnu_bloom_size_; - uint32_t gnu_shift2_; - uintptr_t *gnu_bloom_filter_; - uint32_t *gnu_bucket_; - uint32_t *gnu_chain_; - - mutable std::unordered_map symtabs_; - }; - - constexpr uint32_t ElfImg::ElfHash(std::string_view name) { - uint32_t h = 0, g = 0; - for (unsigned char p: name) { - h = (h << 4) + p; - g = h & 0xf0000000; - h ^= g; - h ^= g >> 24; - } - return h; - } - - constexpr uint32_t ElfImg::GnuHash(std::string_view name) { - uint32_t h = 5381; - for (unsigned char p: name) { - h += (h << 5) + p; - } - return h; - } -} - -#endif //SANDHOOK_ELF_UTIL_H \ No newline at end of file +#endif /* ELF_UTIL_H */ diff --git a/loader/src/include/solist.h b/loader/src/include/solist.h new file mode 100644 index 0000000..906b56f --- /dev/null +++ b/loader/src/include/solist.h @@ -0,0 +1,66 @@ +#ifndef SOLIST_H +#define SOLIST_H + +/* INFO: Temporary */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct SoInfo SoInfo; + +struct SoInfo { + char data[0]; +}; + +#define FuncType(name) void (*name) + +struct pdg { + void *(*ctor)(); + void *(*dtor)(); +}; + +/* + INFO: When dlopen'ing a library, the system will save information of the + opened library so a structure called soinfo, which contains another + called solist, a list with the information of opened objects. + + Due to special handling in ptracer, however, it won't heave gaps in the + memory of the list since we will close there, not loading a library creating + this gap. However, the previously loaded library would remain in the solist, + requiring ReZygisk to clean those up. + + To do that, we use 2 functions: soinfo_free, and set_size, which will + zero the region size, and then remove all traces of that library (libzygisk.so) + which was previously loaded. + + SOURCES: + - https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1712 +*/ +bool solist_drop_so_path(const char *target_path); + +/* + INFO: When dlopen'ing a library, the system will increment 1 to a global + counter that tracks the amount of libraries ever loaded in that process, + the same happening in dlclose. + + This cannot directly be used to detect if ReZygisk is present, however, with + enough data about specific environments, this can be used to detect if any + other library (be it malicious or not) was loaded. To avoid future detections, + we patch that value to the original value. + + To do that, we retrieve the address of both "g_module_load_counter" and "g_module + _unload_counter" variables and force set them to the original value, based on + the modules dlopen'ed. + + SOURCES: + - https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1874 + - https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1944 + - https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#3413 +*/ +void solist_reset_counters(size_t load, size_t unload); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SOLIST_H */ diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp deleted file mode 100644 index 6835517..0000000 --- a/loader/src/include/solist.hpp +++ /dev/null @@ -1,211 +0,0 @@ -// -// Original from https://github.com/LSPosed/NativeDetector/blob/master/app/src/main/jni/solist.cpp -// -#pragma once - -#include -#include "elf_util.h" -#include "logging.h" - -namespace SoList { - class SoInfo { - public: - #ifdef __LP64__ - 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 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); - - return ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str(); - } - - 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 { - 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::*)(); - - inline static FuncType ctor = NULL; - inline static FuncType dtor = NULL; - - union MemFunc { - FuncType f; - - struct { - void *p; - std::ptrdiff_t adj; - } data; - }; - }; - - - static SoInfo *solist = NULL; - 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 - inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) { - auto *addr = reinterpret_cast(linker.getSymbAddress(name)); - - return addr == NULL ? NULL : *addr; - } - - template - inline T *getStaticPointerByPrefix(const SandHook::ElfImg &linker, const char *name) { - auto *addr = reinterpret_cast(linker.getSymbAddressByPrefix(name)); - - return addr == NULL ? NULL : *addr; - } - - static bool DropSoPath(const char* target_path) { - bool path_found = false; - if (solist == NULL && !Initialize()) { - LOGE("Failed to initialize solist"); - return path_found; - } - for (auto iter = solist; iter; iter = iter->get_next()) { - if (iter->get_path() && strstr(iter->get_path(), target_path)) { - SoList::ProtectedDataGuard guard; - LOGV("dropping solist record loaded at %s with size %zu", iter->get_path(), iter->get_size()); - if (iter->get_size() > 0) { - iter->set_size(0); - SoInfo::soinfo_free(iter); - path_found = true; - } - } - } - 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) { - LOGD("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; - 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 - this to work on all versions, we need to iterate over the loaded - symbols and find the correct ones. - - See #63 for more information. - */ - solist = getStaticPointerByPrefix(linker, "__dl__ZL6solist"); - if (solist == NULL) return false; - LOGD("found symbol solist"); - - somain = getStaticPointerByPrefix(linker, "__dl__ZL6somain"); - if (somain == NULL) return false; - LOGD("found symbol somain"); - - sonext = linker.getSymbAddressByPrefix("__dl__ZL6sonext"); - if (sonext == NULL) return false; - LOGD("found symbol sonext"); - - SoInfo *vdso = getStaticPointerByPrefix(linker, "__dl__ZL4vdso"); - 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; - LOGD("found symbol get_realpath_sym"); - - SoInfo::soinfo_free = reinterpret_cast(linker.getSymbAddressByPrefix("__dl__ZL11soinfo_freeP6soinfo")); - 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 *); - 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 true; - } -} diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index c3282c1..c3abc4c 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -21,7 +21,7 @@ #include "files.hpp" #include "misc.hpp" -#include "solist.hpp" +#include "solist.h" #include "art_method.hpp" @@ -616,7 +616,7 @@ void ZygiskContext::run_modules_post() { if (modules.size() > 0) { LOGD("modules unloaded: %zu/%zu", modules_unloaded, modules.size()); - clean_trace("/data/adb", modules.size(), modules_unloaded, true); + clean_trace("/data/adb/rezygisk", modules.size(), modules_unloaded, true); } } @@ -778,8 +778,11 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps) { LOGD("cleaning trace for path %s", path); - if (load > 0 || unload >0) SoList::ResetCounters(load, unload); - bool path_found = SoList::DropSoPath(path); + if (load > 0 || unload > 0) solist_reset_counters(load, unload); + + LOGI("Dropping solist record for %s", path); + + bool path_found = solist_drop_so_path(path); if (!path_found || !spoof_maps) return; LOGD("spoofing virtual maps for %s", path); diff --git a/loader/src/injector/solist.c b/loader/src/injector/solist.c new file mode 100644 index 0000000..b338938 --- /dev/null +++ b/loader/src/injector/solist.c @@ -0,0 +1,236 @@ +/* INFO: This file is written in C99 */ + +#include +#include +#include + +#include "elf_util.h" +#include "logging.h" + +#include "solist.h" + +#ifdef __LP64__ + size_t solist_size_offset = 0x18; + size_t solist_next_offset = 0x30; + size_t solist_realpath_offset = 0x1a8; +#else + size_t solist_size_offset = 0x90; + size_t solist_next_offset = 0xa4; + size_t solist_realpath_offset = 0x174; +#endif + +static const char *(*get_realpath_sym)(SoInfo *) = NULL; +static void (*soinfo_free)(SoInfo *) = NULL; + +static inline SoInfo *get_next(SoInfo *self) { + return *(SoInfo **)((uintptr_t)self + solist_next_offset); +} + +static inline const char *get_path(SoInfo *self) { + if (get_realpath_sym) + return (*get_realpath_sym)(self); + + return ((const char *)((uintptr_t)self + solist_realpath_offset)); +} + +static inline void set_size(SoInfo *self, size_t size) { + *(size_t *) ((uintptr_t)self + solist_size_offset) = size; +} + +static inline size_t get_size(SoInfo *self) { + return *(size_t *) ((uintptr_t)self + solist_size_offset); +} + +struct pdg ppdg; + +static bool pdg_setup(ElfImg *img) { + ppdg.ctor = (void *(*)())getSymbAddress(img, "__dl__ZN18ProtectedDataGuardC2Ev"); + ppdg.dtor = (void *(*)())getSymbAddress(img, "__dl__ZN18ProtectedDataGuardD2Ev"); + + return ppdg.ctor != NULL && ppdg.dtor != NULL; +} + +static void pdg_protect() { + if (ppdg.ctor != NULL) + (*(ppdg.ctor))(); +} + +static void pdg_unprotect() { + if (ppdg.dtor != NULL) + (*(ppdg.dtor))(); +} + +static SoInfo *solist = NULL; +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 solist_init() { + ElfImg *linker = ElfImg_create("/linker"); + if (linker == NULL) { + LOGE("Failed to load linker"); + + return false; + } + + ppdg = (struct pdg) { + .ctor = NULL, + .dtor = NULL + }; + if (!pdg_setup(linker)) { + LOGE("Failed to setup pdg"); + + ElfImg_destroy(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 + this to work on all versions, we need to iterate over the loaded + symbols and find the correct ones. + + See #63 for more information. + */ + solist = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL6solist"); + if (solist == NULL) { + LOGE("Failed to find solist __dl__ZL6solist*"); + + ElfImg_destroy(linker); + + return false; + } + + somain = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL6somain"); + LOGI("%p is somain", (void *)somain); + if (somain == NULL) { + LOGE("Failed to find somain __dl__ZL6somain*"); + + ElfImg_destroy(linker); + + return false; + } + + sonext = (SoInfo **)getSymbAddressByPrefix(linker, "__dl__ZL6sonext"); + if (sonext == NULL) { + LOGE("Failed to find sonext __dl__ZL6sonext*"); + + ElfImg_destroy(linker); + + return false; + } + + SoInfo *vdso = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL4vdso"); + if (vdso == NULL) { + LOGE("Failed to find vsdo __dl__ZL4vdso*"); + + ElfImg_destroy(linker); + + return false; + } + + get_realpath_sym = (const char *(*)(SoInfo *))getSymbAddress(linker, "__dl__ZNK6soinfo12get_realpathEv"); + if (get_realpath_sym == NULL) { + LOGE("Failed to find get_realpath __dl__ZNK6soinfo12get_realpathEv"); + + ElfImg_destroy(linker); + + return false; + } + + soinfo_free = (void (*)(SoInfo *))getSymbAddressByPrefix(linker, "__dl__ZL11soinfo_freeP6soinfo"); + if (soinfo_free == NULL) { + LOGE("Failed to find soinfo_free __dl__ZL11soinfo_freeP6soinfo*"); + + ElfImg_destroy(linker); + + return false; + } + + g_module_load_counter = (uint64_t *)getSymbAddress(linker, "__dl__ZL21g_module_load_counter"); + if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter"); + + g_module_unload_counter = (uint64_t *)getSymbAddress(linker, "__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++) { + uintptr_t possible_field = (uintptr_t)solist + i * sizeof(void *); + size_t possible_size_of_somain = *(size_t *)((uintptr_t)somain + i * sizeof(void *)); + + if (possible_size_of_somain < 0x100000 && possible_size_of_somain > 0x100) { + solist_size_offset = i * sizeof(void *); + + LOGD("solist_size_offset is %zu * %zu = %p", i, sizeof(void *), (void *)solist_size_offset); + } + + if (*(void **)possible_field == somain || (vdso != NULL && *(void **)possible_field == vdso)) { + solist_next_offset = i * sizeof(void *); + LOGD("solist_next_offset is %zu * %zu = %p", i, sizeof(void *), (void *)solist_next_offset); + + break; + } + } + + ElfImg_destroy(linker); + + return true; +} + +bool solist_drop_so_path(const char *target_path) { + if (solist == NULL && !solist_init()) { + LOGE("Failed to initialize solist"); + + return false; + } + + for (SoInfo *iter = solist; iter; iter = get_next(iter)) { + if (get_path(iter) && strstr(get_path(iter), target_path)) { + pdg_protect(); + + LOGV("dropping solist record loaded at %s with size %zu", get_path(iter), get_size(iter)); + if (get_size(iter) > 0) { + set_size(iter, 0); + soinfo_free(iter); + + pdg_unprotect(); + + return true; + } + + pdg_unprotect(); + } + } + + return false; +} + +void solist_reset_counters(size_t load, size_t unload) { + if (solist == NULL && !solist_init()) { + LOGE("Failed to initialize solist"); + + return; + } + + if (g_module_load_counter == NULL || g_module_unload_counter == NULL) { + LOGD("g_module counters not defined, skip reseting them"); + + return; + } + + uint64_t loaded_modules = *g_module_load_counter; + uint64_t 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); + } +}