diff --git a/.gitmodules b/.gitmodules index 598021a..2ce2626 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "module/src/main/cpp/external/libcxx"] path = module/src/main/cpp/external/libcxx url = https://github.com/topjohnwu/libcxx +[submodule "module/src/main/cpp/external/dobby"] + path = module/src/main/cpp/external/dobby + url = https://github.com/LSPosed/dobby diff --git a/module/src/main/cpp/CMakeLists.txt b/module/src/main/cpp/CMakeLists.txt index 57788c2..96940cc 100644 --- a/module/src/main/cpp/CMakeLists.txt +++ b/module/src/main/cpp/CMakeLists.txt @@ -14,6 +14,18 @@ set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") add_subdirectory(external) link_libraries(cxx) +add_library(elf_util STATIC elf_util/elf_util.cpp) +add_library(lspmparser STATIC lspmparser/lsplt.cpp) +add_library(my_logging STATIC logging/logging.cpp) + +target_include_directories(my_logging PUBLIC logging/include) +target_include_directories(elf_util PUBLIC elf_util/include) +target_include_directories(lspmparser PUBLIC lspmparser/include) + +target_link_libraries(my_logging cxx log) +target_link_libraries(elf_util cxx lspmparser my_logging) +target_link_libraries(lspmparser cxx) + # libutils stub add_library(utils SHARED binder/stub_utils.cpp) target_include_directories(utils PUBLIC binder/include) @@ -23,5 +35,5 @@ add_library(binder SHARED binder/stub_binder.cpp) target_include_directories(binder PUBLIC binder/include) target_link_libraries(binder utils) -add_library(${MODULE_NAME} SHARED example.cpp) -target_link_libraries(${MODULE_NAME} log binder utils) +add_library(${MODULE_NAME} SHARED binder_interceptor.cpp) +target_link_libraries(${MODULE_NAME} log binder utils dobby elf_util my_logging) diff --git a/module/src/main/cpp/binder_interceptor.cpp b/module/src/main/cpp/binder_interceptor.cpp new file mode 100644 index 0000000..29bcedd --- /dev/null +++ b/module/src/main/cpp/binder_interceptor.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "logging.hpp" +#include "dobby.h" +#include "elf_util.h" + +#include "hook_util/hook_helper.hpp" + +using namespace SandHook; +using namespace android; +using namespace hook_helper::literals; + +class BinderInterceptor : public BBinder { + enum { + REGISTER_INTERCEPTOR = 1, + UNREGISTER_INTERCEPTOR = 2 + }; + enum { + PRE_TRANSACT = 1, + POST_TRANSACT + }; + enum { + SKIP = 1, + CONTINUE, + OVERRIDE_REPLY, + OVERRIDE_DATA + }; + struct InterceptItem { + wp target{}; + sp interceptor; + }; + using RwLock = std::shared_mutex; + using WriteGuard = std::unique_lock; + using ReadGuard = std::shared_lock; + RwLock lock; + std::map, InterceptItem> items{}; +public: + status_t onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply, + uint32_t flags) override; + + bool handleIntercept(BBinder *thiz, uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags, status_t &result); +}; + +static sp gBinderInterceptor = nullptr; + +CREATE_MEM_HOOK_STUB_ENTRY( + "_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j", + status_t, BBinder_Transact, + (BBinder * thiz, uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags), + { + LOGD("transact: binder=%p code=%d", thiz, code); + if (IPCThreadState::self()->getCallingUid() == 0 && reply != nullptr && + thiz != gBinderInterceptor) { + if (code == 0xdeadbeef) { + LOGD("request binder interceptor"); + reply->writeStrongBinder(gBinderInterceptor); + return OK; + } + } + status_t result; + if (gBinderInterceptor->handleIntercept(thiz, code, data, reply, + flags, result)) { + LOGD("transact intercepted: binder=%p code=%d result=%d", thiz, code, result); + return result; + } + result = backup(thiz, code, data, reply, flags); + LOGD("transact: binder=%p code=%d result=%d", thiz, code, result); + return result; + }); + +status_t +BinderInterceptor::onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply, + uint32_t flags) { + if (code == REGISTER_INTERCEPTOR) { + sp target, interceptor; + if (data.readStrongBinder(&target) != OK) { + return BAD_VALUE; + } + if (!target->localBinder()) { + return BAD_VALUE; + } + if (data.readStrongBinder(&interceptor) != OK) { + return BAD_VALUE; + } + { + WriteGuard wg{lock}; + wp t = target; + auto it = items.lower_bound(t); + if (it == items.end() || it->first != t) { + it = items.emplace_hint(it, t, InterceptItem{}); + it->second.target = t; + } + // TODO: send callback to old interceptor + it->second.interceptor = interceptor; + return OK; + } + } else if (code == UNREGISTER_INTERCEPTOR) { + sp target, interceptor; + if (data.readStrongBinder(&target) != OK) { + return BAD_VALUE; + } + if (!target->localBinder()) { + return BAD_VALUE; + } + if (data.readStrongBinder(&interceptor) != OK) { + return BAD_VALUE; + } + { + WriteGuard wg{lock}; + wp t = target; + auto it = items.find(t); + if (it != items.end()) { + if (it->second.interceptor != interceptor) { + return BAD_VALUE; + } + items.erase(it); + return OK; + } + } + } + return UNKNOWN_TRANSACTION; +} + + +class HookHandler : public hook_helper::HookHandler { +public: + ElfImg img; + + explicit HookHandler(ElfInfo info) : img{std::move(info)} {} + + bool isValid() const { + return img.isValid(); + } + + void *get_symbol(const char *name) const override { + auto addr = img.getSymbAddress(name); + if (!addr) { + LOGE("%s: symbol not found", name); + } + return addr; + } + + void *get_symbol_prefix(const char *prefix) const override { + auto addr = img.getSymbPrefixFirstOffset(prefix); + if (!addr) { + LOGE("%s: prefix symbol not found", prefix); + } + return addr; + } + + void *hook(void *original, void *replacement) const override { + void *result = nullptr; + if (DobbyHook(original, (dobby_dummy_func_t) replacement, (dobby_dummy_func_t *) &result) == + 0) { + return result; + } else { + return nullptr; + } + } + + std::pair get_symbol_info(const char *name) const override { + auto p = img.getSymInfo(name); + if (!p.first) { + LOGE("%s: info not found", name); + } + return p; + } +}; + +bool +BinderInterceptor::handleIntercept(BBinder *thiz, uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags, status_t &result) { +#define CHECK(expr) ({ auto __result = (expr); if (__result != OK) { LOGE(#expr " = %d", __result); return false; } }) + sp interceptor; + { + ReadGuard rg{lock}; + wp target = wp::fromExisting(thiz); + auto it = items.find(target); + if (it == items.end()) return false; + interceptor = it->second.interceptor; + } + LOGD("intercept on binder %p code %d flags %d (reply=%s)", thiz, code, flags, + reply ? "true" : "false"); + sp target = sp::fromExisting(thiz); + Parcel tmpData, tmpReply, realData; + CHECK(tmpData.writeStrongBinder(target)); + CHECK(tmpData.writeUint32(code)); + CHECK(tmpData.writeUint32(flags)); + CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingUid())); + CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingPid())); + CHECK(tmpData.writeUint64(data.dataSize())); + CHECK(tmpData.appendFrom(&data, 0, data.dataSize())); + CHECK(interceptor->transact(PRE_TRANSACT, tmpData, &tmpReply)); + int32_t preType; + CHECK(tmpReply.readInt32(&preType)); + LOGD("pre transact type %d", preType); + if (preType == SKIP) { + return false; + } else if (preType == OVERRIDE_REPLY) { + result = tmpReply.readInt32(); + if (reply) { + size_t sz = tmpReply.readUint64(); + CHECK(reply->appendFrom(&tmpReply, tmpReply.dataPosition(), sz)); + } + return true; + } else if (preType == OVERRIDE_DATA) { + size_t sz = tmpReply.readUint64(); + CHECK(realData.appendFrom(&tmpReply, tmpReply.dataPosition(), sz)); + } else { + CHECK(realData.appendFrom(&data, 0, data.dataSize())); + } + result = BBinder_Transact.backup(thiz, code, realData, reply, flags); + + tmpData.freeData(); + tmpReply.freeData(); + + CHECK(tmpData.writeStrongBinder(target)); + CHECK(tmpData.writeUint32(code)); + CHECK(tmpData.writeUint32(flags)); + CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingUid())); + CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingPid())); + CHECK(tmpData.writeInt32(result)); + CHECK(tmpData.writeUint64(data.dataSize())); + CHECK(tmpData.appendFrom(&data, 0, data.dataSize())); + CHECK(tmpData.writeUint64(reply == nullptr ? 0 : reply->dataSize())); + LOGD("data size %zu reply size %zu", data.dataSize(), reply == nullptr ? 0 : reply->dataSize()); + if (reply) { + CHECK(tmpData.appendFrom(reply, 0, reply->dataSize())); + } + CHECK(interceptor->transact(POST_TRANSACT, tmpData, &tmpReply)); + int32_t postType; + CHECK(tmpReply.readInt32(&postType)); + LOGD("post transact type %d", postType); + if (postType == OVERRIDE_REPLY) { + result = tmpReply.readInt32(); + if (reply) { + size_t sz = tmpReply.readUint64(); + reply->freeData(); + CHECK(reply->appendFrom(&tmpReply, tmpReply.dataPosition(), sz)); + LOGD("reply size=%zu sz=%zu", reply->dataSize(), sz); + } + } + return true; +} + +bool hookBinder() { + HookHandler handler{ElfInfo::getElfInfoForName("libbinder.so")}; + if (!handler.isValid()) { + LOGE("libbinder not found!"); + return false; + } + if (!hook_helper::HookSym(handler, BBinder_Transact)) { + LOGE("hook failed!"); + return false; + } + LOGI("hook success!"); + gBinderInterceptor = sp::make(); + return true; +} + +extern "C" [[gnu::visibility("default")]] [[gnu::used]] bool entry(void *handle) { + LOGI("my handle %p", handle); + return hookBinder(); +} diff --git a/module/src/main/cpp/elf_util/elf_util.cpp b/module/src/main/cpp/elf_util/elf_util.cpp new file mode 100644 index 0000000..aee1396 --- /dev/null +++ b/module/src/main/cpp/elf_util/elf_util.cpp @@ -0,0 +1,305 @@ +/* + * 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.hpp" +#include "elf_util.h" +#include +#include "lsplt.hpp" + +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, void *base, off_t file_offset, size_t size) : elf( + base_name), base(base), size(size) { + // load elf + int fd = open(elf.data(), O_RDONLY); + if (fd < 0) { + LOGE("failed to open %s", elf.data()); + return; + } + + header = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, + file_offset)); + + 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); + dynsym_size = section_h->sh_size; + dynsym_count = dynsym_size / entsize; + } + 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; + } else if (strcmp(sname, ".dynstr") == 0) { + symstr_offset_for_dynsym = 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(Sym) *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; + } + } + return nullptr; +} + +ElfW(Sym) *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; + } + } while ((gnu_chain_[sym_index++] & 1) == 0); + } + } + return nullptr; +} + +void ElfImg::MayInitLinearMap() const { + if (symtabs_.empty()) { + 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]); + } + } + } + } +} + +const std::string ElfImg::findSymbolNameForAddr(ElfW(Addr) addr) const { + if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { + auto addr_off = (ElfW(Addr)) (addr + bias - (uintptr_t) base); + 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) { + auto off = symtab_start[i].st_value; + auto len = symtab_start[i].st_size; + if (off <= addr_off && addr_off < off + len) { + LOGD("found in symtab sym %p name %s", off, + st_name); + char buf[1024]; + snprintf(buf, sizeof(buf), "%s (0x%lx)+0x%lx/(0x%lx) from symtab %d", st_name, + off, addr_off - off, len, i); + return buf; + } + } + } + } + if (dynsym_start != nullptr && symstr_offset_for_dynsym != 0) { + auto addr_off = (ElfW(Addr)) (addr + bias - (uintptr_t) base); + for (ElfW(Off) i = 0; i < dynsym_count; i++) { + unsigned int st_type = ELF_ST_TYPE(dynsym_start[i].st_info); + const char *st_name = offsetOf(header, symstr_offset_for_dynsym + + dynsym_start[i].st_name); + if ((st_type == STT_FUNC || st_type == STT_OBJECT) && dynsym_start[i].st_size) { + auto off = dynsym_start[i].st_value; + auto len = dynsym_start[i].st_size; + if (off <= addr_off && addr_off < off + len) { + LOGD("found in dynsym sym %p name %s", off, + st_name); + char buf[1024]; + snprintf(buf, sizeof(buf), "%s (0x%lx)+0x%lx/(0x%lx) from dynsym %d", st_name, + off, addr_off - off, len, i); + return buf; + } + } + } + } + return "(not found)"; +} + + +ElfW(Sym) *ElfImg::LinearLookup(std::string_view name) const { + MayInitLinearMap(); + if (auto i = symtabs_.find(name); i != symtabs_.end()) { + return i->second; + } else { + return 0; + } +} + +ElfW(Sym) *ElfImg::PrefixLookupFirst(std::string_view prefix) const { + MayInitLinearMap(); + if (auto i = symtabs_.lower_bound(prefix); i != symtabs_.end() && + i->first.starts_with(prefix)) { + LOGD("found prefix %s of %s %p in %s in symtab by linear lookup", prefix.data(), + i->first.data(), reinterpret_cast(i->second->st_value), elf.data()); + return i->second; + } else { + return 0; + } +} + +ElfImg::~ElfImg() { + //open elf file local + if (buffer) { + free(buffer); + buffer = nullptr; + } + //use mmap + if (header) { + if (munmap(header, size) == -1) PLOGE("munmap"); + LOGI("unmapped %p %ld", header, size); + } +} + +ElfW(Sym) * +ElfImg::getSym(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const { + if (auto sym = GnuLookup(name, gnu_hash); sym) { + LOGD("found %s %p/%llu in %s in dynsym by gnuhash", name.data(), + reinterpret_cast(sym->st_value), sym->st_size, elf.data()); + return sym; + } else if (sym = ElfLookup(name, elf_hash); sym) { + LOGD("found %s %p/%llu in %s in dynsym by elfhash", name.data(), + reinterpret_cast(sym->st_value), sym->st_size, elf.data()); + return sym; + } else if (sym = LinearLookup(name); sym) { + LOGD("found %s %p/%llu in %s in symtab by linear lookup", name.data(), + reinterpret_cast(sym->st_value), sym->st_size, elf.data()); + return sym; + } else { + return nullptr; + } + +} + +constexpr inline bool contains(std::string_view a, std::string_view b) { + return a.find(b) != std::string_view::npos; +} + +ElfInfo ElfInfo::getElfInfoForName(const char *name, const char *pid) { + auto maps = lsplt::MapInfo::Scan(pid); + std::string path; + void *base = nullptr; + size_t file_size = 0; + for (auto &map: maps) { + if (map.path.ends_with(name) && map.offset == 0) { + struct stat st{}; + if (stat(map.path.c_str(), &st) == -1) { + PLOGE("stat %s", map.path.c_str()); + break; + } + path = map.path; + file_size = st.st_size; + base = reinterpret_cast(map.start); + LOGD("found path %s size %zu", path.c_str(), file_size); + break; + } + } + return {path, base, 0, file_size}; +} diff --git a/module/src/main/cpp/elf_util/include/elf_util.h b/module/src/main/cpp/elf_util/include/elf_util.h new file mode 100644 index 0000000..3978426 --- /dev/null +++ b/module/src/main/cpp/elf_util/include/elf_util.h @@ -0,0 +1,174 @@ +/* + * 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 { + struct ElfInfo { + std::string name; + void *base; + off_t offset; + size_t size; + + static ElfInfo getElfInfoForName(const char *name, const char *pid = "self"); + }; + class ElfImg { + public: + + ElfImg(std::string_view base_name, void *base, off_t file_offset, size_t size); + + ElfImg(ElfInfo info) : ElfImg(info.name, info.base, info.offset, info.size) {} + + std::pair getSymInfo(std::string_view name) const { + auto sym = getSym(name, GnuHash(name), ElfHash(name)); + if (sym && base != nullptr) { + auto offset = sym->st_value; + return {(uintptr_t) base + offset - bias, sym->st_size}; + } else { + return {0, 0}; + } + } + + template + requires(std::is_pointer_v) + constexpr T getSymbAddress(std::string_view name) const { + auto sym = getSym(name, GnuHash(name), ElfHash(name)); + if (sym && base != nullptr) { + auto offset = sym->st_value; + return reinterpret_cast(static_cast((uintptr_t) base + offset - bias)); + } else { + return nullptr; + } + } + + template + requires(std::is_pointer_v) + constexpr T getSymbPrefixFirstOffset(std::string_view prefix) const { + auto sym = PrefixLookupFirst(prefix); + if (sym && base != nullptr) { + auto offset = sym->st_value; + return reinterpret_cast(static_cast((uintptr_t) base + offset - bias)); + } else { + return nullptr; + } + } + + bool isValid() const { + return base != nullptr; + } + + const std::string name() const { + return elf; + } + + const void *getBase() const { + return base; + } + + const std::string findSymbolNameForAddr(ElfW(Addr) addr) const; + + ~ElfImg(); + + private: + ElfW(Sym) *getSym(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const; + + ElfW(Sym) *ElfLookup(std::string_view name, uint32_t hash) const; + + ElfW(Sym) *GnuLookup(std::string_view name, uint32_t hash) const; + + ElfW(Sym) *LinearLookup(std::string_view name) const; + + ElfW(Sym) *PrefixLookupFirst(std::string_view prefix) const; + + constexpr static uint32_t ElfHash(std::string_view name); + + constexpr static uint32_t GnuHash(std::string_view name); + + void MayInitLinearMap() const; + + std::string elf; + uintptr_t offset_for_zip; + 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) symstr_offset = 0; + ElfW(Off) symstr_offset_for_symtab = 0; + ElfW(Off) symstr_offset_for_dynsym = 0; + ElfW(Off) symtab_offset = 0; + ElfW(Off) dynsym_offset = 0; + ElfW(Off) symtab_count = 0; + ElfW(Off) symtab_size = 0; + ElfW(Off) dynsym_count = 0; + ElfW(Off) dynsym_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::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/module/src/main/cpp/example.cpp b/module/src/main/cpp/example.cpp deleted file mode 100644 index c1a5b44..0000000 --- a/module/src/main/cpp/example.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* Copyright 2022-2023 John "topjohnwu" Wu - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include - -#include "zygisk.hpp" - -using zygisk::Api; -using zygisk::AppSpecializeArgs; -using zygisk::ServerSpecializeArgs; - -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "MyModule", __VA_ARGS__) - -class MyModule : public zygisk::ModuleBase { -public: - void onLoad(Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - - void preAppSpecialize(AppSpecializeArgs *args) override { - // Use JNI to fetch our process name - const char *process = env->GetStringUTFChars(args->nice_name, nullptr); - preSpecialize(process); - env->ReleaseStringUTFChars(args->nice_name, process); - } - - void preServerSpecialize(ServerSpecializeArgs *args) override { - preSpecialize("system_server"); - } - -private: - Api *api; - JNIEnv *env; - - void preSpecialize(const char *process) { - // Demonstrate connecting to to companion process - // We ask the companion for a random number - unsigned r = 0; - int fd = api->connectCompanion(); - read(fd, &r, sizeof(r)); - close(fd); - LOGD("process=[%s], r=[%u]\n", process, r); - - // Since we do not hook any functions, we should let Zygisk dlclose ourselves - api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); - } - -}; - -static int urandom = -1; - -static void companion_handler(int i) { - if (urandom < 0) { - urandom = open("/dev/urandom", O_RDONLY); - } - unsigned r; - read(urandom, &r, sizeof(r)); - LOGD("companion r=[%u]\n", r); - write(i, &r, sizeof(r)); -} - -// Register our module class and the companion handler function -REGISTER_ZYGISK_MODULE(MyModule) -REGISTER_ZYGISK_COMPANION(companion_handler) diff --git a/module/src/main/cpp/external/CMakeLists.txt b/module/src/main/cpp/external/CMakeLists.txt index c42bfa0..89c501e 100644 --- a/module/src/main/cpp/external/CMakeLists.txt +++ b/module/src/main/cpp/external/CMakeLists.txt @@ -1,5 +1,18 @@ project(external) + +# dobby +macro(SET_OPTION option value) + set(${option} ${value} CACHE INTERNAL "" FORCE) +endmacro() + +SET_OPTION(DOBBY_GENERATE_SHARED OFF) +SET_OPTION(Plugin.SymbolResolver OFF) + +add_subdirectory(Dobby) +target_link_libraries(dobby cxx) +# end dobby + # cxx set(LIBCXX_SOURCES algorithm.cpp diff --git a/module/src/main/cpp/external/dobby b/module/src/main/cpp/external/dobby new file mode 160000 index 0000000..6813ca7 --- /dev/null +++ b/module/src/main/cpp/external/dobby @@ -0,0 +1 @@ +Subproject commit 6813ca76ddeafcaece525bf8c6cde7ff4c21d3ce diff --git a/module/src/main/cpp/hook_util/hook_helper.hpp b/module/src/main/cpp/hook_util/hook_helper.hpp new file mode 100644 index 0000000..1f8dee3 --- /dev/null +++ b/module/src/main/cpp/hook_util/hook_helper.hpp @@ -0,0 +1,246 @@ +#pragma once + +#include +#include + +#include "type_traits.hpp" + +#if defined(__LP64__) +#define LP_SELECT(lp32, lp64) lp64 +#else +#define LP_SELECT(lp32, lp64) lp32 +#endif + +#define CONCATENATE(a, b) a##b + +#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \ + inline static struct : public hook_helper::Hooker{ \ + inline static RET replace PARAMS DEF} FUNC + +#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \ + static struct : public hook_helper::MemHooker{ \ + inline static RET replace PARAMS DEF} FUNC + +#define RETRIEVE_FUNC_SYMBOL(name, ...) \ + (name##Sym = reinterpret_cast(hook_helper::Dlsym(handler, __VA_ARGS__))) + +#define RETRIEVE_FUNC_SYMBOL_OR_FAIL(name, ...) \ + RETRIEVE_FUNC_SYMBOL(name, __VA_ARGS__); if (!name##Sym) return false; + +#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \ + (name##Sym = reinterpret_cast(hook_helper::Dlsym(handler, __VA_ARGS__))) + +#define RETRIEVE_MEM_FUNC_SYMBOL_OR_FAIL(name, ...) \ + RETRIEVE_MEM_FUNC_SYMBOL(name, __VA_ARGS__); if (!name##Sym) return false; + +#define RETRIEVE_FIELD_SYMBOL(name, ...) \ + (name = reinterpret_cast(hook_helper::Dlsym(handler, __VA_ARGS__))) + +#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \ + typedef ret (*func##Type)(__VA_ARGS__); \ + inline static ret (*func##Sym)(__VA_ARGS__); \ + inline static ret func(__VA_ARGS__) + +#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \ + using func##Type = hook_helper::MemberFunction; \ + inline static func##Type func##Sym; \ + inline static ret func(thiz, ##__VA_ARGS__) + +namespace hook_helper { + struct HookHandler { + virtual void *get_symbol(const char *name) const = 0; + + virtual void *get_symbol_prefix(const char *prefix) const = 0; + + // return backup + virtual void *hook(void *original, void *replacement) const = 0; + + virtual std::pair get_symbol_info(const char *name) const = 0; + }; + + inline namespace literals { + template + struct tstring : public std::integer_sequence { + inline constexpr static const char *c_str() { return str_; } + + inline constexpr operator std::string_view() const { + return {c_str(), sizeof...(chars)}; + } + + private: + inline static constexpr char str_[]{chars..., '\0'}; + }; + + template + inline constexpr tstring operator ""_tstr() { + return {}; + } + + template + inline constexpr tstring + operator+(const tstring &, const tstring &) { + return {}; + } + + template + inline constexpr auto operator+(const std::string &a, const tstring &) { + char b[]{as..., '\0'}; + return a + b; + } + + template + inline constexpr auto operator+(const tstring &, const std::string &b) { + char a[]{as..., '\0'}; + return a + b; + } + } + + inline void *Dlsym(const HookHandler &handle, const char *name, bool match_prefix = false) { + if (auto match = handle.get_symbol(name); match) { + return match; + } else if (match_prefix) { + return handle.get_symbol_prefix(name); + } + return nullptr; + } + + template + requires(std::is_same_v || + std::is_same_v) inline static auto + memfun_cast(Return (*func)(T *, Args...)) { + union { + Return (Class::*f)(Args...); + + struct { + decltype(func) p; + std::ptrdiff_t adj; + } data; + } u{.data = {func, 0}}; + static_assert(sizeof(u.f) == sizeof(u.data), "Try different T"); + return u.f; + } + + template + inline static auto + memfun_addr(Return (Class::*func)(Args...)) { + union { + Return (Class::*f)(Args...); + + struct { + void *p; + std::ptrdiff_t adj; + } data; + } u{.f = func}; + static_assert(sizeof(u.f) == sizeof(u.data), "Try different T"); + return u.data.p; + } + + template T, typename Return, typename... Args> + inline auto memfun_cast(Return (*func)(T *, Args...)) { + return memfun_cast(func); + } + + template + class MemberFunction; + + template + class MemberFunction { + using SelfType = MemberFunction; + using ThisType = std::conditional_t, SelfType, This>; + using MemFunType = Return (ThisType::*)(Args...); + + public: + using FunType = Return (*)(This *, Args...); + + private: + MemFunType f_ = nullptr; + + public: + MemberFunction() = default; + + MemberFunction(FunType f) : f_(memfun_cast(f)) {} + + MemberFunction(MemFunType f) : f_(f) {} + + Return operator()(This *thiz, Args... args) { + return (reinterpret_cast(thiz)->*f_)(std::forward(args)...); + } + + inline operator bool() { return f_ != nullptr; } + + inline void *addr() { + return memfun_addr(f_); + } + }; + +// deduction guide + template + MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction; + + template + MemberFunction(Return (This::*f)(Args...)) -> MemberFunction; + + template + struct Hooker; + + template + struct Hooker> { + inline static Ret (*backup)(Args...) = nullptr; + + inline static constexpr std::string_view sym = tstring{}; + }; + + template + struct MemHooker; + template + struct MemHooker> { + inline static MemberFunction backup; + inline static constexpr std::string_view sym = tstring{}; + }; + + template + concept HookerType = requires(T a) { + a.backup; + a.replace; + }; + + template + inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) { + if (original) { + if constexpr (is_instance_v) { + void *backup = handler.hook(original, reinterpret_cast(arg.replace)); + arg.backup = reinterpret_cast(backup); + } else { + arg.backup = reinterpret_cast( + handler.hook(original, reinterpret_cast(arg.replace))); + } + return true; + } else { + return false; + } + } + + template + inline static bool HookSym(const HookHandler &handler, T &arg) { + auto original = handler.get_symbol(arg.sym.data()); + return HookSymNoHandle(handler, original, arg); + } + + template + inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) { + if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) { + __android_log_print(ANDROID_LOG_ERROR, +#ifdef LOG_TAG + LOG_TAG, +#else + "HookHelper", +#endif + "Hook Fails: %*s", static_cast(first.sym.size()), + first.sym.data()); + return false; + } + return true; + } + +} // namespace hook_helper \ No newline at end of file diff --git a/module/src/main/cpp/hook_util/type_traits.hpp b/module/src/main/cpp/hook_util/type_traits.hpp new file mode 100644 index 0000000..aa08cd7 --- /dev/null +++ b/module/src/main/cpp/hook_util/type_traits.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace hook_helper { + template class> + struct is_instance : public std::false_type { + }; + + template class U> + struct is_instance, U> : public std::true_type { + }; + + template class U> + inline constexpr bool is_instance_v = is_instance::value; +} diff --git a/module/src/main/cpp/logging/include/logging.hpp b/module/src/main/cpp/logging/include/logging.hpp new file mode 100644 index 0000000..fecb958 --- /dev/null +++ b/module/src/main/cpp/logging/include/logging.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +#ifndef LOG_TAG +# define LOG_TAG "TrickyStore" +#endif + +#ifndef NDEBUG +#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#else +#define LOGD(...) +#define LOGV(...) +#endif +#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) +#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) + +namespace logging { + void setPrintEnabled(bool print); + + [[gnu::format(printf, 3, 4)]] + void log(int prio, const char *tag, const char *fmt, ...); +} diff --git a/module/src/main/cpp/logging/logging.cpp b/module/src/main/cpp/logging/logging.cpp new file mode 100644 index 0000000..f787901 --- /dev/null +++ b/module/src/main/cpp/logging/logging.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include "logging.hpp" + +namespace logging { + static bool use_print = false; + static char prio_str[] = { + 'V', 'D', 'I', 'W', 'E', 'F' + }; + + void setPrintEnabled(bool print) { + use_print = print; + } + + void log(int prio, const char *tag, const char *fmt, ...) { + { + va_list ap; + va_start(ap, fmt); + __android_log_vprint(prio, tag, fmt, ap); + va_end(ap); + } + if (use_print) { + char buf[BUFSIZ]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + auto prio_char = (prio > ANDROID_LOG_DEFAULT && prio <= ANDROID_LOG_FATAL) ? prio_str[ + prio - ANDROID_LOG_VERBOSE] : '?'; + printf("[%c][%d:%d][%s]:%s\n", prio_char, getpid(), gettid(), tag, buf); + } + } +} \ No newline at end of file diff --git a/module/src/main/cpp/lspmparser/include/lsplt.hpp b/module/src/main/cpp/lspmparser/include/lsplt.hpp new file mode 100644 index 0000000..3124871 --- /dev/null +++ b/module/src/main/cpp/lspmparser/include/lsplt.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include +#include + +/// \namespace lsplt +namespace lsplt { + inline namespace v2 { + /// \struct MapInfo + /// \brief An entry that describes a line in /proc/self/maps. You can obtain a list of these entries + /// by calling #Scan(). + struct MapInfo { + /// \brief The start address of the memory region. + uintptr_t start; + /// \brief The end address of the memory region. + uintptr_t end; + /// \brief The permissions of the memory region. This is a bit mask of the following values: + /// - PROT_READ + /// - PROT_WRITE + /// - PROT_EXEC + uint8_t perms; + /// \brief Whether the memory region is private. + bool is_private; + /// \brief The offset of the memory region. + uintptr_t offset; + /// \brief The device number of the memory region. + /// Major can be obtained by #major() + /// Minor can be obtained by #minor() + dev_t dev; + /// \brief The inode number of the memory region. + ino_t inode; + /// \brief The path of the memory region. + std::string path; + + /// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries. + /// This is useful to find out the inode of the library to hook. + /// \return A list of \ref MapInfo entries. + [[maybe_unused, gnu::visibility("default")]] + static std::vector Scan(std::string proc = "self", + std::function filter = []( + auto &map) -> bool { return true; }); + + virtual std::string display() const; + }; + + struct SMapInfo : public MapInfo { + ssize_t size; + ssize_t rss; + ssize_t pss; + ssize_t shared_clean; + ssize_t shared_dirty; + ssize_t private_clean; + ssize_t private_dirty; + ssize_t referenced; + ssize_t anonymous; + + [[maybe_unused, gnu::visibility("default")]] + static std::vector Scan(std::string proc = "self", + std::function filter = []( + auto &map) -> bool { return true; }); + + std::string display() const override; + }; + } +} \ No newline at end of file diff --git a/module/src/main/cpp/lspmparser/lsplt.cpp b/module/src/main/cpp/lspmparser/lsplt.cpp new file mode 100644 index 0000000..660b11a --- /dev/null +++ b/module/src/main/cpp/lspmparser/lsplt.cpp @@ -0,0 +1,199 @@ +#include "lsplt.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lsplt { + inline namespace v2 { + [[maybe_unused]] std::vector + MapInfo::Scan(std::string proc, std::function filter) { + constexpr static auto kPermLength = 5; + constexpr static auto kMapEntry = 7; + std::vector info; + auto name = std::string("/proc/") + proc + "/maps"; + auto maps = std::unique_ptr{fopen(name.c_str(), "r"), + &fclose}; + if (maps) { + char *line = nullptr; + size_t len = 0; + ssize_t read; + while ((read = getline(&line, &len, maps.get())) > 0) { + line[read - 1] = '\0'; + uintptr_t start = 0; + uintptr_t end = 0; + uintptr_t off = 0; + ino_t inode = 0; + unsigned int dev_major = 0; + unsigned int dev_minor = 0; + std::array perm{'\0'}; + int path_off; + if (sscanf(line, "%" PRIxPTR"-%" + PRIxPTR + " %4s %" + PRIxPTR + " %x:%x %lu %n%*s", &start, + &end, perm.data(), &off, &dev_major, &dev_minor, &inode, + &path_off) == kMapEntry) { + while (path_off < read && isspace(line[path_off])) path_off++; + MapInfo sm{}; + sm.start = start; + sm.end = end; + sm.perms = 0; + sm.is_private = perm[3] == 'p'; + sm.offset = off; + sm.dev = static_cast(makedev(dev_major, dev_minor)); + sm.inode = inode; + sm.path = line + path_off; + if (perm[0] == 'r') sm.perms |= PROT_READ; + if (perm[1] == 'w') sm.perms |= PROT_WRITE; + if (perm[2] == 'x') sm.perms |= PROT_EXEC; + if (filter(sm)) info.emplace_back(sm); + continue; + } + } + free(line); + } + return info; + } + + // https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/fs/proc/task_mmu.c;l=827;drc=08582d678fcf11fc86188f0b92239d3d49667d8e + [[maybe_unused]] std::vector + SMapInfo::Scan(std::string proc, std::function filter) { + constexpr static auto kPermLength = 5; + constexpr static auto kMapEntry = 7; + std::vector info; + auto name = std::string("/proc/") + proc + "/smaps"; + auto maps = std::unique_ptr{fopen(name.c_str(), "r"), + &fclose}; + if (!maps) { + return info; + } + char *line = nullptr; + size_t len = 0; + ssize_t read; + while ((read = getline(&line, &len, maps.get())) > 0) { + line[read - 1] = '\0'; + uintptr_t start = 0; + uintptr_t end = 0; + uintptr_t off = 0; + ino_t inode = 0; + unsigned int dev_major = 0; + unsigned int dev_minor = 0; + std::array perm{'\0'}; + int path_off; + if (sscanf(line, "%" PRIxPTR"-%" + PRIxPTR + " %4s %" + PRIxPTR + " %x:%x %lu %n%*s", &start, + &end, perm.data(), &off, &dev_major, &dev_minor, &inode, + &path_off) == kMapEntry) { + while (path_off < read && isspace(line[path_off])) path_off++; + SMapInfo sm{}; + sm.start = start; + sm.end = end; + sm.perms = 0; + sm.is_private = perm[3] == 'p'; + sm.offset = off; + sm.dev = static_cast(makedev(dev_major, dev_minor)); + sm.inode = inode; + sm.path = line + path_off; + if (perm[0] == 'r') sm.perms |= PROT_READ; + if (perm[1] == 'w') sm.perms |= PROT_WRITE; + if (perm[2] == 'x') sm.perms |= PROT_EXEC; + if (filter(sm)) info.emplace_back(sm); + continue; + } + if (info.empty()) continue; + auto ¤t = *info.rbegin(); + ssize_t value; + auto s = strstr(line, ":"); + if (s == nullptr) continue; + *s = 0; + if (sscanf(s + 1, "%zu", &value) != 1) continue; + std::string_view ss{line}; + if (ss == "Size") current.size = value; + else if (ss == "Rss") current.rss = value; + else if (ss == "Pss") current.pss = value; + else if (ss == "Shared_Clean") current.shared_clean = value; + else if (ss == "Shared_Dirty") current.shared_dirty = value; + else if (ss == "Private_Clean") current.private_clean = value; + else if (ss == "Private_Dirty") current.private_dirty = value; + else if (ss == "Referenced") current.referenced = value; + else if (ss == "Anonymous") current.anonymous = value; + } + free(line); + return info; + } + + std::string MapInfo::display() const { + char buf[sizeof(long) * 2 + 1]; + std::string result; + snprintf(buf, sizeof(buf), "%" PRIxPTR, start); + result += buf; + result += "-"; + snprintf(buf, sizeof(buf), "%" PRIxPTR, end); + result += buf; + result += ' '; + result += (perms & PROT_READ) ? 'r' : '-'; + result += (perms & PROT_WRITE) ? 'w' : '-'; + result += (perms & PROT_EXEC) ? 'x' : '-'; + result += is_private ? 'p' : 's'; + result += ' '; + snprintf(buf, sizeof(buf), "%08" PRIxPTR, offset); + result += buf; + result += ' '; + snprintf(buf, sizeof(buf), "%02" PRIxPTR, major(dev)); + result += buf; + result += ':'; + snprintf(buf, sizeof(buf), "%02" PRIxPTR, minor(dev)); + result += buf; + result += ' '; + snprintf(buf, sizeof(buf), "%lu", inode); + result += buf; + result += ' '; + result += path; + return result; + } + + std::string SMapInfo::display() const { + auto result = MapInfo::display(); + char buf[sizeof(long) * 2 + 1]; + result += '\n'; + result += "SZ:"; + snprintf(buf, sizeof(buf), "%zu", size); + result += buf; + result += " RSS:"; + snprintf(buf, sizeof(buf), "%zu", rss); + result += buf; + result += " PSS:"; + snprintf(buf, sizeof(buf), "%zu", pss); + result += buf; + result += " SC:"; + snprintf(buf, sizeof(buf), "%zu", shared_clean); + result += buf; + result += " SD:"; + snprintf(buf, sizeof(buf), "%zu", shared_dirty); + result += buf; + result += " PC:"; + snprintf(buf, sizeof(buf), "%zu", private_clean); + result += buf; + result += " PD:"; + snprintf(buf, sizeof(buf), "%zu", private_dirty); + result += buf; + result += " REF:"; + snprintf(buf, sizeof(buf), "%zu", referenced); + result += buf; + result += " ANON:"; + snprintf(buf, sizeof(buf), "%zu", anonymous); + result += buf; + return result; + } + } +} diff --git a/module/src/main/cpp/zygisk.hpp b/module/src/main/cpp/zygisk.hpp deleted file mode 100644 index 7c861ad..0000000 --- a/module/src/main/cpp/zygisk.hpp +++ /dev/null @@ -1,391 +0,0 @@ -/* Copyright 2022-2023 John "topjohnwu" Wu - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -// This is the public API for Zygisk modules. -// DO NOT MODIFY ANY CODE IN THIS HEADER. - -#pragma once - -#include - -#define ZYGISK_API_VERSION 4 - -/* - -*************** -* Introduction -*************** - -On Android, all app processes are forked from a special daemon called "Zygote". -For each new app process, zygote will fork a new process and perform "specialization". -This specialization operation enforces the Android security sandbox on the newly forked -process to make sure that 3rd party application code is only loaded after it is being -restricted within a sandbox. - -On Android, there is also this special process called "system_server". This single -process hosts a significant portion of system services, which controls how the -Android operating system and apps interact with each other. - -The Zygisk framework provides a way to allow developers to build modules and run custom -code before and after system_server and any app processes' specialization. -This enable developers to inject code and alter the behavior of system_server and app processes. - -Please note that modules will only be loaded after zygote has forked the child process. -THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON! - -********************* -* Development Guide -********************* - -Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. -Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. - -Example code: - -static jint (*orig_logger_entry_max)(JNIEnv *env); -static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } - -class ExampleModule : public zygisk::ModuleBase { -public: - void onLoad(zygisk::Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { - JNINativeMethod methods[] = { - { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, - }; - api->hookJniNativeMethods(env, "android/util/Log", methods, 1); - *(void **) &orig_logger_entry_max = methods[0].fnPtr; - } -private: - zygisk::Api *api; - JNIEnv *env; -}; - -REGISTER_ZYGISK_MODULE(ExampleModule) - ------------------------------------------------------------------------------------------ - -Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize, -or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class -never runs in a true superuser environment. - -If your module require access to superuser permissions, you can create and register -a root companion handler function. This function runs in a separate root companion -daemon process, and an Unix domain socket is provided to allow you to perform IPC between -your target process and the root companion process. - -Example code: - -static void example_handler(int socket) { ... } - -REGISTER_ZYGISK_COMPANION(example_handler) - -*/ - -namespace zygisk { - -struct Api; -struct AppSpecializeArgs; -struct ServerSpecializeArgs; - -class ModuleBase { -public: - - // This method is called as soon as the module is loaded into the target process. - // A Zygisk API handle will be passed as an argument. - virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} - - // This method is called before the app process is specialized. - // At this point, the process just got forked from zygote, but no app specific specialization - // is applied. This means that the process does not have any sandbox restrictions and - // still runs with the same privilege of zygote. - // - // All the arguments that will be sent and used for app specialization is passed as a single - // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app - // process will be specialized. - // - // If you need to run some operations as superuser, you can call Api::connectCompanion() to - // get a socket to do IPC calls with a root companion process. - // See Api::connectCompanion() for more info. - virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} - - // This method is called after the app process is specialized. - // At this point, the process has all sandbox restrictions enabled for this application. - // This means that this method runs with the same privilege of the app's own code. - virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} - - // This method is called before the system server process is specialized. - // See preAppSpecialize(args) for more info. - virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} - - // This method is called after the system server process is specialized. - // At this point, the process runs with the privilege of system_server. - virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} -}; - -struct AppSpecializeArgs { - // Required arguments. These arguments are guaranteed to exist on all Android versions. - jint &uid; - jint &gid; - jintArray &gids; - jint &runtime_flags; - jobjectArray &rlimits; - jint &mount_external; - jstring &se_info; - jstring &nice_name; - jstring &instruction_set; - jstring &app_data_dir; - - // Optional arguments. Please check whether the pointer is null before de-referencing - jintArray *const fds_to_ignore; - jboolean *const is_child_zygote; - jboolean *const is_top_app; - jobjectArray *const pkg_data_info_list; - jobjectArray *const whitelisted_data_info_list; - jboolean *const mount_data_dirs; - jboolean *const mount_storage_dirs; - - AppSpecializeArgs() = delete; -}; - -struct ServerSpecializeArgs { - jint &uid; - jint &gid; - jintArray &gids; - jint &runtime_flags; - jlong &permitted_capabilities; - jlong &effective_capabilities; - - ServerSpecializeArgs() = delete; -}; - -namespace internal { -struct api_table; -template void entry_impl(api_table *, JNIEnv *); -} - -// These values are used in Api::setOption(Option) -enum Option : int { - // Force Magisk's denylist unmount routines to run on this process. - // - // Setting this option only makes sense in preAppSpecialize. - // The actual unmounting happens during app process specialization. - // - // Set this option to force all Magisk and modules' files to be unmounted from the - // mount namespace of the process, regardless of the denylist enforcement status. - FORCE_DENYLIST_UNMOUNT = 0, - - // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. - // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. - // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. - DLCLOSE_MODULE_LIBRARY = 1, -}; - -// Bit masks of the return value of Api::getFlags() -enum StateFlag : uint32_t { - // The user has granted root access to the current process - PROCESS_GRANTED_ROOT = (1u << 0), - - // The current process was added on the denylist - PROCESS_ON_DENYLIST = (1u << 1), -}; - -// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded -// from the specialized process afterwards. -struct Api { - - // Connect to a root companion process and get a Unix domain socket for IPC. - // - // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. - // - // The pre[XXX]Specialize methods run with the same privilege of zygote. - // If you would like to do some operations with superuser permissions, register a handler - // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). - // Another good use case for a companion process is that if you want to share some resources - // across multiple processes, hold the resources in the companion process and pass it over. - // - // The root companion process is ABI aware; that is, when calling this method from a 32-bit - // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. - // - // Returns a file descriptor to a socket that is connected to the socket passed to your - // module's companion request handler. Returns -1 if the connection attempt failed. - int connectCompanion(); - - // Get the file descriptor of the root folder of the current module. - // - // This API only works in the pre[XXX]Specialize methods. - // Accessing the directory returned is only possible in the pre[XXX]Specialize methods - // or in the root companion process (assuming that you sent the fd over the socket). - // Both restrictions are due to SELinux and UID. - // - // Returns -1 if errors occurred. - int getModuleDir(); - - // Set various options for your module. - // Please note that this method accepts one single option at a time. - // Check zygisk::Option for the full list of options available. - void setOption(Option opt); - - // Get information about the current process. - // Returns bitwise-or'd zygisk::StateFlag values. - uint32_t getFlags(); - - // Exempt the provided file descriptor from being automatically closed. - // - // This API only make sense in preAppSpecialize; calling this method in any other situation - // is either a no-op (returns true) or an error (returns false). - // - // When false is returned, the provided file descriptor will eventually be closed by zygote. - bool exemptFd(int fd); - - // Hook JNI native methods for a class - // - // Lookup all registered JNI native methods and replace it with your own methods. - // The original function pointer will be saved in each JNINativeMethod's fnPtr. - // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr - // will be set to nullptr. - void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); - - // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory. - // - // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example: - // - //
- // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64 - // (More details: https://man7.org/linux/man-pages/man5/proc.5.html) - // - // The `dev` and `inode` pair uniquely identifies a file being mapped into memory. - // For matching ELFs loaded in memory, replace function `symbol` with `newFunc`. - // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. - void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc); - - // Commit all the hooks that was previously registered. - // Returns false if an error occurred. - bool pltHookCommit(); - -private: - internal::api_table *tbl; - template friend void internal::entry_impl(internal::api_table *, JNIEnv *); -}; - -// Register a class as a Zygisk module - -#define REGISTER_ZYGISK_MODULE(clazz) \ -void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ - zygisk::internal::entry_impl(table, env); \ -} - -// Register a root companion request handler function for your module -// -// The function runs in a superuser daemon process and handles a root companion request from -// your module running in a target process. The function has to accept an integer value, -// which is a Unix domain socket that is connected to the target process. -// See Api::connectCompanion() for more info. -// -// NOTE: the function can run concurrently on multiple threads. -// Be aware of race conditions if you have globally shared resources. - -#define REGISTER_ZYGISK_COMPANION(func) \ -void zygisk_companion_entry(int client) { func(client); } - -/********************************************************* - * The following is internal ABI implementation detail. - * You do not have to understand what it is doing. - *********************************************************/ - -namespace internal { - -struct module_abi { - long api_version; - ModuleBase *impl; - - void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); - void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); - void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); - void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); - - module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) { - preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); }; - postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); }; - preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); }; - postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); }; - } -}; - -struct api_table { - // Base - void *impl; - bool (*registerModule)(api_table *, module_abi *); - - void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); - void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **); - bool (*exemptFd)(int); - bool (*pltHookCommit)(); - int (*connectCompanion)(void * /* impl */); - void (*setOption)(void * /* impl */, Option); - int (*getModuleDir)(void * /* impl */); - uint32_t (*getFlags)(void * /* impl */); -}; - -template -void entry_impl(api_table *table, JNIEnv *env) { - static Api api; - api.tbl = table; - static T module; - ModuleBase *m = &module; - static module_abi abi(m); - if (!table->registerModule(table, &abi)) return; - m->onLoad(&api, env); -} - -} // namespace internal - -inline int Api::connectCompanion() { - return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1; -} -inline int Api::getModuleDir() { - return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; -} -inline void Api::setOption(Option opt) { - if (tbl->setOption) tbl->setOption(tbl->impl, opt); -} -inline uint32_t Api::getFlags() { - return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; -} -inline bool Api::exemptFd(int fd) { - return tbl->exemptFd != nullptr && tbl->exemptFd(fd); -} -inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { - if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods); -} -inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) { - if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc); -} -inline bool Api::pltHookCommit() { - return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); -} - -} // namespace zygisk - -extern "C" { - -[[gnu::visibility("default"), maybe_unused]] -void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); - -[[gnu::visibility("default"), maybe_unused]] -void zygisk_companion_entry(int); - -} // extern "C"