make it work

This commit is contained in:
5ec1cff
2024-07-10 17:45:13 +08:00
parent f1939a6484
commit 74fe081ad0
15 changed files with 1380 additions and 472 deletions

3
.gitmodules vendored
View File

@@ -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

View File

@@ -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)

View 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();
}

View 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};
}

View 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

View File

@@ -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)

View File

@@ -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

View 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

View 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;
}

View 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, ...);
}

View 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);
}
}
}

View 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;
};
}
}

View 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 &current = *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;
}
}
}

View File

@@ -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"