From b3f214abad6e752664ceccddfbd2e1f35ee1a126 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Fri, 18 Feb 2022 12:03:08 +0800 Subject: [PATCH] Fix unit test --- test/src/main/jni/CMakeLists.txt | 2 +- test/src/main/jni/elf_util.cpp | 270 +++++++++++++++++++++++++++++++ test/src/main/jni/elf_util.h | 137 ++++++++++++++++ test/src/main/jni/logging.h | 51 ++++++ test/src/main/jni/test.cpp | 12 +- 5 files changed, 466 insertions(+), 6 deletions(-) create mode 100644 test/src/main/jni/elf_util.cpp create mode 100644 test/src/main/jni/elf_util.h create mode 100644 test/src/main/jni/logging.h diff --git a/test/src/main/jni/CMakeLists.txt b/test/src/main/jni/CMakeLists.txt index cc4d2d6..e3f19f6 100644 --- a/test/src/main/jni/CMakeLists.txt +++ b/test/src/main/jni/CMakeLists.txt @@ -4,7 +4,7 @@ project("lsplant_test") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_library(test SHARED test.cpp) +add_library(test SHARED test.cpp elf_util.cpp) find_package(dobby REQUIRED CONFIG) find_package(library REQUIRED CONFIG) target_link_libraries(test log dobby::dobby library::lsplant) diff --git a/test/src/main/jni/elf_util.cpp b/test/src/main/jni/elf_util.cpp new file mode 100644 index 0000000..903fb15 --- /dev/null +++ b/test/src/main/jni/elf_util.cpp @@ -0,0 +1,270 @@ +/* + * 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 "logging.h" +#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; + } +} + + +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; + } + +} + +constexpr inline bool contains(std::string_view a, std::string_view b) { + return a.find(b) != std::string_view::npos; +} + +bool ElfImg::findModuleBase() { + off_t load_addr; + bool found = false; + FILE *maps = fopen("/proc/self/maps", "r"); + + char *buff = nullptr; + size_t len = 0; + ssize_t nread; + + while ((nread = getline(&buff, &len, maps)) != -1) { + std::string_view line{buff, static_cast(nread)}; + + if ((contains(line, "r-xp") || contains(line, "r--p")) && contains(line, elf)) { + LOGD("found: %*s", static_cast(line.size()), line.data()); + if (auto begin = line.find_last_of(' '); begin != std::string_view::npos && + line[++begin] == '/') { + found = true; + elf = line.substr(begin); + if (elf.back() == '\n') elf.pop_back(); + LOGD("update path: %s", elf.data()); + break; + } + } + } + if (!found) { + if (buff) free(buff); + LOGE("failed to read load address for %s", elf.data()); + fclose(maps); + return false; + } + + if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) { + LOGE("failed to read load address for %s", elf.data()); + } + + if (buff) free(buff); + + fclose(maps); + + LOGD("get module base %s: %lx", elf.data(), load_addr); + + base = reinterpret_cast(load_addr); + return true; +} diff --git a/test/src/main/jni/elf_util.h b/test/src/main/jni/elf_util.h new file mode 100644 index 0000000..7baa72f --- /dev/null +++ b/test/src/main/jni/elf_util.h @@ -0,0 +1,137 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include + +#define SHT_GNU_HASH 0x6ffffff6 + +namespace SandHook { + class ElfImg { + public: + + ElfImg(std::string_view elf); + + constexpr ElfW(Addr) getSymbOffset(std::string_view name) const { + return getSymbOffset(name, GnuHash(name), ElfHash(name)); + } + + 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; + } + } + + template + requires(std::is_pointer_v) + constexpr T getSymbAddress(std::string_view name) const { + return reinterpret_cast(getSymbAddress(name)); + } + + bool isValid() const { + return base != nullptr; + } + + const std::string name() const { + return elf; + } + + ~ElfImg(); + + private: + ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const; + + ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const; + + ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const; + + ElfW(Addr) LinearLookup(std::string_view name) const; + + constexpr static uint32_t ElfHash(std::string_view name); + + 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; + 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 diff --git a/test/src/main/jni/logging.h b/test/src/main/jni/logging.h new file mode 100644 index 0000000..703d424 --- /dev/null +++ b/test/src/main/jni/logging.h @@ -0,0 +1,51 @@ +/* + * 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) 2020 EdXposed Contributors + * Copyright (C) 2021 LSPosed Contributors + */ + +#ifndef _LOGGING_H +#define _LOGGING_H + +#include + +#ifndef LOG_TAG +#define LOG_TAG "LSPlant-test" +#endif + +#ifdef LOG_DISABLED +#define LOGD(...) +#define LOGV(...) +#define LOGI(...) +#define LOGW(...) +#define LOGE(...) +#else +#ifndef NDEBUG +#define LOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__) +#define LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__) +#else +#define LOGD(...) +#define LOGV(...) +#endif +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) +#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) +#endif + +#endif // _LOGGING_H diff --git a/test/src/main/jni/test.cpp b/test/src/main/jni/test.cpp index 9cb4493..0ee426a 100644 --- a/test/src/main/jni/test.cpp +++ b/test/src/main/jni/test.cpp @@ -1,6 +1,8 @@ #include #include #include +#include "elf_util.h" +#include "logging.h" #define _uintval(p) reinterpret_cast(p) #define _ptr(p) reinterpret_cast(p) @@ -29,10 +31,6 @@ bool InlineUnhooker(void* func) { return DobbyDestroy(func) == RT_SUCCESS; } -void* ArtSymbolResolver(std::string_view symbol_name) { - return DobbySymbolResolver("libart.so", symbol_name.data()); -} - extern "C" JNIEXPORT jboolean JNICALL Java_org_lsposed_lsplant_LSPTest_initHooker(JNIEnv*, jclass) { @@ -45,10 +43,14 @@ JNI_OnLoad(JavaVM* vm, void* reserved) { if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } + SandHook::ElfImg art("libart.so"); lsplant::InitInfo initInfo{ .inline_hooker = InlineHooker, .inline_unhooker = InlineUnhooker, - .art_symbol_resolver = ArtSymbolResolver + .art_symbol_resolver = [&art](std::string_view symbol) -> void* { + auto *out = reinterpret_cast(art.getSymbAddress(symbol)); + return out; + } }; init_result = lsplant::Init(env, initInfo); return JNI_VERSION_1_6;