You've already forked TrickyStore
mirror of
https://github.com/5ec1cff/TrickyStore.git
synced 2025-09-06 06:37:07 +00:00
make it work
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
276
module/src/main/cpp/binder_interceptor.cpp
Normal file
276
module/src/main/cpp/binder_interceptor.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
#include <utils/RefBase.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <binder/Binder.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <binder/Common.h>
|
||||
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#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<IBinder> target{};
|
||||
sp<IBinder> interceptor;
|
||||
};
|
||||
using RwLock = std::shared_mutex;
|
||||
using WriteGuard = std::unique_lock<RwLock>;
|
||||
using ReadGuard = std::shared_lock<RwLock>;
|
||||
RwLock lock;
|
||||
std::map<wp<IBinder>, 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<BinderInterceptor> 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<IBinder> 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<IBinder> 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<IBinder> 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<IBinder> 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<uintptr_t, size_t> 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<IBinder> interceptor;
|
||||
{
|
||||
ReadGuard rg{lock};
|
||||
wp<IBinder> target = wp<IBinder>::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<IBinder> target = sp<IBinder>::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<BinderInterceptor>::make();
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]] bool entry(void *handle) {
|
||||
LOGI("my handle %p", handle);
|
||||
return hookBinder();
|
||||
}
|
||||
305
module/src/main/cpp/elf_util/elf_util.cpp
Normal file
305
module/src/main/cpp/elf_util/elf_util.cpp
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2019 Swift Gan
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
#include <malloc.h>
|
||||
#include <cstring>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <cassert>
|
||||
#include <sys/stat.h>
|
||||
#include "logging.hpp"
|
||||
#include "elf_util.h"
|
||||
#include <vector>
|
||||
#include "lsplt.hpp"
|
||||
|
||||
using namespace SandHook;
|
||||
|
||||
template<typename T>
|
||||
inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) {
|
||||
return reinterpret_cast<std::conditional_t<std::is_pointer_v<T>, T, T *>>(
|
||||
reinterpret_cast<uintptr_t>(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<decltype(header)>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd,
|
||||
file_offset));
|
||||
|
||||
close(fd);
|
||||
|
||||
section_header = offsetOf<decltype(section_header)>(header, header->e_shoff);
|
||||
|
||||
auto shoff = reinterpret_cast<uintptr_t>(section_header);
|
||||
char *section_str = offsetOf<char *>(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<decltype(dynsym_start)>(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<decltype(symtab_start)>(header, symtab_offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_STRTAB: {
|
||||
if (bias == -4396) {
|
||||
strtab = section_h;
|
||||
symstr_offset = section_h->sh_offset;
|
||||
strtab_start = offsetOf<decltype(strtab_start)>(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<ElfW(Word)>(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<ElfW(Word) *>(((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<decltype(gnu_bloom_filter_)>(d_buf + 4);
|
||||
gnu_bucket_ = reinterpret_cast<decltype(gnu_bucket_)>(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<const char *>(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<const char *>(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<const char *>(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<void *>(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<void *>(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<void *>(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<void *>(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<void *>(map.start);
|
||||
LOGD("found path %s size %zu", path.c_str(), file_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {path, base, 0, file_size};
|
||||
}
|
||||
174
module/src/main/cpp/elf_util/include/elf_util.h
Normal file
174
module/src/main/cpp/elf_util/include/elf_util.h
Normal file
@@ -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 <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
|
||||
|
||||
#include <string_view>
|
||||
#include <map>
|
||||
#include <linux/elf.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#include <link.h>
|
||||
|
||||
#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<uintptr_t, size_t> 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<typename T = void*>
|
||||
requires(std::is_pointer_v<T>)
|
||||
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<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T = void*>
|
||||
requires(std::is_pointer_v<T>)
|
||||
constexpr T getSymbPrefixFirstOffset(std::string_view prefix) const {
|
||||
auto sym = PrefixLookupFirst(prefix);
|
||||
if (sym && base != nullptr) {
|
||||
auto offset = sym->st_value;
|
||||
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((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<std::string_view, ElfW(Sym) *> 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
|
||||
@@ -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 <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#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)
|
||||
13
module/src/main/cpp/external/CMakeLists.txt
vendored
13
module/src/main/cpp/external/CMakeLists.txt
vendored
@@ -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
|
||||
|
||||
1
module/src/main/cpp/external/dobby
vendored
Submodule
1
module/src/main/cpp/external/dobby
vendored
Submodule
Submodule module/src/main/cpp/external/dobby added at 6813ca76dd
246
module/src/main/cpp/hook_util/hook_helper.hpp
Normal file
246
module/src/main/cpp/hook_util/hook_helper.hpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <android/log.h>
|
||||
|
||||
#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<RET PARAMS, decltype(CONCATENATE(SYM, _tstr))>{ \
|
||||
inline static RET replace PARAMS DEF} FUNC
|
||||
|
||||
#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
|
||||
static struct : public hook_helper::MemHooker<RET PARAMS, \
|
||||
decltype(CONCATENATE(SYM, _tstr))>{ \
|
||||
inline static RET replace PARAMS DEF} FUNC
|
||||
|
||||
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
|
||||
(name##Sym = reinterpret_cast<name##Type>(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<name##Type::FunType>(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<decltype(name)>(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<ret(__VA_ARGS__)>; \
|
||||
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<uintptr_t, size_t> get_symbol_info(const char *name) const = 0;
|
||||
};
|
||||
|
||||
inline namespace literals {
|
||||
template<char... chars>
|
||||
struct tstring : public std::integer_sequence<char, chars...> {
|
||||
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<typename T, T... chars>
|
||||
inline constexpr tstring<chars...> operator ""_tstr() {
|
||||
return {};
|
||||
}
|
||||
|
||||
template<char... as, char... bs>
|
||||
inline constexpr tstring<as..., bs...>
|
||||
operator+(const tstring<as...> &, const tstring<bs...> &) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template<char... as>
|
||||
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
|
||||
char b[]{as..., '\0'};
|
||||
return a + b;
|
||||
}
|
||||
|
||||
template<char... as>
|
||||
inline constexpr auto operator+(const tstring<as...> &, 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<typename Class, typename Return, typename T, typename... Args>
|
||||
requires(std::is_same_v<T, void> ||
|
||||
std::is_same_v<Class, T>) 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<typename Class, typename Return, typename... Args>
|
||||
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<std::same_as<void> T, typename Return, typename... Args>
|
||||
inline auto memfun_cast(Return (*func)(T *, Args...)) {
|
||||
return memfun_cast<T>(func);
|
||||
}
|
||||
|
||||
template<typename, typename = void>
|
||||
class MemberFunction;
|
||||
|
||||
template<typename This, typename Return, typename... Args>
|
||||
class MemberFunction<Return(Args...), This> {
|
||||
using SelfType = MemberFunction<Return(This *, Args...), This>;
|
||||
using ThisType = std::conditional_t<std::is_same_v<This, void>, 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<ThisType>(f)) {}
|
||||
|
||||
MemberFunction(MemFunType f) : f_(f) {}
|
||||
|
||||
Return operator()(This *thiz, Args... args) {
|
||||
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline operator bool() { return f_ != nullptr; }
|
||||
|
||||
inline void *addr() {
|
||||
return memfun_addr(f_);
|
||||
}
|
||||
};
|
||||
|
||||
// deduction guide
|
||||
template<typename This, typename Return, typename... Args>
|
||||
MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
|
||||
|
||||
template<typename This, typename Return, typename... Args>
|
||||
MemberFunction(Return (This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
|
||||
|
||||
template<typename, typename>
|
||||
struct Hooker;
|
||||
|
||||
template<typename Ret, typename... Args, char... cs>
|
||||
struct Hooker<Ret(Args...), tstring<cs...>> {
|
||||
inline static Ret (*backup)(Args...) = nullptr;
|
||||
|
||||
inline static constexpr std::string_view sym = tstring<cs...>{};
|
||||
};
|
||||
|
||||
template<typename, typename>
|
||||
struct MemHooker;
|
||||
template<typename Ret, typename This, typename... Args, char... cs>
|
||||
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
|
||||
inline static MemberFunction<Ret(Args...)> backup;
|
||||
inline static constexpr std::string_view sym = tstring<cs...>{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept HookerType = requires(T a) {
|
||||
a.backup;
|
||||
a.replace;
|
||||
};
|
||||
|
||||
template<HookerType T>
|
||||
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
|
||||
if (original) {
|
||||
if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
|
||||
void *backup = handler.hook(original, reinterpret_cast<void *>(arg.replace));
|
||||
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
|
||||
} else {
|
||||
arg.backup = reinterpret_cast<decltype(arg.backup)>(
|
||||
handler.hook(original, reinterpret_cast<void *>(arg.replace)));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<HookerType T>
|
||||
inline static bool HookSym(const HookHandler &handler, T &arg) {
|
||||
auto original = handler.get_symbol(arg.sym.data());
|
||||
return HookSymNoHandle(handler, original, arg);
|
||||
}
|
||||
|
||||
template<HookerType T, HookerType... Args>
|
||||
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<int>(first.sym.size()),
|
||||
first.sym.data());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace hook_helper
|
||||
16
module/src/main/cpp/hook_util/type_traits.hpp
Normal file
16
module/src/main/cpp/hook_util/type_traits.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace hook_helper {
|
||||
template<class, template<class, class...> class>
|
||||
struct is_instance : public std::false_type {
|
||||
};
|
||||
|
||||
template<class... Ts, template<class, class...> class U>
|
||||
struct is_instance<U<Ts...>, U> : public std::true_type {
|
||||
};
|
||||
|
||||
template<class T, template<class, class...> class U>
|
||||
inline constexpr bool is_instance_v = is_instance<T, U>::value;
|
||||
}
|
||||
30
module/src/main/cpp/logging/include/logging.hpp
Normal file
30
module/src/main/cpp/logging/include/logging.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#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, ...);
|
||||
}
|
||||
36
module/src/main/cpp/logging/logging.cpp
Normal file
36
module/src/main/cpp/logging/logging.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <android/log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
module/src/main/cpp/lspmparser/include/lsplt.hpp
Normal file
67
module/src/main/cpp/lspmparser/include/lsplt.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/// \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<MapInfo> Scan(std::string proc = "self",
|
||||
std::function<bool(const MapInfo &)> 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<SMapInfo> Scan(std::string proc = "self",
|
||||
std::function<bool(const MapInfo &)> filter = [](
|
||||
auto &map) -> bool { return true; });
|
||||
|
||||
std::string display() const override;
|
||||
};
|
||||
}
|
||||
}
|
||||
199
module/src/main/cpp/lspmparser/lsplt.cpp
Normal file
199
module/src/main/cpp/lspmparser/lsplt.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "lsplt.hpp"
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace lsplt {
|
||||
inline namespace v2 {
|
||||
[[maybe_unused]] std::vector<MapInfo>
|
||||
MapInfo::Scan(std::string proc, std::function<bool(const MapInfo &)> filter) {
|
||||
constexpr static auto kPermLength = 5;
|
||||
constexpr static auto kMapEntry = 7;
|
||||
std::vector<MapInfo> info;
|
||||
auto name = std::string("/proc/") + proc + "/maps";
|
||||
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{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<char, kPermLength> 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<dev_t>(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>
|
||||
SMapInfo::Scan(std::string proc, std::function<bool(const MapInfo &)> filter) {
|
||||
constexpr static auto kPermLength = 5;
|
||||
constexpr static auto kMapEntry = 7;
|
||||
std::vector<SMapInfo> info;
|
||||
auto name = std::string("/proc/") + proc + "/smaps";
|
||||
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{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<char, kPermLength> 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<dev_t>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <jni.h>
|
||||
|
||||
#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 <class T> 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:
|
||||
//
|
||||
// <address> <perms> <offset> <dev> <inode> <pathname>
|
||||
// 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 <class T> 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<clazz>(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 <class T>
|
||||
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"
|
||||
Reference in New Issue
Block a user