19 Commits

Author SHA1 Message Date
ThePedroo
153097f9d8 update: rewrite ART method and module related code to C
This commit rewrites both ART method and module related code to C, following the same behavior, aside from the module's `on load` now be called globally, allowing a better flexibility. This will not impact any module.
2025-09-01 22:38:55 -03:00
ThePedroo
0f27e455e7 improve: preload modules globally
This commit makes Zygisk modules be preloaded globally in the main Zygote process, so that they don't need to reload all the time. This commit is authored by @nampud, and merged into mainline ReZygisk.

Co-authored-by: nampud
2025-08-24 14:19:08 -03:00
ThePedroo
3688df6450 fix: *AppSpecialize inconsistency for root manager process; improve: allow to inject into root manager
This commit fixes an issue where modules would have their "postAppSpecialize" called for root manager processes, but not "preAppSpecialize". The approach taken to fix that was to completely allow to inject into root manager, which instead of making it not call "postAppSpecialize" either, now it allows both.
2025-08-15 21:30:18 -03:00
ThePedroo
0c7a756030 update: LSPlt submodule
This commit updates the LSPlt submodule used in ReZygisk to the latest commit.
2025-08-15 06:30:48 -03:00
ThePedroo
70805bb390 fix: misinterpreted state.json Zygote injection information
This commit fixes the incorrect interpretation of the "state.json"'s Zygote injection information.
2025-08-13 22:06:04 -03:00
ThePedroo
e6344d2e12 add: r_debug_tail trace hiding for undlclosed modules
This commit adds support for hiding "r_debug_tail" trace when a module is not "dlclose"d in the app's process.
2025-08-13 21:43:46 -03:00
ThePedroo
d2ebb2bfed improve: ReZygisk state in module description
This commit improves how ReZygisk state is shown in the module description, reducing its size in favor of the same amount of information but in the WebUI, as all root implementations have WebUI available, even if it's through external applications due to incompetency, such as Magisk.

closes #28
2025-08-13 21:34:13 -03:00
ThePedroo
7e823319b7 improve: SoInfo hiding code complexity
This commit improves the SoInfo hiding code by reducing the complexity of it, using dlclose directly when possible to make it more future proof and simple.

Co-Authored-By: 4h9fbZ <176179231+4h9fbZ@users.noreply.github.com>
2025-08-10 18:18:26 -03:00
ThePedroo
f9fcf1c2e7 fix: remote strlen fail in dlopen path; add: GNU ifunc handling to elf_util.c
This commit fixes the remote call to "strlen" fail because ReZygisk ELF utils would not handle GNU indirect functions, making it call the in-between function instead of the actual function.
2025-08-10 17:21:34 -03:00
ThePedroo
08513b17e8 fix: memory leak, unitialized memory access, FILE pointer leak bugs
This commit fixes numerous general code bugs, improving reliability and consistency of ReZygisk.
2025-08-10 17:06:48 -03:00
ThePedroo
a7917e20fe update: LSPlt
This commit updates LSPlt and adapt to its changes so that it works properly.
2025-07-30 23:09:55 -03:00
ThePedroo
e0ce1473dd fix: crash when umounting preloaded file
This commit fixes an issue where Zygote will Abort when a preloaded file cannot be opened anymore, which happens when ReZygisk umounts a file from a module (such as Quick Switch Overlay or Pixelify) that is preloaded. To fix that, we hook on the function that causes the crash and bypass its execution if it cannot be opened.
2025-07-20 19:27:36 -03:00
ThePedroo
bf3c73d72b fix: not extracting any binaries in some devices
The commit fixes the issue that in devices that "ro.system.product.cpu.abilist" doesn't exist, it will cause ReZygisk "customize.sh" to not extract any binaries. Because of that, we'll fallback to the old "ro.product.cpu.abilist" when "ro.system.product.cpu.abilist" doesn't exist.
2025-07-20 14:10:56 -03:00
ThePedroo
510e8a2de4 fix: not extracting 32-bit binaries in systems using Tango
This commit fixes an issue where ReZygisk would not extract the 32-bit libraries in systems that utilize Tango (binary translation) to run 32-bit applications, as "ro.product.cpu.abilist" will only show the CPU supported architectures, not the system ones, resulting in only extracting 64-bit libzygisk.so and ReZygiskd despite having 32-bit app_process (Zygote).
2025-07-20 02:43:51 -03:00
ThePedroo
38cfbb25ef fix: Permission Denied on setns in 3.19- kernels
This commit fixes the issue where "setns" in older kernels will fail, as until 3.19 Linux used NSFS (Name Space File System) for namespaces (/proc/self/mnt/ns), which was not taken into consideration in ReZygisk SELinux rules.
2025-07-15 05:36:11 -03:00
ThePedroo
d54cac89a7 fix: missing #define for misc.h
This commit fixes the missing "#define", used to ensure a header symbols are not re-defined.
2025-07-15 05:20:59 -03:00
ThePedroo
90da42a10b fix: passing invalid address to reply_ok in KernelSU prctl
This commit fixes an issue that was caused by ReZygiskd passing NULL (an invalid address) in "reply_ok" to KernelSU driver, which expects it to be a valid address to pass if the request was successful, causing it to spam the dmesg with error logs.
2025-07-15 05:12:33 -03:00
ThePedroo
872ba693a1 remove: futile maps hiding
This commit removes the maps hiding, as it not only breaks behavior compatibility with Magisk Zygisk (original), but also can break some modules because of that. It doesn't provide any improved hiding in slightly better detections and makes the codebase more complex.
2025-07-15 03:41:48 -03:00
ThePedroo
2dfa221287 remove: unnecessary unshare hook
This commit removes the "unshare" hook. It used to provide the right timing for manual umount in the past, however with recent umount system changes, basing now on mount namespaces, those are handled way earlier, and this "unshare" hook makes no difference, being considered useless.
2025-07-15 03:31:01 -03:00
38 changed files with 1757 additions and 2772 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
#ifndef MISC_H
#define MISC_H
#ifdef __cplusplus
extern "C" {

View File

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

View 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 */

View File

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

View File

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

View File

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

View File

@@ -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 &reg: register_info) {
if (regexec(&reg.regex, map.path.data(), 0, nullptr, 0) != 0)
if (regexec(&reg.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

View File

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

View 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 */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, &regs, rezygiskd_get_path());
args[2] = (uintptr_t)str;
remote_call(pid, &regs, injector_entry, (uintptr_t)libc_return_addr, args, 3);
remote_call(pid, &regs, injector_entry, (uintptr_t)libc_return_addr, args, 2);
free(args);

View File

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

View File

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

View File

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

View File

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

View 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')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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