merge: port of more of the codebase; fix: memory and general mount bugs (#122)

This commit merges the PR that both ports common, ptracer and include (SoList) to C, and also fixes memory bugs and mount bugs.
This commit is contained in:
Pedro.js
2025-04-22 23:58:34 -03:00
committed by GitHub
36 changed files with 3146 additions and 3182 deletions

View File

@@ -26,6 +26,8 @@ val ccachePath by lazy {
}
val defaultCFlags = arrayOf(
"-D_GNU_SOURCE",
"-Wall", "-Wextra",
"-fno-rtti", "-fno-exceptions",
"-fno-stack-protector", "-fomit-frame-pointer",

View File

@@ -10,7 +10,7 @@ add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\")
aux_source_directory(common COMMON_SRC_LIST)
add_library(common STATIC ${COMMON_SRC_LIST})
target_include_directories(common PRIVATE include)
target_link_libraries(common cxx::cxx log)
target_link_libraries(common log)
aux_source_directory(injector INJECTOR_SRC_LIST)
add_library(zygisk SHARED ${INJECTOR_SRC_LIST})
@@ -20,6 +20,5 @@ target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap)
aux_source_directory(ptracer PTRACER_SRC_LIST)
add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST})
target_include_directories(libzygisk_ptrace.so PRIVATE include)
target_link_libraries(libzygisk_ptrace.so cxx::cxx log common)
target_link_libraries(libzygisk_ptrace.so log common)
add_subdirectory(external)

350
loader/src/common/daemon.c Normal file
View File

@@ -0,0 +1,350 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <linux/un.h>
#include "logging.h"
#include "socket_utils.h"
#include "daemon.h"
int rezygiskd_connect(uint8_t retry) {
retry++;
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd == -1) {
PLOGE("socket create");
return -1;
}
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = { 0 }
};
/*
INFO: Application must assume that sun_path can hold _POSIX_PATH_MAX characters.
Sources:
- https://pubs.opengroup.org/onlinepubs/009696699/basedefs/sys/un.h.html
*/
strcpy(addr.sun_path, TMP_PATH "/" SOCKET_FILE_NAME);
socklen_t socklen = sizeof(addr);
while (--retry) {
int ret = connect(fd, (struct sockaddr *)&addr, socklen);
if (ret == 0) return fd;
if (retry) {
PLOGE("Retrying to connect to ReZygiskd, sleep 1s");
sleep(1);
}
}
close(fd);
return -1;
}
bool rezygiskd_ping() {
int fd = rezygiskd_connect(5);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
return false;
}
write_uint8_t(fd, (uint8_t)PingHeartbeat);
close(fd);
return true;
}
uint32_t rezygiskd_get_process_flags(uid_t uid) {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
return 0;
}
write_uint8_t(fd, (uint8_t)GetProcessFlags);
write_uint32_t(fd, (uint32_t)uid);
uint32_t res = 0;
read_uint32_t(fd, &res);
close(fd);
return res;
}
void rezygiskd_get_info(struct rezygisk_info *info) {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
info->running = false;
return;
}
info->running = true;
write_uint8_t(fd, (uint8_t)GetInfo);
uint32_t flags = 0;
read_uint32_t(fd, &flags);
if (flags & (1 << 27)) info->root_impl = ROOT_IMPL_APATCH;
else if (flags & (1 << 29)) info->root_impl = ROOT_IMPL_KERNELSU;
else if (flags & (1 << 30)) info->root_impl = ROOT_IMPL_MAGISK;
else info->root_impl = ROOT_IMPL_NONE;
read_uint32_t(fd, (uint32_t *)&info->pid);
read_size_t(fd, &info->modules->modules_count);
if (info->modules->modules_count == 0) {
info->modules->modules = NULL;
close(fd);
return;
}
info->modules->modules = (char **)malloc(sizeof(char *) * info->modules->modules_count);
if (info->modules->modules == NULL) {
PLOGE("allocating modules name memory");
free(info->modules);
info->modules = NULL;
info->modules->modules_count = 0;
close(fd);
return;
}
for (size_t i = 0; i < info->modules->modules_count; i++) {
char *module_name = read_string(fd);
if (module_name == NULL) {
PLOGE("reading module name");
info->modules->modules_count = i;
free_rezygisk_info(info);
info->modules = NULL;
info->modules->modules_count = 0;
close(fd);
return;
}
char module_path[PATH_MAX];
snprintf(module_path, sizeof(module_path), "/data/adb/modules/%s/module.prop", module_name);
FILE *module_prop = fopen(module_path, "r");
if (!module_prop) {
PLOGE("failed to open module prop file %s", module_path);
info->modules->modules_count = i;
free_rezygisk_info(info);
info->modules = NULL;
info->modules->modules_count = 0;
close(fd);
return;
}
char line[1024];
while (fgets(line, sizeof(line), module_prop) != NULL) {
if (strncmp(line, "name=", strlen("name=")) != 0) continue;
info->modules->modules[i] = strndup(line + 5, strlen(line) - 6);
break;
}
fclose(module_prop);
}
close(fd);
}
void free_rezygisk_info(struct rezygisk_info *info) {
if (info->modules->modules) {
for (size_t i = 0; i < info->modules->modules_count; i++) {
free(info->modules->modules[i]);
}
free(info->modules->modules);
}
free(info->modules);
}
bool rezygiskd_read_modules(struct zygisk_modules *modules) {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
return false;
}
write_uint8_t(fd, (uint8_t)ReadModules);
size_t len = 0;
read_size_t(fd, &len);
modules->modules = malloc(len * sizeof(char *));
if (!modules->modules) {
PLOGE("allocating modules name memory");
close(fd);
return false;
}
modules->modules_count = len;
for (size_t i = 0; i < len; i++) {
char *lib_path = read_string(fd);
if (!lib_path) {
PLOGE("reading module lib_path");
close(fd);
return false;
}
modules->modules[i] = lib_path;
}
close(fd);
return true;
}
void free_modules(struct zygisk_modules *modules) {
if (modules->modules) {
for (size_t i = 0; i < modules->modules_count; i++) {
free(modules->modules[i]);
}
free(modules->modules);
}
}
int rezygiskd_connect_companion(size_t index) {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
return -1;
}
write_uint8_t(fd, (uint8_t)RequestCompanionSocket);
write_size_t(fd, index);
uint8_t res = 0;
read_uint8_t(fd, &res);
if (res == 1) return fd;
else {
close(fd);
return -1;
}
}
int rezygiskd_get_module_dir(size_t index) {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
return -1;
}
write_uint8_t(fd, (uint8_t)GetModuleDir);
write_size_t(fd, index);
int dirfd = read_fd(fd);
close(fd);
return dirfd;
}
void rezygiskd_zygote_restart() {
int fd = rezygiskd_connect(1);
if (fd == -1) {
if (errno == ENOENT) LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)");
else PLOGE("Could not notify ZygoteRestart");
return;
}
if (!write_uint8_t(fd, (uint8_t)ZygoteRestart))
PLOGE("Failed to request ZygoteRestart");
close(fd);
}
void rezygiskd_system_server_started() {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("Failed to report system server started");
return;
}
if (!write_uint8_t(fd, (uint8_t)SystemServerStarted))
PLOGE("Failed to request SystemServerStarted");
close(fd);
}
bool rezygiskd_update_mns(enum mount_namespace_state nms_state, char *buf, size_t buf_size) {
int fd = rezygiskd_connect(1);
if (fd == -1) {
PLOGE("connection to ReZygiskd");
return false;
}
write_uint8_t(fd, (uint8_t)UpdateMountNamespace);
write_uint32_t(fd, (uint32_t)getpid());
write_uint8_t(fd, (uint8_t)nms_state);
uint32_t target_pid = 0;
if (read_uint32_t(fd, &target_pid) < 0) {
PLOGE("Failed to read target pid");
close(fd);
return false;
}
uint32_t target_fd = 0;
if (read_uint32_t(fd, &target_fd) < 0) {
PLOGE("Failed to read target fd");
close(fd);
return false;
}
snprintf(buf, buf_size, "/proc/%u/fd/%u", target_pid, target_fd);
close(fd);
return true;
}

View File

@@ -1,279 +0,0 @@
#include <linux/un.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include "daemon.h"
#include "socket_utils.h"
namespace zygiskd {
static std::string TMP_PATH;
void Init(const char *path) {
TMP_PATH = path;
}
std::string GetTmpPath() {
return TMP_PATH;
}
int Connect(uint8_t retry) {
retry++;
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = { 0 }
};
auto socket_path = TMP_PATH + kCPSocketName;
strcpy(addr.sun_path, socket_path.c_str());
socklen_t socklen = sizeof(addr);
while (--retry) {
int r = connect(fd, (struct sockaddr *)&addr, socklen);
if (r == 0) return fd;
if (retry) {
PLOGE("Retrying to connect to zygiskd, sleep 1s");
sleep(1);
}
}
close(fd);
return -1;
}
bool PingHeartbeat() {
int fd = Connect(5);
if (fd == -1) {
PLOGE("Connect to zygiskd");
return false;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::PingHeartBeat);
close(fd);
return true;
}
uint32_t GetProcessFlags(uid_t uid) {
int fd = Connect(1);
if (fd == -1) {
PLOGE("GetProcessFlags");
return 0;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags);
socket_utils::write_u32(fd, uid);
uint32_t res = socket_utils::read_u32(fd);
close(fd);
return res;
}
std::vector<ModuleInfo> ReadModules() {
std::vector<ModuleInfo> modules;
int fd = Connect(1);
if (fd == -1) {
PLOGE("ReadModules");
return modules;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules);
size_t len = socket_utils::read_usize(fd);
for (size_t i = 0; i < len; i++) {
std::string lib_path = socket_utils::read_string(fd);
std::string name = socket_utils::read_string(fd);
modules.emplace_back(lib_path, name);
}
close(fd);
return modules;
}
int ConnectCompanion(size_t index) {
int fd = Connect(1);
if (fd == -1) {
PLOGE("ConnectCompanion");
return -1;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket);
socket_utils::write_usize(fd, index);
uint8_t res = socket_utils::read_u8(fd);
if (res == 1) return fd;
else {
close(fd);
return -1;
}
}
int GetModuleDir(size_t index) {
int fd = Connect(1);
if (fd == -1) {
PLOGE("GetModuleDir");
return -1;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir);
socket_utils::write_usize(fd, index);
int nfd = socket_utils::recv_fd(fd);
close(fd);
return nfd;
}
void ZygoteRestart() {
int fd = Connect(1);
if (fd == -1) {
if (errno == ENOENT) LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)");
else PLOGE("Could not notify ZygoteRestart");
return;
}
if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart))
PLOGE("Failed to request ZygoteRestart");
close(fd);
}
void SystemServerStarted() {
int fd = Connect(1);
if (fd == -1) PLOGE("Failed to report system server started");
else {
if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted))
PLOGE("Failed to report system server started");
}
close(fd);
}
void GetInfo(struct zygote_info *info) {
/* TODO: Optimize and avoid re-connect twice here */
int fd = Connect(1);
if (fd != -1) {
info->running = true;
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetInfo);
int flags = socket_utils::read_u32(fd);
if (flags & (1 << 27)) {
info->root_impl = ZYGOTE_ROOT_IMPL_APATCH;
} else if (flags & (1 << 29)) {
info->root_impl = ZYGOTE_ROOT_IMPL_KERNELSU;
} else if (flags & (1 << 30)) {
info->root_impl = ZYGOTE_ROOT_IMPL_MAGISK;
} else {
info->root_impl = ZYGOTE_ROOT_IMPL_NONE;
}
info->pid = socket_utils::read_u32(fd);
info->modules = (struct zygote_modules *)malloc(sizeof(struct zygote_modules));
if (info->modules == NULL) {
info->modules->modules_count = 0;
close(fd);
return;
}
info->modules->modules_count = socket_utils::read_usize(fd);
if (info->modules->modules_count == 0) {
info->modules->modules = NULL;
close(fd);
return;
}
info->modules->modules = (char **)malloc(sizeof(char *) * info->modules->modules_count);
if (info->modules->modules == NULL) {
free(info->modules);
info->modules = NULL;
info->modules->modules_count = 0;
close(fd);
return;
}
for (size_t i = 0; i < info->modules->modules_count; i++) {
/* INFO by ThePedroo: Ugly solution to read with std::string existance (temporary) */
std::string name = socket_utils::read_string(fd);
char module_path[PATH_MAX];
snprintf(module_path, sizeof(module_path), "/data/adb/modules/%s/module.prop", name.c_str());
FILE *module_prop = fopen(module_path, "r");
if (module_prop == NULL) {
info->modules->modules[i] = strdup(name.c_str());
} else {
char line[1024];
while (fgets(line, sizeof(line), module_prop) != NULL) {
if (strncmp(line, "name=", 5) == 0) {
info->modules->modules[i] = strndup(line + 5, strlen(line) - 6);
break;
}
}
fclose(module_prop);
}
}
close(fd);
} else info->running = false;
}
std::string UpdateMountNamespace(enum mount_namespace_state nms_state) {
int fd = Connect(1);
if (fd == -1) {
PLOGE("UpdateMountNamespace");
return "";
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::UpdateMountNamespace);
socket_utils::write_u32(fd, getpid());
socket_utils::write_u8(fd, (uint8_t)nms_state);
uint32_t target_pid = socket_utils::read_u32(fd);
int target_fd = 0;
if (target_pid == 0) goto error;
target_fd = (int)socket_utils::read_u32(fd);
if (target_fd == 0) goto error;
close(fd);
return "/proc/" + std::to_string(target_pid) + "/fd/" + std::to_string(target_fd);
error:
close(fd);
return "";
}
}

View File

@@ -0,0 +1,394 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "logging.h"
#include "elf_util.h"
#define SHT_GNU_HASH 0x6ffffff6
uint32_t ElfHash(const char *name) {
uint32_t h = 0, g = 0;
while (*name) {
h = (h << 4) + (unsigned char)*name++;
g = h & 0xf0000000;
if (g) {
h ^= g >> 24;
}
h &= ~g;
}
return h;
}
uint32_t GnuHash(const char *name) {
uint32_t h = 5381;
while (*name) {
h = (h << 5) + h + (unsigned char)(*name++);
}
return h;
}
ElfW(Shdr) *offsetOf_Shdr(ElfW(Ehdr) * head, ElfW(Off) off) {
return (ElfW(Shdr) *)(((uintptr_t)head) + off);
}
char *offsetOf_char(ElfW(Ehdr) * head, ElfW(Off) off) {
return (char *)(((uintptr_t)head) + off);
}
ElfW(Sym) *offsetOf_Sym(ElfW(Ehdr) * head, ElfW(Off) off) {
return (ElfW(Sym) *)(((uintptr_t)head) + off);
}
ElfW(Word) *offsetOf_Word(ElfW(Ehdr) * head, ElfW(Off) off) {
return (ElfW(Word) *)(((uintptr_t)head) + off);
}
int dl_cb(struct dl_phdr_info *info, size_t size, void *data) {
(void) size;
if ((info)->dlpi_name == NULL) return 0;
ElfImg *img = (ElfImg *)data;
if (strstr(info->dlpi_name, img->elf)) {
img->elf = strdup(info->dlpi_name);
img->base = (void *)info->dlpi_addr;
return 1;
}
return 0;
}
bool find_module_base(ElfImg *img) {
dl_iterate_phdr(dl_cb, img);
return img->base != NULL;
}
size_t calculate_valid_symtabs_amount(ElfImg *img) {
size_t count = 0;
if (img->symtab_start == NULL || img->symstr_offset_for_symtab == 0) return count;
for (ElfW(Off) i = 0; i < img->symtab_count; i++) {
unsigned int st_type = ELF_ST_TYPE(img->symtab_start[i].st_info);
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && img->symtab_start[i].st_size)
count++;
}
return count;
}
void ElfImg_destroy(ElfImg *img) {
if (img->elf) {
free(img->elf);
img->elf = NULL;
}
if (img->symtabs_) {
size_t valid_symtabs_amount = calculate_valid_symtabs_amount(img);
if (valid_symtabs_amount == 0) goto finalize;
for (size_t i = 0; i < valid_symtabs_amount; i++) {
free(img->symtabs_[i].name);
}
free(img->symtabs_);
img->symtabs_ = NULL;
}
if (img->header) {
munmap(img->header, img->size);
img->header = NULL;
}
finalize:
free(img);
img = NULL;
}
ElfImg *ElfImg_create(const char *elf) {
ElfImg *img = (ElfImg *)calloc(1, sizeof(ElfImg));
if (!img) {
LOGE("Failed to allocate memory for ElfImg");
return NULL;
}
img->bias = -4396;
img->elf = strdup(elf);
img->base = NULL;
if (!find_module_base(img)) {
LOGE("Failed to find module base for %s", img->elf);
ElfImg_destroy(img);
return NULL;
}
int fd = open(img->elf, O_RDONLY);
if (fd < 0) {
LOGE("failed to open %s", img->elf);
ElfImg_destroy(img);
return NULL;
}
img->size = lseek(fd, 0, SEEK_END);
if (img->size <= 0) {
LOGE("lseek() failed for %s", img->elf);
ElfImg_destroy(img);
return NULL;
}
img->header = (ElfW(Ehdr) *)mmap(NULL, img->size, PROT_READ, MAP_SHARED, fd, 0);
close(fd);
img->section_header = offsetOf_Shdr(img->header, img->header->e_shoff);
uintptr_t shoff = (uintptr_t)img->section_header;
char *section_str = offsetOf_char(img->header, img->section_header[img->header->e_shstrndx].sh_offset);
for (int i = 0; i < img->header->e_shnum; i++, shoff += img->header->e_shentsize) {
ElfW(Shdr) *section_h = (ElfW(Shdr *))shoff;
char *sname = section_h->sh_name + section_str;
size_t entsize = section_h->sh_entsize;
switch (section_h->sh_type) {
case SHT_DYNSYM: {
if (img->bias == -4396) {
img->dynsym = section_h;
img->dynsym_offset = section_h->sh_offset;
img->dynsym_start = offsetOf_Sym(img->header, img->dynsym_offset);
}
break;
}
case SHT_SYMTAB: {
if (strcmp(sname, ".symtab") == 0) {
img->symtab = section_h;
img->symtab_offset = section_h->sh_offset;
img->symtab_size = section_h->sh_size;
img->symtab_count = img->symtab_size / entsize;
img->symtab_start = offsetOf_Sym(img->header, img->symtab_offset);
}
break;
}
case SHT_STRTAB: {
if (img->bias == -4396) {
img->strtab = section_h;
img->symstr_offset = section_h->sh_offset;
img->strtab_start = offsetOf_Sym(img->header, img->symstr_offset);
}
if (strcmp(sname, ".strtab") == 0) {
img->symstr_offset_for_symtab = section_h->sh_offset;
}
break;
}
case SHT_PROGBITS: {
if (img->strtab == NULL || img->dynsym == NULL)
break;
if (img->bias == -4396) {
img->bias = (off_t)section_h->sh_addr - (off_t)section_h->sh_offset;
}
break;
}
case SHT_HASH: {
ElfW(Word) *d_un = offsetOf_Word(img->header, section_h->sh_offset);
img->nbucket_ = d_un[0];
img->bucket_ = d_un + 2;
img->chain_ = img->bucket_ + img->nbucket_;
break;
}
case SHT_GNU_HASH: {
ElfW(Word) *d_buf = (ElfW(Word) *)(((size_t)img->header) + section_h->sh_offset);
img->gnu_nbucket_ = d_buf[0];
img->gnu_symndx_ = d_buf[1];
img->gnu_bloom_size_ = d_buf[2];
img->gnu_shift2_ = d_buf[3];
img->gnu_bloom_filter_ = (uintptr_t *)(d_buf + 4);
img->gnu_bucket_ = (uint32_t *)(img->gnu_bloom_filter_ + img->gnu_bloom_size_);
img->gnu_chain_ = img->gnu_bucket_ + img->gnu_nbucket_ - img->gnu_symndx_;
break;
}
}
}
return img;
}
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash) {
if (img->nbucket_ == 0)
return 0;
char *strings = (char *)img->strtab_start;
for (size_t n = img->bucket_[hash % img->nbucket_]; n != 0; n = img->chain_[n]) {
ElfW(Sym) *sym = img->dynsym_start + n;
if (strncmp(name, strings + sym->st_name, strlen(name)) == 0)
return sym->st_value;
}
return 0;
}
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
static size_t bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
if (img->gnu_nbucket_ == 0 || img->gnu_bloom_size_ == 0)
return 0;
size_t bloom_word =
img->gnu_bloom_filter_[(hash / bloom_mask_bits) % img->gnu_bloom_size_];
uintptr_t mask = 0 | (uintptr_t)1 << (hash % bloom_mask_bits) |
(uintptr_t)1 << ((hash >> img->gnu_shift2_) % bloom_mask_bits);
if ((mask & bloom_word) == mask) {
size_t sym_index = img->gnu_bucket_[hash % img->gnu_nbucket_];
if (sym_index >= img->gnu_symndx_) {
char *strings = (char *)img->strtab_start;
do {
ElfW(Sym) *sym = img->dynsym_start + sym_index;
if (((img->gnu_chain_[sym_index] ^ hash) >> 1) == 0 &&
name == strings + sym->st_name) {
return sym->st_value;
}
} while ((img->gnu_chain_[sym_index++] & 1) == 0);
}
}
return 0;
}
ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
size_t valid_symtabs_amount = calculate_valid_symtabs_amount(img);
if (valid_symtabs_amount == 0) return 0;
if (!img->symtabs_) {
img->symtabs_ = (struct symtabs *)calloc(1, sizeof(struct symtabs) * valid_symtabs_amount);
if (!img->symtabs_) return 0;
if (img->symtab_start != NULL && img->symstr_offset_for_symtab != 0) {
ElfW(Off) i = 0;
for (ElfW(Off) pos = 0; pos < img->symtab_count; pos++) {
unsigned int st_type = ELF_ST_TYPE(img->symtab_start[pos].st_info);
const char *st_name = offsetOf_char(img->header, img->symstr_offset_for_symtab + img->symtab_start[pos].st_name);
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && img->symtab_start[pos].st_size) {
img->symtabs_[i].name = strdup(st_name);
img->symtabs_[i].sym = &img->symtab_start[pos];
i++;
}
}
}
}
for (size_t i = 0; i < valid_symtabs_amount; i++) {
if (strcmp(name, img->symtabs_[i].name) != 0) continue;
return img->symtabs_[i].sym->st_value;
}
return 0;
}
ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *name) {
size_t valid_symtabs_amount = calculate_valid_symtabs_amount(img);
if (valid_symtabs_amount == 0) return 0;
if (!img->symtabs_) {
img->symtabs_ = (struct symtabs *)malloc(sizeof(struct symtabs) * valid_symtabs_amount);
if (!img->symtabs_) return 0;
if (img->symtab_start != NULL && img->symstr_offset_for_symtab != 0) {
ElfW(Off) i = 0;
for (ElfW(Off) pos = 0; pos < img->symtab_count; pos++) {
unsigned int st_type = ELF_ST_TYPE(img->symtab_start[pos].st_info);
const char *st_name = offsetOf_char(img->header, img->symstr_offset_for_symtab + img->symtab_start[pos].st_name);
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && img->symtab_start[pos].st_size) {
img->symtabs_[i].name = strdup(st_name);
img->symtabs_[i].sym = &img->symtab_start[pos];
i++;
}
}
}
}
for (size_t i = 0; i < valid_symtabs_amount; i++) {
if (strlen(img->symtabs_[i].name) < strlen(name))
continue;
if (strncmp(img->symtabs_[i].name, name, strlen(name)) == 0)
return img->symtabs_[i].sym->st_value;
}
return 0;
}
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name) {
ElfW(Addr) offset = GnuLookup(img, name, GnuHash(name));
if (offset > 0) return offset;
offset = ElfLookup(img, name, ElfHash(name));
if (offset > 0) return offset;
offset = LinearLookup(img, name);
if (offset > 0) return offset;
return 0;
}
ElfW(Addr) getSymbAddress(ElfImg *img, const char *name) {
ElfW(Addr) offset = getSymbOffset(img, name);
if (offset < 0 || !img->base) return 0;
return ((uintptr_t)img->base + offset - img->bias);
}
ElfW(Addr) getSymbAddressByPrefix(ElfImg *img, const char *prefix) {
ElfW(Addr) offset = LinearLookupByPrefix(img, prefix);
if (offset < 0 || !img->base) return 0;
return (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
}
void *getSymbValueByPrefix(ElfImg *img, const char *prefix) {
ElfW(Addr) address = getSymbAddressByPrefix(img, prefix);
return address == 0 ? NULL : *((void **)address);
}

View File

@@ -1,263 +0,0 @@
/*
* 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 "elf_util.h"
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) : elf(base_name) {
if (!findModuleBase()) {
base = nullptr;
return;
}
//load elf
int fd = open(elf.data(), O_RDONLY);
if (fd < 0) {
// LOGE("failed to open %s", elf.data());
return;
}
size = lseek(fd, 0, SEEK_END);
if (size <= 0) {
// LOGE("lseek() failed for %s", elf.data());
}
header = reinterpret_cast<decltype(header)>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0));
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);
}
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;
}
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(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const {
if (nbucket_ == 0) return 0;
char *strings = (char *) strtab_start;
for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
auto *sym = dynsym_start + n;
if (name == strings + sym->st_name) {
return sym->st_value;
}
}
return 0;
}
ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const {
static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0;
auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_];
uintptr_t mask = 0
| (uintptr_t) 1 << (hash % bloom_mask_bits)
| (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits);
if ((mask & bloom_word) == mask) {
auto sym_index = gnu_bucket_[hash % gnu_nbucket_];
if (sym_index >= gnu_symndx_) {
char *strings = (char *) strtab_start;
do {
auto *sym = dynsym_start + sym_index;
if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0
&& name == strings + sym->st_name) {
return sym->st_value;
}
} while ((gnu_chain_[sym_index++] & 1) == 0);
}
}
return 0;
}
ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const {
if (symtabs_.empty()) {
symtabs_.reserve(symtab_count);
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
for (ElfW(Off) i = 0; i < symtab_count; i++) {
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
const char *st_name = offsetOf<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]);
}
}
}
}
if (auto i = symtabs_.find(name); i != symtabs_.end()) {
return i->second->st_value;
} else {
return 0;
}
}
ElfW(Addr) ElfImg::LinearLookupByPrefix(std::string_view name) const {
if (symtabs_.empty()) {
symtabs_.reserve(symtab_count);
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
for (ElfW(Off) i = 0; i < symtab_count; i++) {
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
const char *st_name = offsetOf<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]);
}
}
}
}
auto size = name.size();
for (auto symtab : symtabs_) {
if (symtab.first.size() < size) continue;
if (symtab.first.substr(0, size) == name) {
return symtab.second->st_value;
}
}
return 0;
}
ElfImg::~ElfImg() {
//open elf file local
if (buffer) {
free(buffer);
buffer = nullptr;
}
//use mmap
if (header) {
munmap(header, size);
}
}
ElfW(Addr) ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const {
if (auto offset = GnuLookup(name, gnu_hash); offset > 0) {
// LOGD("found %s %p in %s in dynsym by gnuhash", name.data(), reinterpret_cast<void *>(offset), elf.data());
return offset;
} else if (offset = ElfLookup(name, elf_hash); offset > 0) {
// LOGD("found %s %p in %s in dynsym by elfhash", name.data(), reinterpret_cast<void *>(offset), elf.data());
return offset;
} else if (offset = LinearLookup(name); offset > 0) {
// LOGD("found %s %p in %s in symtab by linear lookup", name.data(), reinterpret_cast<void *>(offset), elf.data());
return offset;
} else {
return 0;
}
}
bool ElfImg::findModuleBase() {
dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data) -> int {
(void) size;
if ((info)->dlpi_name == nullptr) {
return 0;
}
auto *self = reinterpret_cast<ElfImg *>(data);
if (strstr(info->dlpi_name, self->elf.data())) {
self->elf = info->dlpi_name;
self->base = reinterpret_cast<void *>(info->dlpi_addr);
return 1;
}
return 0;
}, this);
return base != 0;
}

View File

@@ -1,137 +0,0 @@
#include <sys/sysmacros.h>
#include "files.hpp"
#include "misc.hpp"
using namespace std::string_view_literals;
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn) {
size_t len = 1024;
char *buf = (char *) malloc(len);
char *start;
ssize_t read;
while ((read = getline(&buf, &len, fp)) >= 0) {
start = buf;
if (trim) {
while (read && "\n\r "sv.find(buf[read - 1]) != std::string::npos)
--read;
buf[read] = '\0';
while (*start == ' ')
++start;
}
if (!fn(start))
break;
}
free(buf);
}
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn) {
if (auto fp = open_file(file, "re"))
file_readline(trim, fp.get(), fn);
}
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn) {
file_readline(false, file, fn);
}
std::vector<mount_info> parse_mount_info(const char *pid) {
char buf[PATH_MAX] = {};
snprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid);
std::vector<mount_info> result;
file_readline(buf, [&result](std::string_view line) -> bool {
int root_start = 0, root_end = 0;
int target_start = 0, target_end = 0;
int vfs_option_start = 0, vfs_option_end = 0;
int type_start = 0, type_end = 0;
int source_start = 0, source_end = 0;
int fs_option_start = 0, fs_option_end = 0;
int optional_start = 0, optional_end = 0;
unsigned int id, parent, maj, min;
sscanf(line.data(),
"%u " // (1) id
"%u " // (2) parent
"%u:%u " // (3) maj:min
"%n%*s%n " // (4) mountroot
"%n%*s%n " // (5) target
"%n%*s%n" // (6) vfs options (fs-independent)
"%n%*[^-]%n - " // (7) optional fields
"%n%*s%n " // (8) FS type
"%n%*s%n " // (9) source
"%n%*s%n", // (10) fs options (fs specific)
&id, &parent, &maj, &min, &root_start, &root_end, &target_start,
&target_end, &vfs_option_start, &vfs_option_end,
&optional_start, &optional_end, &type_start, &type_end,
&source_start, &source_end, &fs_option_start, &fs_option_end);
auto root = line.substr(root_start, root_end - root_start);
auto target = line.substr(target_start, target_end - target_start);
auto vfs_option =
line.substr(vfs_option_start, vfs_option_end - vfs_option_start);
++optional_start;
--optional_end;
auto optional = line.substr(
optional_start,
optional_end - optional_start > 0 ? optional_end - optional_start : 0);
auto type = line.substr(type_start, type_end - type_start);
auto source = line.substr(source_start, source_end - source_start);
auto fs_option =
line.substr(fs_option_start, fs_option_end - fs_option_start);
unsigned int shared = 0;
unsigned int master = 0;
unsigned int propagate_from = 0;
if (auto pos = optional.find("shared:"); pos != std::string_view::npos) {
shared = parse_int(optional.substr(pos + 7));
}
if (auto pos = optional.find("master:"); pos != std::string_view::npos) {
master = parse_int(optional.substr(pos + 7));
}
if (auto pos = optional.find("propagate_from:");
pos != std::string_view::npos) {
propagate_from = parse_int(optional.substr(pos + 15));
}
result.emplace_back(mount_info {
.id = id,
.parent = parent,
.device = static_cast<dev_t>(makedev(maj, min)),
.root {root},
.target {target},
.vfs_option {vfs_option},
.optional {
.shared = shared,
.master = master,
.propagate_from = propagate_from,
},
.type {type},
.source {source},
.fs_option {fs_option},
});
return true;
});
return result;
}
sDIR make_dir(DIR *dp) {
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
}
sFILE make_file(FILE *fp) {
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
}
int get_path_from_fd(int fd, char *buf, size_t size) {
if (fd < 0 || !buf || size == 0) return -1;
/* NOTE: We assume that the path is always at /data/adb/modules/xxx
which should never be longer than 128 chars. */
char proc_path[128];
snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd);
ssize_t len = readlink(proc_path, buf, size - 1);
if (len == -1) return -1;
buf[len] = '\0';
return 0;
}

14
loader/src/common/misc.c Normal file
View File

@@ -0,0 +1,14 @@
int parse_int(const char *str) {
int val = 0;
char *c = (char *)str;
while (*c) {
if (*c > '9' || *c < '0')
return -1;
val = val * 10 + *c - '0';
c++;
}
return val;
}

View File

@@ -1,49 +0,0 @@
#include "misc.hpp"
int new_daemon_thread(thread_entry entry, void *arg) {
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
errno = pthread_create(&thread, &attr, entry, arg);
if (errno) {
PLOGE("pthread_create");
}
return errno;
}
int parse_int(std::string_view s) {
int val = 0;
for (char c : s) {
if (!c) break;
if (c > '9' || c < '0')
return -1;
val = val * 10 + c - '0';
}
return val;
}
std::list<std::string> split_str(std::string_view s, std::string_view delimiter) {
std::list<std::string> ret;
size_t pos = 0;
while (pos < s.size()) {
auto next = s.find(delimiter, pos);
if (next == std::string_view::npos) {
ret.emplace_back(s.substr(pos));
break;
}
ret.emplace_back(s.substr(pos, next - pos));
pos = next + delimiter.size();
}
return ret;
}
std::string join_str(const std::list<std::string>& list, std::string_view delimiter) {
std::string ret;
for (auto& s : list) {
if (!ret.empty())
ret += delimiter;
ret += s;
}
return ret;
}

View File

@@ -0,0 +1,95 @@
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include "logging.h"
#include "socket_utils.h"
/* TODO: Standardize how to log errors */
int read_fd(int fd) {
char cmsgbuf[CMSG_SPACE(sizeof(int))];
int cnt = 1;
struct iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt)
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsgbuf,
.msg_controllen = sizeof(cmsgbuf)
};
ssize_t ret = recvmsg(fd, &msg, MSG_WAITALL);
if (ret == -1) {
PLOGE("recvmsg");
return -1;
}
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg == NULL) {
PLOGE("CMSG_FIRSTHDR");
return -1;
}
int sendfd;
memcpy(&sendfd, CMSG_DATA(cmsg), sizeof(int));
return sendfd;
}
char *read_string(int fd) {
size_t str_len = 0;
ssize_t read_bytes = read(fd, &str_len, sizeof(size_t));
if (read_bytes != (ssize_t)sizeof(size_t)) {
LOGE("Failed to read string length: Not all bytes were read (%zd != %zu).\n", read_bytes, sizeof(size_t));
return NULL;
}
char *buf = malloc(str_len + 1);
if (buf == NULL) {
PLOGE("allocate memory for string");
return NULL;
}
read_bytes = read(fd, buf, str_len);
if (read_bytes != (ssize_t)str_len) {
LOGE("Failed to read string: Promised bytes doesn't exist (%zd != %zu).\n", read_bytes, str_len);
free(buf);
return NULL;
}
if (str_len > 0) buf[str_len] = '\0';
return buf;
}
#define write_func(type) \
ssize_t write_## type(int fd, type val) { \
return write(fd, &val, sizeof(type)); \
}
#define read_func(type) \
ssize_t read_## type(int fd, type *val) { \
return read(fd, val, sizeof(type)); \
}
write_func(uint8_t)
read_func(uint8_t)
write_func(uint32_t)
read_func(uint32_t)
write_func(size_t)
read_func(size_t)

View File

@@ -1,137 +0,0 @@
#include <cstddef>
#include <sys/socket.h>
#include <unistd.h>
#include "socket_utils.h"
namespace socket_utils {
ssize_t xread(int fd, void* buf, size_t count) {
size_t read_sz = 0;
ssize_t ret;
do {
ret = read(fd, (std::byte*) buf + read_sz, count - read_sz);
if (ret < 0) {
if (errno == EINTR) continue;
PLOGE("read");
return ret;
}
read_sz += ret;
} while (read_sz != count && ret != 0);
if (read_sz != count) {
PLOGE("read (%zu != %zu)", count, read_sz);
}
return read_sz;
}
size_t xwrite(int fd, const void* buf, size_t count) {
size_t write_sz = 0;
ssize_t ret;
do {
ret = write(fd, (std::byte*) buf + write_sz, count - write_sz);
if (ret < 0) {
if (errno == EINTR) continue;
PLOGE("write");
return write_sz;
}
write_sz += ret;
} while (write_sz != count && ret != 0);
if (write_sz != count) {
PLOGE("write (%zu != %zu)", count, write_sz);
}
return write_sz;
}
ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) {
int rec = recvmsg(sockfd, msg, flags);
if (rec < 0) PLOGE("recvmsg");
return rec;
}
template<typename T>
inline T read_exact_or(int fd, T fail) {
T res;
return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : fail;
}
template<typename T>
inline bool write_exact(int fd, T val) {
return sizeof(T) == xwrite(fd, &val, sizeof(T));
}
uint8_t read_u8(int fd) {
return read_exact_or<uint8_t>(fd, 0);
}
uint32_t read_u32(int fd) {
return read_exact_or<uint32_t>(fd, 0);
}
size_t read_usize(int fd) {
return read_exact_or<size_t>(fd, 0);
}
bool write_usize(int fd, size_t val) {
return write_exact<size_t>(fd, val);
}
std::string read_string(int fd) {
size_t len = read_usize(fd);
char buf[len + 1];
xread(fd, buf, len);
buf[len] = '\0';
return buf;
}
bool write_u8(int fd, uint8_t val) {
return write_exact<uint8_t>(fd, val);
}
void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) {
iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt),
};
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsgbuf,
.msg_controllen = bufsz
};
xrecvmsg(sockfd, &msg, MSG_WAITALL);
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
if (msg.msg_controllen != bufsz ||
cmsg == nullptr ||
// TODO: pass from rust: 20, expected: 16
// cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_RIGHTS) {
return nullptr;
}
return CMSG_DATA(cmsg);
}
int recv_fd(int sockfd) {
char cmsgbuf[CMSG_SPACE(sizeof(int))];
void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
if (data == nullptr) return -1;
int result;
memcpy(&result, data, sizeof(int));
return result;
}
bool write_u32(int fd, uint32_t val) {
return write_exact<uint32_t>(fd, val);
}
bool write_string(int fd, std::string_view str) {
return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size());
}
}

View File

@@ -1,113 +1,91 @@
#pragma once
#ifndef DAEMON_H
#define DAEMON_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <stdbool.h>
#include <string_view>
#include <string>
#include <unistd.h>
#include <vector>
#if defined(__LP64__)
# define LP_SELECT(lp32, lp64) lp64
#ifdef __LP64__
#define LP_SELECT(lp32, lp64) lp64
#else
# define LP_SELECT(lp32, lp64) lp32
#define LP_SELECT(lp32, lp64) lp32
#endif
constexpr auto kCPSocketName = "/" LP_SELECT("cp32", "cp64") ".sock";
#define SOCKET_FILE_NAME LP_SELECT("cp32", "cp64") ".sock"
class UniqueFd {
using Fd = int;
public:
UniqueFd() = default;
UniqueFd(Fd fd) : fd_(fd) {}
~UniqueFd() { if (fd_ >= 0) close(fd_); }
// Disallow copy
UniqueFd(const UniqueFd&) = delete;
UniqueFd& operator=(const UniqueFd&) = delete;
// Allow move
UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); }
UniqueFd& operator=(UniqueFd&& other) {
std::swap(fd_, other.fd_);
return *this;
}
// Implict cast to Fd
operator const Fd&() const { return fd_; }
private:
Fd fd_ = -1;
enum rezygiskd_actions {
PingHeartbeat,
GetProcessFlags,
GetInfo,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
SystemServerStarted,
UpdateMountNamespace
};
struct zygote_modules {
struct zygisk_modules {
char **modules;
size_t modules_count;
};
enum zygote_root_impl {
ZYGOTE_ROOT_IMPL_NONE,
ZYGOTE_ROOT_IMPL_APATCH,
ZYGOTE_ROOT_IMPL_KERNELSU,
ZYGOTE_ROOT_IMPL_MAGISK
enum root_impl {
ROOT_IMPL_NONE,
ROOT_IMPL_APATCH,
ROOT_IMPL_KERNELSU,
ROOT_IMPL_MAGISK
};
struct zygote_info {
struct zygote_modules *modules;
enum zygote_root_impl root_impl;
struct rezygisk_info {
struct zygisk_modules *modules;
enum root_impl root_impl;
pid_t pid;
bool running;
};
enum mount_namespace_state {
Clean,
Rooted,
Module
Clean,
Rooted,
Module
};
namespace zygiskd {
#define TMP_PATH "/data/adb/rezygisk"
struct ModuleInfo {
std::string path;
/* TODO: Perhaps we can also remove this and just send paths? */
std::string name;
inline explicit ModuleInfo(std::string path, std::string name) : path(path), name(name) {}
};
enum class SocketAction {
PingHeartBeat,
GetProcessFlags,
GetInfo,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
SystemServerStarted,
UpdateMountNamespace
};
void Init(const char *path);
std::string GetTmpPath();
bool PingHeartbeat();
std::vector<ModuleInfo> ReadModules();
uint32_t GetProcessFlags(uid_t uid);
int ConnectCompanion(size_t index);
int GetModuleDir(size_t index);
void ZygoteRestart();
void SystemServerStarted();
void GetInfo(struct zygote_info *info);
std::string UpdateMountNamespace(enum mount_namespace_state mns_state);
static inline const char *rezygiskd_get_path() {
return TMP_PATH;
}
int rezygiskd_connect(uint8_t retry);
bool rezygiskd_ping();
uint32_t rezygiskd_get_process_flags(uid_t uid);
void rezygiskd_get_info(struct rezygisk_info *info);
void free_rezygisk_info(struct rezygisk_info *info);
bool rezygiskd_read_modules(struct zygisk_modules *modules);
void free_modules(struct zygisk_modules *modules);
int rezygiskd_connect_companion(size_t index);
int rezygiskd_get_module_dir(size_t index);
void rezygiskd_zygote_restart();
void rezygiskd_system_server_started();
bool rezygiskd_update_mns(enum mount_namespace_state nms_state, char *buf, size_t buf_size);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* DAEMON_H */

View File

@@ -1,152 +1,74 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2019 Swift Gan
* Copyright (C) 2021 LSPosed Contributors
*/
#ifndef SANDHOOK_ELF_UTIL_H
#define SANDHOOK_ELF_UTIL_H
#ifndef ELF_UTIL_H
#define ELF_UTIL_H
#include <string_view>
#include <unordered_map>
#include <string.h>
#include <link.h>
#include <linux/elf.h>
#include <sys/types.h>
#include <link.h>
#include <string>
#define SHT_GNU_HASH 0x6ffffff6
namespace SandHook {
class ElfImg {
public:
struct symtabs {
char *name;
ElfW(Sym) *sym;
};
ElfImg(std::string_view elf);
typedef struct {
char *elf;
void *base;
char *buffer;
off_t size;
off_t bias;
ElfW(Ehdr) *header;
ElfW(Shdr) *section_header;
ElfW(Shdr) *symtab;
ElfW(Shdr) *strtab;
ElfW(Shdr) *dynsym;
ElfW(Sym) *symtab_start;
ElfW(Sym) *dynsym_start;
ElfW(Sym) *strtab_start;
ElfW(Off) symtab_count;
ElfW(Off) symstr_offset;
ElfW(Off) symstr_offset_for_symtab;
ElfW(Off) symtab_offset;
ElfW(Off) dynsym_offset;
ElfW(Off) symtab_size;
constexpr ElfW(Addr) getSymbOffset(std::string_view name) const {
return getSymbOffset(name, GnuHash(name), ElfHash(name));
}
uint32_t nbucket_;
uint32_t *bucket_;
uint32_t *chain_;
constexpr ElfW(Addr) getSymbAddress(std::string_view name) const {
ElfW(Addr) offset = getSymbOffset(name);
if (offset > 0 && base != nullptr) {
return static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias);
} else {
return 0;
}
}
uint32_t gnu_nbucket_;
uint32_t gnu_symndx_;
uint32_t gnu_bloom_size_;
uint32_t gnu_shift2_;
uintptr_t *gnu_bloom_filter_;
uint32_t *gnu_bucket_;
uint32_t *gnu_chain_;
constexpr ElfW(Addr) getSymbAddressByPrefix(std::string_view prefix) const {
ElfW(Addr) offset = LinearLookupByPrefix(prefix);
if (offset > 0 && base != nullptr) {
return static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias);
} else {
return 0;
}
}
struct symtabs *symtabs_;
} ElfImg;
template<typename T>
constexpr T getSymbAddress(std::string_view name) const {
return reinterpret_cast<T>(getSymbAddress(name));
}
void ElfImg_destroy(ElfImg *img);
template<typename T>
constexpr T getSymbAddressByPrefix(std::string_view prefix) const {
return reinterpret_cast<T>(getSymbAddressByPrefix(prefix));
}
ElfImg *ElfImg_create(const char *elf);
bool isValid() const {
return base != nullptr;
}
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash);
const std::string name() const {
return elf;
}
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash);
~ElfImg();
ElfW(Addr) LinearLookup(ElfImg *restrict img, const char *restrict name);
private:
ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const;
ElfW(Addr) LinearLookupByPrefix(ElfImg *restrict img, const char *name);
ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const;
int dl_cb(struct dl_phdr_info *info, size_t size, void *data);
ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const;
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name);
ElfW(Addr) LinearLookup(std::string_view name) const;
ElfW(Addr) getSymbAddress(ElfImg *img, const char *name);
ElfW(Addr) LinearLookupByPrefix(std::string_view name) const;
ElfW(Addr) getSymbAddressByPrefix(ElfImg *img, const char *prefix);
constexpr static uint32_t ElfHash(std::string_view name);
void *getSymbValueByPrefix(ElfImg *img, const char *prefix);
constexpr static uint32_t GnuHash(std::string_view name);
bool findModuleBase();
std::string elf;
void *base = nullptr;
char *buffer = nullptr;
off_t size = 0;
off_t bias = -4396;
ElfW(Ehdr) *header = nullptr;
ElfW(Shdr) *section_header = nullptr;
ElfW(Shdr) *symtab = nullptr;
ElfW(Shdr) *strtab = nullptr;
ElfW(Shdr) *dynsym = nullptr;
ElfW(Sym) *symtab_start = nullptr;
ElfW(Sym) *dynsym_start = nullptr;
ElfW(Sym) *strtab_start = nullptr;
ElfW(Off) symtab_count = 0;
ElfW(Off) symstr_offset = 0;
ElfW(Off) symstr_offset_for_symtab = 0;
ElfW(Off) symtab_offset = 0;
ElfW(Off) dynsym_offset = 0;
ElfW(Off) symtab_size = 0;
uint32_t nbucket_{};
uint32_t *bucket_ = nullptr;
uint32_t *chain_ = nullptr;
uint32_t gnu_nbucket_{};
uint32_t gnu_symndx_{};
uint32_t gnu_bloom_size_;
uint32_t gnu_shift2_;
uintptr_t *gnu_bloom_filter_;
uint32_t *gnu_bucket_;
uint32_t *gnu_chain_;
mutable std::unordered_map<std::string_view, ElfW(Sym) *> symtabs_;
};
constexpr uint32_t ElfImg::ElfHash(std::string_view name) {
uint32_t h = 0, g = 0;
for (unsigned char p: name) {
h = (h << 4) + p;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
constexpr uint32_t ElfImg::GnuHash(std::string_view name) {
uint32_t h = 5381;
for (unsigned char p: name) {
h += (h << 5) + p;
}
return h;
}
}
#endif //SANDHOOK_ELF_UTIL_H
#endif /* ELF_UTIL_H */

View File

@@ -1,59 +0,0 @@
#include <dirent.h>
#include <functional>
#include <string>
#include <vector>
#include <unistd.h>
struct mount_info {
unsigned int id;
unsigned int parent;
dev_t device;
std::string root;
std::string target;
std::string vfs_option;
struct {
unsigned int shared;
unsigned int master;
unsigned int propagate_from;
} optional;
std::string type;
std::string source;
std::string fs_option;
};
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
std::vector<mount_info> parse_mount_info(const char *pid);
int get_path_from_fd(int fd, char *buf, size_t size);
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
sDIR make_dir(DIR *dp);
sFILE make_file(FILE *fp);
static inline sDIR open_dir(const char *path) {
return make_dir(opendir(path));
}
static inline sDIR xopen_dir(const char *path) {
return make_dir(opendir(path));
}
static inline sDIR xopen_dir(int dirfd) {
return make_dir(fdopendir(dirfd));
}
static inline sFILE open_file(const char *path, const char *mode) {
return make_file(fopen(path, mode));
}
static inline sFILE xopen_file(const char *path, const char *mode) {
return make_file(fopen(path, mode));
}
static inline sFILE xopen_file(int fd, const char *mode) {
return make_file(fdopen(fd, mode));
}

17
loader/src/include/misc.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef MISC_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.
*/
int parse_int(const char *str);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* MISC_H */

View File

@@ -1,98 +0,0 @@
#pragma once
#include <list>
#include <memory>
#include <pthread.h>
#include <string>
#include <string_view>
#include "logging.h"
#define DISALLOW_COPY_AND_MOVE(clazz) \
clazz(const clazz &) = delete; \
clazz(clazz &&) = delete;
class mutex_guard {
DISALLOW_COPY_AND_MOVE(mutex_guard)
public:
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
pthread_mutex_lock(mutex);
}
void unlock() {
pthread_mutex_unlock(mutex);
mutex = nullptr;
}
~mutex_guard() {
if (mutex) pthread_mutex_unlock(mutex);
}
private:
pthread_mutex_t *mutex;
};
using thread_entry = void *(*)(void *);
int new_daemon_thread(thread_entry entry, void *arg);
static inline bool str_contains(std::string_view s, std::string_view ss) {
return s.find(ss) != std::string_view::npos;
}
template<typename T, typename Impl>
class stateless_allocator {
public:
using value_type = T;
T *allocate(size_t num) { return static_cast<T*>(Impl::allocate(sizeof(T) * num)); }
void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); }
stateless_allocator() = default;
stateless_allocator(const stateless_allocator&) = default;
stateless_allocator(stateless_allocator&&) = default;
template <typename U>
stateless_allocator(const stateless_allocator<U, Impl>&) {}
bool operator==(const stateless_allocator&) { return true; }
bool operator!=(const stateless_allocator&) { return false; }
};
template <typename T>
class reversed_container {
public:
reversed_container(T &base) : base(base) {}
decltype(std::declval<T>().rbegin()) begin() { return base.rbegin(); }
decltype(std::declval<T>().crbegin()) begin() const { return base.crbegin(); }
decltype(std::declval<T>().crbegin()) cbegin() const { return base.crbegin(); }
decltype(std::declval<T>().rend()) end() { return base.rend(); }
decltype(std::declval<T>().crend()) end() const { return base.crend(); }
decltype(std::declval<T>().crend()) cend() const { return base.crend(); }
private:
T &base;
};
template <typename T>
reversed_container<T> reversed(T &base) {
return reversed_container<T>(base);
}
template<class T>
static inline void default_new(T *&p) { p = new T(); }
template<class T>
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
struct StringCmp {
using is_transparent = void;
bool operator()(std::string_view a, std::string_view b) const { return a < b; }
};
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.
*/
int parse_int(std::string_view s);
std::list<std::string> split_str(std::string_view s, std::string_view delimiter);
std::string join_str(const std::list<std::string>& list, std::string_view delimiter);
template <typename T>
static inline T align_to(T v, int a) {
static_assert(std::is_integral<T>::value);
return (v + a - 1) / a * a;
}

View File

@@ -1,33 +0,0 @@
#pragma once
#include <android/api-level.h>
#include <cstdint>
template<unsigned>
struct NativeBridgeCallbacks;
template<>
struct NativeBridgeCallbacks<__ANDROID_API_Q__> {
[[maybe_unused]] uint32_t version;
[[maybe_unused]] void *initialize;
[[maybe_unused]] void *loadLibrary;
[[maybe_unused]] void *getTrampoline;
[[maybe_unused]] void *isSupported;
[[maybe_unused]] void *getAppEnv;
[[maybe_unused]] void *isCompatibleWith;
[[maybe_unused]] void *getSignalHandler;
[[maybe_unused]] void *unloadLibrary;
[[maybe_unused]] void *getError;
[[maybe_unused]] void *isPathSupported;
[[maybe_unused]] void *initAnonymousNamespace;
[[maybe_unused]] void *createNamespace;
[[maybe_unused]] void *linkNamespaces;
[[maybe_unused]] void *loadLibraryExt;
[[maybe_unused]] void *getVendorNamespace;
[[maybe_unused]] void *getExportedNamespace;
};
template<>
struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> {
[[maybe_unused]] void *preZygoteFork;
};

View File

@@ -1,31 +1,25 @@
#pragma once
#ifndef SOCKET_UTILS_H
#define SOCKET_UTILS_H
#include <string>
#include <string_view>
#include <stdint.h>
#include "logging.h"
int read_fd(int fd);
namespace socket_utils {
char *read_string(int fd);
ssize_t xread(int fd, void *buf, size_t count);
#define write_func_def(type) \
ssize_t write_## type(int fd, type val)
size_t xwrite(int fd, const void *buf, size_t count);
#define read_func_def(type) \
ssize_t read_## type(int fd, type *val)
uint8_t read_u8(int fd);
write_func_def(uint8_t);
read_func_def(uint8_t);
uint32_t read_u32(int fd);
write_func_def(uint32_t);
read_func_def(uint32_t);
size_t read_usize(int fd);
write_func_def(size_t);
read_func_def(size_t);
std::string read_string(int fd);
bool write_u8(int fd, uint8_t val);
bool write_u32(int fd, uint32_t val);
int recv_fd(int fd);
bool write_usize(int fd, size_t val);
bool write_string(int fd, std::string_view str);
}
#endif /* SOCKET_UTILS_H */

View File

@@ -1,211 +0,0 @@
//
// Original from https://github.com/LSPosed/NativeDetector/blob/master/app/src/main/jni/solist.cpp
//
#pragma once
#include <string>
#include "elf_util.h"
#include "logging.h"
namespace SoList {
class SoInfo {
public:
#ifdef __LP64__
inline static size_t solist_size_offset = 0x18;
inline static size_t solist_next_offset = 0x28;
constexpr static size_t solist_realpath_offset = 0x1a8;
#else
inline static size_t solist_size_offset = 0x90;
inline static size_t solist_next_offset = 0xa4;
constexpr static size_t solist_realpath_offset = 0x174;
#endif
inline static const char *(*get_realpath_sym)(SoInfo *) = NULL;
inline static void (*soinfo_free)(SoInfo *) = NULL;
inline SoInfo *get_next() {
return *(SoInfo **) ((uintptr_t) this + solist_next_offset);
}
inline size_t get_size() {
return *(size_t *) ((uintptr_t) this + solist_size_offset);
}
inline const char *get_path() {
if (get_realpath_sym) return get_realpath_sym(this);
return ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str();
}
void set_next(SoInfo *si) {
*(SoInfo **) ((uintptr_t) this + solist_next_offset) = si;
}
void set_size(size_t size) {
*(size_t *) ((uintptr_t) this + solist_size_offset) = size;
}
};
class ProtectedDataGuard {
public:
ProtectedDataGuard() {
if (ctor != nullptr)
(this->*ctor)();
}
~ProtectedDataGuard() {
if (dtor != nullptr)
(this->*dtor)();
}
static bool setup(const SandHook::ElfImg &linker) {
ctor = MemFunc{.data = {.p = reinterpret_cast<void *>(linker.getSymbAddress(
"__dl__ZN18ProtectedDataGuardC2Ev")), .adj = 0}}.f;
dtor = MemFunc{.data = {.p = reinterpret_cast<void *>(linker.getSymbAddress(
"__dl__ZN18ProtectedDataGuardD2Ev")), .adj = 0}}.f;
return ctor != nullptr && dtor != nullptr;
}
ProtectedDataGuard(const ProtectedDataGuard &) = delete;
void operator=(const ProtectedDataGuard &) = delete;
private:
using FuncType = void (ProtectedDataGuard::*)();
inline static FuncType ctor = NULL;
inline static FuncType dtor = NULL;
union MemFunc {
FuncType f;
struct {
void *p;
std::ptrdiff_t adj;
} data;
};
};
static SoInfo *solist = NULL;
static SoInfo *somain = NULL;
static SoInfo **sonext = NULL;
static uint64_t *g_module_load_counter = NULL;
static uint64_t *g_module_unload_counter = NULL;
static bool Initialize();
template<typename T>
inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) {
auto *addr = reinterpret_cast<T **>(linker.getSymbAddress(name));
return addr == NULL ? NULL : *addr;
}
template<typename T>
inline T *getStaticPointerByPrefix(const SandHook::ElfImg &linker, const char *name) {
auto *addr = reinterpret_cast<T **>(linker.getSymbAddressByPrefix(name));
return addr == NULL ? NULL : *addr;
}
static bool DropSoPath(const char* target_path) {
bool path_found = false;
if (solist == NULL && !Initialize()) {
LOGE("Failed to initialize solist");
return path_found;
}
for (auto iter = solist; iter; iter = iter->get_next()) {
if (iter->get_path() && strstr(iter->get_path(), target_path)) {
SoList::ProtectedDataGuard guard;
LOGV("dropping solist record loaded at %s with size %zu", iter->get_path(), iter->get_size());
if (iter->get_size() > 0) {
iter->set_size(0);
SoInfo::soinfo_free(iter);
path_found = true;
}
}
}
return path_found;
}
static void ResetCounters(size_t load, size_t unload) {
if (solist == NULL && !Initialize()) {
LOGE("Failed to initialize solist");
return;
}
if (g_module_load_counter == NULL || g_module_unload_counter == NULL) {
LOGD("g_module counters not defined, skip reseting them");
return;
}
auto loaded_modules = *g_module_load_counter;
auto unloaded_modules = *g_module_unload_counter;
if (loaded_modules >= load) {
*g_module_load_counter = loaded_modules - load;
LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter);
}
if (unloaded_modules >= unload) {
*g_module_unload_counter = unloaded_modules - unload;
LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter);
}
}
static bool Initialize() {
SandHook::ElfImg linker("/linker");
if (!ProtectedDataGuard::setup(linker)) return false;
LOGD("found symbol ProtectedDataGuard");
/* INFO: Since Android 15, the symbol names for the linker have a suffix,
this makes it impossible to hardcode the symbol names. To allow
this to work on all versions, we need to iterate over the loaded
symbols and find the correct ones.
See #63 for more information.
*/
solist = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL6solist");
if (solist == NULL) return false;
LOGD("found symbol solist");
somain = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL6somain");
if (somain == NULL) return false;
LOGD("found symbol somain");
sonext = linker.getSymbAddressByPrefix<SoInfo **>("__dl__ZL6sonext");
if (sonext == NULL) return false;
LOGD("found symbol sonext");
SoInfo *vdso = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL4vdso");
if (vdso != NULL) LOGD("found symbol vdso");
SoInfo::get_realpath_sym = reinterpret_cast<decltype(SoInfo::get_realpath_sym)>(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv"));
if (SoInfo::get_realpath_sym == NULL) return false;
LOGD("found symbol get_realpath_sym");
SoInfo::soinfo_free = reinterpret_cast<decltype(SoInfo::soinfo_free)>(linker.getSymbAddressByPrefix("__dl__ZL11soinfo_freeP6soinfo"));
if (SoInfo::soinfo_free == NULL) return false;
LOGD("found symbol soinfo_free");
g_module_load_counter = reinterpret_cast<decltype(g_module_load_counter)>(linker.getSymbAddress("__dl__ZL21g_module_load_counter"));
if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter");
g_module_unload_counter = reinterpret_cast<decltype(g_module_unload_counter)>(linker.getSymbAddress("__dl__ZL23g_module_unload_counter"));
if (g_module_unload_counter != NULL) LOGD("found symbol g_module_unload_counter");
for (size_t i = 0; i < 1024 / sizeof(void *); i++) {
auto possible_field = (uintptr_t) solist + i * sizeof(void *);
auto possible_size_of_somain = *(size_t *)((uintptr_t) somain + i * sizeof(void *));
if (possible_size_of_somain < 0x100000 && possible_size_of_somain > 0x100) {
SoInfo::solist_size_offset = i * sizeof(void *);
LOGD("solist_size_offset is %zu * %zu = %p", i, sizeof(void *), (void*) SoInfo::solist_size_offset);
}
if (*(void **)possible_field == somain || (vdso != NULL && *(void **)possible_field == vdso)) {
SoInfo::solist_next_offset = i * sizeof(void *);
LOGD("solist_next_offset is %zu * %zu = %p", i, sizeof(void *), (void*) SoInfo::solist_next_offset);
break;
}
}
return true;
}
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "logging.h"
#include "jni_helper.hpp"
template <typename T>

View File

@@ -10,12 +10,13 @@ size_t block_size = 0;
extern "C" [[gnu::visibility("default")]]
void entry(void* addr, size_t size, const char* path) {
LOGD("Zygisk library injected, version %s", ZKSU_VERSION);
start_addr = addr;
block_size = size;
zygiskd::Init(path);
if (!zygiskd::PingHeartbeat()) {
if (!rezygiskd_ping()) {
LOGE("Zygisk daemon is not running");
return;
}

View File

@@ -1,4 +1,3 @@
#include <android/dlext.h>
#include <sys/mount.h>
#include <dlfcn.h>
#include <regex.h>
@@ -6,22 +5,26 @@
#include <list>
#include <map>
#include <array>
#include <vector>
#include <lsplt.hpp>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <pthread.h>
#include "daemon.h"
#include "zygisk.hpp"
#include "module.hpp"
#include "files.hpp"
#include "misc.hpp"
#include "misc.h"
#include "solist.hpp"
#include "solist.h"
#include "art_method.hpp"
@@ -119,7 +122,7 @@ struct ZygiskContext {
// Global variables
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
map<string, vector<JNINativeMethod>> *jni_hook_list;
bool should_unmap_zygisk = false;
std::vector<lsplt::MapInfo> cached_map_infos = {};
@@ -137,8 +140,8 @@ DCL_HOOK_FUNC(int, fork) {
}
bool update_mnt_ns(enum mount_namespace_state mns_state, bool dry_run) {
std::string ns_path = zygiskd::UpdateMountNamespace(mns_state);
if (ns_path.empty()) {
char ns_path[PATH_MAX];
if (rezygiskd_update_mns(mns_state, ns_path, sizeof(ns_path)) == false) {
PLOGE("Failed to update mount namespace");
return false;
@@ -146,16 +149,16 @@ bool update_mnt_ns(enum mount_namespace_state mns_state, bool dry_run) {
if (dry_run) return true;
int updated_ns = open(ns_path.data(), O_RDONLY);
int updated_ns = open(ns_path, O_RDONLY);
if (updated_ns == -1) {
PLOGE("Failed to open mount namespace [%s]", ns_path.data());
PLOGE("Failed to open mount namespace [%s]", ns_path);
return false;
}
LOGD("set mount namespace to [%s] fd=[%d]\n", ns_path.data(), updated_ns);
LOGD("set mount namespace to [%s] fd=[%d]\n", ns_path, updated_ns);
if (setns(updated_ns, CLONE_NEWNS) == -1) {
PLOGE("Failed to set mount namespace [%s]", ns_path.data());
PLOGE("Failed to set mount namespace [%s]", ns_path);
close(updated_ns);
return false;
@@ -387,8 +390,9 @@ void ZygiskContext::plt_hook_register(const char *regex, const char *symbol, voi
regex_t re;
if (regcomp(&re, regex, REG_NOSUB) != 0)
return;
mutex_guard lock(hook_info_lock);
pthread_mutex_lock(&hook_info_lock);
register_info.emplace_back(RegisterInfo{re, symbol, fn, backup});
pthread_mutex_unlock(&hook_info_lock);
}
void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {
@@ -396,8 +400,9 @@ void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {
regex_t re;
if (regcomp(&re, regex, REG_NOSUB) != 0)
return;
mutex_guard lock(hook_info_lock);
pthread_mutex_lock(&hook_info_lock);
ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""});
pthread_mutex_unlock(&hook_info_lock);
}
void ZygiskContext::plt_hook_process_regex() {
@@ -426,11 +431,13 @@ void ZygiskContext::plt_hook_process_regex() {
bool ZygiskContext::plt_hook_commit() {
{
mutex_guard lock(hook_info_lock);
pthread_mutex_lock(&hook_info_lock);
plt_hook_process_regex();
register_info.clear();
ignore_info.clear();
pthread_mutex_unlock(&hook_info_lock);
}
return lsplt::CommitHook(cached_map_infos);
}
@@ -452,12 +459,12 @@ bool ZygiskModule::valid() const {
/* Zygisksu changed: Use own zygiskd */
int ZygiskModule::connectCompanion() const {
return zygiskd::ConnectCompanion(id);
return rezygiskd_connect_companion(id);
}
/* Zygisksu changed: Use own zygiskd */
int ZygiskModule::getModuleDir() const {
return zygiskd::GetModuleDir(id);
return rezygiskd_get_module_dir(id);
}
void ZygiskModule::setOption(zygisk::Option opt) {
@@ -487,25 +494,38 @@ int sigmask(int how, int signum) {
}
void ZygiskContext::fork_pre() {
// Do our own fork before loading any 3rd party code
// First block SIGCHLD, unblock after original fork is done
/* INFO: Do our own fork before loading any 3rd party code.
First block SIGCHLD, unblock after original fork is done.
*/
sigmask(SIG_BLOCK, SIGCHLD);
pid = old_fork();
if (pid != 0 || flags[SKIP_FD_SANITIZATION])
return;
// Record all open fds
auto dir = xopen_dir("/proc/self/fd");
for (dirent *entry; (entry = readdir(dir.get()));) {
/* INFO: Record all open fds */
DIR *dir = opendir("/proc/self/fd");
if (dir == nullptr) {
PLOGE("Failed to open /proc/self/fd");
return;
}
struct dirent *entry;
while ((entry = readdir(dir))) {
int fd = parse_int(entry->d_name);
if (fd < 0 || fd >= MAX_FD_SIZE) {
close(fd);
continue;
}
allowed_fds[fd] = true;
}
// The dirfd should not be allowed
allowed_fds[dirfd(dir.get())] = false;
/* INFO: The dirfd should not be allowed */
allowed_fds[dirfd(dir)] = false;
closedir(dir);
}
void ZygiskContext::sanitize_fds() {
@@ -554,14 +574,23 @@ void ZygiskContext::sanitize_fds() {
return;
// Close all forbidden fds to prevent crashing
auto dir = open_dir("/proc/self/fd");
int dfd = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
int fd = parse_int(entry->d_name);
if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) {
close(fd);
}
DIR *dir = opendir("/proc/self/fd");
if (dir == nullptr) {
PLOGE("Failed to open /proc/self/fd");
return;
}
int dfd = dirfd(dir);
struct dirent *entry;
while ((entry = readdir(dir))) {
int fd = parse_int(entry->d_name);
if (fd < 0 || fd < MAX_FD_SIZE || fd == dfd || allowed_fds[fd]) continue;
close(fd);
}
closedir(dir);
}
void ZygiskContext::fork_post() {
@@ -572,21 +601,26 @@ void ZygiskContext::fork_post() {
/* Zygisksu changed: Load module fds */
void ZygiskContext::run_modules_pre() {
auto ms = zygiskd::ReadModules();
auto size = ms.size();
for (size_t i = 0; i < size; i++) {
auto &m = ms[i];
struct zygisk_modules ms;
if (rezygiskd_read_modules(&ms) == false) {
LOGE("Failed to read modules from zygiskd");
void *handle = dlopen(m.path.c_str(), RTLD_NOW);
return;
}
for (size_t i = 0; i < ms.modules_count; i++) {
char *lib_path = ms.modules[i];
void *handle = dlopen(lib_path, RTLD_NOW);
if (!handle) {
LOGE("Failed to load module [%s]: %s", m.path.c_str(), dlerror());
LOGE("Failed to load module [%s]: %s", lib_path, dlerror());
continue;
}
void *entry = dlsym(handle, "zygisk_module_entry");
if (!entry) {
LOGE("Failed to find entry point in module [%s]: %s", m.path.c_str(), dlerror());
LOGE("Failed to find entry point in module [%s]: %s", lib_path, dlerror());
dlclose(handle);
@@ -596,8 +630,11 @@ void ZygiskContext::run_modules_pre() {
modules.emplace_back(i, handle, entry);
}
free_modules(&ms);
for (auto &m : modules) {
m.onLoad(env);
if (flags[APP_SPECIALIZE]) m.preAppSpecialize(args.app);
else if (flags[SERVER_FORK_AND_SPECIALIZE]) m.preServerSpecialize(args.server);
}
@@ -624,21 +661,40 @@ void ZygiskContext::run_modules_post() {
void ZygiskContext::app_specialize_pre() {
flags[APP_SPECIALIZE] = true;
info_flags = zygiskd::GetProcessFlags(g_ctx->args.app->uid);
info_flags = rezygiskd_get_process_flags(g_ctx->args.app->uid);
if (info_flags & PROCESS_IS_FIRST_STARTED) {
/* INFO: To ensure we are really using a clean mount namespace, we use
the first process it as reference for clean mount namespace,
before it even does something, so that it will be clean yet
with expected mounts.
*/
update_mnt_ns(Clean, true);
}
if ((info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST) {
flags[DO_REVERT_UNMOUNT] = true;
}
if ((info_flags & (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) == (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) {
LOGD("Manager process detected. Notifying that Zygisk has been enabled.");
/* INFO: This environment variable is related to Magisk Zygisk/Manager. It
it used by Magisk's Zygisk to communicate to Magisk Manager whether
Zygisk is working or not.
To allow Zygisk modules to both work properly and for the manager to
identify Zygisk, being it not built-in, as working, we also set it. */
setenv("ZYGISK_ENABLED", "1", 1);
} else {
run_modules_pre();
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
postSpecialize. While preSpecialie in fact runs with Zygote (not superuser)
privileges, in postSpecialize it will now be with lower permission, in
the app's sandbox and therefore can move to a clean mount namespace after
executing the modules preSpecialize.
*/
if ((info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST) {
flags[DO_REVERT_UNMOUNT] = true;
update_mnt_ns(Clean, false);
}
}
}
@@ -683,7 +739,7 @@ void ZygiskContext::nativeForkSystemServer_pre() {
fork_pre();
if (is_child()) {
run_modules_pre();
zygiskd::SystemServerStarted();
rezygiskd_system_server_started();
}
sanitize_fds();
@@ -702,12 +758,10 @@ void ZygiskContext::nativeForkAndSpecialize_pre() {
LOGV("pre forkAndSpecialize [%s]", process);
flags[APP_FORK_AND_SPECIALIZE] = true;
update_mnt_ns(Clean, false);
fork_pre();
if (pid == 0) {
if (pid == 0)
app_specialize_pre();
}
sanitize_fds();
}
@@ -778,8 +832,11 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_
void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps) {
LOGD("cleaning trace for path %s", path);
if (load > 0 || unload >0) SoList::ResetCounters(load, unload);
bool path_found = SoList::DropSoPath(path);
if (load > 0 || unload > 0) solist_reset_counters(load, unload);
LOGD("Dropping solist record for %s", path);
bool path_found = solist_drop_so_path(path);
if (!path_found || !spoof_maps) return;
LOGD("spoofing virtual maps for %s", path);
@@ -807,14 +864,11 @@ void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps)
}
void hook_functions() {
default_new(plt_hook_list);
default_new(jni_hook_list);
plt_hook_list = new vector<tuple<dev_t, ino_t, const char *, void **>>();
jni_hook_list = new map<string, vector<JNINativeMethod>>();
ino_t android_runtime_inode = 0;
dev_t android_runtime_dev = 0;
/* TODO by ThePedroo: Implement injection via native bridge */
// ino_t native_bridge_inode = 0;
// dev_t native_bridge_dev = 0;
cached_map_infos = lsplt::MapInfo::Scan();
for (auto &map : cached_map_infos) {

View File

@@ -0,0 +1,234 @@
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "elf_util.h"
#include "logging.h"
#include "solist.h"
#ifdef __LP64__
size_t solist_size_offset = 0x18;
size_t solist_next_offset = 0x30;
size_t solist_realpath_offset = 0x1a8;
#else
size_t solist_size_offset = 0x90;
size_t solist_next_offset = 0xa4;
size_t solist_realpath_offset = 0x174;
#endif
static const char *(*get_realpath_sym)(SoInfo *) = NULL;
static void (*soinfo_free)(SoInfo *) = NULL;
static inline SoInfo *get_next(SoInfo *self) {
return *(SoInfo **)((uintptr_t)self + solist_next_offset);
}
static inline const char *get_path(SoInfo *self) {
if (get_realpath_sym)
return (*get_realpath_sym)(self);
return ((const char *)((uintptr_t)self + solist_realpath_offset));
}
static inline void set_size(SoInfo *self, size_t size) {
*(size_t *) ((uintptr_t)self + solist_size_offset) = size;
}
static inline size_t get_size(SoInfo *self) {
return *(size_t *) ((uintptr_t)self + solist_size_offset);
}
struct pdg ppdg;
static bool pdg_setup(ElfImg *img) {
ppdg.ctor = (void *(*)())getSymbAddress(img, "__dl__ZN18ProtectedDataGuardC2Ev");
ppdg.dtor = (void *(*)())getSymbAddress(img, "__dl__ZN18ProtectedDataGuardD2Ev");
return ppdg.ctor != NULL && ppdg.dtor != NULL;
}
static void pdg_protect() {
if (ppdg.ctor != NULL)
(*(ppdg.ctor))();
}
static void pdg_unprotect() {
if (ppdg.dtor != NULL)
(*(ppdg.dtor))();
}
static SoInfo *solist = NULL;
static SoInfo *somain = NULL;
static SoInfo **sonext = NULL;
static uint64_t *g_module_load_counter = NULL;
static uint64_t *g_module_unload_counter = NULL;
static bool solist_init() {
ElfImg *linker = ElfImg_create("/linker");
if (linker == NULL) {
LOGE("Failed to load linker");
return false;
}
ppdg = (struct pdg) {
.ctor = NULL,
.dtor = NULL
};
if (!pdg_setup(linker)) {
LOGE("Failed to setup pdg");
ElfImg_destroy(linker);
return false;
}
/* INFO: Since Android 15, the symbol names for the linker have a suffix,
this makes it impossible to hardcode the symbol names. To allow
this to work on all versions, we need to iterate over the loaded
symbols and find the correct ones.
See #63 for more information.
*/
solist = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL6solist");
if (solist == NULL) {
LOGE("Failed to find solist __dl__ZL6solist*");
ElfImg_destroy(linker);
return false;
}
somain = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL6somain");
LOGI("%p is somain", (void *)somain);
if (somain == NULL) {
LOGE("Failed to find somain __dl__ZL6somain*");
ElfImg_destroy(linker);
return false;
}
sonext = (SoInfo **)getSymbAddressByPrefix(linker, "__dl__ZL6sonext");
if (sonext == NULL) {
LOGE("Failed to find sonext __dl__ZL6sonext*");
ElfImg_destroy(linker);
return false;
}
SoInfo *vdso = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL4vdso");
if (vdso == NULL) {
LOGE("Failed to find vsdo __dl__ZL4vdso*");
ElfImg_destroy(linker);
return false;
}
get_realpath_sym = (const char *(*)(SoInfo *))getSymbAddress(linker, "__dl__ZNK6soinfo12get_realpathEv");
if (get_realpath_sym == NULL) {
LOGE("Failed to find get_realpath __dl__ZNK6soinfo12get_realpathEv");
ElfImg_destroy(linker);
return false;
}
soinfo_free = (void (*)(SoInfo *))getSymbAddressByPrefix(linker, "__dl__ZL11soinfo_freeP6soinfo");
if (soinfo_free == NULL) {
LOGE("Failed to find soinfo_free __dl__ZL11soinfo_freeP6soinfo*");
ElfImg_destroy(linker);
return false;
}
g_module_load_counter = (uint64_t *)getSymbAddress(linker, "__dl__ZL21g_module_load_counter");
if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter");
g_module_unload_counter = (uint64_t *)getSymbAddress(linker, "__dl__ZL23g_module_unload_counter");
if (g_module_unload_counter != NULL) LOGD("found symbol g_module_unload_counter");
for (size_t i = 0; i < 1024 / sizeof(void *); i++) {
uintptr_t possible_field = (uintptr_t)solist + i * sizeof(void *);
size_t possible_size_of_somain = *(size_t *)((uintptr_t)somain + i * sizeof(void *));
if (possible_size_of_somain < 0x100000 && possible_size_of_somain > 0x100) {
solist_size_offset = i * sizeof(void *);
LOGD("solist_size_offset is %zu * %zu = %p", i, sizeof(void *), (void *)solist_size_offset);
}
if (*(void **)possible_field == somain || (vdso != NULL && *(void **)possible_field == vdso)) {
solist_next_offset = i * sizeof(void *);
LOGD("solist_next_offset is %zu * %zu = %p", i, sizeof(void *), (void *)solist_next_offset);
break;
}
}
ElfImg_destroy(linker);
return true;
}
bool solist_drop_so_path(const char *target_path) {
if (solist == NULL && !solist_init()) {
LOGE("Failed to initialize solist");
return false;
}
for (SoInfo *iter = solist; iter; iter = get_next(iter)) {
if (get_path(iter) && strstr(get_path(iter), target_path)) {
pdg_protect();
LOGV("dropping solist record loaded at %s with size %zu", get_path(iter), get_size(iter));
if (get_size(iter) > 0) {
set_size(iter, 0);
soinfo_free(iter);
pdg_unprotect();
return true;
}
pdg_unprotect();
}
}
return false;
}
void solist_reset_counters(size_t load, size_t unload) {
if (solist == NULL && !solist_init()) {
LOGE("Failed to initialize solist");
return;
}
if (g_module_load_counter == NULL || g_module_unload_counter == NULL) {
LOGD("g_module counters not defined, skip reseting them");
return;
}
uint64_t loaded_modules = *g_module_load_counter;
uint64_t unloaded_modules = *g_module_unload_counter;
if (loaded_modules >= load) {
*g_module_load_counter = loaded_modules - load;
LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter);
}
if (unloaded_modules >= unload) {
*g_module_unload_counter = unloaded_modules - unload;
LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter);
}
}

View File

@@ -0,0 +1,65 @@
#ifndef SOLIST_H
#define SOLIST_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef struct SoInfo SoInfo;
struct SoInfo {
char data[0];
};
#define FuncType(name) void (*name)
struct pdg {
void *(*ctor)();
void *(*dtor)();
};
/*
INFO: When dlopen'ing a library, the system will save information of the
opened library so a structure called soinfo, which contains another
called solist, a list with the information of opened objects.
Due to special handling in ptracer, however, it won't heave gaps in the
memory of the list since we will close there, not loading a library creating
this gap. However, the previously loaded library would remain in the solist,
requiring ReZygisk to clean those up.
To do that, we use 2 functions: soinfo_free, and set_size, which will
zero the region size, and then remove all traces of that library (libzygisk.so)
which was previously loaded.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1712
*/
bool solist_drop_so_path(const char *target_path);
/*
INFO: When dlopen'ing a library, the system will increment 1 to a global
counter that tracks the amount of libraries ever loaded in that process,
the same happening in dlclose.
This cannot directly be used to detect if ReZygisk is present, however, with
enough data about specific environments, this can be used to detect if any
other library (be it malicious or not) was loaded. To avoid future detections,
we patch that value to the original value.
To do that, we retrieve the address of both "g_module_load_counter" and "g_module
_unload_counter" variables and force set them to the original value, based on
the modules dlopen'ed.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1874
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1944
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#3413
*/
void solist_reset_counters(size_t load, size_t unload);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* SOLIST_H */

View File

@@ -1,22 +1,19 @@
#include <stdio.h>
#include <stdlib.h>
#include "utils.h"
#include "monitor.h"
#include "utils.hpp"
#include "daemon.h"
int main(int argc, char **argv) {
zygiskd::Init("/data/adb/rezygisk");
printf("The ReZygisk Tracer %s\n\n", ZKSU_VERSION);
if (argc >= 2 && strcmp(argv[1], "monitor") == 0) {
init_monitor();
printf("[ReZygisk]: Started monitoring...\n");
return 0;
} else if (argc >= 3 && strcmp(argv[1], "trace") == 0) {
if (argc >= 4 && strcmp(argv[3], "--restart") == 0) zygiskd::ZygoteRestart();
if (argc >= 4 && strcmp(argv[3], "--restart") == 0) rezygiskd_zygote_restart();
long pid = strtol(argv[2], 0, 0);
if (!trace_zygote(pid)) {
@@ -25,11 +22,9 @@ int main(int argc, char **argv) {
return 1;
}
printf("[ReZygisk]: Tracing %ld...\n", pid);
return 0;
} else if (argc >= 2 && strcmp(argv[1], "ctl") == 0) {
enum Command command;
enum rezygiskd_command command;
if (strcmp(argv[2], "start") == 0) command = START;
else if (strcmp(argv[2], "stop") == 0) command = STOP;
@@ -54,28 +49,28 @@ int main(int argc, char **argv) {
return 0;
} else if (argc >= 2 && strcmp(argv[1], "info") == 0) {
struct zygote_info info;
zygiskd::GetInfo(&info);
struct rezygisk_info info;
rezygiskd_get_info(&info);
printf("Daemon process PID: %d\n", info.pid);
switch (info.root_impl) {
case ZYGOTE_ROOT_IMPL_NONE: {
case ROOT_IMPL_NONE: {
printf("Root implementation: none\n");
break;
}
case ZYGOTE_ROOT_IMPL_APATCH: {
case ROOT_IMPL_APATCH: {
printf("Root implementation: APatch\n");
break;
}
case ZYGOTE_ROOT_IMPL_KERNELSU: {
case ROOT_IMPL_KERNELSU: {
printf("Root implementation: KernelSU\n");
break;
}
case ZYGOTE_ROOT_IMPL_MAGISK: {
case ROOT_IMPL_MAGISK: {
printf("Root implementation: Magisk\n");
break;

View File

@@ -0,0 +1,892 @@
#include <stdlib.h>
#include <time.h>
#include <sys/system_properties.h>
#include <sys/signalfd.h>
#include <err.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <fcntl.h>
#include <unistd.h>
#include "utils.h"
#include "daemon.h"
#include "misc.h"
#include "monitor.h"
#define PROP_PATH TMP_PATH "/module.prop"
#define SOCKET_NAME "init_monitor"
#define STOPPED_WITH(sig, event) WIFSTOPPED(sigchld_status) && (sigchld_status >> 8 == ((sig) | (event << 8)))
static bool update_status(const char *message);
char monitor_stop_reason[32];
enum ptracer_tracing_state {
TRACING,
STOPPING,
STOPPED,
EXITING
};
enum ptracer_tracing_state tracing_state = TRACING;
struct rezygiskd_status {
bool supported;
bool zygote_injected;
bool daemon_running;
pid_t daemon_pid;
char *daemon_info;
char *daemon_error_info;
};
struct rezygiskd_status status64 = {
.daemon_pid = -1
};
struct rezygiskd_status status32 = {
.daemon_pid = -1
};
int monitor_epoll_fd;
bool monitor_events_running = true;
bool monitor_events_init() {
monitor_epoll_fd = epoll_create(1);
if (monitor_epoll_fd == -1) {
PLOGE("epoll_create");
return false;
}
return true;
}
struct monitor_event_cbs {
void (*callback)();
void (*stop_callback)();
};
bool monitor_events_register_event(struct monitor_event_cbs *event_cbs, int fd, uint32_t events) {
struct epoll_event ev = {
.data.ptr = event_cbs,
.events = events
};
if (epoll_ctl(monitor_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
PLOGE("epoll_ctl");
return false;
}
return true;
}
bool monitor_events_unregister_event(int fd) {
if (epoll_ctl(monitor_epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
PLOGE("epoll_ctl");
return false;
}
return true;
}
void monitor_events_stop() {
monitor_events_running = false;
};
void monitor_events_loop() {
struct epoll_event events[2];
while (monitor_events_running) {
int nfds = epoll_wait(monitor_epoll_fd, events, 2, -1);
if (nfds == -1) {
if (errno != EINTR) PLOGE("epoll_wait");
continue;
}
for (int i = 0; i < nfds; i++) {
struct monitor_event_cbs *event_cbs = (struct monitor_event_cbs *)events[i].data.ptr;
event_cbs->callback();
if (!monitor_events_running) break;
}
}
if (monitor_epoll_fd >= 0) close(monitor_epoll_fd);
monitor_epoll_fd = -1;
for (int i = 0; i < (int)(sizeof(events) / sizeof(events[0])); i++) {
struct monitor_event_cbs *event_cbs = (struct monitor_event_cbs *)events[i].data.ptr;
event_cbs->stop_callback();
}
}
int monitor_sock_fd;
bool rezygiskd_listener_init() {
monitor_sock_fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (monitor_sock_fd == -1) {
PLOGE("socket create");
return false;
}
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = { 0 }
};
size_t sun_path_len = sprintf(addr.sun_path, "%s/%s", rezygiskd_get_path(), SOCKET_NAME);
socklen_t socklen = sizeof(sa_family_t) + sun_path_len;
if (bind(monitor_sock_fd, (struct sockaddr *)&addr, socklen) == -1) {
PLOGE("bind socket");
return false;
}
return true;
}
void rezygiskd_listener_callback() {
struct [[gnu::packed]] MsgHead {
enum rezygiskd_command cmd;
int length;
char data[0];
};
while (1) {
struct MsgHead *msg = (struct MsgHead *)malloc(sizeof(struct MsgHead));
ssize_t real_size;
ssize_t nread = recv(monitor_sock_fd, msg, sizeof(struct MsgHead), MSG_PEEK);
if (nread == -1) {
if (errno == EAGAIN) break;
PLOGE("read socket");
}
if ((size_t)nread < sizeof(enum rezygiskd_command)) {
LOGE("read %zu < %zu", nread, sizeof(enum rezygiskd_command));
continue;
}
if (msg->cmd >= DAEMON64_SET_INFO && msg->cmd != SYSTEM_SERVER_STARTED) {
if (nread != sizeof(msg)) {
LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(struct MsgHead));
continue;
}
real_size = sizeof(struct MsgHead) + msg->length;
} else {
if (nread != sizeof(enum rezygiskd_command)) {
LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(enum rezygiskd_command));
continue;
}
real_size = sizeof(enum rezygiskd_command);
}
msg = (struct MsgHead *)realloc(msg, real_size);
nread = recv(monitor_sock_fd, msg, real_size, 0);
if (nread == -1) {
if (errno == EAGAIN) break;
PLOGE("recv");
continue;
}
if (nread != real_size) {
LOGE("real size %zu != %zu", real_size, nread);
continue;
}
switch (msg->cmd) {
case START: {
if (tracing_state == STOPPING) tracing_state = TRACING;
else if (tracing_state == STOPPED) {
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
LOGI("start tracing init");
tracing_state = TRACING;
}
update_status(NULL);
break;
}
case STOP: {
if (tracing_state == TRACING) {
LOGI("stop tracing requested");
tracing_state = STOPPING;
strcpy(monitor_stop_reason, "user requested");
ptrace(PTRACE_INTERRUPT, 1, 0, 0);
update_status(NULL);
}
break;
}
case EXIT: {
LOGI("prepare for exit ...");
tracing_state = EXITING;
strcpy(monitor_stop_reason, "user requested");
update_status(NULL);
monitor_events_stop();
break;
}
case ZYGOTE64_INJECTED: {
status64.zygote_injected = true;
update_status(NULL);
break;
}
case ZYGOTE32_INJECTED: {
status32.zygote_injected = true;
update_status(NULL);
break;
}
case DAEMON64_SET_INFO: {
LOGD("received daemon64 info %s", msg->data);
/* Will only happen if somehow the daemon restarts */
if (status64.daemon_info) {
free(status64.daemon_info);
status64.daemon_info = NULL;
}
status64.daemon_info = (char *)malloc(msg->length);
if (!status64.daemon_info) {
PLOGE("malloc daemon64 info");
break;
}
strcpy(status64.daemon_info, msg->data);
update_status(NULL);
break;
}
case DAEMON32_SET_INFO: {
LOGD("received daemon32 info %s", msg->data);
if (status32.daemon_info) {
free(status32.daemon_info);
status32.daemon_info = NULL;
}
status32.daemon_info = (char *)malloc(msg->length);
if (!status32.daemon_info) {
PLOGE("malloc daemon32 info");
break;
}
strcpy(status32.daemon_info, msg->data);
update_status(NULL);
break;
}
case DAEMON64_SET_ERROR_INFO: {
LOGD("received daemon64 error info %s", msg->data);
status64.daemon_running = false;
if (status64.daemon_error_info) {
free(status64.daemon_error_info);
status64.daemon_error_info = NULL;
}
status64.daemon_error_info = (char *)malloc(msg->length);
if (!status64.daemon_error_info) {
PLOGE("malloc daemon64 error info");
break;
}
strcpy(status64.daemon_error_info, msg->data);
update_status(NULL);
break;
}
case DAEMON32_SET_ERROR_INFO: {
LOGD("received daemon32 error info %s", msg->data);
status32.daemon_running = false;
if (status32.daemon_error_info) {
free(status32.daemon_error_info);
status32.daemon_error_info = NULL;
}
status32.daemon_error_info = (char *)malloc(msg->length);
if (!status32.daemon_error_info) {
PLOGE("malloc daemon32 error info");
break;
}
strcpy(status32.daemon_error_info, msg->data);
update_status(NULL);
break;
}
case SYSTEM_SERVER_STARTED: {
LOGD("system server started, mounting prop");
if (mount(PROP_PATH, "/data/adb/modules/zygisksu/module.prop", NULL, MS_BIND, NULL) == -1) {
PLOGE("failed to mount prop");
}
break;
}
}
free(msg);
}
}
void rezygiskd_listener_stop() {
if (monitor_sock_fd >= 0) close(monitor_sock_fd);
monitor_sock_fd = -1;
}
#define MAX_RETRY_COUNT 5
#define CREATE_ZYGOTE_START_COUNTER(abi) \
struct timespec last_zygote##abi = { \
.tv_sec = 0, \
.tv_nsec = 0 \
}; \
\
int count_zygote ## abi = 0; \
bool should_stop_inject ## abi() { \
struct timespec now = {}; \
clock_gettime(CLOCK_MONOTONIC, &now); \
if (now.tv_sec - last_zygote ## abi.tv_sec < 30) \
count_zygote ## abi++; \
else \
count_zygote ## abi = 0; \
\
last_zygote##abi = now; \
\
return count_zygote##abi >= MAX_RETRY_COUNT; \
}
CREATE_ZYGOTE_START_COUNTER(64)
CREATE_ZYGOTE_START_COUNTER(32)
static bool ensure_daemon_created(bool is_64bit) {
struct rezygiskd_status *status = is_64bit ? &status64 : &status32;
if (is_64bit) {
LOGD("new zygote started.");
umount2("/data/adb/modules/zygisksu/module.prop", MNT_DETACH);
}
status->zygote_injected = false;
if (status->daemon_pid == -1) {
pid_t pid = fork();
if (pid < 0) {
PLOGE("create daemon%s", is_64bit ? "64" : "32");
return false;
} else if (pid == 0) {
char daemon_name[PATH_MAX] = "./bin/zygiskd";
strcat(daemon_name, is_64bit ? "64" : "32");
execl(daemon_name, daemon_name, NULL);
PLOGE("exec daemon %s failed", daemon_name);
exit(1);
} else {
status->supported = true;
status->daemon_pid = pid;
status->daemon_running = true;
return true;
}
} else {
return status->daemon_running;
}
}
#define CHECK_DAEMON_EXIT(abi) \
if (status##abi.supported && pid == status##abi.daemon_pid) { \
char status_str[64]; \
parse_status(sigchld_status, status_str, sizeof(status_str)); \
\
LOGW("daemon" #abi " pid %d exited: %s", pid, status_str); \
status##abi.daemon_running = false; \
\
if (!status##abi.daemon_error_info) { \
status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \
if (!status##abi.daemon_error_info) { \
LOGE("malloc daemon" #abi " error info failed"); \
\
return; \
} \
\
memcpy(status##abi.daemon_error_info, status_str, strlen(status_str) + 1); \
} \
\
update_status(NULL); \
continue; \
}
#define PRE_INJECT(abi, is_64) \
if (strcmp(program, "/system/bin/app_process" # abi) == 0) { \
tracer = "./bin/zygisk-ptrace" # abi; \
\
if (should_stop_inject ## abi()) { \
LOGW("zygote" # abi " restart too much times, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "zygote crashed", sizeof("zygote crashed")); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
if (!ensure_daemon_created(is_64)) { \
LOGW("daemon" #abi " not running, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "daemon not running", sizeof("daemon not running")); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
}
int sigchld_signal_fd;
struct signalfd_siginfo sigchld_fdsi;
int sigchld_status;
pid_t *sigchld_process;
size_t sigchld_process_count = 0;
bool sigchld_listener_init() {
sigchld_process = NULL;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
PLOGE("set sigprocmask");
return false;
}
sigchld_signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (sigchld_signal_fd == -1) {
PLOGE("create signalfd");
return false;
}
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
return true;
}
void sigchld_listener_callback() {
while (1) {
ssize_t s = read(sigchld_signal_fd, &sigchld_fdsi, sizeof(sigchld_fdsi));
if (s == -1) {
if (errno == EAGAIN) break;
PLOGE("read signalfd");
continue;
}
if (s != sizeof(sigchld_fdsi)) {
LOGW("read %zu != %zu", s, sizeof(sigchld_fdsi));
continue;
}
if (sigchld_fdsi.ssi_signo != SIGCHLD) {
LOGW("no sigchld received");
continue;
}
int pid;
while ((pid = waitpid(-1, &sigchld_status, __WALL | WNOHANG)) != 0) {
if (pid == -1) {
if (tracing_state == STOPPED && errno == ECHILD) break;
PLOGE("waitpid");
}
if (pid == 1) {
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) {
long child_pid;
ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid);
LOGV("forked %ld", child_pid);
} else if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP) && tracing_state == STOPPING) {
if (ptrace(PTRACE_DETACH, 1, 0, 0) == -1) PLOGE("failed to detach init");
tracing_state = STOPPED;
LOGI("stop tracing init");
continue;
}
if (WIFSTOPPED(sigchld_status)) {
if (WPTEVENT(sigchld_status) == 0) {
if (WSTOPSIG(sigchld_status) != SIGSTOP && WSTOPSIG(sigchld_status) != SIGTSTP && WSTOPSIG(sigchld_status) != SIGTTIN && WSTOPSIG(sigchld_status) != SIGTTOU) {
LOGW("inject signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(sigchld_status)), WSTOPSIG(sigchld_status));
ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(sigchld_status));
continue;
} else {
LOGW("suppress stopping signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(sigchld_status)), WSTOPSIG(sigchld_status));
}
}
ptrace(PTRACE_CONT, pid, 0, 0);
}
continue;
}
CHECK_DAEMON_EXIT(64)
CHECK_DAEMON_EXIT(32)
pid_t state = 0;
for (size_t i = 0; i < sigchld_process_count; i++) {
if (sigchld_process[i] != pid) continue;
state = sigchld_process[i];
break;
}
if (state == 0) {
LOGV("new process %d attached", pid);
for (size_t i = 0; i < sigchld_process_count; i++) {
if (sigchld_process[i] != 0) continue;
sigchld_process[i] = pid;
goto ptrace_process;
}
sigchld_process = (pid_t *)realloc(sigchld_process, sizeof(pid_t) * (sigchld_process_count + 1));
if (sigchld_process == NULL) {
PLOGE("realloc sigchld_process");
continue;
}
sigchld_process[sigchld_process_count] = pid;
sigchld_process_count++;
ptrace_process:
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
ptrace(PTRACE_CONT, pid, 0, 0);
continue;
} else {
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
char program[PATH_MAX];
if (get_program(pid, program, sizeof(program)) == -1) {
LOGW("failed to get program %d", pid);
continue;
}
LOGV("%d program %s", pid, program);
const char* tracer = NULL;
do {
if (tracing_state != TRACING) {
LOGW("stop injecting %d because not tracing", pid);
break;
}
PRE_INJECT(64, true)
PRE_INJECT(32, false)
if (tracer != NULL) {
LOGD("stopping %d", pid);
kill(pid, SIGSTOP);
ptrace(PTRACE_CONT, pid, 0, 0);
waitpid(pid, &sigchld_status, __WALL);
if (STOPPED_WITH(SIGSTOP, 0)) {
LOGD("detaching %d", pid);
ptrace(PTRACE_DETACH, pid, 0, SIGSTOP);
sigchld_status = 0;
int p = fork_dont_care();
if (p == 0) {
char pid_str[32];
sprintf(pid_str, "%d", pid);
execl(tracer, basename(tracer), "trace", pid_str, "--restart", NULL);
PLOGE("failed to exec, kill");
kill(pid, SIGKILL);
exit(1);
} else if (p == -1) {
PLOGE("failed to fork, kill");
kill(pid, SIGKILL);
}
}
}
} while (false);
update_status(NULL);
} else {
char status_str[64];
parse_status(sigchld_status, status_str, sizeof(status_str));
LOGW("process %d received unknown sigchld_status %s", pid, status_str);
}
for (size_t i = 0; i < sigchld_process_count; i++) {
if (sigchld_process[i] != pid) continue;
sigchld_process[i] = 0;
break;
}
if (WIFSTOPPED(sigchld_status)) {
LOGV("detach process %d", pid);
ptrace(PTRACE_DETACH, pid, 0, 0);
}
}
}
}
}
void sigchld_listener_stop() {
if (sigchld_signal_fd >= 0) close(sigchld_signal_fd);
sigchld_signal_fd = -1;
if (sigchld_process != NULL) free(sigchld_process);
sigchld_process = NULL;
sigchld_process_count = 0;
}
static char pre_section[1024];
static char post_section[1024];
#define WRITE_STATUS_ABI(suffix) \
if (status ## suffix.supported) { \
strcat(status_text, " zygote" # suffix ": "); \
if (tracing_state != TRACING) strcat(status_text, "❓ unknown, "); \
else if (status ## suffix.zygote_injected) strcat(status_text, "😋 injected, "); \
else strcat(status_text, "❌ not injected, "); \
\
strcat(status_text, "daemon" # suffix ": "); \
if (status ## suffix.daemon_running) { \
strcat(status_text, "😋 running "); \
\
if (status ## suffix.daemon_info != NULL) { \
strcat(status_text, "("); \
strcat(status_text, status ## suffix.daemon_info); \
strcat(status_text, ")"); \
} \
} else { \
strcat(status_text, "❌ crashed "); \
\
if (status ## suffix.daemon_error_info != NULL) { \
strcat(status_text, "("); \
strcat(status_text, status ## suffix.daemon_error_info); \
strcat(status_text, ")"); \
} \
} \
}
static bool update_status(const char *message) {
FILE *prop = fopen(PROP_PATH, "w");
if (prop == NULL) {
PLOGE("failed to open prop");
return false;
}
if (message) {
fprintf(prop, "%s[%s] %s", pre_section, message, post_section);
fclose(prop);
return true;
}
char status_text[1024] = "monitor: ";
switch (tracing_state) {
case TRACING: {
strcat(status_text, "😋 tracing");
break;
}
case STOPPING: [[fallthrough]];
case STOPPED: {
strcat(status_text, "❌ stopped");
break;
}
case EXITING: {
strcat(status_text, "❌ exited");
break;
}
}
if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') {
strcat(status_text, " (");
strcat(status_text, monitor_stop_reason);
strcat(status_text, ")");
}
strcat(status_text, ",");
WRITE_STATUS_ABI(64)
WRITE_STATUS_ABI(32)
fprintf(prop, "%s[%s] %s", pre_section, status_text, post_section);
fclose(prop);
return true;
}
static bool prepare_environment() {
/* INFO: We need to create the file first, otherwise the mount will fail */
close(open(PROP_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0644));
FILE *orig_prop = fopen("/data/adb/modules/zygisksu/module.prop", "r");
if (orig_prop == NULL) {
PLOGE("failed to open orig prop");
return false;
}
bool after_description = false;
char line[1024];
while (fgets(line, sizeof(line), orig_prop) != NULL) {
if (strncmp(line, "description=", strlen("description=")) == 0) {
strcat(pre_section, "description=");
strcat(post_section, line + strlen("description="));
after_description = true;
continue;
}
if (after_description) strcat(post_section, line);
else strcat(pre_section, line);
}
fclose(orig_prop);
/* INFO: This environment variable is related to Magisk Zygisk/Manager. It
it used by Magisk's Zygisk to communicate to Magisk Manager whether
Zygisk is working or not.
Because of that behavior, we can knowledge built-in Zygisk is being
used and stop the continuation of initialization of ReZygisk.*/
if (getenv("ZYGISK_ENABLED")) {
update_status("❌ Disable Magisk's built-in Zygisk");
return false;
}
return update_status(NULL);
}
void init_monitor() {
LOGI("ReZygisk %s", ZKSU_VERSION);
if (!prepare_environment()) exit(1);
monitor_events_init();
rezygiskd_listener_init();
struct monitor_event_cbs listener_cbs = {
.callback = rezygiskd_listener_callback,
.stop_callback = rezygiskd_listener_stop
};
monitor_events_register_event(&listener_cbs, monitor_sock_fd, EPOLLIN | EPOLLET);
sigchld_listener_init();
struct monitor_event_cbs sigchld_cbs = {
.callback = sigchld_listener_callback,
.stop_callback = sigchld_listener_stop
};
monitor_events_register_event(&sigchld_cbs, sigchld_signal_fd, EPOLLIN | EPOLLET);
monitor_events_loop();
if (status64.daemon_info) free(status64.daemon_info);
if (status64.daemon_error_info) free(status64.daemon_error_info);
if (status32.daemon_info) free(status32.daemon_info);
if (status32.daemon_error_info) free(status32.daemon_error_info);
LOGI("exit");
}
int send_control_command(enum rezygiskd_command cmd) {
int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sockfd == -1) return -1;
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = { 0 }
};
size_t sun_path_len = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", rezygiskd_get_path(), SOCKET_NAME);
socklen_t socklen = sizeof(sa_family_t) + sun_path_len;
ssize_t nsend = sendto(sockfd, (void *)&cmd, sizeof(cmd), 0, (struct sockaddr *)&addr, socklen);
close(sockfd);
return nsend != sizeof(cmd) ? -1 : 0;
}

View File

@@ -1,829 +0,0 @@
#include <stdlib.h>
#include <sys/system_properties.h>
#include <unistd.h>
#include <set>
#include <sys/signalfd.h>
#include <err.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <fcntl.h>
#include "monitor.h"
#include "utils.hpp"
#include "files.hpp"
#include "misc.hpp"
#define STOPPED_WITH(sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8)))
static void updateStatus();
char monitor_stop_reason[32];
constexpr char SOCKET_NAME[] = "init_monitor";
struct EventLoop;
struct EventHandler {
virtual int GetFd() = 0;
virtual void HandleEvent(EventLoop& loop, uint32_t event) = 0;
};
struct EventLoop {
private:
int epoll_fd_;
bool running = false;
public:
bool Init() {
epoll_fd_ = epoll_create(1);
if (epoll_fd_ == -1) {
PLOGE("failed to create");
return false;
}
return true;
}
void Stop() {
running = false;
}
void Loop() {
running = true;
constexpr auto MAX_EVENTS = 2;
struct epoll_event events[MAX_EVENTS];
while (running) {
int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1);
if (nfds == -1) {
if (errno != EINTR) PLOGE("epoll_wait");
continue;
}
for (int i = 0; i < nfds; i++) {
reinterpret_cast<EventHandler *>(events[i].data.ptr)->HandleEvent(*this,
events[i].events);
if (!running) break;
}
}
}
bool RegisterHandler(EventHandler &handler, uint32_t events) {
struct epoll_event ev{};
ev.events = events;
ev.data.ptr = &handler;
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, handler.GetFd(), &ev) == -1) {
PLOGE("failed to add event handler");
return false;
}
return true;
}
bool UnregisterHandler(EventHandler &handler) {
if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, handler.GetFd(), NULL) == -1) {
PLOGE("failed to del event handler");
return false;
}
return true;
}
~EventLoop() {
if (epoll_fd_ >= 0) close(epoll_fd_);
}
};
enum TracingState {
TRACING = 1,
STOPPING,
STOPPED,
EXITING
};
TracingState tracing_state = TRACING;
static char prop_path[PATH_MAX];
struct Status {
bool supported = false;
bool zygote_injected = false;
bool daemon_running = false;
pid_t daemon_pid = -1;
char *daemon_info = NULL;
char *daemon_error_info = NULL;
};
Status status64;
Status status32;
struct SocketHandler : public EventHandler {
int sock_fd_;
bool Init() {
sock_fd_ = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (sock_fd_ == -1) {
PLOGE("socket create");
return false;
}
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = { 0 }
};
size_t sun_path_len = sprintf(addr.sun_path, "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME);
socklen_t socklen = sizeof(sa_family_t) + sun_path_len;
if (bind(sock_fd_, (struct sockaddr *)&addr, socklen) == -1) {
PLOGE("bind socket");
return false;
}
return true;
}
int GetFd() override {
return sock_fd_;
}
void HandleEvent(EventLoop &loop, uint32_t) override {
struct [[gnu::packed]] MsgHead {
enum Command cmd;
int length;
char data[0];
};
while (1) {
struct MsgHead *msg = (struct MsgHead *)malloc(sizeof(struct MsgHead));
ssize_t real_size;
ssize_t nread = recv(sock_fd_, msg, sizeof(struct MsgHead), MSG_PEEK);
if (nread == -1) {
if (errno == EAGAIN) break;
PLOGE("read socket");
}
if ((size_t)nread < sizeof(Command)) {
LOGE("read %zu < %zu", nread, sizeof(Command));
continue;
}
if (msg->cmd >= Command::DAEMON64_SET_INFO && msg->cmd != Command::SYSTEM_SERVER_STARTED) {
if (nread != sizeof(msg)) {
LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(MsgHead));
continue;
}
real_size = sizeof(MsgHead) + msg->length;
} else {
if (nread != sizeof(Command)) {
LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(Command));
continue;
}
real_size = sizeof(Command);
}
msg = (struct MsgHead *)realloc(msg, real_size);
nread = recv(sock_fd_, msg, real_size, 0);
if (nread == -1) {
if (errno == EAGAIN) break;
PLOGE("recv");
continue;
}
if (nread != real_size) {
LOGE("real size %zu != %zu", real_size, nread);
continue;
}
switch (msg->cmd) {
case START: {
if (tracing_state == STOPPING) tracing_state = TRACING;
else if (tracing_state == STOPPED) {
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
LOGI("start tracing init");
tracing_state = TRACING;
}
updateStatus();
break;
}
case STOP: {
if (tracing_state == TRACING) {
LOGI("stop tracing requested");
tracing_state = STOPPING;
strcpy(monitor_stop_reason, "user requested");
ptrace(PTRACE_INTERRUPT, 1, 0, 0);
updateStatus();
}
break;
}
case EXIT: {
LOGI("prepare for exit ...");
tracing_state = EXITING;
strcpy(monitor_stop_reason, "user requested");
updateStatus();
loop.Stop();
break;
}
case ZYGOTE64_INJECTED: {
status64.zygote_injected = true;
updateStatus();
break;
}
case ZYGOTE32_INJECTED: {
status32.zygote_injected = true;
updateStatus();
break;
}
case DAEMON64_SET_INFO: {
LOGD("received daemon64 info %s", msg->data);
/* Will only happen if somehow the daemon restarts */
if (status64.daemon_info) {
free(status64.daemon_info);
status64.daemon_info = NULL;
}
status64.daemon_info = (char *)malloc(msg->length);
if (!status64.daemon_info) {
PLOGE("malloc daemon64 info");
break;
}
strcpy(status64.daemon_info, msg->data);
updateStatus();
break;
}
case DAEMON32_SET_INFO: {
LOGD("received daemon32 info %s", msg->data);
if (status32.daemon_info) {
free(status32.daemon_info);
status32.daemon_info = NULL;
}
status32.daemon_info = (char *)malloc(msg->length);
if (!status32.daemon_info) {
PLOGE("malloc daemon32 info");
break;
}
strcpy(status32.daemon_info, msg->data);
updateStatus();
break;
}
case DAEMON64_SET_ERROR_INFO: {
LOGD("received daemon64 error info %s", msg->data);
status64.daemon_running = false;
if (status64.daemon_error_info) {
free(status64.daemon_error_info);
status64.daemon_error_info = NULL;
}
status64.daemon_error_info = (char *)malloc(msg->length);
if (!status64.daemon_error_info) {
PLOGE("malloc daemon64 error info");
break;
}
strcpy(status64.daemon_error_info, msg->data);
updateStatus();
break;
}
case DAEMON32_SET_ERROR_INFO: {
LOGD("received daemon32 error info %s", msg->data);
status32.daemon_running = false;
if (status32.daemon_error_info) {
free(status32.daemon_error_info);
status32.daemon_error_info = NULL;
}
status32.daemon_error_info = (char *)malloc(msg->length);
if (!status32.daemon_error_info) {
PLOGE("malloc daemon32 error info");
break;
}
strcpy(status32.daemon_error_info, msg->data);
updateStatus();
break;
}
case SYSTEM_SERVER_STARTED: {
LOGD("system server started, mounting prop");
if (mount(prop_path, "/data/adb/modules/zygisksu/module.prop", NULL, MS_BIND, NULL) == -1) {
PLOGE("failed to mount prop");
}
break;
}
}
free(msg);
}
}
~SocketHandler() {
if (sock_fd_ >= 0) close(sock_fd_);
}
};
constexpr int MAX_RETRY_COUNT = 5;
#define CREATE_ZYGOTE_START_COUNTER(abi) \
struct timespec last_zygote##abi = { \
.tv_sec = 0, \
.tv_nsec = 0 \
}; \
\
int count_zygote ## abi = 0; \
bool should_stop_inject ## abi() { \
struct timespec now = {}; \
clock_gettime(CLOCK_MONOTONIC, &now); \
if (now.tv_sec - last_zygote ## abi.tv_sec < 30) \
count_zygote ## abi++; \
else \
count_zygote ## abi = 0; \
\
last_zygote##abi = now; \
\
return count_zygote##abi >= MAX_RETRY_COUNT; \
}
CREATE_ZYGOTE_START_COUNTER(64)
CREATE_ZYGOTE_START_COUNTER(32)
static bool ensure_daemon_created(bool is_64bit) {
Status *status = is_64bit ? &status64 : &status32;
if (is_64bit) {
LOGD("new zygote started.");
umount2("/data/adb/modules/zygisksu/module.prop", MNT_DETACH);
}
status->zygote_injected = false;
if (status->daemon_pid == -1) {
pid_t pid = fork();
if (pid < 0) {
PLOGE("create daemon%s", is_64bit ? "64" : "32");
return false;
} else if (pid == 0) {
char daemon_name[PATH_MAX] = "./bin/zygiskd";
strcat(daemon_name, is_64bit ? "64" : "32");
execl(daemon_name, daemon_name, NULL);
PLOGE("exec daemon %s failed", daemon_name);
exit(1);
} else {
status->supported = true;
status->daemon_pid = pid;
status->daemon_running = true;
return true;
}
} else {
return status->daemon_running;
}
}
#define CHECK_DAEMON_EXIT(abi) \
if (status##abi.supported && pid == status##abi.daemon_pid) { \
char status_str[64]; \
parse_status(status, status_str, sizeof(status_str)); \
\
LOGW("daemon" #abi " pid %d exited: %s", pid, status_str); \
status##abi.daemon_running = false; \
\
if (!status##abi.daemon_error_info) { \
status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \
if (!status##abi.daemon_error_info) { \
LOGE("malloc daemon" #abi " error info failed"); \
\
return; \
} \
\
memcpy(status##abi.daemon_error_info, status_str, strlen(status_str) + 1); \
} \
\
updateStatus(); \
continue; \
}
#define PRE_INJECT(abi, is_64) \
if (strcmp(program, "/system/bin/app_process" # abi) == 0) { \
tracer = "./bin/zygisk-ptrace" # abi; \
\
if (should_stop_inject ## abi()) { \
LOGW("zygote" # abi " restart too much times, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "zygote crashed", sizeof("zygote crashed")); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
if (!ensure_daemon_created(is_64)) { \
LOGW("daemon" #abi " not running, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "daemon not running", sizeof("daemon not running")); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
}
struct SigChldHandler : public EventHandler {
private:
int signal_fd_;
struct signalfd_siginfo fdsi;
int status;
std::set<pid_t> process;
public:
bool Init() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
PLOGE("set sigprocmask");
return false;
}
signal_fd_ = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (signal_fd_ == -1) {
PLOGE("create signalfd");
return false;
}
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
return true;
}
int GetFd() override {
return signal_fd_;
}
void HandleEvent(EventLoop &, uint32_t) override {
while (1) {
ssize_t s = read(signal_fd_, &fdsi, sizeof(fdsi));
if (s == -1) {
if (errno == EAGAIN) break;
PLOGE("read signalfd");
continue;
}
if (s != sizeof(fdsi)) {
LOGW("read %zu != %zu", s, sizeof(fdsi));
continue;
}
if (fdsi.ssi_signo != SIGCHLD) {
LOGW("no sigchld received");
continue;
}
int pid;
while ((pid = waitpid(-1, &status, __WALL | WNOHANG)) != 0) {
if (pid == -1) {
if (tracing_state == STOPPED && errno == ECHILD) break;
PLOGE("waitpid");
}
if (pid == 1) {
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) {
long child_pid;
ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid);
LOGV("forked %ld", child_pid);
} else if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP) && tracing_state == STOPPING) {
if (ptrace(PTRACE_DETACH, 1, 0, 0) == -1) PLOGE("failed to detach init");
tracing_state = STOPPED;
LOGI("stop tracing init");
continue;
}
if (WIFSTOPPED(status)) {
if (WPTEVENT(status) == 0) {
if (WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTTOU) {
LOGW("inject signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status));
ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status));
continue;
} else {
LOGW("suppress stopping signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status));
}
}
ptrace(PTRACE_CONT, pid, 0, 0);
}
continue;
}
CHECK_DAEMON_EXIT(64)
CHECK_DAEMON_EXIT(32)
auto state = process.find(pid);
if (state == process.end()) {
LOGV("new process %d attached", pid);
process.emplace(pid);
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
ptrace(PTRACE_CONT, pid, 0, 0);
continue;
} else {
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
char program[PATH_MAX];
if (get_program(pid, program, sizeof(program)) == -1) {
LOGW("failed to get program %d", pid);
continue;
}
LOGV("%d program %s", pid, program);
const char* tracer = NULL;
do {
if (tracing_state != TRACING) {
LOGW("stop injecting %d because not tracing", pid);
break;
}
PRE_INJECT(64, true)
PRE_INJECT(32, false)
if (tracer != NULL) {
LOGD("stopping %d", pid);
kill(pid, SIGSTOP);
ptrace(PTRACE_CONT, pid, 0, 0);
waitpid(pid, &status, __WALL);
if (STOPPED_WITH(SIGSTOP, 0)) {
LOGD("detaching %d", pid);
ptrace(PTRACE_DETACH, pid, 0, SIGSTOP);
status = 0;
auto p = fork_dont_care();
if (p == 0) {
char pid_str[32];
sprintf(pid_str, "%d", pid);
execl(tracer, basename(tracer), "trace", pid_str, "--restart", NULL);
PLOGE("failed to exec, kill");
kill(pid, SIGKILL);
exit(1);
} else if (p == -1) {
PLOGE("failed to fork, kill");
kill(pid, SIGKILL);
}
}
}
} while (false);
updateStatus();
} else {
char status_str[64];
parse_status(status, status_str, sizeof(status_str));
LOGW("process %d received unknown status %s", pid, status_str);
}
process.erase(state);
if (WIFSTOPPED(status)) {
LOGV("detach process %d", pid);
ptrace(PTRACE_DETACH, pid, 0, 0);
}
}
}
}
}
~SigChldHandler() {
if (signal_fd_ >= 0) close(signal_fd_);
}
};
static char pre_section[1024];
static char post_section[1024];
#define WRITE_STATUS_ABI(suffix) \
if (status ## suffix.supported) { \
strcat(status_text, " zygote" # suffix ": "); \
if (tracing_state != TRACING) strcat(status_text, "❓ unknown, "); \
else if (status ## suffix.zygote_injected) strcat(status_text, "😋 injected, "); \
else strcat(status_text, "❌ not injected, "); \
\
strcat(status_text, "daemon" # suffix ": "); \
if (status ## suffix.daemon_running) { \
strcat(status_text, "😋 running "); \
\
if (status ## suffix.daemon_info != NULL) { \
strcat(status_text, "("); \
strcat(status_text, status ## suffix.daemon_info); \
strcat(status_text, ")"); \
} \
} else { \
strcat(status_text, "❌ crashed "); \
\
if (status ## suffix.daemon_error_info != NULL) { \
strcat(status_text, "("); \
strcat(status_text, status ## suffix.daemon_error_info); \
strcat(status_text, ")"); \
} \
} \
}
static void updateStatus() {
FILE *prop = fopen(prop_path, "w");
char status_text[1024] = "monitor: ";
switch (tracing_state) {
case TRACING: {
strcat(status_text, "😋 tracing");
break;
}
case STOPPING: [[fallthrough]];
case STOPPED: {
strcat(status_text, "❌ stopped");
break;
}
case EXITING: {
strcat(status_text, "❌ exited");
break;
}
}
if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') {
strcat(status_text, " (");
strcat(status_text, monitor_stop_reason);
strcat(status_text, ")");
}
strcat(status_text, ",");
WRITE_STATUS_ABI(64)
WRITE_STATUS_ABI(32)
fprintf(prop, "%s[%s] %s", pre_section, status_text, post_section);
fclose(prop);
}
static bool prepare_environment() {
strcat(prop_path, zygiskd::GetTmpPath().c_str());
strcat(prop_path, "/module.prop");
close(open(prop_path, O_WRONLY | O_CREAT | O_TRUNC, 0644));
FILE *orig_prop = fopen("./module.prop", "r");
if (orig_prop == NULL) {
PLOGE("failed to open orig prop");
return false;
}
bool after_description = false;
char line[1024];
while (fgets(line, sizeof(line), orig_prop) != NULL) {
if (strncmp(line, "description=", strlen("description=")) == 0) {
strcat(pre_section, "description=");
strcat(post_section, line + strlen("description="));
after_description = true;
continue;
}
if (after_description) strcat(post_section, line);
else strcat(pre_section, line);
}
fclose(orig_prop);
/* TODO: See if ZYGISK_ENABLED flag is already set,
if so, set a status saying to disable built-in Zygisk. */
updateStatus();
return true;
}
void init_monitor() {
LOGI("ReZygisk %s", ZKSU_VERSION);
if (!prepare_environment()) exit(1);
SocketHandler socketHandler{};
socketHandler.Init();
SigChldHandler ptraceHandler{};
ptraceHandler.Init();
EventLoop looper;
looper.Init();
looper.RegisterHandler(socketHandler, EPOLLIN | EPOLLET);
looper.RegisterHandler(ptraceHandler, EPOLLIN | EPOLLET);
looper.Loop();
if (status64.daemon_info) free(status64.daemon_info);
if (status64.daemon_error_info) free(status64.daemon_error_info);
if (status32.daemon_info) free(status32.daemon_info);
if (status32.daemon_error_info) free(status32.daemon_error_info);
LOGI("exit");
}
int send_control_command(enum Command cmd) {
int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sockfd == -1) return -1;
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = { 0 }
};
size_t sun_path_len = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME);
socklen_t socklen = sizeof(sa_family_t) + sun_path_len;
ssize_t nsend = sendto(sockfd, (void *)&cmd, sizeof(cmd), 0, (sockaddr *)&addr, socklen);
/* TODO: Should we close even when it fails? */
close(sockfd);
return nsend != sizeof(cmd) ? -1 : 0;
}

View File

@@ -1,5 +1,5 @@
#ifndef MAIN_HPP
#define MAIN_HPP
#ifndef MONITOR_H
#define MONITOR_H
#include <stdbool.h>
@@ -7,7 +7,7 @@ void init_monitor();
bool trace_zygote(int pid);
enum Command {
enum rezygiskd_command {
START = 1,
STOP = 2,
EXIT = 3,
@@ -22,6 +22,6 @@ enum Command {
SYSTEM_SERVER_STARTED = 10
};
int send_control_command(enum Command cmd);
int send_control_command(enum rezygiskd_command cmd);
#endif /* MAIN_HPP */
#endif /* MONITOR_H */

View File

@@ -1,20 +1,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <vector>
#include <string>
#include <sys/mman.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <signal.h>
#include <sys/system_properties.h>
#include <string>
#include <cinttypes>
#include "utils.hpp"
#include <unistd.h>
#include "utils.h"
bool inject_on_main(int pid, const char *lib_path) {
LOGI("injecting %s to zygote %d", lib_path, pid);
@@ -25,16 +23,26 @@ bool inject_on_main(int pid, const char *lib_path) {
https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/private/KernelArgumentBlock.h;l=30;drc=6d1ee77ee32220e4202c3066f7e1f69572967ad8
*/
struct user_regs_struct regs {},
backup {};
struct user_regs_struct regs = { 0 };
/* WARNING: C++ keyword */
std::vector<MapInfo> map = MapInfo::Scan(std::to_string(pid));
if (!get_regs(pid, regs)) return false;
char pid_maps[PATH_MAX];
snprintf(pid_maps, sizeof(pid_maps), "/proc/%d/maps", pid);
struct maps *map = parse_maps(pid_maps);
if (map == NULL) {
LOGE("failed to parse remote maps");
return false;
}
if (!get_regs(pid, &regs)) return false;
uintptr_t arg = (uintptr_t)regs.REG_SP;
LOGV("kernel argument %" PRIxPTR " %s", arg, get_addr_mem_region(map, arg).c_str());
char addr_mem_region[1024];
get_addr_mem_region(map, arg, addr_mem_region, sizeof(addr_mem_region));
LOGV("kernel argument %" PRIxPTR " %s", arg, addr_mem_region);
int argc;
char **argv = (char **)((uintptr_t *)arg + 1);
@@ -43,12 +51,10 @@ bool inject_on_main(int pid, const char *lib_path) {
read_proc(pid, arg, &argc, sizeof(argc));
LOGV("argc %d", argc);
/* WARNING: C++ keyword */
auto envp = argv + argc + 1;
char **envp = argv + argc + 1;
LOGV("envp %p", (void *)envp);
/* WARNING: C++ keyword */
auto p = envp;
char **p = envp;
while (1) {
uintptr_t *buf;
read_proc(pid, (uintptr_t)p, &buf, sizeof(buf));
@@ -63,7 +69,9 @@ bool inject_on_main(int pid, const char *lib_path) {
p++;
ElfW(auxv_t) *auxv = (ElfW(auxv_t) *)p;
LOGV("auxv %p %s", auxv, get_addr_mem_region(map, (uintptr_t) auxv).c_str());
get_addr_mem_region(map, (uintptr_t)auxv, addr_mem_region, sizeof(addr_mem_region));
LOGV("auxv %p %s", auxv, addr_mem_region);
ElfW(auxv_t) *v = auxv;
uintptr_t entry_addr = 0;
@@ -78,8 +86,9 @@ bool inject_on_main(int pid, const char *lib_path) {
entry_addr = (uintptr_t)buf.a_un.a_val;
addr_of_entry_addr = (uintptr_t)v + offsetof(ElfW(auxv_t), a_un);
get_addr_mem_region(map, entry_addr, addr_mem_region, sizeof(addr_mem_region));
LOGV("entry address %" PRIxPTR " %s (entry=%" PRIxPTR ", entry_addr=%" PRIxPTR ")", entry_addr,
get_addr_mem_region(map, entry_addr).c_str(), (uintptr_t)v, addr_of_entry_addr);
addr_mem_region, (uintptr_t)v, addr_of_entry_addr);
break;
}
@@ -113,7 +122,7 @@ bool inject_on_main(int pid, const char *lib_path) {
int status;
wait_for_trace(pid, &status, __WALL);
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
if (!get_regs(pid, regs)) return false;
if (!get_regs(pid, &regs)) return false;
if (((int)regs.REG_IP & ~1) != ((int)break_addr & ~1)) {
LOGE("stopped at unknown addr %p", (void *) regs.REG_IP);
@@ -128,13 +137,25 @@ bool inject_on_main(int pid, const char *lib_path) {
if (!write_proc(pid, (uintptr_t) addr_of_entry_addr, &entry_addr, sizeof(entry_addr))) return false;
/* backup registers */
struct user_regs_struct backup;
memcpy(&backup, &regs, sizeof(regs));
/* WARNING: C++ keyword */
map = MapInfo::Scan(std::to_string(pid));
free_maps(map);
map = parse_maps(pid_maps);
if (!map) {
LOGE("failed to parse remote maps");
return false;
}
struct maps *local_map = parse_maps("/proc/self/maps");
if (!local_map) {
LOGE("failed to parse local maps");
return false;
}
/* WARNING: C++ keyword */
std::vector<MapInfo> local_map = MapInfo::Scan();
void *libc_return_addr = find_module_return_addr(map, "libc.so");
LOGD("libc return addr %p", libc_return_addr);
@@ -142,17 +163,19 @@ bool inject_on_main(int pid, const char *lib_path) {
void *dlopen_addr = find_func_addr(local_map, map, "libdl.so", "dlopen");
if (dlopen_addr == NULL) return false;
/* WARNING: C++ keyword */
std::vector<long> args;
long *args = (long *)malloc(3 * sizeof(long));
if (args == NULL) {
LOGE("malloc args");
/* WARNING: C++ keyword */
uintptr_t str = push_string(pid, regs, lib_path);
return false;
}
args.clear();
args.push_back((long) str);
args.push_back((long) RTLD_NOW);
uintptr_t str = push_string(pid, &regs, lib_path);
uintptr_t remote_handle = remote_call(pid, regs, (uintptr_t)dlopen_addr, (uintptr_t)libc_return_addr, args);
args[0] = (long) str;
args[1] = (long) RTLD_NOW;
uintptr_t remote_handle = remote_call(pid, &regs, (uintptr_t)dlopen_addr, (uintptr_t)libc_return_addr, args, 2);
LOGD("remote handle %p", (void *)remote_handle);
if (remote_handle == 0) {
LOGE("handle is null");
@@ -162,37 +185,47 @@ bool inject_on_main(int pid, const char *lib_path) {
if (dlerror_addr == NULL) {
LOGE("find dlerror");
free(args);
return false;
}
args.clear();
uintptr_t dlerror_str_addr = remote_call(pid, &regs, (uintptr_t)dlerror_addr, (uintptr_t)libc_return_addr, args, 0);
LOGD("dlerror str %p", (void *)dlerror_str_addr);
if (dlerror_str_addr == 0) {
LOGE("dlerror str is null");
uintptr_t dlerror_str_addr = remote_call(pid, regs, (uintptr_t)dlerror_addr, (uintptr_t)libc_return_addr, args);
LOGD("dlerror str %p", (void*) dlerror_str_addr);
if (dlerror_str_addr == 0) return false;
free(args);
return false;
}
void *strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen");
if (strlen_addr == NULL) {
LOGE("find strlen");
free(args);
return false;
}
args.clear();
args.push_back(dlerror_str_addr);
args[0] = (long) dlerror_str_addr;
uintptr_t dlerror_len = remote_call(pid, regs, (uintptr_t)strlen_addr, (uintptr_t)libc_return_addr, args);
uintptr_t dlerror_len = remote_call(pid, &regs, (uintptr_t)strlen_addr, (uintptr_t)libc_return_addr, args, 1);
if (dlerror_len <= 0) {
LOGE("dlerror len <= 0");
free(args);
return false;
}
/* NOTICE: C++ -> C */
char *err = (char *)malloc((dlerror_len + 1) * sizeof(char));
if (err == NULL) {
LOGE("malloc err");
free(args);
return false;
}
@@ -201,6 +234,7 @@ bool inject_on_main(int pid, const char *lib_path) {
LOGE("dlerror info %s", err);
free(err);
free(args);
return false;
}
@@ -209,12 +243,13 @@ bool inject_on_main(int pid, const char *lib_path) {
void *dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym");
if (dlsym_addr == NULL) return false;
args.clear();
str = push_string(pid, regs, "entry");
args.push_back(remote_handle);
args.push_back((long) str);
free_maps(local_map);
uintptr_t injector_entry = remote_call(pid, regs, (uintptr_t)dlsym_addr, (uintptr_t)libc_return_addr, args);
str = push_string(pid, &regs, "entry");
args[0] = remote_handle;
args[1] = (long) str;
uintptr_t injector_entry = remote_call(pid, &regs, (uintptr_t)dlsym_addr, (uintptr_t)libc_return_addr, args, 2);
LOGD("injector entry %p", (void *)injector_entry);
if (injector_entry == 0) {
LOGE("injector entry is null");
@@ -223,34 +258,41 @@ bool inject_on_main(int pid, const char *lib_path) {
}
/* record the address range of libzygisk.so */
map = MapInfo::Scan(std::to_string(pid));
void *start_addr = nullptr;
map = parse_maps(pid_maps);
void *start_addr = NULL;
size_t block_size = 0;
for (auto &info : map) {
if (strstr(info.path.c_str(), "libzygisk.so")) {
void *addr = (void *)info.start;
if (start_addr == nullptr) start_addr = addr;
size_t size = info.end - info.start;
block_size += size;
LOGD("found block %s: [%p-%p] with size %zu", info.path.c_str(), addr, (void *)info.end, size);
}
for (size_t i = 0; i < map->size; i++) {
if (!strstr(map->maps[i].path, "libzygisk.so")) continue;
if (start_addr == NULL) start_addr = (void *)map->maps[i].start;
size_t size = map->maps[i].end - map->maps[i].start;
block_size += size;
LOGD("found block %s: [%p-%p] with size %zu", map->maps[i].path, (void *)map->maps[i].start,
(void *)map->maps[i].end, size);
}
/* call injector entry(start_addr, block_size, path) */
args.clear();
args.push_back((uintptr_t) start_addr);
args.push_back(block_size);
str = push_string(pid, regs, zygiskd::GetTmpPath().c_str());
args.push_back((long) str);
free_maps(map);
remote_call(pid, regs, injector_entry, (uintptr_t)libc_return_addr, args);
/* call injector entry(start_addr, block_size, path) */
args[0] = (uintptr_t)start_addr;
args[1] = block_size;
str = push_string(pid, &regs, rezygiskd_get_path());
args[2] = (uintptr_t)str;
remote_call(pid, &regs, injector_entry, (uintptr_t)libc_return_addr, args, 3);
free(args);
/* reset pc to entry */
backup.REG_IP = (long) entry_addr;
LOGD("invoke entry");
/* restore registers */
if (!set_regs(pid, backup)) return false;
if (!set_regs(pid, &backup)) return false;
return true;
} else {
@@ -286,11 +328,10 @@ bool trace_zygote(int pid) {
WAIT_OR_DIE
if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) {
/* WARNING: C++ keyword */
std::string lib_path = zygiskd::GetTmpPath();
lib_path += "/lib" LP_SELECT("", "64") "/libzygisk.so";
char lib_path[PATH_MAX];
snprintf(lib_path, sizeof(lib_path), "%s/lib" LP_SELECT("", "64") "/libzygisk.so", rezygiskd_get_path());
if (!inject_on_main(pid, lib_path.c_str())) {
if (!inject_on_main(pid, lib_path)) {
LOGE("failed to inject");
return false;

583
loader/src/ptracer/utils.c Normal file
View File

@@ -0,0 +1,583 @@
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <ctype.h>
#include <sys/sysmacros.h>
#include <sys/ptrace.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/auxv.h>
#include <sys/uio.h>
#include <signal.h>
#include <dlfcn.h>
#include <sched.h>
#include <fcntl.h>
#include <link.h>
#include <unistd.h>
#include <linux/limits.h>
#include "utils.h"
/* INFO: utils.h must be before logging.h so that it defined LOG_TAG first */
#include "logging.h"
bool switch_mnt_ns(int pid, int *fd) {
int nsfd, old_nsfd = -1;
char path[PATH_MAX];
if (pid == 0) {
if (fd != NULL) {
nsfd = *fd;
*fd = -1;
} else return false;
snprintf(path, sizeof(path), "/proc/self/fd/%d", nsfd);
} else {
if (fd != NULL) {
old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
if (old_nsfd == -1) {
PLOGE("get old nsfd");
return false;
}
*fd = old_nsfd;
}
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
nsfd = open(path, O_RDONLY | O_CLOEXEC);
if (nsfd == -1) {
PLOGE("open nsfd %s", path);
close(old_nsfd);
return false;
}
}
if (setns(nsfd, CLONE_NEWNS) == -1) {
PLOGE("set ns to %s", path);
close(nsfd);
close(old_nsfd);
return false;
}
close(nsfd);
return true;
}
struct maps *parse_maps(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) {
LOGE("Failed to open %s", filename);
return NULL;
}
struct maps *maps = (struct maps *)malloc(sizeof(struct maps));
if (!maps) {
LOGE("Failed to allocate memory for maps");
fclose(fp);
return NULL;
}
/* INFO: To ensure in the realloc the libc will know it is meant
to allocate, and not reallocate from a garbage address. */
maps->maps = NULL;
char line[4096 * 2];
size_t i = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
/* INFO: Remove line ending at the end */
line[strlen(line) - 1] = '\0';
uintptr_t addr_start;
uintptr_t addr_end;
uintptr_t addr_offset;
ino_t inode;
unsigned int dev_major;
unsigned int dev_minor;
char permissions[5] = "";
int path_offset;
sscanf(line,
"%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s",
&addr_start, &addr_end, permissions, &addr_offset, &dev_major, &dev_minor,
&inode, &path_offset);
while (isspace(line[path_offset])) {
path_offset++;
}
maps->maps = (struct map *)realloc(maps->maps, (i + 1) * sizeof(struct map));
if (!maps->maps) {
LOGE("Failed to allocate memory for maps->maps");
maps->size = i;
fclose(fp);
free_maps(maps);
return NULL;
}
maps->maps[i].start = addr_start;
maps->maps[i].end = addr_end;
maps->maps[i].offset = addr_offset;
maps->maps[i].perms = 0;
if (permissions[0] == 'r') maps->maps[i].perms |= PROT_READ;
if (permissions[1] == 'w') maps->maps[i].perms |= PROT_WRITE;
if (permissions[2] == 'x') maps->maps[i].perms |= PROT_EXEC;
maps->maps[i].is_private = permissions[3] == 'p';
maps->maps[i].dev = makedev(dev_major, dev_minor);
maps->maps[i].inode = inode;
maps->maps[i].path = strdup(line + path_offset);
if (!maps->maps[i].path) {
LOGE("Failed to allocate memory for maps->maps[%zu].path", i);
maps->size = i;
fclose(fp);
free_maps(maps);
return NULL;
}
i++;
}
fclose(fp);
maps->size = i;
return maps;
}
void free_maps(struct maps *maps) {
if (!maps) {
return;
}
for (size_t i = 0; i < maps->size; i++) {
free((void *)maps->maps[i].path);
}
free(maps->maps);
free(maps);
}
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len) {
LOGV("write to remote addr %" PRIxPTR " size %zu", remote_addr, len);
struct iovec local = {
.iov_base = (void *)buf,
.iov_len = len
};
struct iovec remote = {
.iov_base = (void *)remote_addr,
.iov_len = len
};
ssize_t l = process_vm_writev(pid, &local, 1, &remote, 1, 0);
if (l == -1) PLOGE("process_vm_writev");
else if ((size_t)l != len) LOGW("not fully written: %zu, excepted %zu", l, len);
return l;
}
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) {
struct iovec local = {
.iov_base = (void *)buf,
.iov_len = len
};
struct iovec remote = {
.iov_base = (void *)remote_addr,
.iov_len = len
};
ssize_t l = process_vm_readv(pid, &local, 1, &remote, 1, 0);
if (l == -1) PLOGE("process_vm_readv");
else if ((size_t)l != len) LOGW("not fully read: %zu, excepted %zu", l, len);
return l;
}
bool get_regs(int pid, struct user_regs_struct *regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_GETREGS, pid, 0, regs) == -1) {
PLOGE("getregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = regs,
.iov_len = sizeof(struct user_regs_struct),
};
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
PLOGE("getregs");
return false;
}
#endif
return true;
}
bool set_regs(int pid, struct user_regs_struct *regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_SETREGS, pid, 0, regs) == -1) {
PLOGE("setregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = regs,
.iov_len = sizeof(struct user_regs_struct),
};
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
PLOGE("setregs");
return false;
}
#endif
return true;
}
void get_addr_mem_region(struct maps *info, uintptr_t addr, char *buf, size_t buf_size) {
for (size_t i = 0; i < info->size; i++) {
/* TODO: Early "leave" */
if (info->maps[i].start <= addr && info->maps[i].end > addr) {
snprintf(buf, buf_size, "%s %s%s%s",
info->maps[i].path,
info->maps[i].perms & PROT_READ ? "r" : "-",
info->maps[i].perms & PROT_WRITE ? "w" : "-",
info->maps[i].perms & PROT_EXEC ? "x" : "-");
return;
}
}
snprintf(buf, buf_size, "<unknown>");
}
/* INFO: strrchr but without modifying the string */
const char *position_after(const char *str, const char needle) {
const char *positioned = str + strlen(str);
int i = strlen(str);
while (i != 0) {
i--;
if (str[i] == needle) {
positioned = str + i + 1;
break;
}
}
return positioned;
}
void *find_module_return_addr(struct maps *map, const char *suffix) {
for (size_t i = 0; i < map->size; i++) {
/* TODO: Make it NULL in 1 length path */
if (map->maps[i].path == NULL) continue;
const char *file_name = position_after(map->maps[i].path, '/');
if (!file_name) continue;
if (strlen(file_name) < strlen(suffix) || (map->maps[i].perms & PROT_EXEC) != 0 || strncmp(file_name, suffix, strlen(suffix)) != 0) continue;
return (void *)map->maps[i].start;
}
return NULL;
}
void *find_module_base(struct maps *map, const char *suffix) {
for (size_t i = 0; i < map->size; i++) {
/* TODO: Make it NULL in 1 length path */
if (map->maps[i].path == NULL) continue;
const char *file_name = position_after(map->maps[i].path, '/');
if (!file_name) continue;
if (strlen(file_name) < strlen(suffix) || map->maps[i].offset != 0 || strncmp(file_name, suffix, strlen(suffix)) != 0) continue;
return (void *)map->maps[i].start;
}
return NULL;
}
void *find_func_addr(struct maps *local_info, struct maps *remote_info, const char *module, const char *func) {
void *lib = dlopen(module, RTLD_NOW);
if (lib == NULL) {
LOGE("failed to open lib %s: %s", module, dlerror());
return NULL;
}
uint8_t *sym = (uint8_t *)dlsym(lib, func);
if (sym == NULL) {
LOGE("failed to find sym %s in %s: %s", func, module, dlerror());
dlclose(lib);
return NULL;
}
LOGD("sym %s: %p", func, sym);
dlclose(lib);
uint8_t *local_base = (uint8_t *)find_module_base(local_info, module);
if (local_base == NULL) {
LOGE("failed to find local base for module %s", module);
return NULL;
}
uint8_t *remote_base = (uint8_t *)find_module_base(remote_info, module);
if (remote_base == NULL) {
LOGE("failed to find remote base for module %s", module);
return NULL;
}
LOGD("found local base %p remote base %p", local_base, remote_base);
uint8_t *addr = (sym - local_base) + remote_base;
LOGD("addr %p", addr);
return addr;
}
void align_stack(struct user_regs_struct *regs, long preserve) {
/* INFO: ~0xf is a negative value, and REG_SP is unsigned,
so we must cast REG_SP to signed type before subtracting
then cast back to unsigned type.
*/
regs->REG_SP = (uintptr_t)((intptr_t)(regs->REG_SP - preserve) & ~0xf);
}
uintptr_t push_string(int pid, struct user_regs_struct *regs, const char *str) {
size_t len = strlen(str) + 1;
regs->REG_SP -= len;
align_stack(regs, 0);
uintptr_t addr = (uintptr_t)regs->REG_SP;
if (!write_proc(pid, addr, str, len)) LOGE("failed to write string %s", str);
LOGD("pushed string %" PRIxPTR, addr);
return addr;
}
uintptr_t remote_call(int pid, struct user_regs_struct *regs, uintptr_t func_addr, uintptr_t return_addr, long *args, size_t args_size) {
align_stack(regs, 0);
LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args_size);
for (size_t i = 0; i < args_size; i++) {
LOGV("arg %p", (void *)args[i]);
}
#if defined(__x86_64__)
if (args_size >= 1) regs->rdi = args[0];
if (args_size >= 2) regs->rsi = args[1];
if (args_size >= 3) regs->rdx = args[2];
if (args_size >= 4) regs->rcx = args[3];
if (args_size >= 5) regs->r8 = args[4];
if (args_size >= 6) regs->r9 = args[5];
if (args_size > 6) {
long remain = (args_size - 6L) * sizeof(long);
align_stack(regs, remain);
if (!write_proc(pid, (uintptr_t) regs->REG_SP, args, remain)) LOGE("failed to push arguments");
}
regs->REG_SP -= sizeof(long);
if (!write_proc(pid, (uintptr_t) regs->REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr");
regs->REG_IP = func_addr;
#elif defined(__i386__)
if (args_size > 0) {
long remain = (args_size) * sizeof(long);
align_stack(regs, remain);
if (!write_proc(pid, (uintptr_t) regs->REG_SP, args, remain)) LOGE("failed to push arguments");
}
regs->REG_SP -= sizeof(long);
if (!write_proc(pid, (uintptr_t) regs->REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr");
regs->REG_IP = func_addr;
#elif defined(__aarch64__)
for (size_t i = 0; i < args_size && i < 8; i++) {
regs->regs[i] = args[i];
}
if (args_size > 8) {
long remain = (args_size - 8) * sizeof(long);
align_stack(regs, remain);
write_proc(pid, (uintptr_t)regs->REG_SP, args, remain);
}
regs->regs[30] = return_addr;
regs->REG_IP = func_addr;
#elif defined(__arm__)
for (size_t i = 0; i < args_size && i < 4; i++) {
regs->uregs[i] = args[i];
}
if (args_size > 4) {
long remain = (args_size - 4) * sizeof(long);
align_stack(regs, remain);
write_proc(pid, (uintptr_t)regs->REG_SP, args, remain);
}
regs->uregs[14] = return_addr;
regs->REG_IP = func_addr;
unsigned long CPSR_T_MASK = 1lu << 5;
if ((regs->REG_IP & 1) != 0) {
regs->REG_IP = regs->REG_IP & ~1;
regs->uregs[16] = regs->uregs[16] | CPSR_T_MASK;
} else {
regs->uregs[16] = regs->uregs[16] & ~CPSR_T_MASK;
}
#endif
if (!set_regs(pid, regs)) {
LOGE("failed to set regs");
return 0;
}
ptrace(PTRACE_CONT, pid, 0, 0);
int status;
wait_for_trace(pid, &status, __WALL);
if (!get_regs(pid, regs)) {
LOGE("failed to get regs after call");
return 0;
}
if (WSTOPSIG(status) == SIGSEGV) {
if ((uintptr_t)regs->REG_IP != return_addr) {
LOGE("wrong return addr %p", (void *) regs->REG_IP);
return 0;
}
return regs->REG_RET;
} else {
char status_str[64];
parse_status(status, status_str, sizeof(status_str));
LOGE("stopped by other reason %s at addr %p", status_str, (void *)regs->REG_IP);
}
return 0;
}
int fork_dont_care() {
pid_t pid = fork();
if (pid < 0) PLOGE("fork 1");
else if (pid == 0) {
pid = fork();
if (pid < 0) PLOGE("fork 2");
else if (pid > 0) exit(0);
} else {
int status;
waitpid(pid, &status, __WALL);
}
return pid;
}
void wait_for_trace(int pid, int *status, int flags) {
while (1) {
pid_t result = waitpid(pid, status, flags);
if (result == -1) {
if (errno == EINTR) continue;
PLOGE("wait %d failed", pid);
exit(1);
}
if (!WIFSTOPPED(*status)) {
char status_str[64];
parse_status(*status, status_str, sizeof(status_str));
LOGE("process %d not stopped for trace: %s, exit", pid, status_str);
exit(1);
}
return;
}
}
void parse_status(int status, char *buf, size_t len) {
snprintf(buf, len, "0x%x ", status);
if (WIFEXITED(status)) {
snprintf(buf + strlen(buf), len - strlen(buf), "exited with %d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
snprintf(buf + strlen(buf), len - strlen(buf), "signaled with %s(%d)", sigabbrev_np(WTERMSIG(status)), WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
snprintf(buf + strlen(buf), len - strlen(buf), "stopped by ");
int stop_sig = WSTOPSIG(status);
snprintf(buf + strlen(buf), len - strlen(buf), "signal=%s(%d),", sigabbrev_np(stop_sig), stop_sig);
snprintf(buf + strlen(buf), len - strlen(buf), "event=%s", parse_ptrace_event(status));
} else {
snprintf(buf + strlen(buf), len - strlen(buf), "unknown");
}
}
int get_program(int pid, char *buf, size_t size) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "/proc/%d/exe", pid);
ssize_t sz = readlink(path, buf, size);
if (sz == -1) {
PLOGE("readlink /proc/%d/exe", pid);
return -1;
}
buf[sz] = '\0';
return 0;
}

View File

@@ -1,528 +0,0 @@
#include <linux/limits.h>
#include <vector>
#include <sys/mman.h>
#include <sys/sysmacros.h>
#include <array>
#include <cinttypes>
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <vector>
#include <string>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include <signal.h>
#include <sstream>
#include <ios>
#include <cstring>
#include <sys/stat.h>
#include <sched.h>
#include <fcntl.h>
#include "utils.hpp"
#include "logging.h"
bool switch_mnt_ns(int pid, int *fd) {
int nsfd, old_nsfd = -1;
/* WARNING: C++ keyword */
char path[PATH_MAX];
if (pid == 0) {
if (fd != NULL) {
nsfd = *fd;
*fd = -1;
} else return false;
snprintf(path, sizeof(path), "/proc/self/fd/%d", nsfd);
} else {
if (fd != NULL) {
old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
if (old_nsfd == -1) {
PLOGE("get old nsfd");
return false;
}
*fd = old_nsfd;
}
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
nsfd = open(path, O_RDONLY | O_CLOEXEC);
if (nsfd == -1) {
PLOGE("open nsfd %s", path);
close(old_nsfd);
return false;
}
}
if (setns(nsfd, CLONE_NEWNS) == -1) {
PLOGE("set ns to %s", path);
close(nsfd);
close(old_nsfd);
return false;
}
close(nsfd);
return true;
}
/* WARNING: C++ keyword */
std::vector<MapInfo> MapInfo::Scan(const std::string &pid) {
constexpr static auto kPermLength = 5;
constexpr static auto kMapEntry = 7;
/* WARNING: C++ keyword */
std::vector<MapInfo> info;
char file_name[NAME_MAX];
snprintf(file_name, sizeof(file_name), "/proc/%s/maps", pid.c_str());
/* WARNING: C++ keyword */
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{fopen(file_name, "r"), &fclose};
if (maps) {
char *line = NULL;
size_t len = 0;
ssize_t read;
/* WARNING: C++ keyword */
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;
/* WARNING: C++ keyword */
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) continue;
while (path_off < read && isspace(line[path_off])) path_off++;
/* WARNING: C++ keyword */
MapInfo &ref = info.emplace_back(MapInfo{
start,
end,
0,
perm[3] == 'p',
off,
static_cast<dev_t>(makedev(dev_major, dev_minor)),
inode,
line + path_off
});
if (perm[0] == 'r') ref.perms |= PROT_READ;
if (perm[1] == 'w') ref.perms |= PROT_WRITE;
if (perm[2] == 'x') ref.perms |= PROT_EXEC;
}
free(line);
}
return info;
}
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len) {
LOGV("write to remote addr %" PRIxPTR " size %zu", remote_addr, len);
struct iovec local = {
.iov_base = (void *)buf,
.iov_len = len
};
struct iovec remote = {
.iov_base = (void *)remote_addr,
.iov_len = len
};
ssize_t l = process_vm_writev(pid, &local, 1, &remote, 1, 0);
if (l == -1) PLOGE("process_vm_writev");
else if ((size_t)l != len) LOGW("not fully written: %zu, excepted %zu", l, len);
return l;
}
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) {
struct iovec local = {
.iov_base = (void *)buf,
.iov_len = len
};
struct iovec remote = {
.iov_base = (void *)remote_addr,
.iov_len = len
};
ssize_t l = process_vm_readv(pid, &local, 1, &remote, 1, 0);
if (l == -1) PLOGE("process_vm_readv");
else if ((size_t)l != len) LOGW("not fully read: %zu, excepted %zu", l, len);
return l;
}
bool get_regs(int pid, struct user_regs_struct &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_GETREGS, pid, 0, &regs) == -1) {
PLOGE("getregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.iov_len = sizeof(struct user_regs_struct),
};
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
PLOGE("getregs");
return false;
}
#endif
return true;
}
bool set_regs(int pid, struct user_regs_struct &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_SETREGS, pid, 0, &regs) == -1) {
PLOGE("setregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.iov_len = sizeof(struct user_regs_struct),
};
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
PLOGE("setregs");
return false;
}
#endif
return true;
}
/* WARNING: C++ keyword */
std::string get_addr_mem_region(std::vector<MapInfo> &info, uintptr_t addr) {
/* WARNING: C++ keyword */
for (auto &map: info) {
if (map.start <= addr && map.end > addr) {
/* WARNING: C++ keyword */
auto s = std::string(map.path);
s += ' ';
s += map.perms & PROT_READ ? 'r' : '-';
s += map.perms & PROT_WRITE ? 'w' : '-';
s += map.perms & PROT_EXEC ? 'x' : '-';
return s;
}
}
return "<unknown>";
}
/* WARNING: C++ keyword */
void *find_module_return_addr(std::vector<MapInfo> &info, std::string_view suffix) {
/* WARNING: C++ keyword */
for (auto &map: info) {
/* WARNING: C++ keyword */
if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) return (void *)map.start;
}
return NULL;
}
/* WARNING: C++ keyword */
void *find_module_base(std::vector<MapInfo> &info, std::string_view suffix) {
/* WARNING: C++ keyword */
for (auto &map: info) {
/* WARNING: C++ keyword */
if (map.offset == 0 && map.path.ends_with(suffix)) return (void *)map.start;
}
return NULL;
}
/* WARNING: C++ keyword */
void *find_func_addr(std::vector<MapInfo> &local_info, std::vector<MapInfo> &remote_info, std::string_view module, std::string_view func) {
void *lib = dlopen(module.data(), RTLD_NOW);
if (lib == NULL) {
LOGE("failed to open lib %s: %s", module.data(), dlerror());
return NULL;
}
uint8_t *sym = (uint8_t *)dlsym(lib, func.data());
if (sym == NULL) {
LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror());
dlclose(lib);
return NULL;
}
LOGD("sym %s: %p", func.data(), sym);
dlclose(lib);
uint8_t *local_base = (uint8_t *)find_module_base(local_info, module);
if (local_base == NULL) {
LOGE("failed to find local base for module %s", module.data());
return NULL;
}
uint8_t *remote_base = (uint8_t *)find_module_base(remote_info, module);
if (remote_base == NULL) {
LOGE("failed to find remote base for module %s", module.data());
return NULL;
}
LOGD("found local base %p remote base %p", local_base, remote_base);
uint8_t *addr = (sym - local_base) + remote_base;
LOGD("addr %p", addr);
return addr;
}
/* WARNING: C++ keyword */
void align_stack(struct user_regs_struct &regs, long preserve) {
/* INFO: ~0xf is a negative value, and REG_SP is unsigned,
so we must cast REG_SP to signed type before subtracting
then cast back to unsigned type.
*/
regs.REG_SP = (uintptr_t)((intptr_t)(regs.REG_SP - preserve) & ~0xf);
}
/* WARNING: C++ keyword */
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str) {
size_t len = strlen(str) + 1;
regs.REG_SP -= len;
align_stack(regs);
uintptr_t addr = (uintptr_t)regs.REG_SP;
if (!write_proc(pid, addr, str, len)) LOGE("failed to write string %s", str);
LOGD("pushed string %" PRIxPTR, addr);
return addr;
}
/* WARNING: C++ keyword */
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr, std::vector<long> &args) {
align_stack(regs);
/* WARNING: C++ keyword */
LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args.size());
/* WARNING: C++ keyword */
for (auto &a: args) {
LOGV("arg %p", (void *) a);
}
#if defined(__x86_64__)
if (args.size() >= 1) regs.rdi = args[0];
if (args.size() >= 2) regs.rsi = args[1];
if (args.size() >= 3) regs.rdx = args[2];
if (args.size() >= 4) regs.rcx = args[3];
if (args.size() >= 5) regs.r8 = args[4];
if (args.size() >= 6) regs.r9 = args[5];
if (args.size() > 6) {
long remain = (args.size() - 6L) * sizeof(long);
align_stack(regs, remain);
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) LOGE("failed to push arguments");
}
regs.REG_SP -= sizeof(long);
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr");
regs.REG_IP = func_addr;
#elif defined(__i386__)
if (args.size() > 0) {
long remain = (args.size()) * sizeof(long);
align_stack(regs, remain);
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) LOGE("failed to push arguments");
}
regs.REG_SP -= sizeof(long);
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr");
regs.REG_IP = func_addr;
#elif defined(__aarch64__)
for (size_t i = 0; i < args.size() && i < 8; i++) {
regs.regs[i] = args[i];
}
if (args.size() > 8) {
long remain = (args.size() - 8) * sizeof(long);
align_stack(regs, remain);
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
}
regs.regs[30] = return_addr;
regs.REG_IP = func_addr;
#elif defined(__arm__)
for (size_t i = 0; i < args.size() && i < 4; i++) {
regs.uregs[i] = args[i];
}
if (args.size() > 4) {
long remain = (args.size() - 4) * sizeof(long);
align_stack(regs, remain);
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
}
regs.uregs[14] = return_addr;
regs.REG_IP = func_addr;
constexpr auto CPSR_T_MASK = 1lu << 5;
if ((regs.REG_IP & 1) != 0) {
regs.REG_IP = regs.REG_IP & ~1;
regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK;
} else {
regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK;
}
#endif
if (!set_regs(pid, regs)) {
LOGE("failed to set regs");
return 0;
}
ptrace(PTRACE_CONT, pid, 0, 0);
int status;
wait_for_trace(pid, &status, __WALL);
if (!get_regs(pid, regs)) {
LOGE("failed to get regs after call");
return 0;
}
if (WSTOPSIG(status) == SIGSEGV) {
if ((uintptr_t)regs.REG_IP != return_addr) {
LOGE("wrong return addr %p", (void *) regs.REG_IP);
return 0;
}
return regs.REG_RET;
} else {
char status_str[64];
parse_status(status, status_str, sizeof(status_str));
LOGE("stopped by other reason %s at addr %p", status_str, (void *)regs.REG_IP);
}
return 0;
}
int fork_dont_care() {
pid_t pid = fork();
if (pid < 0) PLOGE("fork 1");
else if (pid == 0) {
pid = fork();
if (pid < 0) PLOGE("fork 2");
else if (pid > 0) exit(0);
} else {
int status;
waitpid(pid, &status, __WALL);
}
return pid;
}
void wait_for_trace(int pid, int *status, int flags) {
while (1) {
pid_t result = waitpid(pid, status, flags);
if (result == -1) {
if (errno == EINTR) continue;
PLOGE("wait %d failed", pid);
exit(1);
}
if (!WIFSTOPPED(*status)) {
char status_str[64];
parse_status(*status, status_str, sizeof(status_str));
LOGE("process %d not stopped for trace: %s, exit", pid, status_str);
exit(1);
}
return;
}
}
void parse_status(int status, char *buf, size_t len) {
snprintf(buf, len, "0x%x ", status);
if (WIFEXITED(status)) {
snprintf(buf + strlen(buf), len - strlen(buf), "exited with %d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
snprintf(buf + strlen(buf), len - strlen(buf), "signaled with %s(%d)", sigabbrev_np(WTERMSIG(status)), WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
snprintf(buf + strlen(buf), len - strlen(buf), "stopped by ");
int stop_sig = WSTOPSIG(status);
snprintf(buf + strlen(buf), len - strlen(buf), "signal=%s(%d),", sigabbrev_np(stop_sig), stop_sig);
snprintf(buf + strlen(buf), len - strlen(buf), "event=%s", parse_ptrace_event(status));
} else {
snprintf(buf + strlen(buf), len - strlen(buf), "unknown");
}
}
int get_program(int pid, char *buf, size_t size) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "/proc/%d/exe", pid);
ssize_t sz = readlink(path, buf, size);
if (sz == -1) {
PLOGE("readlink /proc/%d/exe", pid);
return -1;
}
buf[sz] = 0;
return 0;
}

115
loader/src/ptracer/utils.h Normal file
View File

@@ -0,0 +1,115 @@
#ifndef UTILS_H
#define UTILS_H
#include <sys/ptrace.h>
#include "daemon.h"
#ifdef __LP64__
#define LOG_TAG "zygisk-ptrace64"
#else
#define LOG_TAG "zygisk-ptrace32"
#endif
#include "logging.h"
struct map {
uintptr_t start;
uintptr_t end;
uint8_t perms;
bool is_private;
uintptr_t offset;
dev_t dev;
ino_t inode;
const char *path;
};
struct maps {
struct map *maps;
size_t size;
};
struct maps *parse_maps(const char *filename);
void free_maps(struct maps *maps);
#if defined(__x86_64__)
#define REG_SP rsp
#define REG_IP rip
#define REG_RET rax
#elif defined(__i386__)
#define REG_SP esp
#define REG_IP eip
#define REG_RET eax
#elif defined(__aarch64__)
#define REG_SP sp
#define REG_IP pc
#define REG_RET regs[0]
#elif defined(__arm__)
#define REG_SP uregs[13]
#define REG_IP uregs[15]
#define REG_RET uregs[0]
#define user_regs_struct user_regs
#endif
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len);
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
bool get_regs(int pid, struct user_regs_struct *regs);
bool set_regs(int pid, struct user_regs_struct *regs);
void get_addr_mem_region(struct maps *map, uintptr_t addr, char *buf, size_t buf_size);
void *find_module_return_addr(struct maps *map, const char *suffix);
void *find_func_addr(struct maps *local_info, struct maps *remote_info, const char *module, const char *func);
void align_stack(struct user_regs_struct *regs, long preserve);
uintptr_t push_string(int pid, struct user_regs_struct *regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct *regs, uintptr_t func_addr, uintptr_t return_addr, long *args, size_t args_size);
int fork_dont_care();
void wait_for_trace(int pid, int* status, int flags);
void parse_status(int status, char *buf, size_t len);
#define WPTEVENT(x) (x >> 16)
#define CASE_CONST_RETURN(x) case x: return #x;
static inline const char *parse_ptrace_event(int status) {
status = status >> 16;
switch (status) {
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
CASE_CONST_RETURN(PTRACE_EVENT_STOP)
default:
return "(no event)";
}
}
static inline const char *sigabbrev_np(int sig) {
if (sig > 0 && sig < NSIG) return sys_signame[sig];
return "(unknown)";
}
int get_program(int pid, char *buf, size_t size);
/* INFO: pid = 0, fd != nullptr -> set to fd
pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd
*/
bool switch_mnt_ns(int pid, int *fd);
#endif /* UTILS_H */

View File

@@ -1,125 +0,0 @@
#pragma once
#include <string>
#include <sys/ptrace.h>
#include <map>
#include "daemon.h"
#ifdef __LP64__
#define LOG_TAG "zygisk-ptrace64"
#else
#define LOG_TAG "zygisk-ptrace32"
#endif
#include "logging.h"
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.
static std::vector<MapInfo> Scan(const std::string& pid = "self");
};
#if defined(__x86_64__)
#define REG_SP rsp
#define REG_IP rip
#define REG_RET rax
#elif defined(__i386__)
#define REG_SP esp
#define REG_IP eip
#define REG_RET eax
#elif defined(__aarch64__)
#define REG_SP sp
#define REG_IP pc
#define REG_RET regs[0]
#elif defined(__arm__)
#define REG_SP uregs[13]
#define REG_IP uregs[15]
#define REG_RET uregs[0]
#define user_regs_struct user_regs
#endif
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len);
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
bool get_regs(int pid, struct user_regs_struct &regs);
bool set_regs(int pid, struct user_regs_struct &regs);
std::string get_addr_mem_region(std::vector<MapInfo> &info, uintptr_t addr);
void *find_module_base(std::vector<MapInfo> &info, std::string_view suffix);
void *find_func_addr(
std::vector<MapInfo> &local_info,
std::vector<MapInfo> &remote_info,
std::string_view module,
std::string_view func);
void align_stack(struct user_regs_struct &regs, long preserve = 0);
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<long> &args);
int fork_dont_care();
void wait_for_trace(int pid, int* status, int flags);
void parse_status(int status, char *buf, size_t len);
#define WPTEVENT(x) (x >> 16)
#define CASE_CONST_RETURN(x) case x: return #x;
inline const char* parse_ptrace_event(int status) {
status = status >> 16;
switch (status) {
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
CASE_CONST_RETURN(PTRACE_EVENT_STOP)
default:
return "(no event)";
}
}
inline const char* sigabbrev_np(int sig) {
if (sig > 0 && sig < NSIG) return sys_signame[sig];
return "(unknown)";
}
int get_program(int pid, char *buf, size_t size);
void *find_module_return_addr(std::vector<MapInfo> &info, std::string_view suffix);
// pid = 0, fd != nullptr -> set to fd
// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd
bool switch_mnt_ns(int pid, int *fd);

View File

@@ -27,7 +27,7 @@ enum DaemonSocketAction {
GetModuleDir = 5,
ZygoteRestart = 6,
SystemServerStarted = 7,
GetCleanNamespace = 8
UpdateMountNamespace = 8
};
enum ProcessFlags: uint32_t {

View File

@@ -65,7 +65,7 @@
return -1; \
}
#define write_func_def(type) \
#define write_func_def(type) \
ssize_t write_## type(int fd, type val)
#define read_func_def(type) \

View File

@@ -58,34 +58,6 @@ static enum Architecture get_arch(void) {
exit(1);
}
int create_library_fd(const char *restrict so_path) {
int so_fd = open(so_path, O_RDONLY);
if (so_fd == -1) {
LOGE("Failed opening so file: %s\n", strerror(errno));
return -1;
}
off_t so_size = lseek(so_fd, 0, SEEK_END);
if (so_size == -1) {
LOGE("Failed getting so file size: %s\n", strerror(errno));
close(so_fd);
return -1;
}
if (lseek(so_fd, 0, SEEK_SET) == -1) {
LOGE("Failed seeking so file: %s\n", strerror(errno));
close(so_fd);
return -1;
}
return so_fd;
}
/* WARNING: Dynamic memory based */
static void load_modules(enum Architecture arch, struct Context *restrict context) {
context->len = 0;
@@ -138,7 +110,7 @@ static void load_modules(enum Architecture arch, struct Context *restrict contex
errno = 0;
} else continue;
int lib_fd = create_library_fd(so_path);
int lib_fd = open(so_path, O_RDONLY | O_CLOEXEC);
if (lib_fd == -1) {
LOGE("Failed loading module `%s`\n", name);
@@ -556,12 +528,6 @@ void zygiskd_start(char *restrict argv[]) {
break;
}
if (write_string(client_fd, context.modules[i].name) == -1) {
LOGE("Failed writing module name.\n");
break;
}
}
break;
@@ -663,18 +629,18 @@ void zygiskd_start(char *restrict argv[]) {
break;
}
case GetCleanNamespace: {
case UpdateMountNamespace: {
pid_t pid = 0;
ssize_t ret = read_uint32_t(client_fd, (uint32_t *)&pid);
ASSURE_SIZE_READ_BREAK("GetCleanNamespace", "pid", ret, sizeof(pid));
ASSURE_SIZE_READ_BREAK("UpdateMountNamespace", "pid", ret, sizeof(pid));
uint8_t mns_state = 0;
ret = read_uint8_t(client_fd, &mns_state);
ASSURE_SIZE_READ_BREAK("GetCleanNamespace", "mns_state", ret, sizeof(mns_state));
ASSURE_SIZE_READ_BREAK("UpdateMountNamespace", "mns_state", ret, sizeof(mns_state));
uint32_t our_pid = (uint32_t)getpid();
ret = write_uint32_t(client_fd, (uint32_t)our_pid);
ASSURE_SIZE_WRITE_BREAK("GetCleanNamespace", "our_pid", ret, sizeof(our_pid));
ASSURE_SIZE_WRITE_BREAK("UpdateMountNamespace", "our_pid", ret, sizeof(our_pid));
if ((enum MountNamespaceState)mns_state == Clean) {
save_mns_fd(pid, Rooted, impl);
@@ -683,7 +649,7 @@ void zygiskd_start(char *restrict argv[]) {
uint32_t clean_namespace_fd = (uint32_t)save_mns_fd(pid, (enum MountNamespaceState)mns_state, impl);
ret = write_uint32_t(client_fd, clean_namespace_fd);
ASSURE_SIZE_WRITE_BREAK("GetCleanNamespace", "clean_namespace_fd", ret, sizeof(clean_namespace_fd));
ASSURE_SIZE_WRITE_BREAK("UpdateMountNamespace", "clean_namespace_fd", ret, sizeof(clean_namespace_fd));
break;
}