You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
Compare commits
19 Commits
v1.0.0-rc.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
153097f9d8 | ||
|
|
0f27e455e7 | ||
|
|
3688df6450 | ||
|
|
0c7a756030 | ||
|
|
70805bb390 | ||
|
|
e6344d2e12 | ||
|
|
d2ebb2bfed | ||
|
|
7e823319b7 | ||
|
|
f9fcf1c2e7 | ||
|
|
08513b17e8 | ||
|
|
a7917e20fe | ||
|
|
e0ce1473dd | ||
|
|
bf3c73d72b | ||
|
|
510e8a2de4 | ||
|
|
38cfbb25ef | ||
|
|
d54cac89a7 | ||
|
|
90da42a10b | ||
|
|
872ba693a1 | ||
|
|
2dfa221287 |
@@ -57,7 +57,6 @@ android {
|
||||
defaultConfig {
|
||||
externalNativeBuild.cmake {
|
||||
arguments += "-DANDROID_STL=none"
|
||||
arguments += "-DLSPLT_STANDALONE=ON"
|
||||
arguments += "-DCMAKE_BUILD_PARALLEL_LEVEL=${Runtime.getRuntime().availableProcessors()}"
|
||||
cFlags("-std=c18", *defaultCFlags)
|
||||
cppFlags("-std=c++20", *defaultCFlags)
|
||||
|
||||
@@ -15,7 +15,7 @@ target_link_libraries(common log)
|
||||
aux_source_directory(injector INJECTOR_SRC_LIST)
|
||||
add_library(zygisk SHARED ${INJECTOR_SRC_LIST})
|
||||
target_include_directories(zygisk PRIVATE include)
|
||||
target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap)
|
||||
target_link_libraries(zygisk cxx::cxx log common lsplt_static)
|
||||
|
||||
aux_source_directory(ptracer PTRACER_SRC_LIST)
|
||||
add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST})
|
||||
|
||||
@@ -108,43 +108,32 @@ void rezygiskd_get_info(struct rezygisk_info *info) {
|
||||
|
||||
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;
|
||||
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) {
|
||||
info->modules.modules = (char **)malloc(sizeof(char *) * info->modules.modules_count);
|
||||
if (!info->modules.modules) {
|
||||
PLOGE("allocating modules name memory");
|
||||
|
||||
free(info->modules);
|
||||
info->modules = NULL;
|
||||
info->modules->modules_count = 0;
|
||||
info->modules.modules_count = 0;
|
||||
|
||||
close(fd);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < info->modules->modules_count; i++) {
|
||||
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;
|
||||
goto info_cleanup;
|
||||
}
|
||||
|
||||
char module_path[PATH_MAX];
|
||||
@@ -156,43 +145,49 @@ void rezygiskd_get_info(struct rezygisk_info *info) {
|
||||
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;
|
||||
goto info_cleanup;
|
||||
}
|
||||
|
||||
info->modules.modules[i] = NULL;
|
||||
|
||||
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);
|
||||
info->modules.modules[i] = strndup(line + 5, strlen(line) - 6);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (info->modules.modules[i] == NULL) {
|
||||
PLOGE("failed to read module name from %s", module_path);
|
||||
|
||||
fclose(module_prop);
|
||||
|
||||
goto info_cleanup;
|
||||
}
|
||||
|
||||
fclose(module_prop);
|
||||
|
||||
continue;
|
||||
|
||||
info_cleanup:
|
||||
info->modules.modules_count = i;
|
||||
free_rezygisk_info(info);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
for (size_t i = 0; i < info->modules.modules_count; i++) {
|
||||
free(info->modules.modules[i]);
|
||||
}
|
||||
|
||||
free(info->modules);
|
||||
free(info->modules.modules);
|
||||
info->modules.modules = NULL;
|
||||
}
|
||||
|
||||
bool rezygiskd_read_modules(struct zygisk_modules *modules) {
|
||||
@@ -237,13 +232,11 @@ bool rezygiskd_read_modules(struct zygisk_modules *modules) {
|
||||
}
|
||||
|
||||
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);
|
||||
for (size_t i = 0; i < modules->modules_count; i++) {
|
||||
free(modules->modules[i]);
|
||||
}
|
||||
|
||||
free(modules->modules);
|
||||
}
|
||||
|
||||
int rezygiskd_connect_companion(size_t index) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/auxv.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -159,14 +160,14 @@ ElfImg *ElfImg_create(const char *elf, void *base) {
|
||||
}
|
||||
|
||||
if (base) {
|
||||
/* LOGI: Due to the use in zygisk-ptracer, we need to allow pre-
|
||||
/* INFO: Due to the use in zygisk-ptracer, we need to allow pre-
|
||||
fetched bases to be passed, as the linker (Android 7.1
|
||||
and below) is not loaded from dlopen, which makes it not
|
||||
be visible with dl_iterate_phdr.
|
||||
*/
|
||||
img->base = base;
|
||||
|
||||
LOGI("Using provided base address 0x%p for %s", base, elf);
|
||||
LOGD("Using provided base address 0x%p for %s", base, elf);
|
||||
} else {
|
||||
if (!_find_module_base(img)) {
|
||||
LOGE("Failed to find module base for %s using dl_iterate_phdr", elf);
|
||||
@@ -387,7 +388,7 @@ ElfImg *ElfImg_create(const char *elf, void *base) {
|
||||
img->symstr_offset_for_symtab = 0;
|
||||
}
|
||||
} else {
|
||||
LOGI("No .symtab section found or section headers missing");
|
||||
LOGD("No .symtab section found or section headers missing");
|
||||
|
||||
img->symtab_start = NULL;
|
||||
img->symtab_count = 0;
|
||||
@@ -403,43 +404,22 @@ ElfImg *ElfImg_create(const char *elf, void *base) {
|
||||
img->bias = phdr[i].p_vaddr - phdr[i].p_offset;
|
||||
bias_calculated = true;
|
||||
|
||||
LOGI("Calculated bias %ld from PT_LOAD segment %d (vaddr %lx)", (long)img->bias, i, (unsigned long)phdr[i].p_vaddr);
|
||||
LOGD("Calculated bias %ld from PT_LOAD segment %d (vaddr %lx)", (long)img->bias, i, (unsigned long)phdr[i].p_vaddr);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bias_calculated) {
|
||||
for (int i = 0; i < img->header->e_phnum; ++i) {
|
||||
if (phdr[i].p_type == PT_LOAD) {
|
||||
img->bias = phdr[i].p_vaddr - phdr[i].p_offset;
|
||||
bias_calculated = true;
|
||||
if (!bias_calculated) for (int i = 0; i < img->header->e_phnum; ++i) {
|
||||
if (phdr[i].p_type != PT_LOAD) continue;
|
||||
|
||||
LOGI("Calculated bias %ld from first PT_LOAD segment %d (vaddr %lx, offset %lx)",
|
||||
(long)img->bias, i, (unsigned long)phdr[i].p_vaddr, (unsigned long)phdr[i].p_offset);
|
||||
img->bias = phdr[i].p_vaddr - phdr[i].p_offset;
|
||||
bias_calculated = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGD("Calculated bias %ld from first PT_LOAD segment %d (vaddr %lx, offset %lx)",
|
||||
(long)img->bias, i, (unsigned long)phdr[i].p_vaddr, (unsigned long)phdr[i].p_offset);
|
||||
|
||||
if (!bias_calculated && shdr_base) {
|
||||
LOGW("Could not calculate bias from program headers, falling back to section method.");
|
||||
uintptr_t shoff_for_bias = (uintptr_t)shdr_base;
|
||||
for (int i = 0; i < img->header->e_shnum; i++, shoff_for_bias += img->header->e_shentsize) {
|
||||
ElfW(Shdr) *section_h = (ElfW(Shdr *))shoff_for_bias;
|
||||
|
||||
if ((section_h->sh_flags & SHF_ALLOC) && section_h->sh_addr != 0) {
|
||||
img->bias = (off_t)section_h->sh_addr - (off_t)section_h->sh_offset;
|
||||
bias_calculated = true;
|
||||
|
||||
char *sname = section_str ? (section_h->sh_name + section_str) : "<?>";
|
||||
LOGI("Calculated bias %ld from first allocated section %s (addr %lx, offset %lx)",
|
||||
(long)img->bias, sname, (unsigned long)section_h->sh_addr, (unsigned long)section_h->sh_offset);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +503,7 @@ bool _load_symtabs(ElfImg *img) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
|
||||
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash, unsigned char *sym_type) {
|
||||
if (img->gnu_nbucket_ == 0 || img->gnu_bloom_size_ == 0 || !img->gnu_bloom_filter_ || !img->gnu_bucket_ || !img->gnu_chain_ || !img->dynsym_start || !img->strtab_start)
|
||||
return 0;
|
||||
|
||||
@@ -545,7 +525,7 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
|
||||
|
||||
uint32_t sym_index = img->gnu_bucket_[hash % img->gnu_nbucket_];
|
||||
if (sym_index < img->gnu_symndx_) {
|
||||
LOGI("Symbol %s hash %u maps to bucket %u index %u (below gnu_symndx %u), not exported?", name, hash, hash % img->gnu_nbucket_, sym_index, img->gnu_symndx_);
|
||||
LOGW("Symbol %s hash %u maps to bucket %u index %u (below gnu_symndx %u), not exported?", name, hash, hash % img->gnu_nbucket_, sym_index, img->gnu_symndx_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -568,8 +548,12 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF)
|
||||
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF) {
|
||||
unsigned int type = ELF_ST_TYPE(sym->st_info);
|
||||
if (sym_type) *sym_type = type;
|
||||
|
||||
return sym->st_value;
|
||||
}
|
||||
|
||||
while ((chain_val & 1) == 0) {
|
||||
sym_index++;
|
||||
@@ -589,14 +573,18 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF)
|
||||
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF) {
|
||||
unsigned int type = ELF_ST_TYPE(sym->st_info);
|
||||
if (sym_type) *sym_type = type;
|
||||
|
||||
return sym->st_value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash) {
|
||||
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash, unsigned char *sym_type) {
|
||||
if (img->nbucket_ == 0 || !img->bucket_ || !img->chain_ || !img->dynsym_start || !img->strtab_start)
|
||||
return 0;
|
||||
|
||||
@@ -605,14 +593,18 @@ ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t h
|
||||
for (size_t n = img->bucket_[hash % img->nbucket_]; n != STN_UNDEF; n = img->chain_[n]) {
|
||||
ElfW(Sym) *sym = img->dynsym_start + n;
|
||||
|
||||
if (strcmp(name, strings + sym->st_name) == 0 && sym->st_shndx != SHN_UNDEF)
|
||||
if (strcmp(name, strings + sym->st_name) == 0 && sym->st_shndx != SHN_UNDEF) {
|
||||
unsigned int type = ELF_ST_TYPE(sym->st_info);
|
||||
if (sym_type) *sym_type = type;
|
||||
|
||||
return sym->st_value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
|
||||
ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name, unsigned char *sym_type) {
|
||||
if (!_load_symtabs(img)) {
|
||||
LOGE("Failed to load symtabs for linear lookup of %s", name);
|
||||
|
||||
@@ -623,7 +615,7 @@ ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
|
||||
if (valid_symtabs_amount == 0) {
|
||||
LOGW("No valid symbols (FUNC/OBJECT with size > 0) found in .symtab for %s", img->elf);
|
||||
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < valid_symtabs_amount; i++) {
|
||||
@@ -633,13 +625,16 @@ ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
|
||||
if (img->symtabs_[i].sym->st_shndx == SHN_UNDEF)
|
||||
continue;
|
||||
|
||||
unsigned int type = ELF_ST_TYPE(img->symtabs_[i].sym->st_info);
|
||||
if (sym_type) *sym_type = type;
|
||||
|
||||
return img->symtabs_[i].sym->st_value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix) {
|
||||
ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix, unsigned char *sym_type) {
|
||||
if (!_load_symtabs(img)) {
|
||||
LOGE("Failed to load symtabs for linear lookup by prefix of %s", prefix);
|
||||
|
||||
@@ -650,7 +645,7 @@ ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix) {
|
||||
if (valid_symtabs_amount == 0) {
|
||||
LOGW("No valid symbols (FUNC/OBJECT with size > 0) found in .symtab for %s", img->elf);
|
||||
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t prefix_len = strlen(prefix);
|
||||
@@ -666,45 +661,161 @@ ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix) {
|
||||
if (img->symtabs_[i].sym->st_shndx == SHN_UNDEF)
|
||||
continue;
|
||||
|
||||
unsigned int type = ELF_ST_TYPE(img->symtabs_[i].sym->st_info);
|
||||
if (sym_type) *sym_type = type;
|
||||
|
||||
return img->symtabs_[i].sym->st_value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name) {
|
||||
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name, unsigned char *sym_type) {
|
||||
ElfW(Addr) offset = 0;
|
||||
|
||||
offset = GnuLookup(img, name, GnuHash(name));
|
||||
offset = GnuLookup(img, name, GnuHash(name), sym_type);
|
||||
if (offset != 0) return offset;
|
||||
|
||||
offset = ElfLookup(img, name, ElfHash(name));
|
||||
offset = ElfLookup(img, name, ElfHash(name), sym_type);
|
||||
if (offset != 0) return offset;
|
||||
|
||||
offset = LinearLookup(img, name);
|
||||
offset = LinearLookup(img, name, sym_type);
|
||||
if (offset != 0) return offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __aarch64__
|
||||
/* INFO: Struct containing information about hardware capabilities used in resolver. This
|
||||
struct information is pulled directly from the AOSP code.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/include/sys/ifunc.h#53
|
||||
*/
|
||||
struct __ifunc_arg_t {
|
||||
unsigned long _size;
|
||||
unsigned long _hwcap;
|
||||
unsigned long _hwcap2;
|
||||
};
|
||||
|
||||
/* INFO: This is a constant used in the AOSP code to indicate that the struct __ifunc_arg_t
|
||||
contains hardware capabilities.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/include/sys/ifunc.h#74
|
||||
*/
|
||||
#define _IFUNC_ARG_HWCAP (1ULL << 62)
|
||||
#elif defined(__riscv)
|
||||
/* INFO: Struct used in Linux RISC-V architecture to probe hardware capabilities.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/kernel/uapi/asm-riscv/asm/hwprobe.h#10
|
||||
*/
|
||||
struct riscv_hwprobe {
|
||||
int64_t key;
|
||||
uint64_t value;
|
||||
};
|
||||
|
||||
/* INFO: This function is used in the AOSP code to probe hardware capabilities on RISC-V architecture
|
||||
by calling the syscall __NR_riscv_hwprobe and passing the parameters that will filled with
|
||||
the device hardware capabilities.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/bionic/vdso.cpp#86
|
||||
*/
|
||||
int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpu_count, unsigned long *cpus, unsigned flags) {
|
||||
register long a0 __asm__("a0") = (long)pairs;
|
||||
register long a1 __asm__("a1") = pair_count;
|
||||
register long a2 __asm__("a2") = cpu_count;
|
||||
register long a3 __asm__("a3") = (long)cpus;
|
||||
register long a4 __asm__("a4") = flags;
|
||||
register long a7 __asm__("a7") = __NR_riscv_hwprobe;
|
||||
|
||||
__asm__ volatile(
|
||||
"ecall"
|
||||
: "=r"(a0)
|
||||
: "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7)
|
||||
);
|
||||
|
||||
return -a0;
|
||||
}
|
||||
|
||||
/* INFO: This is a function pointer type that points how the signature of the __riscv_hwprobe
|
||||
function is.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/include/sys/hwprobe.h#62
|
||||
*/
|
||||
typedef int (*__riscv_hwprobe_t)(struct riscv_hwprobe *__pairs, size_t __pair_count, size_t __cpu_count, unsigned long *__cpus, unsigned __flags);
|
||||
#endif
|
||||
|
||||
/* INFO: GNU ifuncs (indirect functions) are functions that does not execute the code by itself,
|
||||
but instead lead to other functions that may very according to hardware capabilities,
|
||||
or other reasons, depending of the architecture.
|
||||
|
||||
This function is based on AOSP's (Android Open Source Project) code, and resolves the
|
||||
indirect symbol, leading to the correct, most appropriate for the hardware, symbol.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/linker/linker.cpp#2594
|
||||
- https://android.googlesource.com/platform/bionic/+/tags/android-16.0.0_r1/libc/bionic/bionic_call_ifunc_resolver.cpp#41
|
||||
*/
|
||||
static ElfW(Addr) handle_indirect_symbol(ElfImg *img, ElfW(Off) offset) {
|
||||
ElfW(Addr) resolver_addr = (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
|
||||
|
||||
#ifdef __aarch64__
|
||||
typedef ElfW(Addr) (*ifunc_resolver_t)(uint64_t, struct __ifunc_arg_t *);
|
||||
|
||||
struct __ifunc_arg_t args = {
|
||||
._size = sizeof(struct __ifunc_arg_t),
|
||||
._hwcap = getauxval(AT_HWCAP),
|
||||
._hwcap2 = getauxval(AT_HWCAP2)
|
||||
};
|
||||
|
||||
return ((ifunc_resolver_t)resolver_addr)(args._hwcap | _IFUNC_ARG_HWCAP, &args);
|
||||
#elif defined(__arm__)
|
||||
typedef ElfW(Addr) (*ifunc_resolver_t)(unsigned long);
|
||||
|
||||
return ((ifunc_resolver_t)resolver_addr)(getauxval(AT_HWCAP));
|
||||
#elif defined(__riscv)
|
||||
typedef ElfW(Addr) (*ifunc_resolver_t)(uint64_t, __riscv_hwprobe_t, void *);
|
||||
|
||||
return ((ifunc_resolver_t)resolver_addr)(getauxval(AT_HWCAP), __riscv_hwprobe, NULL);
|
||||
#else
|
||||
typedef ElfW(Addr) (*ifunc_resolver_t)(void);
|
||||
|
||||
return ((ifunc_resolver_t)resolver_addr)();
|
||||
#endif
|
||||
}
|
||||
|
||||
ElfW(Addr) getSymbAddress(ElfImg *img, const char *name) {
|
||||
ElfW(Addr) offset = getSymbOffset(img, name);
|
||||
unsigned char sym_type = 0;
|
||||
ElfW(Addr) offset = getSymbOffset(img, name, &sym_type);
|
||||
|
||||
if (offset == 0 || !img->base) return 0;
|
||||
|
||||
ElfW(Addr) address = (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
|
||||
if (sym_type == STT_GNU_IFUNC) {
|
||||
LOGD("Resolving STT_GNU_IFUNC symbol %s", name);
|
||||
|
||||
return address;
|
||||
return handle_indirect_symbol(img, offset);
|
||||
}
|
||||
|
||||
return (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
|
||||
}
|
||||
|
||||
ElfW(Addr) getSymbAddressByPrefix(ElfImg *img, const char *prefix) {
|
||||
ElfW(Addr) offset = LinearLookupByPrefix(img, prefix);
|
||||
unsigned char sym_type = 0;
|
||||
ElfW(Addr) offset = LinearLookupByPrefix(img, prefix, &sym_type);
|
||||
|
||||
if (offset == 0 || !img->base) return 0;
|
||||
|
||||
ElfW(Addr) address = (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
|
||||
if (sym_type == STT_GNU_IFUNC) {
|
||||
LOGD("Resolving STT_GNU_IFUNC symbol by prefix %s", prefix);
|
||||
|
||||
return address;
|
||||
return handle_indirect_symbol(img, offset);
|
||||
}
|
||||
|
||||
return (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
|
||||
}
|
||||
|
||||
void *getSymbValueByPrefix(ElfImg *img, const char *prefix) {
|
||||
|
||||
@@ -8,6 +8,50 @@
|
||||
|
||||
#include "socket_utils.h"
|
||||
|
||||
ssize_t write_loop(int fd, const void *buf, size_t count) {
|
||||
ssize_t written = 0;
|
||||
while (written < (ssize_t)count) {
|
||||
ssize_t ret = write(fd, (const char *)buf + written, count - written);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN) continue;
|
||||
|
||||
PLOGE("write");
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
LOGE("write: 0 bytes written");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
written += ret;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
ssize_t read_loop(int fd, void *buf, size_t count) {
|
||||
ssize_t read_bytes = 0;
|
||||
while (read_bytes < (ssize_t)count) {
|
||||
ssize_t ret = read(fd, (char *)buf + read_bytes, count - read_bytes);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN) continue;
|
||||
|
||||
PLOGE("read");
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
LOGE("read: 0 bytes read");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
read_bytes += ret;
|
||||
}
|
||||
|
||||
return read_bytes;
|
||||
}
|
||||
|
||||
/* TODO: Standardize how to log errors */
|
||||
int read_fd(int fd) {
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
@@ -25,7 +69,7 @@ int read_fd(int fd) {
|
||||
.msg_controllen = sizeof(cmsgbuf)
|
||||
};
|
||||
|
||||
ssize_t ret = recvmsg(fd, &msg, MSG_WAITALL);
|
||||
ssize_t ret = TEMP_FAILURE_RETRY(recvmsg(fd, &msg, MSG_WAITALL));
|
||||
if (ret == -1) {
|
||||
PLOGE("recvmsg");
|
||||
|
||||
@@ -47,14 +91,14 @@ int read_fd(int fd) {
|
||||
|
||||
ssize_t write_string(int fd, const char *str) {
|
||||
size_t str_len = strlen(str);
|
||||
ssize_t write_bytes = write(fd, &str_len, sizeof(size_t));
|
||||
ssize_t write_bytes = write_loop(fd, &str_len, sizeof(size_t));
|
||||
if (write_bytes != (ssize_t)sizeof(size_t)) {
|
||||
LOGE("Failed to write string length: Not all bytes were written (%zd != %zu).\n", write_bytes, sizeof(size_t));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
write_bytes = write(fd, str, str_len);
|
||||
write_bytes = write_loop(fd, str, str_len);
|
||||
if (write_bytes != (ssize_t)str_len) {
|
||||
LOGE("Failed to write string: Promised bytes doesn't exist (%zd != %zu).\n", write_bytes, str_len);
|
||||
|
||||
@@ -66,7 +110,7 @@ ssize_t write_string(int fd, const char *str) {
|
||||
|
||||
char *read_string(int fd) {
|
||||
size_t str_len = 0;
|
||||
ssize_t read_bytes = read(fd, &str_len, sizeof(size_t));
|
||||
ssize_t read_bytes = read_loop(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));
|
||||
|
||||
@@ -80,7 +124,7 @@ char *read_string(int fd) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
read_bytes = read(fd, buf, str_len);
|
||||
read_bytes = read_loop(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);
|
||||
|
||||
@@ -94,14 +138,14 @@ char *read_string(int fd) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
#define write_func(type) \
|
||||
ssize_t write_## type(int fd, type val) { \
|
||||
return write(fd, &val, sizeof(type)); \
|
||||
#define write_func(type) \
|
||||
ssize_t write_## type(int fd, type val) { \
|
||||
return write_loop(fd, &val, sizeof(type)); \
|
||||
}
|
||||
|
||||
#define read_func(type) \
|
||||
ssize_t read_## type(int fd, type *val) { \
|
||||
return read(fd, val, sizeof(type)); \
|
||||
#define read_func(type) \
|
||||
ssize_t read_## type(int fd, type *val) { \
|
||||
return read_loop(fd, val, sizeof(type)); \
|
||||
}
|
||||
|
||||
write_func(uint8_t)
|
||||
|
||||
4
loader/src/external/CMakeLists.txt
vendored
4
loader/src/external/CMakeLists.txt
vendored
@@ -2,7 +2,3 @@ project(external)
|
||||
|
||||
OPTION(LSPLT_BUILD_SHARED OFF)
|
||||
add_subdirectory(lsplt/lsplt/src/main/jni)
|
||||
|
||||
add_library(phmap INTERFACE)
|
||||
target_include_directories(phmap INTERFACE parallel-hashmap)
|
||||
target_compile_options(phmap INTERFACE -Wno-unused-value)
|
||||
|
||||
2
loader/src/external/lsplt
vendored
2
loader/src/external/lsplt
vendored
Submodule loader/src/external/lsplt updated: dc62fbe05e...5092fb399f
@@ -1,395 +0,0 @@
|
||||
/* Copyright 2022 John "topjohnwu" Wu
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// This is the public API for Zygisk modules.
|
||||
// DO NOT MODIFY ANY CODE IN THIS HEADER.
|
||||
|
||||
// WARNING: this file may contain changes that are not finalized.
|
||||
// Always use the following published header for development:
|
||||
// https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define ZYGISK_API_VERSION 4
|
||||
|
||||
/*
|
||||
|
||||
***************
|
||||
* Introduction
|
||||
***************
|
||||
|
||||
On Android, all app processes are forked from a special daemon called "Zygote".
|
||||
For each new app process, zygote will fork a new process and perform "specialization".
|
||||
This specialization operation enforces the Android security sandbox on the newly forked
|
||||
process to make sure that 3rd party application code is only loaded after it is being
|
||||
restricted within a sandbox.
|
||||
|
||||
On Android, there is also this special process called "system_server". This single
|
||||
process hosts a significant portion of system services, which controls how the
|
||||
Android operating system and apps interact with each other.
|
||||
|
||||
The Zygisk framework provides a way to allow developers to build modules and run custom
|
||||
code before and after system_server and any app processes' specialization.
|
||||
This enable developers to inject code and alter the behavior of system_server and app processes.
|
||||
|
||||
Please note that modules will only be loaded after zygote has forked the child process.
|
||||
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
|
||||
|
||||
*********************
|
||||
* Development Guide
|
||||
*********************
|
||||
|
||||
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
|
||||
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
|
||||
|
||||
Example code:
|
||||
|
||||
static jint (*orig_logger_entry_max)(JNIEnv *env);
|
||||
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
|
||||
|
||||
class ExampleModule : public zygisk::ModuleBase {
|
||||
public:
|
||||
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
||||
this->api = api;
|
||||
this->env = env;
|
||||
}
|
||||
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
|
||||
JNINativeMethod methods[] = {
|
||||
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
|
||||
};
|
||||
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
|
||||
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
|
||||
}
|
||||
private:
|
||||
zygisk::Api *api;
|
||||
JNIEnv *env;
|
||||
};
|
||||
|
||||
REGISTER_ZYGISK_MODULE(ExampleModule)
|
||||
|
||||
-----------------------------------------------------------------------------------------
|
||||
|
||||
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
|
||||
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
|
||||
never runs in a true superuser environment.
|
||||
|
||||
If your module require access to superuser permissions, you can create and register
|
||||
a root companion handler function. This function runs in a separate root companion
|
||||
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
|
||||
your target process and the root companion process.
|
||||
|
||||
Example code:
|
||||
|
||||
static void example_handler(int socket) { ... }
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(example_handler)
|
||||
|
||||
*/
|
||||
|
||||
namespace zygisk {
|
||||
|
||||
struct Api;
|
||||
struct AppSpecializeArgs;
|
||||
struct ServerSpecializeArgs;
|
||||
|
||||
class ModuleBase {
|
||||
public:
|
||||
|
||||
// This method is called as soon as the module is loaded into the target process.
|
||||
// A Zygisk API handle will be passed as an argument.
|
||||
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
|
||||
|
||||
// This method is called before the app process is specialized.
|
||||
// At this point, the process just got forked from zygote, but no app specific specialization
|
||||
// is applied. This means that the process does not have any sandbox restrictions and
|
||||
// still runs with the same privilege of zygote.
|
||||
//
|
||||
// All the arguments that will be sent and used for app specialization is passed as a single
|
||||
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
|
||||
// process will be specialized.
|
||||
//
|
||||
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
|
||||
// get a socket to do IPC calls with a root companion process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called after the app process is specialized.
|
||||
// At this point, the process has all sandbox restrictions enabled for this application.
|
||||
// This means that this method runs with the same privilege of the app's own code.
|
||||
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called before the system server process is specialized.
|
||||
// See preAppSpecialize(args) for more info.
|
||||
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
|
||||
|
||||
// This method is called after the system server process is specialized.
|
||||
// At this point, the process runs with the privilege of system_server.
|
||||
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs {
|
||||
// Required arguments. These arguments are guaranteed to exist on all Android versions.
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jobjectArray &rlimits;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
// Optional arguments. Please check whether the pointer is null before de-referencing
|
||||
jintArray *const fds_to_ignore;
|
||||
jboolean *const is_child_zygote;
|
||||
jboolean *const is_top_app;
|
||||
jobjectArray *const pkg_data_info_list;
|
||||
jobjectArray *const whitelisted_data_info_list;
|
||||
jboolean *const mount_data_dirs;
|
||||
jboolean *const mount_storage_dirs;
|
||||
jboolean *const mount_sysprop_overrides;
|
||||
|
||||
AppSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
struct ServerSpecializeArgs {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ServerSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
struct api_table;
|
||||
template <class T> void entry_impl(api_table *, JNIEnv *);
|
||||
}
|
||||
|
||||
// These values are used in Api::setOption(Option)
|
||||
enum Option : int {
|
||||
// Force Magisk's denylist unmount routines to run on this process.
|
||||
//
|
||||
// Setting this option only makes sense in preAppSpecialize.
|
||||
// The actual unmounting happens during app process specialization.
|
||||
//
|
||||
// Set this option to force all Magisk and modules' files to be unmounted from the
|
||||
// mount namespace of the process, regardless of the denylist enforcement status.
|
||||
FORCE_DENYLIST_UNMOUNT = 0,
|
||||
|
||||
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
|
||||
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
|
||||
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
|
||||
DLCLOSE_MODULE_LIBRARY = 1,
|
||||
};
|
||||
|
||||
// Bit masks of the return value of Api::getFlags()
|
||||
enum StateFlag : uint32_t {
|
||||
// The user has granted root access to the current process
|
||||
PROCESS_GRANTED_ROOT = (1u << 0),
|
||||
|
||||
// The current process was added on the denylist
|
||||
PROCESS_ON_DENYLIST = (1u << 1),
|
||||
};
|
||||
|
||||
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
|
||||
// from the specialized process afterwards.
|
||||
struct Api {
|
||||
|
||||
// Connect to a root companion process and get a Unix domain socket for IPC.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
|
||||
//
|
||||
// The pre[XXX]Specialize methods run with the same privilege of zygote.
|
||||
// If you would like to do some operations with superuser permissions, register a handler
|
||||
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
|
||||
// Another good use case for a companion process is that if you want to share some resources
|
||||
// across multiple processes, hold the resources in the companion process and pass it over.
|
||||
//
|
||||
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
|
||||
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
|
||||
//
|
||||
// Returns a file descriptor to a socket that is connected to the socket passed to your
|
||||
// module's companion request handler. Returns -1 if the connection attempt failed.
|
||||
int connectCompanion();
|
||||
|
||||
// Get the file descriptor of the root folder of the current module.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize methods.
|
||||
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
|
||||
// or in the root companion process (assuming that you sent the fd over the socket).
|
||||
// Both restrictions are due to SELinux and UID.
|
||||
//
|
||||
// Returns -1 if errors occurred.
|
||||
int getModuleDir();
|
||||
|
||||
// Set various options for your module.
|
||||
// Please note that this method accepts one single option at a time.
|
||||
// Check zygisk::Option for the full list of options available.
|
||||
void setOption(Option opt);
|
||||
|
||||
// Get information about the current process.
|
||||
// Returns bitwise-or'd zygisk::StateFlag values.
|
||||
uint32_t getFlags();
|
||||
|
||||
// Exempt the provided file descriptor from being automatically closed.
|
||||
//
|
||||
// This API only make sense in preAppSpecialize; calling this method in any other situation
|
||||
// is either a no-op (returns true) or an error (returns false).
|
||||
//
|
||||
// When false is returned, the provided file descriptor will eventually be closed by zygote.
|
||||
bool exemptFd(int fd);
|
||||
|
||||
// Hook JNI native methods for a class
|
||||
//
|
||||
// Lookup all registered JNI native methods and replace it with your own methods.
|
||||
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
|
||||
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
|
||||
// will be set to nullptr.
|
||||
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
|
||||
|
||||
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
|
||||
//
|
||||
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
|
||||
//
|
||||
// <address> <perms> <offset> <dev> <inode> <pathname>
|
||||
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
|
||||
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
|
||||
//
|
||||
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
|
||||
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
|
||||
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
|
||||
void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
|
||||
|
||||
// Commit all the hooks that was previously registered.
|
||||
// Returns false if an error occurred.
|
||||
bool pltHookCommit();
|
||||
|
||||
private:
|
||||
internal::api_table *tbl;
|
||||
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
|
||||
};
|
||||
|
||||
// Register a class as a Zygisk module
|
||||
|
||||
#define REGISTER_ZYGISK_MODULE(clazz) \
|
||||
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
|
||||
zygisk::internal::entry_impl<clazz>(table, env); \
|
||||
}
|
||||
|
||||
// Register a root companion request handler function for your module
|
||||
//
|
||||
// The function runs in a superuser daemon process and handles a root companion request from
|
||||
// your module running in a target process. The function has to accept an integer value,
|
||||
// which is a Unix domain socket that is connected to the target process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
//
|
||||
// NOTE: the function can run concurrently on multiple threads.
|
||||
// Be aware of race conditions if you have globally shared resources.
|
||||
|
||||
#define REGISTER_ZYGISK_COMPANION(func) \
|
||||
void zygisk_companion_entry(int client) { func(client); }
|
||||
|
||||
/*********************************************************
|
||||
* The following is internal ABI implementation detail.
|
||||
* You do not have to understand what it is doing.
|
||||
*********************************************************/
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct module_abi {
|
||||
long api_version;
|
||||
ModuleBase *impl;
|
||||
|
||||
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
|
||||
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
|
||||
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
|
||||
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
|
||||
|
||||
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
|
||||
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
|
||||
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
|
||||
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
|
||||
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
|
||||
}
|
||||
};
|
||||
|
||||
struct api_table {
|
||||
// Base
|
||||
void *impl;
|
||||
bool (*registerModule)(api_table *, module_abi *);
|
||||
|
||||
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
bool (*exemptFd)(int);
|
||||
bool (*pltHookCommit)();
|
||||
int (*connectCompanion)(void * /* impl */);
|
||||
void (*setOption)(void * /* impl */, Option);
|
||||
int (*getModuleDir)(void * /* impl */);
|
||||
uint32_t (*getFlags)(void * /* impl */);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void entry_impl(api_table *table, JNIEnv *env) {
|
||||
ModuleBase *module = new T();
|
||||
if (!table->registerModule(table, new module_abi(module)))
|
||||
return;
|
||||
auto api = new Api();
|
||||
api->tbl = table;
|
||||
module->onLoad(api, env);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline int Api::connectCompanion() {
|
||||
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
|
||||
}
|
||||
inline int Api::getModuleDir() {
|
||||
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
|
||||
}
|
||||
inline void Api::setOption(Option opt) {
|
||||
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
|
||||
}
|
||||
inline uint32_t Api::getFlags() {
|
||||
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
|
||||
}
|
||||
inline bool Api::exemptFd(int fd) {
|
||||
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
|
||||
}
|
||||
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
|
||||
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
|
||||
}
|
||||
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {
|
||||
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
|
||||
}
|
||||
inline bool Api::pltHookCommit() {
|
||||
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
|
||||
}
|
||||
|
||||
} // namespace zygisk
|
||||
|
||||
extern "C" {
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
void zygisk_companion_entry(int);
|
||||
|
||||
} // extern "C"
|
||||
@@ -42,7 +42,7 @@ enum root_impl {
|
||||
};
|
||||
|
||||
struct rezygisk_info {
|
||||
struct zygisk_modules *modules;
|
||||
struct zygisk_modules modules;
|
||||
enum root_impl root_impl;
|
||||
pid_t pid;
|
||||
bool running;
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
#ifndef ELF_UTIL_H
|
||||
#define ELF_UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <link.h>
|
||||
#include <linux/elf.h>
|
||||
#include <sys/types.h>
|
||||
#include <pthread.h> // Added for threading primitives
|
||||
|
||||
#define SHT_GNU_HASH 0x6ffffff6
|
||||
|
||||
// Function pointer types for constructors and destructors
|
||||
typedef void (*linker_simple_func_t)(void);
|
||||
typedef void (*linker_ctor_function_t)(int, char**, char**);
|
||||
typedef void (*linker_dtor_function_t)(void);
|
||||
|
||||
|
||||
struct symtabs {
|
||||
char *name;
|
||||
ElfW(Sym) *sym;
|
||||
@@ -54,17 +61,7 @@ void ElfImg_destroy(ElfImg *img);
|
||||
|
||||
ElfImg *ElfImg_create(const char *elf, void *base);
|
||||
|
||||
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash);
|
||||
|
||||
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash);
|
||||
|
||||
ElfW(Addr) LinearLookup(ElfImg *restrict img, const char *restrict name);
|
||||
|
||||
ElfW(Addr) LinearLookupByPrefix(ElfImg *restrict img, const char *name);
|
||||
|
||||
int dl_cb(struct dl_phdr_info *info, size_t size, void *data);
|
||||
|
||||
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name);
|
||||
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name, unsigned char *sym_type);
|
||||
|
||||
ElfW(Addr) getSymbAddress(ElfImg *img, const char *name);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
ssize_t write_loop(int fd, const void *buf, size_t count);
|
||||
|
||||
ssize_t read_loop(int fd, void *buf, size_t count);
|
||||
|
||||
int read_fd(int fd);
|
||||
|
||||
ssize_t write_string(int fd, const char *str);
|
||||
|
||||
107
loader/src/injector/art_method.h
Normal file
107
loader/src/injector/art_method.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#ifndef ART_METHOD_H
|
||||
#define ART_METHOD_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
inline static jfieldID art_method_field = nullptr;
|
||||
inline static size_t art_method_size = 0;
|
||||
inline static size_t entry_point_offset = 0;
|
||||
inline static size_t data_offset = 0;
|
||||
|
||||
void *amethod_from_reflected_method(JNIEnv *env, jobject method);
|
||||
|
||||
bool amethod_init(JNIEnv *env) {
|
||||
jclass clazz = env->FindClass("java/lang/reflect/Executable");
|
||||
if (!clazz) {
|
||||
LOGE("Failed to found Executable");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (art_method_field = env->GetFieldID(clazz, "artMethod", "J"); !art_method_field) {
|
||||
LOGE("Failed to find artMethod field");
|
||||
|
||||
env->DeleteLocalRef(clazz);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass throwable = env->FindClass("java/lang/Throwable");
|
||||
if (!throwable) {
|
||||
LOGE("Failed to found Executable");
|
||||
|
||||
env->DeleteLocalRef(clazz);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass clz = env->FindClass("java/lang/Class");
|
||||
if (!clz) {
|
||||
LOGE("Failed to found Class");
|
||||
|
||||
env->DeleteLocalRef(clazz);
|
||||
env->DeleteLocalRef(throwable);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
jmethodID get_declared_constructors = env->GetMethodID(clz, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;");
|
||||
env->DeleteLocalRef(clz);
|
||||
|
||||
const auto constructors = (jobjectArray) env->CallObjectMethod(throwable, get_declared_constructors);
|
||||
env->DeleteLocalRef(throwable);
|
||||
if (!constructors || env->GetArrayLength(constructors) < 2) {
|
||||
LOGE("Throwable has less than 2 constructors");
|
||||
|
||||
env->DeleteLocalRef(clazz);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
jobject first_ctor = env->GetObjectArrayElement(constructors, 0);
|
||||
jobject second_ctor = env->GetObjectArrayElement(constructors, 1);
|
||||
|
||||
uintptr_t first = (uintptr_t)amethod_from_reflected_method(env, first_ctor);
|
||||
uintptr_t second = (uintptr_t)amethod_from_reflected_method(env, second_ctor);
|
||||
|
||||
env->DeleteLocalRef(first_ctor);
|
||||
env->DeleteLocalRef(second_ctor);
|
||||
env->DeleteLocalRef(constructors);
|
||||
|
||||
art_method_size = (size_t)(second - first);
|
||||
LOGD("ArtMethod size: %zu", art_method_size);
|
||||
if ((4 * 9 + 3 * sizeof(void *)) < art_method_size) {
|
||||
LOGE("ArtMethod size exceeds maximum assume. There may be something wrong.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
entry_point_offset = art_method_size - sizeof(void *);
|
||||
data_offset = entry_point_offset - sizeof(void *);
|
||||
LOGD("ArtMethod entrypoint offset: %zu", entry_point_offset);
|
||||
LOGD("ArtMethod data offset: %zu", data_offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void *amethod_get_data(uintptr_t self) {
|
||||
return *(void **)((uintptr_t)self + data_offset);
|
||||
}
|
||||
|
||||
void *amethod_from_reflected_method(JNIEnv *env, jobject method) {
|
||||
if (art_method_field) {
|
||||
return (void *)env->GetLongField(method, art_method_field);
|
||||
} else {
|
||||
return (void *)env->FromReflectedMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* ART_METHOD_H */
|
||||
@@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "logging.h"
|
||||
#include "jni_helper.hpp"
|
||||
|
||||
template <typename T>
|
||||
constexpr inline auto RoundUpTo(T v, size_t size) {
|
||||
return v + size - 1 - ((v + size - 1) & (size - 1));
|
||||
}
|
||||
|
||||
inline static constexpr auto kPointerSize = sizeof(void *);
|
||||
|
||||
namespace lsplant::art {
|
||||
|
||||
class ArtMethod {
|
||||
|
||||
public:
|
||||
void *GetData() {
|
||||
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + data_offset);
|
||||
}
|
||||
|
||||
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
|
||||
if (art_method_field) [[likely]] {
|
||||
return reinterpret_cast<art::ArtMethod *>(
|
||||
JNI_GetLongField(env, method, art_method_field));
|
||||
} else {
|
||||
return reinterpret_cast<art::ArtMethod *>(env->FromReflectedMethod(method));
|
||||
}
|
||||
}
|
||||
|
||||
static bool Init(JNIEnv *env) {
|
||||
ScopedLocalRef<jclass> executable{env, nullptr};
|
||||
executable = JNI_FindClass(env, "java/lang/reflect/Executable");
|
||||
if (!executable) {
|
||||
LOGE("Failed to found Executable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J");
|
||||
!art_method_field) {
|
||||
LOGE("Failed to find artMethod field");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto throwable = JNI_FindClass(env, "java/lang/Throwable");
|
||||
if (!throwable) {
|
||||
LOGE("Failed to found Executable");
|
||||
return false;
|
||||
}
|
||||
auto clazz = JNI_FindClass(env, "java/lang/Class");
|
||||
static_assert(std::is_same_v<decltype(clazz)::BaseType, jclass>);
|
||||
jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors",
|
||||
"()[Ljava/lang/reflect/Constructor;");
|
||||
const auto constructors =
|
||||
JNI_Cast<jobjectArray>(JNI_CallObjectMethod(env, throwable, get_declared_constructors));
|
||||
if (constructors.size() < 2) {
|
||||
LOGE("Throwable has less than 2 constructors");
|
||||
return false;
|
||||
}
|
||||
auto &first_ctor = constructors[0];
|
||||
auto &second_ctor = constructors[1];
|
||||
auto *first = FromReflectedMethod(env, first_ctor.get());
|
||||
auto *second = FromReflectedMethod(env, second_ctor.get());
|
||||
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
|
||||
LOGD("ArtMethod size: %zu", art_method_size);
|
||||
if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] {
|
||||
LOGW("ArtMethod size exceeds maximum assume. There may be something wrong.");
|
||||
}
|
||||
entry_point_offset = art_method_size - kPointerSize;
|
||||
data_offset = entry_point_offset - kPointerSize;
|
||||
LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset);
|
||||
LOGD("ArtMethod::data offset: %zu", data_offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
inline static jfieldID art_method_field = nullptr;
|
||||
inline static size_t art_method_size = 0;
|
||||
inline static size_t entry_point_offset = 0;
|
||||
inline static size_t data_offset = 0;
|
||||
};
|
||||
|
||||
} // namespace lsplant::art
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "daemon.h"
|
||||
#include "logging.h"
|
||||
#include "solist.h"
|
||||
#include "zygisk.hpp"
|
||||
|
||||
using namespace std;
|
||||
@@ -8,7 +9,7 @@ void *start_addr = nullptr;
|
||||
size_t block_size = 0;
|
||||
|
||||
extern "C" [[gnu::visibility("default")]]
|
||||
void entry(void* addr, size_t size, const char* path) {
|
||||
void entry(void *addr, size_t size) {
|
||||
LOGD("Zygisk library injected, version %s", ZKSU_VERSION);
|
||||
|
||||
start_addr = addr;
|
||||
@@ -23,7 +24,10 @@ void entry(void* addr, size_t size, const char* path) {
|
||||
LOGD("start plt hooking");
|
||||
hook_functions();
|
||||
|
||||
void *module_addrs[1] = { addr };
|
||||
clean_trace(path, module_addrs, 1, 1, 0, false);
|
||||
solist_drop_so_path(addr, true);
|
||||
solist_reset_counters(1, 1);
|
||||
|
||||
send_seccomp_event();
|
||||
|
||||
LOGD("Zygisk library execution done, addr: %p, size: %zu", addr, size);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ForkAndSpec(JNIHook):
|
||||
return 'nativeForkAndSpecialize'
|
||||
|
||||
def init_args(self):
|
||||
return 'AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);'
|
||||
return 'struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);'
|
||||
|
||||
def body(self):
|
||||
decl = ''
|
||||
@@ -117,7 +117,7 @@ class ForkServer(ForkAndSpec):
|
||||
return 'nativeForkSystemServer'
|
||||
|
||||
def init_args(self):
|
||||
return 'ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);'
|
||||
return 'struct server_specialize_args_v1 args(&uid, &gid, &gids, &runtime_flags, &permitted_capabilities, &effective_capabilities);'
|
||||
|
||||
# Common args
|
||||
uid = Argument('uid', jint)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <sys/mount.h>
|
||||
#include <dlfcn.h>
|
||||
#include <link.h>
|
||||
#include <regex.h>
|
||||
#include <bitset>
|
||||
#include <list>
|
||||
@@ -7,7 +8,7 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <lsplt.hpp>
|
||||
#include <lsplt.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
@@ -21,12 +22,12 @@
|
||||
|
||||
#include "daemon.h"
|
||||
#include "zygisk.hpp"
|
||||
#include "module.hpp"
|
||||
#include "module.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include "solist.h"
|
||||
|
||||
#include "art_method.hpp"
|
||||
#include "art_method.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -61,12 +62,11 @@ struct ZygiskContext {
|
||||
JNIEnv *env;
|
||||
union {
|
||||
void *ptr;
|
||||
AppSpecializeArgs_v5 *app;
|
||||
ServerSpecializeArgs_v1 *server;
|
||||
struct app_specialize_args_v5 *app;
|
||||
struct server_specialize_args_v1 *server;
|
||||
} args;
|
||||
|
||||
const char *process;
|
||||
list<ZygiskModule> modules;
|
||||
|
||||
int pid;
|
||||
bitset<FLAG_MAX> flags;
|
||||
@@ -124,10 +124,14 @@ struct ZygiskContext {
|
||||
// Global variables
|
||||
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
|
||||
map<string, vector<JNINativeMethod>> *jni_hook_list;
|
||||
|
||||
bool modules_loaded = false;
|
||||
struct rezygisk_module *zygisk_modules = NULL;
|
||||
size_t zygisk_module_length = 0;
|
||||
|
||||
bool should_unmap_zygisk = false;
|
||||
bool enable_unloader = false;
|
||||
bool hooked_unloader = false;
|
||||
std::vector<lsplt::MapInfo> cached_map_infos = {};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -137,7 +141,10 @@ namespace {
|
||||
ret (*old_##func)(__VA_ARGS__); \
|
||||
ret new_##func(__VA_ARGS__)
|
||||
|
||||
// Skip actual fork and return cached result if applicable
|
||||
/* INFO: ReZygisk already performs a fork in ZygiskContext::fork_pre, because of that,
|
||||
we avoid duplicate fork in nativeForkAndSpecialize and nativeForkSystemServer
|
||||
by caching the pid in fork_pre function and only performing fork if the pid
|
||||
is non-0, or in other words, if we (libzygisk.so) already forked. */
|
||||
DCL_HOOK_FUNC(int, fork) {
|
||||
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
|
||||
}
|
||||
@@ -176,39 +183,51 @@ bool update_mnt_ns(enum mount_namespace_state mns_state, bool dry_run) {
|
||||
|
||||
return true;
|
||||
}
|
||||
struct FileDescriptorInfo {
|
||||
const int fd;
|
||||
const struct stat stat;
|
||||
const std::string file_path;
|
||||
const int open_flags;
|
||||
const int fd_flags;
|
||||
const int fs_flags;
|
||||
const off_t offset;
|
||||
const bool is_sock;
|
||||
};
|
||||
|
||||
// Unmount stuffs in the process's private mount namespace
|
||||
DCL_HOOK_FUNC(int, unshare, int flags) {
|
||||
int res = old_unshare(flags);
|
||||
if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0 &&
|
||||
// For some unknown reason, unmounting app_process in SysUI can break.
|
||||
// This is reproducible on the official AVD running API 26 and 27.
|
||||
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
|
||||
!g_ctx->flags[SERVER_FORK_AND_SPECIALIZE] && !(g_ctx->info_flags & PROCESS_IS_FIRST_STARTED)) {
|
||||
/* INFO: This hook avoids that umounted overlays made by root modules lead to Zygote
|
||||
to Abort its operation as it cannot open anymore.
|
||||
|
||||
/* INFO: There might be cases, specifically in Magisk, where the app is in
|
||||
DenyList but also has root privileges. For those, it is up to the
|
||||
user remove it, and the weird behavior is expected, as the weird
|
||||
user behavior. */
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/core/jni/fd_utils.cpp#346
|
||||
- https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/core/jni/fd_utils.cpp#544
|
||||
- https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/core/jni/com_android_internal_os_Zygote.cpp#2329
|
||||
*/
|
||||
DCL_HOOK_FUNC(void, _ZNK18FileDescriptorInfo14ReopenOrDetachERKNSt3__18functionIFvNS0_12basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEEEEE, void *_this, void *fail_fn) {
|
||||
const int fd = *(const int *)((uintptr_t)_this + offsetof(FileDescriptorInfo, fd));
|
||||
const std::string *file_path = (const std::string *)((uintptr_t)_this + offsetof(FileDescriptorInfo, file_path));
|
||||
const int open_flags = *(const int *)((uintptr_t)_this + offsetof(FileDescriptorInfo, open_flags));
|
||||
const bool is_sock = *(const bool *)((uintptr_t)_this + offsetof(FileDescriptorInfo, is_sock));
|
||||
|
||||
/* INFO: For cases like Magisk, where you can only give an app SU if it was
|
||||
either requested before or if it's not in DenyList, we cannot
|
||||
umount it, or else it will not be (easily) possible to give new
|
||||
apps SU. Apps that are not marked in APatch/KernelSU to be umounted
|
||||
are also expected to have AP/KSU mounts there, so we will follow the
|
||||
same idea by not umounting any mount. */
|
||||
int new_fd;
|
||||
|
||||
if (g_ctx->info_flags & (PROCESS_IS_MANAGER | PROCESS_GRANTED_ROOT) || !(g_ctx->flags[DO_REVERT_UNMOUNT])) {
|
||||
update_mnt_ns(Mounted, false);
|
||||
}
|
||||
if (is_sock)
|
||||
goto bypass_fd_check;
|
||||
|
||||
old_unshare(CLONE_NEWNS);
|
||||
if (strncmp(file_path->c_str(), "/memfd:/boot-image-methods.art", strlen("/memfd:/boot-image-methods.art")) == 0)
|
||||
goto bypass_fd_check;
|
||||
|
||||
new_fd = TEMP_FAILURE_RETRY(open(file_path->c_str(), open_flags));
|
||||
close(new_fd);
|
||||
if (new_fd == -1) {
|
||||
LOGD("Failed to open file %s, detaching it", file_path->c_str());
|
||||
|
||||
close(fd);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* INFO: To spoof the errno value */
|
||||
errno = 0;
|
||||
|
||||
return res;
|
||||
bypass_fd_check:
|
||||
old__ZNK18FileDescriptorInfo14ReopenOrDetachERKNSt3__18functionIFvNS0_12basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEEEEE(_this, fail_fn);
|
||||
}
|
||||
|
||||
// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,
|
||||
@@ -227,7 +246,12 @@ DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) {
|
||||
|
||||
if (should_unmap_zygisk) {
|
||||
unhook_functions();
|
||||
cached_map_infos.clear();
|
||||
|
||||
/* INFO: Modules might use libzygisk.so after postAppSpecialize. We can only
|
||||
free it when we are really before our unmap. */
|
||||
free(zygisk_modules);
|
||||
|
||||
lsplt_free_resources();
|
||||
|
||||
if (should_unmap_zygisk) {
|
||||
// Because both `pthread_attr_setstacksize` and `dlclose` have the same function signature,
|
||||
@@ -248,8 +272,6 @@ DCL_HOOK_FUNC(char *, strdup, const char *s) {
|
||||
if (strcmp(s, "com.android.internal.os.ZygoteInit") == 0) {
|
||||
LOGV("strdup %s", s);
|
||||
initialize_jni_hook();
|
||||
cached_map_infos = lsplt::MapInfo::Scan();
|
||||
LOGD("cached_map_infos updated");
|
||||
}
|
||||
|
||||
return old_strdup(s);
|
||||
@@ -310,21 +332,23 @@ void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods
|
||||
nm.fnPtr = nullptr;
|
||||
continue;
|
||||
}
|
||||
auto method = lsplant::JNI_ToReflectedMethod(env, clazz, mid, is_static);
|
||||
auto modifier = lsplant::JNI_CallIntMethod(env, method, member_getModifiers);
|
||||
auto method = env->ToReflectedMethod(clazz, mid, is_static);
|
||||
auto modifier = env->CallIntMethod(method, member_getModifiers);
|
||||
if ((modifier & MODIFIER_NATIVE) == 0) {
|
||||
nm.fnPtr = nullptr;
|
||||
continue;
|
||||
}
|
||||
auto artMethod = lsplant::art::ArtMethod::FromReflectedMethod(env, method);
|
||||
auto artMethod = amethod_from_reflected_method(env, method);
|
||||
hooks.push_back(nm);
|
||||
auto orig = artMethod->GetData();
|
||||
auto orig = amethod_get_data((uintptr_t)artMethod);
|
||||
LOGV("replaced %s %s orig %p", clz, nm.name, orig);
|
||||
nm.fnPtr = orig;
|
||||
}
|
||||
|
||||
if (hooks.empty()) return;
|
||||
env->RegisterNatives(clazz, hooks.data(), hooks.size());
|
||||
|
||||
env->DeleteLocalRef(clazz);
|
||||
}
|
||||
|
||||
// JNI method hook definitions, auto generated
|
||||
@@ -334,9 +358,18 @@ void initialize_jni_hook() {
|
||||
auto get_created_java_vms = reinterpret_cast<jint (*)(JavaVM **, jsize, jsize *)>(
|
||||
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
|
||||
if (!get_created_java_vms) {
|
||||
for (auto &map: cached_map_infos) {
|
||||
if (!map.path.ends_with("/libnativehelper.so")) continue;
|
||||
void *h = dlopen(map.path.data(), RTLD_LAZY);
|
||||
struct lsplt_map_info *map_infos = lsplt_scan_maps("self");
|
||||
if (!map_infos) {
|
||||
LOGE("Failed to scan maps for self");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < map_infos->length; i++) {
|
||||
struct lsplt_map_entry map = map_infos->maps[i];
|
||||
|
||||
if (!strstr(map.path, "/libnativehelper.so")) continue;
|
||||
void *h = dlopen(map.path, RTLD_LAZY);
|
||||
if (!h) {
|
||||
LOGW("cannot dlopen libnativehelper.so: %s", dlerror());
|
||||
break;
|
||||
@@ -345,6 +378,9 @@ void initialize_jni_hook() {
|
||||
dlclose(h);
|
||||
break;
|
||||
}
|
||||
|
||||
lsplt_free_maps(map_infos);
|
||||
|
||||
if (!get_created_java_vms) {
|
||||
LOGW("JNI_GetCreatedJavaVMs not found");
|
||||
return;
|
||||
@@ -358,16 +394,21 @@ void initialize_jni_hook() {
|
||||
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
|
||||
if (res != JNI_OK || env == nullptr) return;
|
||||
|
||||
auto classMember = lsplant::JNI_FindClass(env, "java/lang/reflect/Member");
|
||||
if (classMember != nullptr) member_getModifiers = lsplant::JNI_GetMethodID(env, classMember, "getModifiers", "()I");
|
||||
auto classModifier = lsplant::JNI_FindClass(env, "java/lang/reflect/Modifier");
|
||||
auto classMember = env->FindClass("java/lang/reflect/Member");
|
||||
if (classMember != nullptr) member_getModifiers = env->GetMethodID(classMember, "getModifiers", "()I");
|
||||
auto classModifier = env->FindClass("java/lang/reflect/Modifier");
|
||||
if (classModifier != nullptr) {
|
||||
auto fieldId = lsplant::JNI_GetStaticFieldID(env, classModifier, "NATIVE", "I");
|
||||
if (fieldId != nullptr) MODIFIER_NATIVE = lsplant::JNI_GetStaticIntField(env, classModifier, fieldId);
|
||||
auto fieldId = env->GetStaticFieldID(classModifier, "NATIVE", "I");
|
||||
if (fieldId != nullptr) MODIFIER_NATIVE = env->GetStaticIntField(classModifier, fieldId);
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(classMember);
|
||||
env->DeleteLocalRef(classModifier);
|
||||
|
||||
if (member_getModifiers == nullptr || MODIFIER_NATIVE == 0) return;
|
||||
if (!lsplant::art::ArtMethod::Init(env)) {
|
||||
LOGE("failed to init ArtMethod");
|
||||
if (!amethod_init(env)) {
|
||||
LOGE("failed to init amethod");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -377,51 +418,101 @@ void initialize_jni_hook() {
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
|
||||
: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {
|
||||
// Make sure all pointers are null
|
||||
memset(&api, 0, sizeof(api));
|
||||
api.base.impl = this;
|
||||
api.base.registerModule = &ZygiskModule::RegisterModuleImpl;
|
||||
}
|
||||
bool rezygisk_module_register(struct rezygisk_api *api, struct rezygisk_abi *module) {
|
||||
LOGD("Registering module with API version %ld", module->api_version);
|
||||
|
||||
bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
||||
if (api == nullptr || module == nullptr)
|
||||
return false;
|
||||
|
||||
long api_version = *module;
|
||||
// Unsupported version
|
||||
if (api_version > ZYGISK_API_VERSION)
|
||||
if (module->api_version > REZYGISK_API_VERSION)
|
||||
return false;
|
||||
|
||||
// Set the actual module_abi*
|
||||
api->base.impl->mod = { module };
|
||||
struct rezygisk_module *m = &zygisk_modules[(size_t)api->impl];
|
||||
m->abi = *module;
|
||||
m->api = *api;
|
||||
|
||||
api->hook_jni_native_methods = hookJniNativeMethods;
|
||||
if (module->api_version >= 4) {
|
||||
api->plt_hook_register_v4 = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
|
||||
LOGD("plt_hook_register_v4 called for dev=%lu, inode=%lu, symbol=%s, fn=%p, backup=%p",
|
||||
(unsigned long)dev, (unsigned long)inode, symbol, fn, backup);
|
||||
if (dev == 0 || inode == 0 || symbol == NULL || fn == NULL) {
|
||||
LOGE("Invalid arguments to plt_hook_register");
|
||||
|
||||
// Fill in API accordingly with module API version
|
||||
if (api_version >= 1) {
|
||||
api->v1.hookJniNativeMethods = hookJniNativeMethods;
|
||||
api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) {
|
||||
if (g_ctx) g_ctx->plt_hook_register(a, b, c, d);
|
||||
};
|
||||
api->v1.pltHookExclude = [](auto a, auto b) {
|
||||
if (g_ctx) g_ctx->plt_hook_exclude(a, b);
|
||||
};
|
||||
api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); };
|
||||
api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };
|
||||
api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); };
|
||||
}
|
||||
if (api_version >= 2) {
|
||||
api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); };
|
||||
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
|
||||
}
|
||||
if (api_version >= 4) {
|
||||
api->v4.pltHookCommit = []() { return lsplt::CommitHook(cached_map_infos); };
|
||||
api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
|
||||
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
|
||||
return;
|
||||
lsplt::RegisterHook(dev, inode, symbol, fn, backup);
|
||||
}
|
||||
|
||||
lsplt_register_hook(dev, inode, symbol, fn, backup);
|
||||
};
|
||||
api->exempt_fd = [](int fd) {
|
||||
LOGD("exempt_fd called for fd=%d", fd);
|
||||
if (g_ctx) g_ctx->exempt_fd(fd);
|
||||
};
|
||||
api->plt_hook_commit = []() {
|
||||
LOGD("plt_hook_commit called");
|
||||
return lsplt_commit_hook();
|
||||
};
|
||||
} else {
|
||||
api->plt_hook_register = [](const char *regex, const char *symbol, void *fn, void **backup) {
|
||||
if (g_ctx) g_ctx->plt_hook_register(regex, symbol, fn, backup);
|
||||
};
|
||||
api->plt_hook_exclude = [](const char *regex, const char *symbol) {
|
||||
if (g_ctx) g_ctx->plt_hook_exclude(regex, symbol);
|
||||
};
|
||||
api->plt_hook_commit = []() {
|
||||
return g_ctx && g_ctx->plt_hook_commit();
|
||||
};
|
||||
}
|
||||
api->connect_companion = [](void *id) {
|
||||
LOGD("connect_companion called for id=%p", id);
|
||||
if ((size_t)id >= zygisk_module_length) {
|
||||
LOGE("Invalid module id %zu", (size_t)id);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return rezygiskd_connect_companion((size_t)id);
|
||||
};
|
||||
api->set_option = [](void *id, enum rezygisk_options opt) {
|
||||
if (!g_ctx) return;
|
||||
|
||||
LOGD("set_option called for id=%p, opt=%d", id, opt);
|
||||
|
||||
if ((size_t)id >= zygisk_module_length) {
|
||||
LOGE("Invalid module id %zu", (size_t)id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (opt) {
|
||||
case FORCE_DENYLIST_UNMOUNT: {
|
||||
g_ctx->flags[DO_REVERT_UNMOUNT] = true;
|
||||
|
||||
break;
|
||||
}
|
||||
case DLCLOSE_MODULE_LIBRARY: {
|
||||
struct rezygisk_module *m = &zygisk_modules[(size_t)id];
|
||||
m->unload = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (module->api_version >= 2) {
|
||||
api->get_module_dir = [](void *id) {
|
||||
LOGD("get_module_dir called for id=%p", id);
|
||||
if ((size_t)id >= zygisk_module_length) {
|
||||
LOGE("Invalid module id %zu", (size_t)id);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return rezygiskd_get_module_dir((size_t)id);
|
||||
};
|
||||
api->get_flags = []() {
|
||||
LOGD("get_flags called");
|
||||
return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0;
|
||||
};
|
||||
api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); };
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -451,14 +542,24 @@ void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {
|
||||
void ZygiskContext::plt_hook_process_regex() {
|
||||
if (register_info.empty())
|
||||
return;
|
||||
for (auto &map : cached_map_infos) {
|
||||
|
||||
struct lsplt_map_info *map_infos = lsplt_scan_maps("self");
|
||||
if (!map_infos) {
|
||||
LOGE("Failed to scan maps for self");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < map_infos->length; i++) {
|
||||
struct lsplt_map_entry map = map_infos->maps[i];
|
||||
|
||||
if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue;
|
||||
for (auto ®: register_info) {
|
||||
if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0)
|
||||
if (regexec(®.regex, map.path, 0, nullptr, 0) != 0)
|
||||
continue;
|
||||
bool ignored = false;
|
||||
for (auto &ign: ignore_info) {
|
||||
if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0)
|
||||
if (regexec(&ign.regex, map.path, 0, nullptr, 0) != 0)
|
||||
continue;
|
||||
if (ign.symbol.empty() || ign.symbol == reg.symbol) {
|
||||
ignored = true;
|
||||
@@ -466,10 +567,12 @@ void ZygiskContext::plt_hook_process_regex() {
|
||||
}
|
||||
}
|
||||
if (!ignored) {
|
||||
lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup);
|
||||
lsplt_register_hook(map.dev, map.inode, reg.symbol.c_str(), reg.callback, reg.backup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lsplt_free_maps(map_infos);
|
||||
}
|
||||
|
||||
bool ZygiskContext::plt_hook_commit() {
|
||||
@@ -481,50 +584,7 @@ bool ZygiskContext::plt_hook_commit() {
|
||||
pthread_mutex_unlock(&hook_info_lock);
|
||||
}
|
||||
|
||||
return lsplt::CommitHook(cached_map_infos);
|
||||
}
|
||||
|
||||
|
||||
bool ZygiskModule::valid() const {
|
||||
if (mod.api_version == nullptr)
|
||||
return false;
|
||||
switch (*mod.api_version) {
|
||||
case 4:
|
||||
case 3:
|
||||
case 2:
|
||||
case 1:
|
||||
return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize &&
|
||||
mod.v1->preServerSpecialize && mod.v1->postServerSpecialize;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Zygisksu changed: Use own zygiskd */
|
||||
int ZygiskModule::connectCompanion() const {
|
||||
return rezygiskd_connect_companion(id);
|
||||
}
|
||||
|
||||
/* Zygisksu changed: Use own zygiskd */
|
||||
int ZygiskModule::getModuleDir() const {
|
||||
return rezygiskd_get_module_dir(id);
|
||||
}
|
||||
|
||||
void ZygiskModule::setOption(zygisk::Option opt) {
|
||||
if (g_ctx == nullptr)
|
||||
return;
|
||||
switch (opt) {
|
||||
case zygisk::FORCE_DENYLIST_UNMOUNT:
|
||||
g_ctx->flags[DO_REVERT_UNMOUNT] = true;
|
||||
break;
|
||||
case zygisk::DLCLOSE_MODULE_LIBRARY:
|
||||
unload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ZygiskModule::getFlags() {
|
||||
return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0;
|
||||
return lsplt_commit_hook();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
@@ -590,8 +650,14 @@ void ZygiskContext::sanitize_fds() {
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
}
|
||||
|
||||
jintArray old_array = *args.app->fds_to_ignore;
|
||||
|
||||
*args.app->fds_to_ignore = array;
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
|
||||
env->DeleteLocalRef(old_array);
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
@@ -652,6 +718,15 @@ bool ZygiskContext::load_modules_only() {
|
||||
return false;
|
||||
}
|
||||
|
||||
zygisk_modules = (struct rezygisk_module *)malloc(ms.modules_count * sizeof(struct rezygisk_module));
|
||||
if (!zygisk_modules) {
|
||||
LOGE("Failed to allocate memory for modules");
|
||||
|
||||
free_modules(&ms);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ms.modules_count; i++) {
|
||||
char *lib_path = ms.modules[i];
|
||||
|
||||
@@ -671,7 +746,18 @@ bool ZygiskContext::load_modules_only() {
|
||||
continue;
|
||||
}
|
||||
|
||||
modules.emplace_back(i, handle, entry);
|
||||
zygisk_modules[zygisk_module_length].api.register_module = rezygisk_module_register;
|
||||
zygisk_modules[zygisk_module_length].api.impl = (void *)zygisk_module_length;
|
||||
|
||||
zygisk_modules[zygisk_module_length].handle = handle;
|
||||
zygisk_modules[zygisk_module_length].zygisk_module_entry = (void (*)(void *, void *))entry;
|
||||
|
||||
zygisk_modules[zygisk_module_length].unload = false;
|
||||
|
||||
zygisk_module_length++;
|
||||
|
||||
/* INFO: The module will call register module function, so by then, it must be fully registered. */
|
||||
rezygisk_module_call_on_load(&zygisk_modules[zygisk_module_length - 1], env);
|
||||
}
|
||||
|
||||
free_modules(&ms);
|
||||
@@ -681,11 +767,9 @@ bool ZygiskContext::load_modules_only() {
|
||||
|
||||
/* Zygisksu changed: Load module fds */
|
||||
void ZygiskContext::run_modules_pre() {
|
||||
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);
|
||||
for (size_t i = 0; i < zygisk_module_length; i++) {
|
||||
if (flags[APP_SPECIALIZE]) rezygisk_module_call_pre_app_specialize(&zygisk_modules[i], args.app);
|
||||
else if (flags[SERVER_FORK_AND_SPECIALIZE]) rezygisk_module_call_pre_server_specialize(&zygisk_modules[i], args.server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,28 +777,31 @@ void ZygiskContext::run_modules_post() {
|
||||
flags[POST_SPECIALIZE] = true;
|
||||
|
||||
size_t modules_unloaded = 0;
|
||||
for (const auto &m : modules) {
|
||||
if (flags[APP_SPECIALIZE]) m.postAppSpecialize(args.app);
|
||||
else if (flags[SERVER_FORK_AND_SPECIALIZE]) m.postServerSpecialize(args.server);
|
||||
for (size_t i = 0; i < zygisk_module_length; i++) {
|
||||
struct rezygisk_module *m = &zygisk_modules[i];
|
||||
|
||||
if (m.tryUnload()) modules_unloaded++;
|
||||
if (flags[APP_SPECIALIZE]) rezygisk_module_call_post_app_specialize(m, args.app);
|
||||
else if (flags[SERVER_FORK_AND_SPECIALIZE]) rezygisk_module_call_post_server_specialize(m, args.server);
|
||||
|
||||
/* INFO: If module is unloaded by dlclose, there's no need to
|
||||
hide it from soinfo manually. */
|
||||
if (m->unload && dlclose(m->handle) == 0) modules_unloaded++;
|
||||
else if (m->unload) {
|
||||
PLOGE("Failed to unload module %zu", i);
|
||||
} else {
|
||||
bool has_dropped = solist_drop_so_path((void *)m->zygisk_module_entry, false);
|
||||
if (!has_dropped) continue;
|
||||
|
||||
LOGD("Dropped solist record for %p", (void *)m->zygisk_module_entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (modules.size() > 0) {
|
||||
LOGD("modules unloaded: %zu/%zu", modules_unloaded, modules.size());
|
||||
if (zygisk_module_length > 0) {
|
||||
LOGD("Modules unloaded: %zu/%zu", modules_unloaded, zygisk_module_length);
|
||||
|
||||
/* INFO: While Variable Length Arrays (VLAs) aren't usually
|
||||
recommended due to the ease of using too much of the
|
||||
stack, this should be fine since it should not be
|
||||
possible to exhaust the stack with only a few addresses. */
|
||||
void *module_addrs[modules.size() * sizeof(void *)];
|
||||
solist_reset_counters(zygisk_module_length, modules_unloaded);
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &m : modules) {
|
||||
module_addrs[i++] = m.getEntry();
|
||||
}
|
||||
|
||||
clean_trace("/data/adb", module_addrs, modules.size(), modules.size(), modules_unloaded, true);
|
||||
LOGD("Returned global counters to their original values");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,13 +821,13 @@ void ZygiskContext::app_specialize_pre() {
|
||||
and even if so, it should not impact in detections, performance or
|
||||
any area.
|
||||
*/
|
||||
uid_t uid = args.app->uid;
|
||||
uid_t uid = *args.app->uid;
|
||||
if (IS_ISOLATED_SERVICE(uid) && args.app->app_data_dir) {
|
||||
/* INFO: If the app is an isolated service, we use the UID of the
|
||||
app's process data directory, which is the UID of the
|
||||
app itself, which root implementations actually use.
|
||||
*/
|
||||
const char *data_dir = env->GetStringUTFChars(args.app->app_data_dir, NULL);
|
||||
const char *data_dir = env->GetStringUTFChars(*args.app->app_data_dir, NULL);
|
||||
if (!data_dir) {
|
||||
LOGE("Failed to get app data directory");
|
||||
|
||||
@@ -751,7 +838,7 @@ void ZygiskContext::app_specialize_pre() {
|
||||
if (stat(data_dir, &st) == -1) {
|
||||
PLOGE("Failed to stat app data directory [%s]", data_dir);
|
||||
|
||||
env->ReleaseStringUTFChars(args.app->app_data_dir, data_dir);
|
||||
env->ReleaseStringUTFChars(*args.app->app_data_dir, data_dir);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -760,7 +847,7 @@ void ZygiskContext::app_specialize_pre() {
|
||||
|
||||
LOGD("Isolated service being related to UID %d, app data dir: %s", uid, data_dir);
|
||||
|
||||
env->ReleaseStringUTFChars(args.app->app_data_dir, data_dir);
|
||||
env->ReleaseStringUTFChars(*args.app->app_data_dir, data_dir);
|
||||
}
|
||||
|
||||
info_flags = rezygiskd_get_process_flags(uid, (const char *const)process);
|
||||
@@ -786,52 +873,43 @@ void ZygiskContext::app_specialize_pre() {
|
||||
if Zygisk is enabled.
|
||||
*/
|
||||
setenv("ZYGISK_ENABLED", "1", 1);
|
||||
} else {
|
||||
/* INFO: Because we load directly from the file, we need to do it before we umount
|
||||
the mounts, or else it won't have access to /data/adb anymore.
|
||||
*/
|
||||
if (!load_modules_only()) {
|
||||
LOGE("Failed to load modules");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
|
||||
postSpecialize. In preSpecialize, the process still has privileged
|
||||
permissions, and therefore can execute mount/umount/setns functions.
|
||||
If we update the mount namespace AFTER executing them, any mounts made
|
||||
will be lost, and the process will not have access to them anymore.
|
||||
|
||||
In postSpecialize, while still could have its mounts modified with the
|
||||
assistance of a Zygisk companion, it will already have the mount
|
||||
namespace switched by then, so there won't be issues.
|
||||
|
||||
Knowing this, we update the mns before execution, so that they can still
|
||||
make changes to mounts in DenyListed processes without being reverted.
|
||||
*/
|
||||
bool in_denylist = (info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST;
|
||||
if (in_denylist) {
|
||||
flags[DO_REVERT_UNMOUNT] = true;
|
||||
|
||||
update_mnt_ns(Clean, false);
|
||||
}
|
||||
|
||||
/* INFO: Executed after setns to ensure a module can update the mounts of an
|
||||
application without worrying about it being overwritten by setns.
|
||||
*/
|
||||
run_modules_pre();
|
||||
|
||||
/* INFO: The modules may request that although the process is NOT in
|
||||
the DenyList, it has its mount namespace switched to the clean
|
||||
one.
|
||||
|
||||
So to ensure this behavior happens, we must also check after the
|
||||
modules are loaded and executed, so that the modules can have
|
||||
the chance to request it.
|
||||
*/
|
||||
if (!in_denylist && flags[DO_REVERT_UNMOUNT])
|
||||
update_mnt_ns(Clean, false);
|
||||
}
|
||||
|
||||
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
|
||||
postSpecialize. In preSpecialize, the process still has privileged
|
||||
permissions, and therefore can execute mount/umount/setns functions.
|
||||
If we update the mount namespace AFTER executing them, any mounts made
|
||||
will be lost, and the process will not have access to them anymore.
|
||||
|
||||
In postSpecialize, while still could have its mounts modified with the
|
||||
assistance of a Zygisk companion, it will already have the mount
|
||||
namespace switched by then, so there won't be issues.
|
||||
|
||||
Knowing this, we update the mns before execution, so that they can still
|
||||
make changes to mounts in DenyListed processes without being reverted.
|
||||
*/
|
||||
bool in_denylist = (info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST;
|
||||
if (in_denylist) {
|
||||
flags[DO_REVERT_UNMOUNT] = true;
|
||||
|
||||
update_mnt_ns(Clean, false);
|
||||
}
|
||||
|
||||
/* INFO: Executed after setns to ensure a module can update the mounts of an
|
||||
application without worrying about it being overwritten by setns.
|
||||
*/
|
||||
run_modules_pre();
|
||||
|
||||
/* INFO: The modules may request that although the process is NOT in
|
||||
the DenyList, it has its mount namespace switched to the clean
|
||||
one.
|
||||
|
||||
So to ensure this behavior happens, we must also check after the
|
||||
modules are loaded and executed, so that the modules can have
|
||||
the chance to request it.
|
||||
*/
|
||||
if (!in_denylist && flags[DO_REVERT_UNMOUNT])
|
||||
update_mnt_ns(Clean, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -839,7 +917,7 @@ void ZygiskContext::app_specialize_post() {
|
||||
run_modules_post();
|
||||
|
||||
// Cleanups
|
||||
env->ReleaseStringUTFChars(args.app->nice_name, process);
|
||||
env->ReleaseStringUTFChars(*args.app->nice_name, process);
|
||||
g_ctx = nullptr;
|
||||
}
|
||||
|
||||
@@ -855,7 +933,7 @@ bool ZygiskContext::exempt_fd(int fd) {
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
void ZygiskContext::nativeSpecializeAppProcess_pre() {
|
||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||
process = env->GetStringUTFChars(*args.app->nice_name, nullptr);
|
||||
LOGV("pre specialize [%s]", process);
|
||||
// App specialize does not check FD
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
@@ -872,11 +950,15 @@ void ZygiskContext::nativeForkSystemServer_pre() {
|
||||
LOGV("pre forkSystemServer");
|
||||
flags[SERVER_FORK_AND_SPECIALIZE] = true;
|
||||
|
||||
if (!modules_loaded) {
|
||||
load_modules_only();
|
||||
modules_loaded = true;
|
||||
}
|
||||
|
||||
fork_pre();
|
||||
if (!is_child())
|
||||
return;
|
||||
|
||||
load_modules_only();
|
||||
run_modules_pre();
|
||||
rezygiskd_system_server_started();
|
||||
|
||||
@@ -892,13 +974,15 @@ void ZygiskContext::nativeForkSystemServer_post() {
|
||||
}
|
||||
|
||||
void ZygiskContext::nativeForkAndSpecialize_pre() {
|
||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||
process = env->GetStringUTFChars(*args.app->nice_name, nullptr);
|
||||
LOGV("pre forkAndSpecialize [%s]", process);
|
||||
flags[APP_FORK_AND_SPECIALIZE] = true;
|
||||
|
||||
fork_pre();
|
||||
if (pid == 0)
|
||||
app_specialize_pre();
|
||||
if (!is_child())
|
||||
return;
|
||||
|
||||
app_specialize_pre();
|
||||
|
||||
sanitize_fds();
|
||||
}
|
||||
@@ -924,19 +1008,22 @@ ZygiskContext::~ZygiskContext() {
|
||||
|
||||
// Unhook JNI methods
|
||||
for (const auto &[clz, methods] : *jni_hook_list) {
|
||||
if (!methods.empty() && env->RegisterNatives(
|
||||
env->FindClass(clz.data()), methods.data(),
|
||||
static_cast<int>(methods.size())) != 0) {
|
||||
LOGE("Failed to restore JNI hook of class [%s]", clz.data());
|
||||
should_unmap_zygisk = false;
|
||||
jclass jc = env->FindClass(clz.data());
|
||||
if (jc) {
|
||||
if (!methods.empty() && env->RegisterNatives(jc, methods.data(),
|
||||
static_cast<jint>(methods.size())) != 0) {
|
||||
LOGE("Failed to restore JNI hook of class [%s]", clz.data());
|
||||
should_unmap_zygisk = false;
|
||||
}
|
||||
env->DeleteLocalRef(jc);
|
||||
}
|
||||
}
|
||||
delete jni_hook_list;
|
||||
jni_hook_list = nullptr;
|
||||
|
||||
// Strip out all API function pointers
|
||||
for (auto &m : modules) {
|
||||
m.clearApi();
|
||||
for (size_t i = 0; i < zygisk_module_length; i++) {
|
||||
memset(&zygisk_modules[i], 0, sizeof(zygisk_modules[i]));
|
||||
}
|
||||
|
||||
enable_unloader = true;
|
||||
@@ -944,8 +1031,8 @@ ZygiskContext::~ZygiskContext() {
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool hook_commit(std::vector<lsplt::MapInfo> &map_infos = cached_map_infos) {
|
||||
if (lsplt::CommitHook(map_infos)) {
|
||||
static bool hook_commit(struct lsplt_map_info *map_infos) {
|
||||
if (map_infos ? lsplt_commit_hook_manual(map_infos) : lsplt_commit_hook()) {
|
||||
return true;
|
||||
} else {
|
||||
LOGE("plt_hook failed");
|
||||
@@ -954,7 +1041,7 @@ static bool hook_commit(std::vector<lsplt::MapInfo> &map_infos = cached_map_info
|
||||
}
|
||||
|
||||
static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {
|
||||
if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {
|
||||
if (!lsplt_register_hook(dev, inode, symbol, new_func, old_func)) {
|
||||
LOGE("Failed to register plt_hook \"%s\"", symbol);
|
||||
return;
|
||||
}
|
||||
@@ -967,56 +1054,6 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_
|
||||
#define PLT_HOOK_REGISTER(DEV, INODE, NAME) \
|
||||
PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME)
|
||||
|
||||
/* INFO: module_addrs_length is always the same as "load" */
|
||||
void clean_trace(const char *path, void **module_addrs, size_t module_addrs_length, size_t load, size_t unload, bool spoof_maps) {
|
||||
LOGD("cleaning trace for path %s", path);
|
||||
|
||||
if (load > 0 || unload > 0) solist_reset_counters(load, unload);
|
||||
|
||||
LOGD("Dropping solist record for %s", path);
|
||||
|
||||
bool any_dropped = false;
|
||||
for (size_t i = 0; i < module_addrs_length; i++) {
|
||||
bool local_any_dropped = solist_drop_so_path(module_addrs[i]);
|
||||
if (!local_any_dropped) continue;
|
||||
|
||||
any_dropped = true;
|
||||
|
||||
LOGD("Dropped solist record for %p", module_addrs[i]);
|
||||
}
|
||||
|
||||
if (!any_dropped || !spoof_maps) return;
|
||||
|
||||
LOGD("spoofing virtual maps for %s", path);
|
||||
|
||||
/* INFO: Spoofing maps names is futile, after all it will
|
||||
still show up in /proc/self/(s)maps but with a
|
||||
different name, however still detectable by
|
||||
checking the permissions. This, however, avoids
|
||||
just checking for "zygisk". */
|
||||
|
||||
/* TODO: Use SoList to map through libraries to avoid open /proc/self/maps here */
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (strstr(map.path.c_str(), path) && strstr(map.path.c_str(), "libzygisk") == 0)
|
||||
{
|
||||
void *addr = (void *)map.start;
|
||||
size_t size = map.end - map.start;
|
||||
void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
|
||||
if (copy == MAP_FAILED) {
|
||||
LOGE("failed to backup block %s [%p, %p]", map.path.c_str(), addr, (void*)map.end);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((map.perms & PROT_READ) == 0) {
|
||||
mprotect(addr, size, PROT_READ);
|
||||
}
|
||||
memcpy(copy, addr, size);
|
||||
mprotect(copy, size, map.perms);
|
||||
mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hook_functions() {
|
||||
plt_hook_list = new vector<tuple<dev_t, ino_t, const char *, void **>>();
|
||||
jni_hook_list = new map<string, vector<JNINativeMethod>>();
|
||||
@@ -1024,21 +1061,33 @@ void hook_functions() {
|
||||
ino_t android_runtime_inode = 0;
|
||||
dev_t android_runtime_dev = 0;
|
||||
|
||||
cached_map_infos = lsplt::MapInfo::Scan();
|
||||
for (auto &map : cached_map_infos) {
|
||||
if (map.path.ends_with("libandroid_runtime.so")) {
|
||||
android_runtime_inode = map.inode;
|
||||
android_runtime_dev = map.dev;
|
||||
struct lsplt_map_info *map_infos = lsplt_scan_maps("self");
|
||||
if (!map_infos) {
|
||||
LOGE("Failed to scan maps for self");
|
||||
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < map_infos->length; i++) {
|
||||
struct lsplt_map_entry map = map_infos->maps[i];
|
||||
|
||||
if (!strstr(map.path, "libandroid_runtime.so")) continue;
|
||||
|
||||
android_runtime_inode = map.inode;
|
||||
android_runtime_dev = map.dev;
|
||||
|
||||
LOGD("Found libandroid_runtime.so at [%zu:%lu]", android_runtime_dev, android_runtime_inode);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare);
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup);
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, property_get);
|
||||
hook_commit();
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, _ZNK18FileDescriptorInfo14ReopenOrDetachERKNSt3__18functionIFvNS0_12basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEEEEE);
|
||||
hook_commit(map_infos);
|
||||
|
||||
lsplt_free_maps(map_infos);
|
||||
|
||||
// Remove unhooked methods
|
||||
plt_hook_list->erase(
|
||||
@@ -1054,11 +1103,22 @@ static void hook_unloader() {
|
||||
ino_t art_inode = 0;
|
||||
dev_t art_dev = 0;
|
||||
|
||||
cached_map_infos = lsplt::MapInfo::Scan();
|
||||
for (auto &map : cached_map_infos) {
|
||||
if (map.path.ends_with("/libart.so")) {
|
||||
struct lsplt_map_info *map_infos = lsplt_scan_maps("self");
|
||||
if (!map_infos) {
|
||||
LOGE("Failed to scan maps for self");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < map_infos->length; i++) {
|
||||
struct lsplt_map_entry map = map_infos->maps[i];
|
||||
|
||||
if (strstr(map.path, "/libart.so") != nullptr) {
|
||||
art_inode = map.inode;
|
||||
art_dev = map.dev;
|
||||
|
||||
LOGD("Found libart.so at [%zu:%lu]", art_dev, art_inode);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1073,24 +1133,25 @@ static void hook_unloader() {
|
||||
LOGE("virtual map for libart.so is not cached");
|
||||
|
||||
hooked_unloader = false;
|
||||
|
||||
return;
|
||||
} else {
|
||||
LOGD("hook_unloader called with libart.so [%zu:%lu]", art_dev, art_inode);
|
||||
|
||||
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_setstacksize);
|
||||
hook_commit(map_infos);
|
||||
}
|
||||
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_setstacksize);
|
||||
hook_commit();
|
||||
|
||||
lsplt_free_maps(map_infos);
|
||||
}
|
||||
|
||||
static void unhook_functions() {
|
||||
// Unhook plt_hook
|
||||
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
|
||||
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
|
||||
if (!lsplt_register_hook(dev, inode, sym, *old_func, NULL)) {
|
||||
LOGE("Failed to register plt_hook [%s]", sym);
|
||||
}
|
||||
}
|
||||
delete plt_hook_list;
|
||||
if (!hook_commit()) {
|
||||
if (!hook_commit(NULL)) {
|
||||
LOGE("Failed to restore plt_hook");
|
||||
should_unmap_zygisk = false;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ namespace {
|
||||
|
||||
void *nativeForkAndSpecialize_orig = nullptr;
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_l)>(nativeForkAndSpecialize_orig)(
|
||||
@@ -14,7 +14,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
@@ -25,7 +25,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
@@ -37,7 +37,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
@@ -50,7 +50,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_r(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
@@ -67,7 +67,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
@@ -85,7 +85,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_m)>(nativeForkAndSpecialize_orig)(
|
||||
@@ -95,7 +95,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_n)>(nativeForkAndSpecialize_orig)(
|
||||
@@ -105,7 +105,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
@@ -116,7 +116,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
@@ -128,7 +128,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_grapheneos_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides, jlongArray _13) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
@@ -205,7 +205,7 @@ std::array nativeForkAndSpecialize_methods = {
|
||||
|
||||
void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
@@ -215,7 +215,7 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
ZygiskContext ctx(env, &args);
|
||||
@@ -226,7 +226,7 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_r(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
@@ -241,7 +241,7 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
@@ -257,7 +257,7 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
@@ -267,7 +267,7 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_grapheneos_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides, jlongArray _14) {
|
||||
AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
struct app_specialize_args_v5 args(&uid, &gid, &gids, &runtime_flags, &rlimits, &mount_external, &se_info, &nice_name, &instruction_set, &app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
@@ -317,7 +317,7 @@ std::array nativeSpecializeAppProcess_methods = {
|
||||
|
||||
void *nativeForkSystemServer_orig = nullptr;
|
||||
[[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
struct server_specialize_args_v1 args(&uid, &gid, &gids, &runtime_flags, &permitted_capabilities, &effective_capabilities);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkSystemServer_pre();
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
|
||||
@@ -327,7 +327,7 @@ void *nativeForkSystemServer_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _11, jint _12, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
struct server_specialize_args_v1 args(&uid, &gid, &gids, &runtime_flags, &permitted_capabilities, &effective_capabilities);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkSystemServer_pre();
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
|
||||
@@ -337,7 +337,7 @@ void *nativeForkSystemServer_orig = nullptr;
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkSystemServer_grapheneos_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
struct server_specialize_args_v1 args(&uid, &gid, &gids, &runtime_flags, &permitted_capabilities, &effective_capabilities);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkSystemServer_pre();
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_grapheneos_u)>(nativeForkSystemServer_orig)(
|
||||
|
||||
252
loader/src/injector/module.h
Normal file
252
loader/src/injector/module.h
Normal file
@@ -0,0 +1,252 @@
|
||||
#ifndef MODULE_H
|
||||
#define MODULE_H
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
#define REZYGISK_API_VERSION 5
|
||||
|
||||
enum rezygiskd_flags : uint32_t {
|
||||
PROCESS_GRANTED_ROOT = (1u << 0),
|
||||
PROCESS_ON_DENYLIST = (1u << 1),
|
||||
|
||||
PROCESS_IS_MANAGER = (1u << 27),
|
||||
PROCESS_ROOT_IS_APATCH = (1u << 28),
|
||||
PROCESS_ROOT_IS_KSU = (1u << 29),
|
||||
PROCESS_ROOT_IS_MAGISK = (1u << 30),
|
||||
PROCESS_IS_FIRST_STARTED = (1u << 31),
|
||||
|
||||
PRIVATE_MASK = PROCESS_IS_FIRST_STARTED
|
||||
};
|
||||
|
||||
struct app_specialize_args_v1 {
|
||||
jint *uid;
|
||||
jint *gid;
|
||||
jintArray *gids;
|
||||
jint *runtime_flags;
|
||||
jint *mount_external;
|
||||
jstring *se_info;
|
||||
jstring *nice_name;
|
||||
jstring *instruction_set;
|
||||
jstring *app_data_dir;
|
||||
|
||||
jboolean *is_child_zygote;
|
||||
jboolean *is_top_app;
|
||||
jobjectArray *pkg_data_info_list;
|
||||
jobjectArray *whitelisted_data_info_list;
|
||||
jboolean *mount_data_dirs;
|
||||
jboolean *mount_storage_dirs;
|
||||
};
|
||||
|
||||
struct app_specialize_args_v4 {
|
||||
jint *uid;
|
||||
jint *gid;
|
||||
jintArray *gids;
|
||||
jint *runtime_flags;
|
||||
jobjectArray *rlimits;
|
||||
jint *mount_external;
|
||||
jstring *se_info;
|
||||
jstring *nice_name;
|
||||
jstring *instruction_set;
|
||||
jstring *app_data_dir;
|
||||
|
||||
jintArray *fds_to_ignore;
|
||||
jboolean *is_child_zygote;
|
||||
jboolean *is_top_app;
|
||||
jobjectArray *pkg_data_info_list;
|
||||
jobjectArray *whitelisted_data_info_list;
|
||||
jboolean *mount_data_dirs;
|
||||
jboolean *mount_storage_dirs;
|
||||
};
|
||||
|
||||
struct app_specialize_args_v5 {
|
||||
jint *uid;
|
||||
jint *gid;
|
||||
jintArray *gids;
|
||||
jint *runtime_flags;
|
||||
jobjectArray *rlimits;
|
||||
jint *mount_external;
|
||||
jstring *se_info;
|
||||
jstring *nice_name;
|
||||
jstring *instruction_set;
|
||||
jstring *app_data_dir;
|
||||
|
||||
jintArray *fds_to_ignore;
|
||||
jboolean *is_child_zygote;
|
||||
jboolean *is_top_app;
|
||||
jobjectArray *pkg_data_info_list;
|
||||
jobjectArray *whitelisted_data_info_list;
|
||||
jboolean *mount_data_dirs;
|
||||
jboolean *mount_storage_dirs;
|
||||
|
||||
jboolean *mount_sysprop_overrides;
|
||||
};
|
||||
|
||||
struct server_specialize_args_v1 {
|
||||
jint *uid;
|
||||
jint *gid;
|
||||
jintArray *gids;
|
||||
jint *runtime_flags;
|
||||
jlong *permitted_capabilities;
|
||||
jlong *effective_capabilities;
|
||||
};
|
||||
|
||||
enum rezygisk_options : uint32_t {
|
||||
/* INFO: Force ReZygisk to umount the root related mounts on this process. This option
|
||||
will only take effect if set in pre...Specialize, as ReZygisk umounts at
|
||||
that point.
|
||||
|
||||
ReZygisk Umount System will not umount all root related mounts, read ReZygiskd
|
||||
umount_root function in utils.c file to understand how it selects the ones
|
||||
to umount.
|
||||
*/
|
||||
FORCE_DENYLIST_UNMOUNT = 0,
|
||||
|
||||
/* INFO: Once set, ReZygisk will dlclose your library from the process, this is assured to
|
||||
happen after post...Specialize, but not at a specific moment due to different
|
||||
implementations.
|
||||
|
||||
You should not use this option if you leave references in the process such as hooks,
|
||||
which will try to execute unitialized memory.
|
||||
*/
|
||||
DLCLOSE_MODULE_LIBRARY = 1
|
||||
};
|
||||
|
||||
struct rezygisk_api {
|
||||
void *impl;
|
||||
bool (*register_module)(struct rezygisk_api *, struct rezygisk_abi *);
|
||||
|
||||
void (*hook_jni_native_methods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
union {
|
||||
void (*plt_hook_register)(const char *, const char *, void *, void **); /* INFO: v3 and below */
|
||||
void (*plt_hook_register_v4)(dev_t, ino_t, const char *, void *, void **); /* INFO: v4 */
|
||||
};
|
||||
union {
|
||||
void (*plt_hook_exclude)(const char *, const char *); /* INFO: v3 and below */
|
||||
void (*exempt_fd)(int); /* INFO: v4 */
|
||||
};
|
||||
bool (*plt_hook_commit)();
|
||||
int (*connect_companion)(void *);
|
||||
void (*set_option)(void *, enum rezygisk_options opt);
|
||||
int (*get_module_dir)(void *);
|
||||
uint32_t (*get_flags)();
|
||||
};
|
||||
|
||||
struct rezygisk_abi {
|
||||
long api_version;
|
||||
void *impl;
|
||||
|
||||
void (*pre_app_specialize)(void *, void *);
|
||||
void (*post_app_specialize)(void *, const void *);
|
||||
void (*pre_server_specialize)(void *, void *);
|
||||
void (*post_server_specialize)(void *, const void *);
|
||||
};
|
||||
|
||||
struct rezygisk_module {
|
||||
struct rezygisk_abi abi;
|
||||
struct rezygisk_api api;
|
||||
|
||||
void *handle;
|
||||
void (*zygisk_module_entry)(void *, void *);
|
||||
|
||||
bool unload;
|
||||
};
|
||||
|
||||
void rezygisk_module_call_on_load(struct rezygisk_module *m, void *env) {
|
||||
m->zygisk_module_entry((void *)&m->api, env);
|
||||
}
|
||||
|
||||
void rezygisk_module_call_pre_app_specialize(struct rezygisk_module *m, struct app_specialize_args_v5 *args) {
|
||||
switch (m->abi.api_version) {
|
||||
case 1:
|
||||
case 2: {
|
||||
struct app_specialize_args_v1 versioned_args = {
|
||||
.uid = args->uid,
|
||||
.gid = args->gid,
|
||||
.gids = args->gids,
|
||||
.runtime_flags = args->runtime_flags,
|
||||
.mount_external = args->mount_external,
|
||||
.se_info = args->se_info,
|
||||
.nice_name = args->nice_name,
|
||||
.instruction_set = args->instruction_set,
|
||||
.app_data_dir = args->app_data_dir,
|
||||
.is_child_zygote = args->is_child_zygote,
|
||||
.is_top_app = args->is_top_app,
|
||||
.pkg_data_info_list = args->pkg_data_info_list,
|
||||
.whitelisted_data_info_list = args->whitelisted_data_info_list,
|
||||
.mount_data_dirs = args->mount_data_dirs,
|
||||
.mount_storage_dirs = args->mount_storage_dirs
|
||||
};
|
||||
|
||||
m->abi.pre_app_specialize(m->abi.impl, &versioned_args);
|
||||
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
case 4: {
|
||||
struct app_specialize_args_v4 versioned_args;
|
||||
memcpy(&versioned_args, args, sizeof(struct app_specialize_args_v4));
|
||||
|
||||
m->abi.pre_app_specialize(m->abi.impl, &versioned_args);
|
||||
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
m->abi.pre_app_specialize(m->abi.impl, args);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rezygisk_module_call_post_app_specialize(struct rezygisk_module *m, const struct app_specialize_args_v5 *args) {
|
||||
switch (m->abi.api_version) {
|
||||
case 1:
|
||||
case 2: {
|
||||
struct app_specialize_args_v1 versioned_args = {
|
||||
.uid = args->uid,
|
||||
.gid = args->gid,
|
||||
.gids = args->gids,
|
||||
.runtime_flags = args->runtime_flags,
|
||||
.mount_external = args->mount_external,
|
||||
.se_info = args->se_info,
|
||||
.nice_name = args->nice_name,
|
||||
.instruction_set = args->instruction_set,
|
||||
.app_data_dir = args->app_data_dir,
|
||||
.is_child_zygote = args->is_child_zygote,
|
||||
.is_top_app = args->is_top_app,
|
||||
.pkg_data_info_list = args->pkg_data_info_list,
|
||||
.whitelisted_data_info_list = args->whitelisted_data_info_list,
|
||||
.mount_data_dirs = args->mount_data_dirs,
|
||||
.mount_storage_dirs = args->mount_storage_dirs
|
||||
};
|
||||
|
||||
m->abi.post_app_specialize(m->abi.impl, &versioned_args);
|
||||
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
case 4: {
|
||||
struct app_specialize_args_v4 versioned_args;
|
||||
memcpy(&versioned_args, args, sizeof(struct app_specialize_args_v4));
|
||||
|
||||
m->abi.post_app_specialize(m->abi.impl, &versioned_args);
|
||||
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
m->abi.post_app_specialize(m->abi.impl, args);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rezygisk_module_call_pre_server_specialize(struct rezygisk_module *m, struct server_specialize_args_v1 *args) {
|
||||
m->abi.pre_server_specialize(m->abi.impl, args);
|
||||
}
|
||||
|
||||
void rezygisk_module_call_post_server_specialize(struct rezygisk_module *m, const struct server_specialize_args_v1 *args) {
|
||||
m->abi.post_server_specialize(m->abi.impl, args);
|
||||
}
|
||||
|
||||
#endif /* MODULE_H */
|
||||
@@ -1,240 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include "api.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct ZygiskContext;
|
||||
struct ZygiskModule;
|
||||
|
||||
struct AppSpecializeArgs_v1;
|
||||
using AppSpecializeArgs_v2 = AppSpecializeArgs_v1;
|
||||
struct AppSpecializeArgs_v3;
|
||||
using AppSpecializeArgs_v4 = AppSpecializeArgs_v3;
|
||||
struct AppSpecializeArgs_v5;
|
||||
|
||||
struct module_abi_v1;
|
||||
using module_abi_v2 = module_abi_v1;
|
||||
using module_abi_v3 = module_abi_v1;
|
||||
using module_abi_v4 = module_abi_v1;
|
||||
using module_abi_v5 = module_abi_v1;
|
||||
|
||||
struct api_abi_v1;
|
||||
struct api_abi_v2;
|
||||
using api_abi_v3 = api_abi_v2;
|
||||
struct api_abi_v4;
|
||||
using api_abi_v5 = api_abi_v4;
|
||||
|
||||
union ApiTable;
|
||||
|
||||
struct AppSpecializeArgs_v3 {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jobjectArray &rlimits;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
jintArray *fds_to_ignore = nullptr;
|
||||
jboolean *is_child_zygote = nullptr;
|
||||
jboolean *is_top_app = nullptr;
|
||||
jobjectArray *pkg_data_info_list = nullptr;
|
||||
jobjectArray *whitelisted_data_info_list = nullptr;
|
||||
jboolean *mount_data_dirs = nullptr;
|
||||
jboolean *mount_storage_dirs = nullptr;
|
||||
|
||||
AppSpecializeArgs_v3(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
|
||||
jstring &instruction_set, jstring &app_data_dir) :
|
||||
uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), rlimits(rlimits),
|
||||
mount_external(mount_external), se_info(se_info), nice_name(nice_name),
|
||||
instruction_set(instruction_set), app_data_dir(app_data_dir) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs_v5 : public AppSpecializeArgs_v3 {
|
||||
jboolean *mount_sysprop_overrides = nullptr;
|
||||
|
||||
AppSpecializeArgs_v5(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
|
||||
jstring &instruction_set, jstring &app_data_dir) : AppSpecializeArgs_v3(
|
||||
uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, nice_name, instruction_set, app_data_dir) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs_v1 {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
jboolean *const is_child_zygote;
|
||||
jboolean *const is_top_app;
|
||||
jobjectArray *const pkg_data_info_list;
|
||||
jobjectArray *const whitelisted_data_info_list;
|
||||
jboolean *const mount_data_dirs;
|
||||
jboolean *const mount_storage_dirs;
|
||||
|
||||
AppSpecializeArgs_v1(const AppSpecializeArgs_v5 *a) :
|
||||
uid(a->uid), gid(a->gid), gids(a->gids), runtime_flags(a->runtime_flags),
|
||||
mount_external(a->mount_external), se_info(a->se_info), nice_name(a->nice_name),
|
||||
instruction_set(a->instruction_set), app_data_dir(a->app_data_dir),
|
||||
is_child_zygote(a->is_child_zygote), is_top_app(a->is_top_app),
|
||||
pkg_data_info_list(a->pkg_data_info_list),
|
||||
whitelisted_data_info_list(a->whitelisted_data_info_list),
|
||||
mount_data_dirs(a->mount_data_dirs), mount_storage_dirs(a->mount_storage_dirs) {}
|
||||
};
|
||||
|
||||
struct ServerSpecializeArgs_v1 {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ServerSpecializeArgs_v1(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jlong &permitted_capabilities, jlong &effective_capabilities) :
|
||||
uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags),
|
||||
permitted_capabilities(permitted_capabilities),
|
||||
effective_capabilities(effective_capabilities) {}
|
||||
};
|
||||
|
||||
struct module_abi_v1 {
|
||||
long api_version;
|
||||
void *impl;
|
||||
void (*preAppSpecialize)(void *, void *);
|
||||
void (*postAppSpecialize)(void *, const void *);
|
||||
void (*preServerSpecialize)(void *, void *);
|
||||
void (*postServerSpecialize)(void *, const void *);
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
|
||||
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
|
||||
|
||||
PROCESS_IS_MANAGER = (1u << 27),
|
||||
PROCESS_ROOT_IS_APATCH = (1u << 28),
|
||||
PROCESS_ROOT_IS_KSU = (1u << 29),
|
||||
PROCESS_ROOT_IS_MAGISK = (1u << 30),
|
||||
PROCESS_IS_FIRST_STARTED = (1u << 31),
|
||||
|
||||
PRIVATE_MASK = PROCESS_IS_FIRST_STARTED
|
||||
};
|
||||
|
||||
struct api_abi_base {
|
||||
ZygiskModule *impl;
|
||||
bool (*registerModule)(ApiTable *, long *);
|
||||
};
|
||||
|
||||
struct api_abi_v1 : public api_abi_base {
|
||||
/* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
/* 1 */ void (*pltHookRegister)(const char *, const char *, void *, void **);
|
||||
/* 2 */ void (*pltHookExclude)(const char *, const char *);
|
||||
/* 3 */ bool (*pltHookCommit)();
|
||||
/* 4 */ int (*connectCompanion)(ZygiskModule *);
|
||||
/* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);
|
||||
};
|
||||
|
||||
struct api_abi_v2 : public api_abi_v1 {
|
||||
/* 6 */ int (*getModuleDir)(ZygiskModule *);
|
||||
/* 7 */ uint32_t (*getFlags)(ZygiskModule *);
|
||||
};
|
||||
|
||||
struct api_abi_v4 : public api_abi_base {
|
||||
/* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
/* 1 */ void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
/* 2 */ bool (*exemptFd)(int);
|
||||
/* 3 */ bool (*pltHookCommit)();
|
||||
/* 4 */ int (*connectCompanion)(ZygiskModule *);
|
||||
/* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);
|
||||
/* 6 */ int (*getModuleDir)(ZygiskModule *);
|
||||
/* 7 */ uint32_t (*getFlags)(ZygiskModule *);
|
||||
};
|
||||
|
||||
union ApiTable {
|
||||
api_abi_base base;
|
||||
api_abi_v1 v1;
|
||||
api_abi_v2 v2;
|
||||
api_abi_v4 v4;
|
||||
};
|
||||
|
||||
#define call_app(method) \
|
||||
switch (*mod.api_version) { \
|
||||
case 1: \
|
||||
case 2: { \
|
||||
AppSpecializeArgs_v1 a(args); \
|
||||
mod.v1->method(mod.v1->impl, &a); \
|
||||
break; \
|
||||
} \
|
||||
case 3: \
|
||||
case 4: \
|
||||
case 5: \
|
||||
mod.v1->method(mod.v1->impl, args);\
|
||||
break; \
|
||||
}
|
||||
|
||||
struct ZygiskModule {
|
||||
|
||||
void onLoad(void *env) {
|
||||
entry.fn(&api, env);
|
||||
}
|
||||
void preAppSpecialize(AppSpecializeArgs_v5 *args) const {
|
||||
call_app(preAppSpecialize)
|
||||
}
|
||||
void postAppSpecialize(const AppSpecializeArgs_v5 *args) const {
|
||||
call_app(postAppSpecialize)
|
||||
}
|
||||
void preServerSpecialize(ServerSpecializeArgs_v1 *args) const {
|
||||
mod.v1->preServerSpecialize(mod.v1->impl, args);
|
||||
}
|
||||
void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const {
|
||||
mod.v1->postServerSpecialize(mod.v1->impl, args);
|
||||
}
|
||||
|
||||
bool valid() const;
|
||||
int connectCompanion() const;
|
||||
int getModuleDir() const;
|
||||
void setOption(zygisk::Option opt);
|
||||
static uint32_t getFlags();
|
||||
bool tryUnload() const { return unload && dlclose(handle) == 0; };
|
||||
void clearApi() { memset(&api, 0, sizeof(api)); }
|
||||
int getId() const { return id; }
|
||||
void *getEntry() const { return entry.ptr; }
|
||||
|
||||
ZygiskModule(int id, void *handle, void *entry);
|
||||
|
||||
static bool RegisterModuleImpl(ApiTable *api, long *module);
|
||||
|
||||
private:
|
||||
const int id;
|
||||
bool unload = false;
|
||||
|
||||
void * const handle;
|
||||
union {
|
||||
void * const ptr;
|
||||
void (* const fn)(void *, void *);
|
||||
} entry;
|
||||
|
||||
ApiTable api;
|
||||
|
||||
union {
|
||||
long *api_version;
|
||||
module_abi_v1 *v1;
|
||||
} mod;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -1,8 +1,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/dlext.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <linux/limits.h>
|
||||
|
||||
@@ -24,6 +23,8 @@
|
||||
static const char *(*get_realpath_sym)(SoInfo *) = NULL;
|
||||
static void (*soinfo_free)(SoInfo *) = NULL;
|
||||
static SoInfo *(*find_containing_library)(const void *p) = NULL;
|
||||
static void (*purge_unused_memory)(void) = NULL;
|
||||
struct link_map *r_debug_tail = NULL;
|
||||
|
||||
static inline const char *get_path(SoInfo *self) {
|
||||
if (get_realpath_sym)
|
||||
@@ -104,6 +105,8 @@ static bool solist_init() {
|
||||
|
||||
ElfImg_destroy(linker);
|
||||
|
||||
somain = NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -115,6 +118,8 @@ static bool solist_init() {
|
||||
|
||||
ElfImg_destroy(linker);
|
||||
|
||||
somain = NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,6 +131,34 @@ static bool solist_init() {
|
||||
|
||||
ElfImg_destroy(linker);
|
||||
|
||||
somain = NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("%p is find_containing_library", (void *)find_containing_library);
|
||||
|
||||
purge_unused_memory = (void (*)())getSymbAddress(linker, "__dl__Z19purge_unused_memoryv");
|
||||
if (purge_unused_memory == NULL) {
|
||||
LOGE("Failed to find purge_unused_memory __dl__Z19purge_unused_memoryv");
|
||||
|
||||
ElfImg_destroy(linker);
|
||||
|
||||
somain = NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("%p is purge_unused_memory", (void *)purge_unused_memory);
|
||||
|
||||
r_debug_tail = (struct link_map *)getSymbValueByPrefix(linker, "__dl__ZL12r_debug_tail");
|
||||
if (r_debug_tail == NULL) {
|
||||
LOGE("Failed to find r_debug_tail __dl__ZL10r_debug_tail");
|
||||
|
||||
ElfImg_destroy(linker);
|
||||
|
||||
somain = NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -152,9 +185,61 @@ static bool solist_init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* INFO: This is an AOSP function to remove a link map from
|
||||
the link map list.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker_gdb_support.cpp#63
|
||||
*/
|
||||
static void remove_link_map_from_debug_map(struct link_map *map) {
|
||||
if (r_debug_tail == map) {
|
||||
r_debug_tail = map->l_prev;
|
||||
}
|
||||
|
||||
if (map->l_prev) {
|
||||
map->l_prev->l_next = map->l_next;
|
||||
}
|
||||
|
||||
if (map->l_next) {
|
||||
map->l_next->l_prev = map->l_prev;
|
||||
}
|
||||
}
|
||||
|
||||
static struct link_map *find_link_map(SoInfo *si) {
|
||||
const char *path = get_path(si);
|
||||
if (path == NULL) {
|
||||
LOGE("Failed to get path for SoInfo %p", (void *)si);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOGD("Searching for link_map for %s", path);
|
||||
|
||||
struct link_map *map = r_debug_tail;
|
||||
while (map) {
|
||||
/* INFO: l_name uses the same pointer as realpath function of SoInfo, allowing us
|
||||
to directly compare the pointers instead of the strings.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#283
|
||||
*/
|
||||
if (map->l_name && (uintptr_t)map->l_name == (uintptr_t)path) {
|
||||
LOGD("Found link_map for %s: %p", path, (void *)map);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
map = map->l_next;
|
||||
}
|
||||
|
||||
LOGE("Failed to find link_map for %s", path);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* INFO: find_containing_library returns the SoInfo for the library that contains
|
||||
that memory inside its limits, hence why named "lib_memory" in ReZygisk. */
|
||||
bool solist_drop_so_path(void *lib_memory) {
|
||||
bool solist_drop_so_path(void *lib_memory, bool unload) {
|
||||
if (somain == NULL && !solist_init()) {
|
||||
LOGE("Failed to initialize solist");
|
||||
|
||||
@@ -165,6 +250,8 @@ bool solist_drop_so_path(void *lib_memory) {
|
||||
if (found == NULL) {
|
||||
LOGD("Could not find containing library for %p", lib_memory);
|
||||
|
||||
purge_unused_memory();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -176,16 +263,57 @@ bool solist_drop_so_path(void *lib_memory) {
|
||||
|
||||
return false;
|
||||
}
|
||||
strcpy(path, get_path(found));
|
||||
strncpy(path, get_path(found), sizeof(path) - 1);
|
||||
|
||||
/* INFO: This area is guarded. Must unprotect first. */
|
||||
pdg_unprotect();
|
||||
|
||||
set_size(found, 0);
|
||||
soinfo_free(found);
|
||||
if (unload) pdg_protect();
|
||||
|
||||
pdg_protect();
|
||||
LOGD("Set size of %p to 0", (void *)found);
|
||||
|
||||
LOGD("Successfully dropped so path for: %s", path);
|
||||
/* INFO: We know that as libzygisk.so our limits, but modules are arbitrary, so
|
||||
calling deconstructors might break them. To avoid that, we manually call
|
||||
the separated structures, that however won't clean all traces in soinfo,
|
||||
not for now, at least. */
|
||||
if (unload && dlclose((void *)found) == -1) {
|
||||
LOGE("Failed to dlclose so path for %s: %s", path, dlerror());
|
||||
|
||||
return false;
|
||||
} else if (!unload) {
|
||||
LOGD("Not unloading so path for %s, only dropping it", path);
|
||||
|
||||
/*
|
||||
INFO: If the link map is not removed from the list, it gets inconsistent, resulting
|
||||
in a loop when listing through it, which can be detected. To fix that, we
|
||||
can remove the map, like expected.
|
||||
|
||||
We cannot use the notify_gdb_of_unload function as it is static, and not available
|
||||
in all linker binaries.
|
||||
*/
|
||||
struct link_map *map = find_link_map(found);
|
||||
if (!map) {
|
||||
LOGE("Failed to find link map for %s", path);
|
||||
|
||||
pdg_protect();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
remove_link_map_from_debug_map(map);
|
||||
/* INFO: unregister_soinfo_tls cannot be used since module might use JNI which may
|
||||
require TLS, so we cannot remove it. */
|
||||
soinfo_free(found);
|
||||
|
||||
pdg_protect();
|
||||
}
|
||||
|
||||
LOGD("Successfully hidden soinfo traces for %s", path);
|
||||
|
||||
/* INFO: Avoid leaks by ensuring the freed places are munmapped */
|
||||
purge_unused_memory();
|
||||
|
||||
LOGD("Purged unused memory successfully");
|
||||
|
||||
/* INFO: Let's avoid trouble regarding detections */
|
||||
memset(path, strlen(path), 0);
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
typedef struct SoInfo SoInfo;
|
||||
|
||||
struct SoInfo {
|
||||
char data[0];
|
||||
};
|
||||
typedef void SoInfo;
|
||||
|
||||
#define FuncType(name) void (*name)
|
||||
|
||||
@@ -28,14 +24,20 @@ struct pdg {
|
||||
libzygisk.so, so that it doesn't create gaps between current module info
|
||||
and the next (soinfo).
|
||||
|
||||
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.
|
||||
To do that, we use 2 functions: set_size and dlclose, which will first zero
|
||||
zero the size that the linker believes the shared library is, and then dlclose.
|
||||
Because the size is 0, it won't munmap the library, allowing us to keep loaded while
|
||||
having all other traces removed.
|
||||
|
||||
For the case of modules, which are arbitrary, we won't call dlclose, as it could break
|
||||
the module. Instead of using dlclose, we separately call soinfo_free, which will free
|
||||
the soinfo structure. That will allow to keep the data initialized by constructors
|
||||
mmaped, hence properly dropping most traces without breaking the module.
|
||||
|
||||
SOURCES:
|
||||
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#1712
|
||||
*/
|
||||
bool solist_drop_so_path(void *lib_memory);
|
||||
bool solist_drop_so_path(void *lib_memory, bool unload);
|
||||
|
||||
/*
|
||||
INFO: When dlopen'ing a library, the system will increment 1 to a global
|
||||
|
||||
@@ -7,6 +7,4 @@ extern size_t block_size;
|
||||
|
||||
void hook_functions();
|
||||
|
||||
void clean_trace(const char *path, void **module_addrs, size_t module_addrs_length, size_t load, size_t unload, bool spoof_maps);
|
||||
|
||||
extern "C" void send_seccomp_event();
|
||||
|
||||
@@ -77,11 +77,11 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (info.modules->modules_count != 0) {
|
||||
printf("Modules: %zu\n", info.modules->modules_count);
|
||||
if (info.modules.modules_count != 0) {
|
||||
printf("Modules: %zu\n", info.modules.modules_count);
|
||||
|
||||
for (size_t i = 0; i < info.modules->modules_count; i++) {
|
||||
printf(" - %s\n", info.modules->modules[i]);
|
||||
for (size_t i = 0; i < info.modules.modules_count; i++) {
|
||||
printf(" - %s\n", info.modules.modules[i]);
|
||||
}
|
||||
} else {
|
||||
printf("Modules: N/A\n");
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "utils.h"
|
||||
#include "daemon.h"
|
||||
#include "misc.h"
|
||||
#include "socket_utils.h"
|
||||
|
||||
#include "monitor.h"
|
||||
|
||||
@@ -29,6 +30,15 @@ static bool update_status(const char *message);
|
||||
|
||||
char monitor_stop_reason[32];
|
||||
|
||||
struct environment_information {
|
||||
char *root_impl;
|
||||
char **modules;
|
||||
uint32_t modules_len;
|
||||
};
|
||||
|
||||
static struct environment_information environment_information64;
|
||||
static struct environment_information environment_information32;
|
||||
|
||||
enum ptracer_tracing_state {
|
||||
TRACING,
|
||||
STOPPING,
|
||||
@@ -66,6 +76,7 @@ struct rezygiskd_status status32 = {
|
||||
|
||||
int monitor_epoll_fd;
|
||||
bool monitor_events_running = true;
|
||||
typedef void (*monitor_event_callback_t)();
|
||||
|
||||
bool monitor_events_init() {
|
||||
monitor_epoll_fd = epoll_create(1);
|
||||
@@ -78,14 +89,9 @@ bool monitor_events_init() {
|
||||
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) {
|
||||
bool monitor_events_register_event(monitor_event_callback_t event_cb, int fd, uint32_t events) {
|
||||
struct epoll_event ev = {
|
||||
.data.ptr = event_cbs,
|
||||
.data.ptr = (void *)event_cb,
|
||||
.events = events
|
||||
};
|
||||
|
||||
@@ -116,15 +122,16 @@ 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");
|
||||
if (nfds == -1 && errno != EINTR) {
|
||||
PLOGE("epoll_wait");
|
||||
|
||||
continue;
|
||||
monitor_events_running = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
struct monitor_event_cbs *event_cbs = (struct monitor_event_cbs *)events[i].data.ptr;
|
||||
event_cbs->callback();
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
((monitor_event_callback_t)events[i].data.ptr)();
|
||||
|
||||
if (!monitor_events_running) break;
|
||||
}
|
||||
@@ -132,11 +139,6 @@ void monitor_events_loop() {
|
||||
|
||||
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;
|
||||
@@ -166,57 +168,26 @@ bool rezygiskd_listener_init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct __attribute__((__packed__)) MsgHead {
|
||||
unsigned int cmd;
|
||||
int length;
|
||||
};
|
||||
|
||||
void rezygiskd_listener_callback() {
|
||||
while (1) {
|
||||
struct MsgHead msg = { 0 };
|
||||
uint8_t cmd;
|
||||
ssize_t nread = TEMP_FAILURE_RETRY(read(monitor_sock_fd, &cmd, sizeof(cmd)));
|
||||
if (nread == -1) {
|
||||
PLOGE("read socket");
|
||||
|
||||
size_t nread;
|
||||
|
||||
again:
|
||||
nread = read(monitor_sock_fd, &msg, sizeof(msg));
|
||||
if ((int)nread == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) goto again;
|
||||
|
||||
PLOGE("read socket");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
char *msg_data = NULL;
|
||||
|
||||
if (msg.length != 0) {
|
||||
msg_data = malloc(msg.length);
|
||||
if (!msg_data) {
|
||||
LOGE("malloc msg data failed");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
again_msg_data:
|
||||
nread = read(monitor_sock_fd, msg_data, msg.length);
|
||||
if ((int)nread == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) goto again_msg_data;
|
||||
|
||||
PLOGE("read socket");
|
||||
|
||||
free(msg_data);
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (msg.cmd) {
|
||||
switch (cmd) {
|
||||
case START: {
|
||||
if (tracing_state == STOPPING) tracing_state = TRACING;
|
||||
else if (tracing_state == STOPPED) {
|
||||
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
|
||||
if (tracing_state == STOPPING) {
|
||||
LOGI("Continue tracing init");
|
||||
|
||||
LOGI("start tracing init");
|
||||
tracing_state = TRACING;
|
||||
} else if (tracing_state == STOPPED) {
|
||||
LOGI("Start tracing init");
|
||||
|
||||
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
|
||||
|
||||
tracing_state = TRACING;
|
||||
}
|
||||
@@ -227,7 +198,7 @@ void rezygiskd_listener_callback() {
|
||||
}
|
||||
case STOP: {
|
||||
if (tracing_state == TRACING) {
|
||||
LOGI("stop tracing requested");
|
||||
LOGI("Stop tracing requested");
|
||||
|
||||
tracing_state = STOPPING;
|
||||
strcpy(monitor_stop_reason, "user requested");
|
||||
@@ -239,7 +210,7 @@ void rezygiskd_listener_callback() {
|
||||
break;
|
||||
}
|
||||
case EXIT: {
|
||||
LOGI("prepare for exit ...");
|
||||
LOGI("Prepare for exit ...");
|
||||
|
||||
tracing_state = EXITING;
|
||||
strcpy(monitor_stop_reason, "user requested");
|
||||
@@ -249,104 +220,166 @@ void rezygiskd_listener_callback() {
|
||||
|
||||
break;
|
||||
}
|
||||
case ZYGOTE64_INJECTED: {
|
||||
status64.zygote_injected = true;
|
||||
|
||||
update_status(NULL);
|
||||
|
||||
break;
|
||||
}
|
||||
case ZYGOTE64_INJECTED:
|
||||
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);
|
||||
LOGI("Received Zygote%s injected command", cmd == ZYGOTE64_INJECTED ? "64" : "32");
|
||||
|
||||
struct rezygiskd_status *status = cmd == ZYGOTE64_INJECTED ? &status64 : &status32;
|
||||
status->zygote_injected = true;
|
||||
|
||||
update_status(NULL);
|
||||
|
||||
break;
|
||||
}
|
||||
case DAEMON64_SET_INFO:
|
||||
case DAEMON32_SET_INFO: {
|
||||
LOGD("received daemon32 info %s", msg_data);
|
||||
LOGD("Received ReZygiskd%s info", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
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");
|
||||
uint32_t root_impl_len;
|
||||
if (read_uint32_t(monitor_sock_fd, &root_impl_len) != sizeof(root_impl_len)) {
|
||||
LOGE("read ReZygiskd%s root impl len", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
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);
|
||||
struct environment_information *environment_information = cmd == DAEMON64_SET_INFO ? &environment_information64 : &environment_information32;
|
||||
if (environment_information->root_impl) {
|
||||
LOGD("freeing old ReZygiskd%s root impl", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
free((void *)environment_information->root_impl);
|
||||
environment_information->root_impl = NULL;
|
||||
}
|
||||
|
||||
environment_information->root_impl = malloc(root_impl_len + 1);
|
||||
if (environment_information->root_impl == NULL) {
|
||||
PLOGE("malloc ReZygiskd%s root impl", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (read_loop(monitor_sock_fd, (void *)environment_information->root_impl, root_impl_len) != (ssize_t)root_impl_len) {
|
||||
LOGE("read ReZygiskd%s root impl", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
free((void *)environment_information->root_impl);
|
||||
environment_information->root_impl = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
environment_information->root_impl[root_impl_len] = '\0';
|
||||
LOGD("ReZygiskd%s root impl: %s", cmd == DAEMON64_SET_INFO ? "64" : "32", environment_information->root_impl);
|
||||
|
||||
if (read_uint32_t(monitor_sock_fd, &environment_information->modules_len) != sizeof(environment_information->modules_len)) {
|
||||
LOGE("read ReZygiskd%s modules len", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
free((void *)environment_information->root_impl);
|
||||
environment_information->root_impl = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (environment_information->modules) {
|
||||
LOGD("freeing old ReZygiskd%s modules", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
for (size_t i = 0; i < environment_information->modules_len; i++) {
|
||||
free((void *)environment_information->modules[i]);
|
||||
}
|
||||
|
||||
free((void *)environment_information->modules);
|
||||
environment_information->modules = NULL;
|
||||
}
|
||||
|
||||
environment_information->modules = malloc(environment_information->modules_len * sizeof(char *));
|
||||
if (environment_information->modules == NULL) {
|
||||
PLOGE("malloc ReZygiskd%s modules", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
free((void *)environment_information->root_impl);
|
||||
environment_information->root_impl = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < environment_information->modules_len; i++) {
|
||||
uint32_t module_name_len;
|
||||
if (read_uint32_t(monitor_sock_fd, &module_name_len) != sizeof(module_name_len)) {
|
||||
LOGE("read ReZygiskd%s module name len", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
goto rezygiskd64_set_info_modules_cleanup;
|
||||
}
|
||||
|
||||
environment_information->modules[i] = malloc(module_name_len + 1);
|
||||
if (environment_information->modules[i] == NULL) {
|
||||
PLOGE("malloc ReZygiskd%s module name", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
goto rezygiskd64_set_info_modules_cleanup;
|
||||
}
|
||||
|
||||
if (read_loop(monitor_sock_fd, (void *)environment_information->modules[i], module_name_len) != (ssize_t)module_name_len) {
|
||||
LOGE("read ReZygiskd%s module name", cmd == DAEMON64_SET_INFO ? "64" : "32");
|
||||
|
||||
goto rezygiskd64_set_info_modules_cleanup;
|
||||
}
|
||||
|
||||
environment_information->modules[i][module_name_len] = '\0';
|
||||
LOGD("ReZygiskd%s module %zu: %s", cmd == DAEMON64_SET_INFO ? "64" : "32", i, environment_information->modules[i]);
|
||||
|
||||
continue;
|
||||
|
||||
rezygiskd64_set_info_modules_cleanup:
|
||||
free((void *)environment_information->root_impl);
|
||||
environment_information->root_impl = NULL;
|
||||
|
||||
for (size_t j = 0; j < i; j++) {
|
||||
free((void *)environment_information->modules[j]);
|
||||
}
|
||||
|
||||
free((void *)environment_information->modules);
|
||||
environment_information->modules = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
update_status(NULL);
|
||||
|
||||
break;
|
||||
}
|
||||
case DAEMON64_SET_ERROR_INFO:
|
||||
case DAEMON32_SET_ERROR_INFO: {
|
||||
LOGD("received daemon32 error info %s", msg_data);
|
||||
LOGD("Received ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
|
||||
|
||||
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");
|
||||
uint32_t error_info_len;
|
||||
if (read_uint32_t(monitor_sock_fd, &error_info_len) != sizeof(error_info_len)) {
|
||||
LOGE("read ReZygiskd%s error info len", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
strcpy(status32.daemon_error_info, msg_data);
|
||||
struct rezygiskd_status *status = cmd == DAEMON64_SET_ERROR_INFO ? &status64 : &status32;
|
||||
if (status->daemon_error_info) {
|
||||
LOGD("freeing old ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
|
||||
|
||||
free(status->daemon_error_info);
|
||||
status->daemon_error_info = NULL;
|
||||
}
|
||||
|
||||
status->daemon_error_info = malloc(error_info_len + 1);
|
||||
if (status->daemon_error_info == NULL) {
|
||||
PLOGE("malloc ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (read_loop(monitor_sock_fd, status->daemon_error_info, error_info_len) != (ssize_t)error_info_len) {
|
||||
LOGE("read ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
|
||||
|
||||
free(status->daemon_error_info);
|
||||
status->daemon_error_info = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
status->daemon_error_info[error_info_len] = '\0';
|
||||
LOGD("ReZygiskd%s error info: %s", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32", status->daemon_error_info);
|
||||
|
||||
update_status(NULL);
|
||||
|
||||
@@ -363,8 +396,6 @@ void rezygiskd_listener_callback() {
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_data) free(msg_data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -439,51 +470,49 @@ static bool ensure_daemon_created(bool is_64bit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#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 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 = strdup(status_str); \
|
||||
if (!status##abi.daemon_error_info) { \
|
||||
LOGE("malloc daemon" #abi " error info failed"); \
|
||||
\
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
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; \
|
||||
} \
|
||||
#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; \
|
||||
strcpy(monitor_stop_reason, "Zygote crashed"); \
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
|
||||
\
|
||||
break; \
|
||||
} \
|
||||
\
|
||||
if (!ensure_daemon_created(is_64)) { \
|
||||
LOGW("ReZygiskd " #abi "-bit not running, stop injecting"); \
|
||||
\
|
||||
tracing_state = STOPPING; \
|
||||
strcpy(monitor_stop_reason, "ReZygiskd not running"); \
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
|
||||
\
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
int sigchld_signal_fd;
|
||||
@@ -677,8 +706,6 @@ void sigchld_listener_callback() {
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
|
||||
update_status(NULL);
|
||||
} else {
|
||||
char status_str[64];
|
||||
parse_status(sigchld_status, status_str, sizeof(status_str));
|
||||
@@ -718,27 +745,20 @@ 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, ", ReZygisk " # suffix "-bit: "); \
|
||||
\
|
||||
strcat(status_text, "daemon" # suffix ": "); \
|
||||
if (status ## suffix.daemon_running) { \
|
||||
strcat(status_text, "😋 running "); \
|
||||
if (tracing_state != TRACING) strcat(status_text, "❌"); \
|
||||
else if (status ## suffix.zygote_injected && status ## suffix.daemon_running) \
|
||||
strcat(status_text, "✅"); \
|
||||
else strcat(status_text, "⚠️"); \
|
||||
\
|
||||
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, "("); \
|
||||
if (!status ## suffix.daemon_running) { \
|
||||
if (status ## suffix.daemon_error_info) { \
|
||||
strcat(status_text, "(ReZygiskd: "); \
|
||||
strcat(status_text, status ## suffix.daemon_error_info); \
|
||||
strcat(status_text, ")"); \
|
||||
} else { \
|
||||
strcat(status_text, "(ReZygiskd: not running)"); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
@@ -758,41 +778,106 @@ static bool update_status(const char *message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char status_text[1024] = "monitor: ";
|
||||
|
||||
char status_text[256] = "Monitor: ";
|
||||
switch (tracing_state) {
|
||||
case TRACING: {
|
||||
strcat(status_text, "😋 tracing");
|
||||
strcat(status_text, "✅");
|
||||
|
||||
break;
|
||||
}
|
||||
case STOPPING: [[fallthrough]];
|
||||
case STOPPED: {
|
||||
strcat(status_text, "❌ stopped");
|
||||
strcat(status_text, "⛔");
|
||||
|
||||
break;
|
||||
}
|
||||
case EXITING: {
|
||||
strcat(status_text, "❌ exited");
|
||||
strcat(status_text, "❌");
|
||||
|
||||
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);
|
||||
|
||||
if (environment_information64.root_impl || environment_information32.root_impl) {
|
||||
FILE *json = fopen("/data/adb/rezygisk/state.json", "w");
|
||||
if (json == NULL) {
|
||||
PLOGE("failed to open state.json");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(json, "{\n");
|
||||
fprintf(json, " \"root\": \"%s\",\n", environment_information64.root_impl ? environment_information64.root_impl : environment_information32.root_impl);
|
||||
|
||||
fprintf(json, " \"monitor\": {\n");
|
||||
fprintf(json, " \"state\": \"%d\"", tracing_state);
|
||||
if (monitor_stop_reason[0] != '\0') fprintf(json, ",\n \"reason\": \"%s\",\n", monitor_stop_reason);
|
||||
else fprintf(json, "\n");
|
||||
fprintf(json, " },\n");
|
||||
|
||||
|
||||
fprintf(json, " \"rezygiskd\": {\n");
|
||||
if (status64.supported) {
|
||||
fprintf(json, " \"64\": {\n");
|
||||
fprintf(json, " \"state\": %d,\n", status64.daemon_running);
|
||||
if (status64.daemon_error_info) fprintf(json, " \"reason\": \"%s\",\n", status64.daemon_error_info);
|
||||
fprintf(json, " \"modules\": [");
|
||||
|
||||
if (environment_information64.modules) for (uint32_t i = 0; i < environment_information64.modules_len; i++) {
|
||||
if (i > 0) fprintf(json, ", ");
|
||||
fprintf(json, "\"%s\"", environment_information64.modules[i]);
|
||||
}
|
||||
|
||||
fprintf(json, "]\n");
|
||||
fprintf(json, " }");
|
||||
if (status32.supported) fprintf(json, ",\n");
|
||||
else fprintf(json, "\n");
|
||||
}
|
||||
|
||||
if (status32.supported) {
|
||||
fprintf(json, " \"32\": {\n");
|
||||
fprintf(json, " \"state\": %d,\n", status32.daemon_running);
|
||||
if (status32.daemon_error_info) fprintf(json, " \"reason\": \"%s\",\n", status32.daemon_error_info);
|
||||
fprintf(json, " \"modules\": [");
|
||||
|
||||
if (environment_information32.modules) for (uint32_t i = 0; i < environment_information32.modules_len; i++) {
|
||||
if (i > 0) fprintf(json, ", ");
|
||||
fprintf(json, "\"%s\"", environment_information32.modules[i]);
|
||||
}
|
||||
|
||||
fprintf(json, "]\n");
|
||||
fprintf(json, " }\n");
|
||||
}
|
||||
|
||||
fprintf(json, " },\n");
|
||||
|
||||
fprintf(json, " \"zygote\": {\n");
|
||||
if (status64.supported) {
|
||||
fprintf(json, " \"64\": %d", status64.zygote_injected);
|
||||
if (status32.zygote_injected) fprintf(json, ",\n");
|
||||
else fprintf(json, "\n");
|
||||
}
|
||||
if (status32.zygote_injected) {
|
||||
fprintf(json, " \"32\": %d\n", status32.zygote_injected);
|
||||
}
|
||||
fprintf(json, " }\n");
|
||||
fprintf(json, "}\n");
|
||||
|
||||
fclose(json);
|
||||
} else {
|
||||
if (remove("/data/adb/rezygisk/state.json") == -1) {
|
||||
PLOGE("failed to remove state.json");
|
||||
}
|
||||
}
|
||||
|
||||
LOGI("status updated: %s", status_text);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -837,7 +922,7 @@ static bool prepare_environment() {
|
||||
return false;
|
||||
}
|
||||
|
||||
return update_status(NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void init_monitor() {
|
||||
@@ -847,10 +932,6 @@ void init_monitor() {
|
||||
|
||||
monitor_events_init();
|
||||
|
||||
struct monitor_event_cbs listener_cbs = {
|
||||
.callback = rezygiskd_listener_callback,
|
||||
.stop_callback = rezygiskd_listener_stop
|
||||
};
|
||||
if (!rezygiskd_listener_init()) {
|
||||
LOGE("failed to create socket");
|
||||
|
||||
@@ -859,12 +940,8 @@ void init_monitor() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
monitor_events_register_event(&listener_cbs, monitor_sock_fd, EPOLLIN | EPOLLET);
|
||||
monitor_events_register_event(rezygiskd_listener_callback, monitor_sock_fd, EPOLLIN | EPOLLET);
|
||||
|
||||
struct monitor_event_cbs sigchld_cbs = {
|
||||
.callback = sigchld_listener_callback,
|
||||
.stop_callback = sigchld_listener_stop
|
||||
};
|
||||
if (sigchld_listener_init() == false) {
|
||||
LOGE("failed to create signalfd");
|
||||
|
||||
@@ -874,16 +951,37 @@ void init_monitor() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
monitor_events_register_event(&sigchld_cbs, sigchld_signal_fd, EPOLLIN | EPOLLET);
|
||||
monitor_events_register_event(sigchld_listener_callback, sigchld_signal_fd, EPOLLIN | EPOLLET);
|
||||
|
||||
monitor_events_loop();
|
||||
|
||||
/* INFO: Once it stops the loop, we cannot access the epool data, so we
|
||||
either manually call the stops or save to a structure. */
|
||||
rezygiskd_listener_stop();
|
||||
sigchld_listener_stop();
|
||||
|
||||
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");
|
||||
if (environment_information64.root_impl) free((void *)environment_information64.root_impl);
|
||||
if (environment_information64.modules) {
|
||||
for (uint32_t i = 0; i < environment_information64.modules_len; i++) {
|
||||
free((void *)environment_information64.modules[i]);
|
||||
}
|
||||
free((void *)environment_information64.modules);
|
||||
}
|
||||
|
||||
if (environment_information32.root_impl) free((void *)environment_information32.root_impl);
|
||||
if (environment_information32.modules) {
|
||||
for (uint32_t i = 0; i < environment_information32.modules_len; i++) {
|
||||
free((void *)environment_information32.modules[i]);
|
||||
}
|
||||
free((void *)environment_information32.modules);
|
||||
}
|
||||
|
||||
LOGI("Terminating ReZygisk monitor");
|
||||
}
|
||||
|
||||
int send_control_command(enum rezygiskd_command cmd) {
|
||||
@@ -899,9 +997,10 @@ int send_control_command(enum rezygiskd_command cmd) {
|
||||
|
||||
socklen_t socklen = sizeof(sa_family_t) + sun_path_len;
|
||||
|
||||
ssize_t nsend = sendto(sockfd, (void *)&cmd, sizeof(cmd), 0, (struct sockaddr *)&addr, socklen);
|
||||
uint8_t cmd_op = cmd;
|
||||
ssize_t nsend = sendto(sockfd, (void *)&cmd_op, sizeof(cmd_op), 0, (struct sockaddr *)&addr, socklen);
|
||||
|
||||
close(sockfd);
|
||||
|
||||
return nsend != sizeof(cmd) ? -1 : 0;
|
||||
return nsend != sizeof(cmd_op) ? -1 : 0;
|
||||
}
|
||||
|
||||
@@ -161,13 +161,15 @@ bool inject_on_main(int pid, const char *lib_path) {
|
||||
|
||||
const char *libdl_path = NULL;
|
||||
const char *libc_path = NULL;
|
||||
for (size_t i = 0; i < local_map->size; i++) {
|
||||
if (local_map->maps[i].path == NULL) continue;
|
||||
for (size_t i = 0; i < map->size; i++) {
|
||||
if (map->maps[i].path == NULL) continue;
|
||||
|
||||
const char *filename = position_after(local_map->maps[i].path, '/');
|
||||
const char *filename = position_after(map->maps[i].path, '/');
|
||||
|
||||
if (strcmp(filename, "libdl.so") == 0) {
|
||||
libdl_path = local_map->maps[i].path;
|
||||
if (!libdl_path && strcmp(filename, "libdl.so") == 0) {
|
||||
libdl_path = map->maps[i].path;
|
||||
|
||||
LOGD("found libdl.so at %s", libdl_path);
|
||||
|
||||
/* INFO: If we had found libc.so too, no need to continue searching */
|
||||
if (libc_path) break;
|
||||
@@ -175,8 +177,10 @@ bool inject_on_main(int pid, const char *lib_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(filename, "libc.so") == 0) {
|
||||
libc_path = local_map->maps[i].path;
|
||||
if (!libc_path && strcmp(filename, "libc.so") == 0) {
|
||||
libc_path = map->maps[i].path;
|
||||
|
||||
LOGD("found libc.so at %s", libc_path);
|
||||
|
||||
/* INFO: If we had found libdl.so too, no need to continue searching */
|
||||
if (libdl_path) break;
|
||||
@@ -364,10 +368,8 @@ bool inject_on_main(int pid, const char *lib_path) {
|
||||
/* call injector entry(start_addr, block_size, path) */
|
||||
args[0] = (uintptr_t)start_addr;
|
||||
args[1] = block_size;
|
||||
str = push_string(pid, ®s, rezygiskd_get_path());
|
||||
args[2] = (uintptr_t)str;
|
||||
|
||||
remote_call(pid, ®s, injector_entry, (uintptr_t)libc_return_addr, args, 3);
|
||||
remote_call(pid, ®s, injector_entry, (uintptr_t)libc_return_addr, args, 2);
|
||||
|
||||
free(args);
|
||||
|
||||
|
||||
@@ -119,8 +119,8 @@ struct maps *parse_maps(const char *filename) {
|
||||
path_offset++;
|
||||
}
|
||||
|
||||
maps->maps = (struct map *)realloc(maps->maps, (i + 1) * sizeof(struct map));
|
||||
if (!maps->maps) {
|
||||
struct map *tmp_maps = (struct map *)realloc(maps->maps, (i + 1) * sizeof(struct map));
|
||||
if (!tmp_maps) {
|
||||
LOGE("Failed to allocate memory for maps->maps");
|
||||
|
||||
maps->size = i;
|
||||
@@ -130,6 +130,7 @@ struct maps *parse_maps(const char *filename) {
|
||||
|
||||
return NULL;
|
||||
}
|
||||
maps->maps = tmp_maps;
|
||||
|
||||
maps->maps[i].start = addr_start;
|
||||
maps->maps[i].end = addr_end;
|
||||
@@ -558,7 +559,15 @@ void wait_for_trace(int pid, int *status, int flags) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (*status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
|
||||
/* INFO: We'll fork there. This will signal SIGCHLD. We just ignore and continue
|
||||
to avoid blocking/not continuing. */
|
||||
if (WSTOPSIG(*status) == SIGCHLD) {
|
||||
LOGI("process %d stopped by SIGCHLD, continue", pid);
|
||||
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
|
||||
continue;
|
||||
} else if (*status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
|
||||
tracee_skip_syscall(pid);
|
||||
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
|
||||
@@ -112,7 +112,8 @@ mkdir "$MODPATH/webroot"
|
||||
ui_print "- Extracting webroot"
|
||||
unzip -o "$ZIPFILE" "webroot/*" -x "*.sha256" -d "$MODPATH"
|
||||
|
||||
CPU_ABIS=$(getprop ro.product.cpu.abilist)
|
||||
CPU_ABIS=$(getprop ro.system.product.cpu.abilist)
|
||||
CPU_ABIS=${CPU_ABIS:-$(getprop ro.product.cpu.abilist)}
|
||||
|
||||
SUPPORTS_32BIT=false
|
||||
SUPPORTS_64BIT=false
|
||||
|
||||
@@ -46,7 +46,8 @@ if [ -f $MODDIR/lib/libzygisk.so ];then
|
||||
chcon u:object_r:system_file:s0 $TMP_PATH/lib/libzygisk.so
|
||||
fi
|
||||
|
||||
CPU_ABIS=$(getprop ro.product.cpu.abilist)
|
||||
CPU_ABIS=$(getprop ro.system.product.cpu.abilist)
|
||||
CPU_ABIS=${CPU_ABIS:-$(getprop ro.product.cpu.abilist)}
|
||||
|
||||
if [[ "$CPU_ABIS" == *"arm64-v8a"* || "$CPU_ABIS" == *"x86_64"* ]]; then
|
||||
./bin/zygisk-ptrace64 monitor &
|
||||
|
||||
@@ -11,6 +11,7 @@ allow zygote su {lnk_file file} read
|
||||
allow zygote adb_data_file dir search
|
||||
allow zygote adb_data_file file *
|
||||
allow zygote proc file {read open}
|
||||
allow zygote nsfs file {read open}
|
||||
allow zygote zygote process execmem
|
||||
allow system_server system_server process execmem
|
||||
allow zygote tmpfs file *
|
||||
|
||||
@@ -91,101 +91,106 @@ async function getModuleNames(modules) {
|
||||
const unameCmd = await exec('/system/bin/uname -r')
|
||||
if (unameCmd.errno !== 0) return setError('WebUI', unameCmd.stderr)
|
||||
|
||||
document.getElementById('kernel_version_div').innerHTML = unameCmd.stdout
|
||||
console.log('[rezygisk.js] Kernel version: ', unameCmd.stdout)
|
||||
document.getElementById('kernel_version_div').innerHTML = unameCmd.stdout.trim()
|
||||
console.log('[rezygisk.js] Kernel version: ', unameCmd.stdout.trim())
|
||||
|
||||
const catCmd = await exec('/system/bin/cat /data/adb/rezygisk/module.prop')
|
||||
console.log(`[rezygisk.js] ReZygisk module infomation:\n${catCmd.stdout}`)
|
||||
|
||||
let expectedWorking = 0
|
||||
let actuallyWorking = 0
|
||||
if (catCmd.errno !== 0) {
|
||||
console.error('[rezygisk.js] Failed to retrieve ReZygisk module information:', catCmd.stderr)
|
||||
|
||||
const ReZygiskInfo = {
|
||||
rootImpl: null,
|
||||
monitor: null,
|
||||
zygotes: [],
|
||||
daemons: []
|
||||
}
|
||||
rezygisk_state.innerHTML = translations.page.home.status.notWorking
|
||||
rezygisk_icon_state.innerHTML = '<img class="dimc" src="assets/cross.svg">'
|
||||
|
||||
if (catCmd.errno === 0) {
|
||||
/* INFO: Just ensure that they won't appear unless there's info */
|
||||
rootCss.style.setProperty('--bright', '#766000')
|
||||
|
||||
/* INFO: Hide zygote divs */
|
||||
zygote_divs.forEach((zygote_div) => {
|
||||
zygote_div.style.display = 'none'
|
||||
})
|
||||
|
||||
version.innerHTML = catCmd.stdout.split('\n').find((line) => line.startsWith('version=')).substring('version='.length).trim()
|
||||
loading_screen.style.display = 'none'
|
||||
bottom_nav.style.display = 'flex'
|
||||
|
||||
let moduleInfo = catCmd.stdout.split('\n').find((line) => line.startsWith('description=')).substring('description='.length).split('[')[1].split(']')[0]
|
||||
return;
|
||||
}
|
||||
|
||||
const daemonModules = []
|
||||
moduleInfo.match(/\(([^)]+)\)/g).forEach((area) => {
|
||||
moduleInfo = moduleInfo.replace(area, ',')
|
||||
/* INFO: Just ensure that they won't appear unless there's info */
|
||||
zygote_divs.forEach((zygote_div) => {
|
||||
zygote_div.style.display = 'none'
|
||||
})
|
||||
|
||||
const info = area.substring(1, area.length - 1).split(', ')
|
||||
if (info.length === 1) return; /* INFO: undefined as object */
|
||||
version.innerHTML = catCmd.stdout.split('\n').find((line) => line.startsWith('version=')).substring('version='.length).trim()
|
||||
|
||||
const rootImpl = info[0].substring('Root: '.length)
|
||||
const stateCmd = await exec('/system/bin/cat /data/adb/rezygisk/state.json')
|
||||
if (stateCmd.errno !== 0) {
|
||||
console.error('[rezygisk.js] Failed to retrieve ReZygisk state information:', stateCmd.stderr)
|
||||
|
||||
info[1] = info[1].substring('Modules: '.length)
|
||||
const modules = info.slice(1, info.length)
|
||||
rezygisk_state.innerHTML = translations.page.home.status.notWorking
|
||||
rezygisk_icon_state.innerHTML = '<img class="dimc" src="assets/cross.svg">'
|
||||
|
||||
ReZygiskInfo.rootImpl = rootImpl
|
||||
if (modules[0] !== 'None') daemonModules.push(modules)
|
||||
rootCss.style.setProperty('--bright', '#766000')
|
||||
|
||||
/* INFO: Hide zygote divs */
|
||||
zygote_divs.forEach((zygote_div) => {
|
||||
zygote_div.style.display = 'none'
|
||||
})
|
||||
|
||||
const infoArea = moduleInfo.split(', ')
|
||||
infoArea.forEach((info) => {
|
||||
if (info.startsWith('monitor:')) {
|
||||
ReZygiskInfo.monitor = info.substring('monitor: X '.length).trim()
|
||||
}
|
||||
loading_screen.style.display = 'none'
|
||||
bottom_nav.style.display = 'flex'
|
||||
|
||||
if (info.startsWith('zygote')) {
|
||||
ReZygiskInfo.zygotes.push({
|
||||
bits: info.substring('zygote'.length, 'zygote'.length + 'XX'.length),
|
||||
state: info.substring('zygoteXX: X '.length).trim()
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.startsWith('daemon')) {
|
||||
ReZygiskInfo.daemons.push({
|
||||
bits: info.substring('daemon'.length, 'daemon'.length + 'XX'.length),
|
||||
state: info.substring('daemonXX: X '.length).trim(),
|
||||
modules: daemonModules[ReZygiskInfo.daemons.length] || []
|
||||
})
|
||||
}
|
||||
})
|
||||
const ReZygiskState = JSON.parse(stateCmd.stdout)
|
||||
|
||||
switch (ReZygiskInfo.monitor) {
|
||||
case 'tracing': monitor_status.innerHTML = translations.page.actions.status.tracing; break;
|
||||
case 'stopping': monitor_status.innerHTML = translations.page.actions.status.stopping; break;
|
||||
case 'stopped': monitor_status.innerHTML = translations.page.actions.status.stopped; break;
|
||||
case 'exiting': monitor_status.innerHTML = translations.page.actions.status.exiting; break;
|
||||
default: monitor_status.innerHTML = translations.page.actions.status.unknown;
|
||||
root_impl.innerHTML = ReZygiskState.root
|
||||
|
||||
switch (ReZygiskState.monitor.state) {
|
||||
case 0: monitor_status.innerHTML = translations.page.actions.status.tracing; break;
|
||||
case 1: monitor_status.innerHTML = translations.page.actions.status.stopping; break;
|
||||
case 2: monitor_status.innerHTML = translations.page.actions.status.stopped; break;
|
||||
case 3: monitor_status.innerHTML = translations.page.actions.status.exiting; break;
|
||||
default: monitor_status.innerHTML = translations.page.actions.status.unknown;
|
||||
}
|
||||
|
||||
const expectedWorking = (ReZygiskState.zygote['64'] !== undefined ? 1 : 0) + (ReZygiskState.zygote['32'] !== undefined ? 1 : 0)
|
||||
let actuallyWorking = 0
|
||||
|
||||
if (ReZygiskState.zygote['64'] !== undefined) {
|
||||
const zygote64 = ReZygiskState.zygote['64']
|
||||
|
||||
zygote_divs[0].style.display = 'block'
|
||||
|
||||
switch (zygote64) {
|
||||
case 1: {
|
||||
zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.injected
|
||||
|
||||
actuallyWorking++
|
||||
|
||||
break
|
||||
}
|
||||
case 0: zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.notInjected; break
|
||||
default: zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.unknown
|
||||
}
|
||||
}
|
||||
|
||||
expectedWorking = ReZygiskInfo.zygotes.length
|
||||
if (ReZygiskState.zygote['32'] !== undefined) {
|
||||
const zygote32 = ReZygiskState.zygote['32']
|
||||
|
||||
for (let i = 0; i < ReZygiskInfo.zygotes.length; i++) {
|
||||
const zygote = ReZygiskInfo.zygotes[i]
|
||||
/* INFO: Not used ATM */
|
||||
/* const daemon = ReZygiskInfo.daemons[i] */
|
||||
zygote_divs[1].style.display = 'block'
|
||||
|
||||
const zygoteDiv = zygote_divs[zygote.bits === '64' ? 0 : 1]
|
||||
const zygoteStatusDiv = zygote_status_divs[zygote.bits === '64' ? 0 : 1]
|
||||
switch (zygote32) {
|
||||
case 1: {
|
||||
zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.injected
|
||||
|
||||
zygoteDiv.style.display = 'block'
|
||||
actuallyWorking++
|
||||
|
||||
switch (zygote.state) {
|
||||
case 'injected': {
|
||||
zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.injected;
|
||||
|
||||
actuallyWorking++
|
||||
|
||||
break;
|
||||
}
|
||||
case 'not injected': zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.notInjected; break;
|
||||
default: zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.unknown;
|
||||
break
|
||||
}
|
||||
case 0: zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.notInjected; break
|
||||
default: zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,24 +208,24 @@ async function getModuleNames(modules) {
|
||||
rezygisk_icon_state.innerHTML = '<img class="brightc" src="assets/warn.svg">'
|
||||
}
|
||||
|
||||
if (ReZygiskInfo.rootImpl)
|
||||
root_impl.innerHTML = ReZygiskInfo.rootImpl
|
||||
|
||||
const all_modules = []
|
||||
ReZygiskInfo.daemons.forEach((daemon) => {
|
||||
daemon.modules.forEach((module_id) => {
|
||||
const module = all_modules.find((mod) => mod.id === module_id)
|
||||
Object.keys(ReZygiskState.rezygiskd).forEach((daemon_bit) => {
|
||||
const daemon = ReZygiskState.rezygiskd[daemon_bit]
|
||||
|
||||
if (module) {
|
||||
module.bitsUsed.push(daemon.bits)
|
||||
} else {
|
||||
all_modules.push({
|
||||
id: module_id,
|
||||
name: null,
|
||||
bitsUsed: [ daemon.bits ]
|
||||
})
|
||||
}
|
||||
})
|
||||
if (daemon.modules && daemon.modules.length > 0) {
|
||||
daemon.modules.forEach((module_id) => {
|
||||
const module = all_modules.find((mod) => mod.id === module_id)
|
||||
if (module) {
|
||||
module.bitsUsed.push(daemon_bit)
|
||||
} else {
|
||||
all_modules.push({
|
||||
id: module_id,
|
||||
name: null,
|
||||
bitsUsed: [ daemon_bit ]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (all_modules.length !== 0) {
|
||||
@@ -251,7 +256,6 @@ async function getModuleNames(modules) {
|
||||
loading_screen.style.display = 'none'
|
||||
bottom_nav.style.display = 'flex'
|
||||
|
||||
|
||||
const start_time = Number(localStorage.getItem('/system/boot-time'))
|
||||
console.log('[rezygisk.js] boot time: ', Date.now() - start_time, 'ms')
|
||||
localStorage.removeItem('/system/boot_time')
|
||||
|
||||
@@ -153,7 +153,14 @@ void companion_entry(int fd) {
|
||||
LOGI("New companion request.\n - Module name: %s\n - Client fd: %d\n", name, client_fd);
|
||||
|
||||
ret = write_uint8_t(client_fd, 1);
|
||||
ASSURE_SIZE_WRITE("ZygiskdCompanion", "client_fd", ret, sizeof(uint8_t));
|
||||
if (ret != sizeof(uint8_t)) {
|
||||
LOGE("Failed to sent client_fd in ZygiskdCompanion: Expected %zu, got %zd\n", sizeof(uint8_t), ret);
|
||||
|
||||
free(args);
|
||||
close(client_fd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, NULL, entry_thread, (void *)args) == 0)
|
||||
|
||||
@@ -73,6 +73,14 @@ struct packages_config {
|
||||
size_t size;
|
||||
};
|
||||
|
||||
void _apatch_free_package_config(struct packages_config *restrict config) {
|
||||
for (size_t i = 0; i < config->size; i++) {
|
||||
free(config->configs[i].process);
|
||||
}
|
||||
|
||||
free(config->configs);
|
||||
}
|
||||
|
||||
/* WARNING: Dynamic memory based */
|
||||
bool _apatch_get_package_config(struct packages_config *restrict config) {
|
||||
config->configs = NULL;
|
||||
@@ -96,14 +104,16 @@ bool _apatch_get_package_config(struct packages_config *restrict config) {
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
config->configs = realloc(config->configs, (config->size + 1) * sizeof(struct package_config));
|
||||
if (config->configs == NULL) {
|
||||
struct package_config *tmp_configs = realloc(config->configs, (config->size + 1) * sizeof(struct package_config));
|
||||
if (tmp_configs == NULL) {
|
||||
LOGE("Failed to realloc APatch config struct: %s\n", strerror(errno));
|
||||
|
||||
_apatch_free_package_config(config);
|
||||
fclose(fp);
|
||||
|
||||
return false;
|
||||
}
|
||||
config->configs = tmp_configs;
|
||||
|
||||
config->configs[config->size].process = strdup(strtok(line, ","));
|
||||
|
||||
@@ -128,21 +138,9 @@ bool _apatch_get_package_config(struct packages_config *restrict config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void _apatch_free_package_config(struct packages_config *restrict config) {
|
||||
for (size_t i = 0; i < config->size; i++) {
|
||||
free(config->configs[i].process);
|
||||
}
|
||||
|
||||
free(config->configs);
|
||||
}
|
||||
|
||||
bool apatch_uid_granted_root(uid_t uid) {
|
||||
struct packages_config config;
|
||||
if (!_apatch_get_package_config(&config)) {
|
||||
_apatch_free_package_config(&config);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!_apatch_get_package_config(&config)) return false;
|
||||
|
||||
for (size_t i = 0; i < config.size; i++) {
|
||||
if (config.configs[i].uid != uid) continue;
|
||||
@@ -162,11 +160,7 @@ bool apatch_uid_granted_root(uid_t uid) {
|
||||
|
||||
bool apatch_uid_should_umount(uid_t uid, const char *const process) {
|
||||
struct packages_config config;
|
||||
if (!_apatch_get_package_config(&config)) {
|
||||
_apatch_free_package_config(&config);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!_apatch_get_package_config(&config)) return false;
|
||||
|
||||
for (size_t i = 0; i < config.size; i++) {
|
||||
if (config.configs[i].uid != uid) continue;
|
||||
|
||||
@@ -74,8 +74,7 @@ void root_impls_setup(void) {
|
||||
}
|
||||
|
||||
void get_impl(struct root_impl *uimpl) {
|
||||
uimpl->impl = impl.impl;
|
||||
uimpl->variant = impl.variant;
|
||||
*uimpl = impl;
|
||||
}
|
||||
|
||||
bool uid_granted_root(uid_t uid) {
|
||||
|
||||
@@ -27,8 +27,10 @@ static enum kernelsu_variants variant = KOfficial;
|
||||
static bool supports_manager_uid_retrieval = false;
|
||||
|
||||
void ksu_get_existence(struct root_impl_state *state) {
|
||||
int reply_ok = 0;
|
||||
|
||||
int version = 0;
|
||||
prctl((signed int)KERNEL_SU_OPTION, CMD_GET_VERSION, &version, 0, 0);
|
||||
prctl((signed int)KERNEL_SU_OPTION, CMD_GET_VERSION, &version, 0, &reply_ok);
|
||||
|
||||
if (version == 0) state->state = Abnormal;
|
||||
else if (version >= MIN_KSU_VERSION && version <= MAX_KSU_VERSION) {
|
||||
@@ -50,7 +52,7 @@ void ksu_get_existence(struct root_impl_state *state) {
|
||||
state->state = Supported;
|
||||
|
||||
char mode[16] = { 0 };
|
||||
prctl((signed int)KERNEL_SU_OPTION, CMD_HOOK_MODE, mode, 0, 0);
|
||||
prctl((signed int)KERNEL_SU_OPTION, CMD_HOOK_MODE, mode, NULL, &reply_ok);
|
||||
|
||||
if (mode[0] != '\0') state->variant = KNext;
|
||||
else state->variant = KOfficial;
|
||||
@@ -60,8 +62,7 @@ void ksu_get_existence(struct root_impl_state *state) {
|
||||
/* INFO: CMD_GET_MANAGER_UID is a KernelSU Next feature, however we won't
|
||||
limit to KernelSU Next only in case other forks wish to implement
|
||||
it. */
|
||||
int reply_ok = 0;
|
||||
prctl((signed int)KERNEL_SU_OPTION, CMD_GET_MANAGER_UID, 0, 0, &reply_ok);
|
||||
prctl((signed int)KERNEL_SU_OPTION, CMD_GET_MANAGER_UID, NULL, NULL, &reply_ok);
|
||||
|
||||
if (reply_ok == KERNEL_SU_OPTION) {
|
||||
LOGI("KernelSU implementation supports CMD_GET_MANAGER_UID.\n");
|
||||
@@ -100,8 +101,10 @@ bool ksu_uid_is_manager(uid_t uid) {
|
||||
KernelSU Next have different package names.
|
||||
*/
|
||||
if (supports_manager_uid_retrieval) {
|
||||
int reply_ok = 0;
|
||||
|
||||
uid_t manager_uid = 0;
|
||||
prctl(KERNEL_SU_OPTION, CMD_GET_MANAGER_UID, &manager_uid, NULL, NULL);
|
||||
prctl(KERNEL_SU_OPTION, CMD_GET_MANAGER_UID, &manager_uid, NULL, &reply_ok);
|
||||
|
||||
return uid == manager_uid;
|
||||
}
|
||||
|
||||
@@ -109,12 +109,9 @@ static void get_current_attr(char *restrict output, size_t size) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fread(output, 1, size, current) == 0) {
|
||||
if (fread(output, 1, size, current) == 0)
|
||||
LOGE("fread: %s\n", strerror(errno));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(current);
|
||||
}
|
||||
|
||||
@@ -543,15 +540,13 @@ bool parse_mountinfo(const char *restrict pid, struct mountinfos *restrict mount
|
||||
&optional_start, &optional_end, &type_start, &type_end,
|
||||
&source_start, &source_end, &fs_option_start, &fs_option_end);
|
||||
|
||||
mounts->mounts = (struct mountinfo *)realloc(mounts->mounts, (i + 1) * sizeof(struct mountinfo));
|
||||
if (!mounts->mounts) {
|
||||
struct mountinfo *tmp_mounts = (struct mountinfo *)realloc(mounts->mounts, (i + 1) * sizeof(struct mountinfo));
|
||||
if (!tmp_mounts) {
|
||||
LOGE("Failed to allocate memory for mounts->mounts");
|
||||
|
||||
fclose(mountinfo);
|
||||
free_mounts(mounts);
|
||||
|
||||
return false;
|
||||
goto cleanup_mount_allocs;
|
||||
}
|
||||
mounts->mounts = tmp_mounts;
|
||||
|
||||
unsigned int shared = 0;
|
||||
unsigned int master = 0;
|
||||
@@ -572,16 +567,64 @@ bool parse_mountinfo(const char *restrict pid, struct mountinfos *restrict mount
|
||||
mounts->mounts[i].parent = parent;
|
||||
mounts->mounts[i].device = (dev_t)(makedev(maj, min));
|
||||
mounts->mounts[i].root = strndup(line + root_start, (size_t)(root_end - root_start));
|
||||
if (mounts->mounts[i].root == NULL) {
|
||||
LOGE("Failed to allocate memory for root\n");
|
||||
|
||||
goto cleanup_mount_allocs;
|
||||
}
|
||||
mounts->mounts[i].target = strndup(line + target_start, (size_t)(target_end - target_start));
|
||||
if (mounts->mounts[i].target == NULL) {
|
||||
LOGE("Failed to allocate memory for target\n");
|
||||
|
||||
goto cleanup_root;
|
||||
}
|
||||
mounts->mounts[i].vfs_option = strndup(line + vfs_option_start, (size_t)(vfs_option_end - vfs_option_start));
|
||||
if (mounts->mounts[i].vfs_option == NULL) {
|
||||
LOGE("Failed to allocate memory for vfs_option\n");
|
||||
|
||||
goto cleanup_target;
|
||||
}
|
||||
mounts->mounts[i].optional.shared = shared;
|
||||
mounts->mounts[i].optional.master = master;
|
||||
mounts->mounts[i].optional.propagate_from = propagate_from;
|
||||
mounts->mounts[i].type = strndup(line + type_start, (size_t)(type_end - type_start));
|
||||
if (mounts->mounts[i].type == NULL) {
|
||||
LOGE("Failed to allocate memory for type\n");
|
||||
|
||||
goto cleanup_vfs_option;
|
||||
}
|
||||
mounts->mounts[i].source = strndup(line + source_start, (size_t)(source_end - source_start));
|
||||
if (mounts->mounts[i].source == NULL) {
|
||||
LOGE("Failed to allocate memory for source\n");
|
||||
|
||||
goto cleanup_type;
|
||||
}
|
||||
mounts->mounts[i].fs_option = strndup(line + fs_option_start, (size_t)(fs_option_end - fs_option_start));
|
||||
if (mounts->mounts[i].fs_option == NULL) {
|
||||
LOGE("Failed to allocate memory for fs_option\n");
|
||||
|
||||
goto cleanup_source;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
continue;
|
||||
|
||||
cleanup_source:
|
||||
free((void *)mounts->mounts[i].source);
|
||||
cleanup_type:
|
||||
free((void *)mounts->mounts[i].type);
|
||||
cleanup_vfs_option:
|
||||
free((void *)mounts->mounts[i].vfs_option);
|
||||
cleanup_target:
|
||||
free((void *)mounts->mounts[i].target);
|
||||
cleanup_root:
|
||||
free((void *)mounts->mounts[i].root);
|
||||
cleanup_mount_allocs:
|
||||
fclose(mountinfo);
|
||||
free_mounts(mounts);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(mountinfo);
|
||||
|
||||
@@ -48,8 +48,11 @@ enum Architecture {
|
||||
#define ZYGISKD_PATH "/data/adb/modules/rezygisk/bin/zygiskd" lp_select("32", "64")
|
||||
|
||||
static enum Architecture get_arch(void) {
|
||||
char system_arch[64];
|
||||
get_property("ro.product.cpu.abilist", system_arch);
|
||||
char system_arch[64] = { 0 };
|
||||
get_property("ro.system.product.cpu.abilist", system_arch);
|
||||
|
||||
if (system_arch[0] == '\0')
|
||||
get_property("ro.product.cpu.abilist", system_arch);
|
||||
|
||||
/* INFO: "PC" architectures should have priority because in an emulator
|
||||
the native architecture should have priority over the emulated
|
||||
@@ -120,7 +123,6 @@ static void load_modules(enum Architecture arch, struct Context *restrict contex
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
context->modules = realloc(context->modules, (size_t)((context->len + 1) * sizeof(struct Module)));
|
||||
if (context->modules == NULL) {
|
||||
LOGE("Failed reallocating memory for modules.\n");
|
||||
@@ -140,8 +142,10 @@ static void load_modules(enum Architecture arch, struct Context *restrict contex
|
||||
static void free_modules(struct Context *restrict context) {
|
||||
for (size_t i = 0; i < context->len; i++) {
|
||||
free(context->modules[i].name);
|
||||
if (context->modules[i].companion != -1) close(context->modules[i].companion);
|
||||
if (context->modules[i].companion >= 0) close(context->modules[i].companion);
|
||||
}
|
||||
|
||||
free(context->modules);
|
||||
}
|
||||
|
||||
static int create_daemon_socket(void) {
|
||||
@@ -257,11 +261,6 @@ static int spawn_companion(char *restrict argv[], char *restrict name, int lib_f
|
||||
exit(0);
|
||||
}
|
||||
|
||||
struct __attribute__((__packed__)) MsgHead {
|
||||
unsigned int cmd;
|
||||
int length;
|
||||
};
|
||||
|
||||
/* WARNING: Dynamic memory based */
|
||||
void zygiskd_start(char *restrict argv[]) {
|
||||
/* INFO: When implementation is None or Multiple, it won't set the values
|
||||
@@ -272,91 +271,50 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
struct root_impl impl;
|
||||
get_impl(&impl);
|
||||
if (impl.impl == None || impl.impl == Multiple) {
|
||||
char *msg_data = NULL;
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ DAEMON_SET_ERROR_INFO }, sizeof(uint8_t));
|
||||
|
||||
if (impl.impl == None) msg_data = "Unsupported environment: Unknown root implementation";
|
||||
else msg_data = "Unsupported environment: Multiple root implementations found";
|
||||
const char *msg = NULL;
|
||||
if (impl.impl == None) msg = "Unsupported environment: Unknown root implementation";
|
||||
else msg = "Unsupported environment: Multiple root implementations found";
|
||||
|
||||
struct MsgHead msg = {
|
||||
.cmd = DAEMON_SET_ERROR_INFO,
|
||||
.length = (int)strlen(msg_data) + 1
|
||||
};
|
||||
LOGE("%s", msg);
|
||||
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, msg_data, (size_t)msg.length);
|
||||
uint32_t msg_len = (uint32_t)strlen(msg);
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &msg_len, sizeof(msg_len));
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, (void *)msg, msg_len);
|
||||
|
||||
free(msg_data);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
enum Architecture arch = get_arch();
|
||||
load_modules(arch, &context);
|
||||
|
||||
char *module_list = NULL;
|
||||
size_t module_list_len = 0;
|
||||
if (context.len == 0) {
|
||||
module_list = strdup("None");
|
||||
module_list_len = strlen("None");
|
||||
} else {
|
||||
for (size_t i = 0; i < context.len; i++) {
|
||||
if (i != context.len - 1) {
|
||||
module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + strlen(", ") + 1);
|
||||
if (module_list == NULL) {
|
||||
LOGE("Failed reallocating memory for module list.\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy(module_list + module_list_len, context.modules[i].name);
|
||||
|
||||
module_list_len += strlen(context.modules[i].name);
|
||||
|
||||
strcpy(module_list + module_list_len, ", ");
|
||||
|
||||
module_list_len += strlen(", ");
|
||||
} else {
|
||||
module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + 1);
|
||||
if (module_list == NULL) {
|
||||
LOGE("Failed reallocating memory for module list.\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy(module_list + module_list_len, context.modules[i].name);
|
||||
|
||||
module_list_len += strlen(context.modules[i].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ DAEMON_SET_INFO }, sizeof(uint8_t));
|
||||
|
||||
char impl_name[LONGEST_ROOT_IMPL_NAME];
|
||||
stringify_root_impl_name(impl, impl_name);
|
||||
|
||||
size_t msg_length = strlen("Root: , Modules: ") + strlen(impl_name) + module_list_len + 1;
|
||||
uint32_t root_impl_len = (uint32_t)strlen(impl_name);
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &root_impl_len, sizeof(root_impl_len));
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, impl_name, root_impl_len);
|
||||
|
||||
struct MsgHead msg = {
|
||||
.cmd = DAEMON_SET_INFO,
|
||||
.length = (int)msg_length
|
||||
};
|
||||
uint32_t modules_len = (uint32_t)context.len;
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &modules_len, sizeof(modules_len));
|
||||
|
||||
char *msg_data = malloc(msg_length);
|
||||
if (msg_data == NULL) {
|
||||
LOGE("Failed allocating memory for message data.\n");
|
||||
|
||||
return;
|
||||
for (size_t i = 0; i < context.len; i++) {
|
||||
uint32_t module_name_len = (uint32_t)strlen(context.modules[i].name);
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &module_name_len, sizeof(module_name_len));
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, context.modules[i].name, module_name_len);
|
||||
}
|
||||
|
||||
snprintf(msg_data, msg_length, "Root: %s, Modules: %s", impl_name, module_list);
|
||||
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, msg_data, msg_length);
|
||||
|
||||
free(msg_data);
|
||||
free(module_list);
|
||||
LOGI("Sent root implementation and modules information to controller socket");
|
||||
}
|
||||
|
||||
int socket_fd = create_daemon_socket();
|
||||
if (socket_fd == -1) {
|
||||
LOGE("Failed creating daemon socket\n");
|
||||
|
||||
free_modules(&context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -369,7 +327,7 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
if (client_fd == -1) {
|
||||
LOGE("accept: %s\n", strerror(errno));
|
||||
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t action8 = 0;
|
||||
@@ -377,53 +335,33 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
if (len == -1) {
|
||||
LOGE("read: %s\n", strerror(errno));
|
||||
|
||||
return;
|
||||
break;
|
||||
} else if (len == 0) {
|
||||
LOGI("Client disconnected\n");
|
||||
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
enum DaemonSocketAction action = (enum DaemonSocketAction)action8;
|
||||
|
||||
switch (action) {
|
||||
case PingHeartbeat: {
|
||||
struct MsgHead msg = {
|
||||
.cmd = ZYGOTE_INJECTED,
|
||||
.length = 0
|
||||
};
|
||||
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ ZYGOTE_INJECTED }, sizeof(uint8_t));
|
||||
|
||||
break;
|
||||
}
|
||||
case ZygoteRestart: {
|
||||
for (size_t i = 0; i < context.len; i++) {
|
||||
if (context.modules[i].companion != -1) {
|
||||
close(context.modules[i].companion);
|
||||
context.modules[i].companion = -1;
|
||||
}
|
||||
if (context.modules[i].companion <= -1) continue;
|
||||
|
||||
close(context.modules[i].companion);
|
||||
context.modules[i].companion = -1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SystemServerStarted: {
|
||||
struct MsgHead msg = {
|
||||
.cmd = SYSTEM_SERVER_STARTED,
|
||||
.length = 0
|
||||
};
|
||||
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
|
||||
|
||||
if (impl.impl == None || impl.impl == Multiple) {
|
||||
LOGI("Unsupported environment detected. Exiting.\n");
|
||||
|
||||
close(client_fd);
|
||||
close(socket_fd);
|
||||
free_modules(&context);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ SYSTEM_SERVER_STARTED }, sizeof(uint8_t));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -563,9 +501,19 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
ssize_t ret = read_size_t(client_fd, &index);
|
||||
ASSURE_SIZE_READ_BREAK("RequestCompanionSocket", "index", ret, sizeof(index));
|
||||
|
||||
struct Module *module = &context.modules[index];
|
||||
if (index >= context.len) {
|
||||
LOGE("Invalid module index: %zu\n", index);
|
||||
|
||||
if (module->companion != -1) {
|
||||
ret = write_uint8_t(client_fd, 0);
|
||||
ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(int));
|
||||
|
||||
close(client_fd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
struct Module *module = &context.modules[index];
|
||||
if (module->companion >= 0) {
|
||||
if (!check_unix_socket(module->companion, false)) {
|
||||
LOGE(" - Companion for module \"%s\" crashed\n", module->name);
|
||||
|
||||
@@ -574,10 +522,10 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (module->companion == -1) {
|
||||
if (module->companion <= -1) {
|
||||
module->companion = spawn_companion(argv, module->name, module->lib_fd);
|
||||
|
||||
if (module->companion > 0) {
|
||||
if (module->companion >= 0) {
|
||||
LOGI(" - Spawned companion for \"%s\": %d\n", module->name, module->companion);
|
||||
} else {
|
||||
if (module->companion == -2) {
|
||||
@@ -594,7 +542,7 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
so just sending the file descriptor of the client is
|
||||
safe.
|
||||
*/
|
||||
if (module->companion != -1) {
|
||||
if (module->companion >= 0) {
|
||||
LOGI(" - Sending companion fd socket of module \"%s\"\n", module->name);
|
||||
|
||||
if (write_fd(module->companion, client_fd) == -1) {
|
||||
@@ -626,6 +574,17 @@ void zygiskd_start(char *restrict argv[]) {
|
||||
ssize_t ret = read_size_t(client_fd, &index);
|
||||
ASSURE_SIZE_READ_BREAK("GetModuleDir", "index", ret, sizeof(index));
|
||||
|
||||
if (index >= context.len) {
|
||||
LOGE("Invalid module index: %zu\n", index);
|
||||
|
||||
ret = write_uint8_t(client_fd, 0);
|
||||
ASSURE_SIZE_WRITE_BREAK("GetModuleDir", "response", ret, sizeof(int));
|
||||
|
||||
close(client_fd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
char module_dir[PATH_MAX];
|
||||
snprintf(module_dir, PATH_MAX, "%s/%s", PATH_MODULES_DIR, context.modules[index].name);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user