improve: ELF utils and SoList code

This commit improves the code related to ELF and SoList, porting them to C.
This commit is contained in:
ThePedroo
2025-03-31 19:30:21 -03:00
parent 52885faf8b
commit 4625587ea9
7 changed files with 759 additions and 608 deletions
+54 -130
View File
@@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <string_view>
#include <unordered_map>
#include <string.h>
#include <link.h>
#include <linux/elf.h>
#include <sys/types.h>
#include <link.h>
#include <string>
#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<ElfW(Addr)>((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<ElfW(Addr)>((uintptr_t) base + offset - bias);
} else {
return 0;
}
}
struct symtabs *symtabs_;
} ElfImg;
template<typename T>
constexpr T getSymbAddress(std::string_view name) const {
return reinterpret_cast<T>(getSymbAddress(name));
}
void ElfImg_destroy(ElfImg *img);
template<typename T>
constexpr T getSymbAddressByPrefix(std::string_view prefix) const {
return reinterpret_cast<T>(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<std::string_view, ElfW(Sym) *> 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
#endif /* ELF_UTIL_H */
+66
View File
@@ -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 */
-211
View File
@@ -1,211 +0,0 @@
//
// Original from https://github.com/LSPosed/NativeDetector/blob/master/app/src/main/jni/solist.cpp
//
#pragma once
#include <string>
#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<void *>(linker.getSymbAddress(
"__dl__ZN18ProtectedDataGuardC2Ev")), .adj = 0}}.f;
dtor = MemFunc{.data = {.p = reinterpret_cast<void *>(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<typename T>
inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) {
auto *addr = reinterpret_cast<T **>(linker.getSymbAddress(name));
return addr == NULL ? NULL : *addr;
}
template<typename T>
inline T *getStaticPointerByPrefix(const SandHook::ElfImg &linker, const char *name) {
auto *addr = reinterpret_cast<T **>(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<SoInfo>(linker, "__dl__ZL6solist");
if (solist == NULL) return false;
LOGD("found symbol solist");
somain = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL6somain");
if (somain == NULL) return false;
LOGD("found symbol somain");
sonext = linker.getSymbAddressByPrefix<SoInfo **>("__dl__ZL6sonext");
if (sonext == NULL) return false;
LOGD("found symbol sonext");
SoInfo *vdso = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL4vdso");
if (vdso != NULL) LOGD("found symbol vdso");
SoInfo::get_realpath_sym = reinterpret_cast<decltype(SoInfo::get_realpath_sym)>(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv"));
if (SoInfo::get_realpath_sym == NULL) return false;
LOGD("found symbol get_realpath_sym");
SoInfo::soinfo_free = reinterpret_cast<decltype(SoInfo::soinfo_free)>(linker.getSymbAddressByPrefix("__dl__ZL11soinfo_freeP6soinfo"));
if (SoInfo::soinfo_free == NULL) return false;
LOGD("found symbol soinfo_free");
g_module_load_counter = reinterpret_cast<decltype(g_module_load_counter)>(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<decltype(g_module_unload_counter)>(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;
}
}