52 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
Rifat Azad
bc1b757bb8 update: CMD_HOOK_MODE and CMD_GET_MANAGER_UID values (#200)
This commit updates the values for "CMD_HOOK_MODE" to match newest KernelSU Next value and "CMD_GET_MANAGER_UID", now unified in both KernelSU and KernelSU Next.

Signed-off-by: Rifat Azad <33044977+rifsxd@users.noreply.github.com>
2025-07-11 09:32:17 -03:00
Re*Index. (ot_inc)
a0a54f2153 add: ja_JP translation to WebUI (#195)
This commit adds Japanese translation to ReZygisk WebUI.

Signed-off-by: Re*Index. (ot_inc) <32851879+reindex-ot@users.noreply.github.com>
2025-07-05 00:30:22 -03:00
Ada
295a62b649 update: tr_TR credits in TRANSLATOR.md (#192)
This commit updates the credits for "tr_TR" translator, updating witchfuneral's GitHub account name.

Signed-off-by: Ada <65596971+witchfuneral@users.noreply.github.com>
2025-07-05 00:25:45 -03:00
ThePedroo
b6f02b39b3 fix: runtime KSUN manager switch detection
This commit fixes the issue where the new KernelSU Next manager wouldn't be recognized if it was changed without a reboot.
2025-07-01 14:31:06 -03:00
ThePedroo
e036b1f40a add: KernelSU Next spoofed manager recognition support
This commit adds support for ReZygiskd to identify which app is the manager in KSUN (KernelSU Next) builds with spoofed manager package name.
2025-07-01 14:11:39 -03:00
ThePedroo
9a3b2f4a79 fix: KernelSU variant detection
This commit fixes the KSU variant detection, which previously was hardcoded on accident to always set it to KernelSU Next.
2025-06-29 14:23:04 -03:00
nampud
9810eb3974 improve: ptrace_message reset with seccomp (#191)
This commit improves how "ptrace_message" is cleaned by utilizing seccomp (Secure Computation Mode) to clear its value.

"ptrace_message" not being cleared is a Linux kernel vulnerability/bug that impacts all versions below 6.1, as the fix only came in 6.1, and because of that, we need to find ways to "0" it. This is the second fix for that, being the GKI2 fix the first: 70697be9a5
2025-06-29 03:41:42 -03:00
ThePedroo
823623a96f improve: set ZYGISK_ENABLED in all supported managers' process
This commit sets the environment variable "ZYGISK_ENABLED" for all managers' process, not limiting to Magisk anymore, so that managers can easily detect (Re)Zygisk.
2025-06-27 14:39:36 -03:00
ThePedroo
a75b2fe2b8 add: KernelSU Next recognition support
This commit adds support for recognizing KernelSU (KSUN) manager to ReZygiskd.
2025-06-27 14:39:21 -03:00
ThePedroo
48238521df fix: root related mounts leak in KernelSU in isolated services
This commit fixes the leak of mounts in KernelSU with global umount disabled in isolated services. This happens because KernelSU doesn't handle isolated services in kernel side, so we must find the main app UID and see if that UID is in denylist instead. With that, also improve APatch detection to take advantage of faster integer/UID comparison rather than always check process/string comparison, and only fallback to process name based if UID is not found.

Co-Authored-By: nampud <nampud@users.noreply.github.com>
2025-06-24 19:21:26 -03:00
ThePedroo
fa9adcf3b5 fix: FORCE_DENYLIST_UNMOUNT not forcing umount
This commit fixes the issue where because the mount namespace switch happened only before the Zygisk modules execution, they wouldn't have the opportunity to set "FORCE_DENYLIST_UNMOUNT" flag. Now, with this commit, which added another check to know if that flag was set by a Zygisk module, and if so, switched to mount namespace, adjusts the behavior to the expected one.
2025-06-22 18:33:10 -03:00
ThePedroo
6c05527ffa fix: Zygisk modules not being recognized in WSA
This commit fixes the issue where due to ARM architectures having priority in ReZygiskd code, ReZygisk, running on emulators, wouldn't be capable of recognizing Zygisk modules. This order is important, Zygisk modules should always give priority to the native architecture over the emulated one, since WSA runs Zygote in x64/x86.
2025-06-22 17:09:37 -03:00
ThePedroo
aff2ad8d3c add: SIGPIPE handling in ReZygiskd
This commit adds "SIGPIPE" signal handling in ReZygiskd. Some processes might die while ReZygiskd is still processing the response, and it will fail to write to the reader, now dead, resulting in a "SIGPIPE". Without proper handling, the process (ReZygiskd) would die, and this commit properly handled it to gracefully log it instead of dying.
2025-06-22 02:10:43 -03:00
ThePedroo
b7fe7b3dbe fix: root related mounts leak in APatch in isolated services
This commit fixes the issue where mounts related to APatch and ReZygisk would be leaked in isolated services for APatch environments as the UID between the main process and isolated service is different, resulting it to not be found in "package_config" and default to not switch to clean mount namespace.
2025-06-21 14:29:03 -03:00
ThePedroo
f432550f07 improve: dynamically retrieve libc.so path
This commit makes ptracer dynamically get "libc.so" library path, avoiding errors when errors happen in environments where libc.so from zygote doesn't come from "/system/lib" or "/system/lib64".
2025-06-15 15:22:44 -03:00
nampud
a0ab02cedc fix: not dropping SoInfo of unclosed modules; fix: not performing maps spoofing (#187)
This commit fixes the issue where unclosed modules, as in not being requested to be "dlclose"d, wouldn't have their SoInfo structures freed. It also fixes the issue of maps spoofing not being performed due to the "spoof_maps" parameter being erroneously set to false in the second "clean_trace" call.
2025-06-15 09:47:54 -03:00
ThePedroo
f9a23a2882 fix: checking if find_containing_library exists and error'ing
This commit fixes the typo where ReZygisk would check if a function exists in linker, and if it did, not proceed instead of proceeding.

closes #184
2025-06-13 17:21:55 -03:00
nampud
d111a2dfc5 fix: zygote64 crashes due to perfetto by unloading earlier (#177)
This commit fixes the crashes in "zygote64" caused by libperfetto hooks (more information in #177) by unloading earlier.
2025-06-10 23:00:59 -03:00
ThePedroo
cd4784376e fix: ReZygiskd out-of-bounds write due to too small system_arch buffer
This commit fixes the issue where when the "ro.product.cpu.abilist" prop has a value bigger than 31 characters, which mostly happens in WSA (Windows Subsystem For Android) as it can support 5+ architectures, it will write outside the buffer, leading to undefined behavior, but most of the time crashes.
2025-06-10 19:32:35 -03:00
ThePedroo
c786790b0f fix: Trusted CI failing in forks
This commit fixes issue that due to the lack of private and private key in forks, the Trusted CI ends up failing.
2025-06-09 22:59:44 -03:00
ThePedroo
4f35e06ac4 fix: gradle related warnings
This commit fixes the warnings caused by using deprecated functions in ReZygisk building system.
2025-06-09 14:36:07 -03:00
ThePedroo
57f985292e fix: leak of dir fd in ReZygiskd
This commit fixes the leak of dir fd caused due to not calling "closedir" after dir being used.
2025-06-09 14:35:04 -03:00
ThePedroo
34643c794f improve: SoInfo hiding code compatibility
This commit improves the compatibility of SoInfo hiding code with more Android versions, like Android 16 QPR1 Beta 1 and newer versions, and also reduces complexity of the code.
2025-06-09 14:34:00 -03:00
ThePedroo
ec705fb260 fix: removal of all PLT hooks unconditionally
This commit fixes the issue where due to a confusion, ReZygisk was coded so that it would remove all PLT hooks, even if they were meant to be kept. This has been fixed appropriately in LSPlt side, allowing to revert back to how it was before.
2025-06-06 03:09:55 -03:00
ThePedroo
c023da0fd6 fix: Code of Conduct URL in templates
This commit fixes the URL of CoC which would previously point to the wrong place.
2025-06-02 17:08:50 -03:00
ThePedroo
63f29f0771 update: setup-gradle action
This commit updates the "setup-gradle" action to major 4 so that it isn't in a specific version.
2025-06-01 23:27:21 -03:00
ThePedroo
c975722795 update: PLT hooks unload
This commit changes how PLT hooks are unloaded, so that we're able to bypass detections caused by page faulting libandroid_runtime.so.
2025-06-01 23:21:43 -03:00
ThePedroo
2f589d0eda update: LSPlt source
This commit fixes the fork of LSPlt used in ReZygisk, from JingMatrix to PerformanC, for more control.
2025-06-01 23:21:24 -03:00
ThePedroo
70697be9a5 fix: ptrace_message leaking ReZygisk existence (zygote pid)
This commit fixes a trace left due to a kernel bug. In some cases (and all of them here), the sequence of events may lead to "ptrace_message" not be properly reset/not represent the actual state. This happens here, as when TRACEFORK is set in "monitor.c", setting "ptrace_message" as the PID of the new process, persists even when not tracing anymore, causing leaks.

This fix has been given by @nampud, in #171.
2025-05-27 19:32:20 -03:00
ThePedroo
6261466e44 fix: Zygisk module loading
This commit fixes the issue where ReZygisk would try to load Zygisk modules after umounting mounts, leading to them not being found anymore.
2025-05-25 01:10:25 -03:00
ThePedroo
d455117c49 fix: injection when libdl.so is not in apex
This commit fixes the issue where "libdl.so" wouldn't be found, when trying to inject "lizygisk.so", in systems where apex is in "/system/lib/libdl.so".
2025-05-23 12:27:15 -03:00
ThePedroo
6272e0a2ac improve: ReZygiskd umount code by unifying; improve: umount system by making umount if less strict
This commit both improves ReZygiskd umount system by unifying all root implementations code into a single one, and also by making it less strict to umount.
2025-05-20 14:25:39 -03:00
ThePedroo
62481ca2b6 fix: wrong logic for detecting leaked fds; add: leaked fd warning
This commit fixes the logic issue which made leaked fds not be closed by "libzygisk.so", causing crashes for some. It also adds a warning for when it finds a leaked fd, so that module developers can be notified.

fixes #163
2025-05-20 14:20:37 -03:00
53 changed files with 2362 additions and 2946 deletions

View File

@@ -59,7 +59,7 @@ body:
id: code_of_conduct
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PerformanC/voice/blob/main/CODE_OF_CONDUCT.md)
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PerformanC/contributing/blob/main/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -35,7 +35,7 @@ body:
id: code_of_conduct
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PerformanC/voice/blob/main/CODE_OF_CONDUCT.md)
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PerformanC/contributing/blob/main/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -22,7 +22,7 @@ jobs:
java-version: "17"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4.2.1
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
run: |

View File

@@ -21,15 +21,19 @@ jobs:
java-version: "17"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4.2.1
uses: gradle/actions/setup-gradle@v4
- name: Setup keys
env:
private_key: ${{ secrets.ORG_PRIVATE_KEY }}
public_key: ${{ secrets.ORG_PUBLIC_KEY }}
run: |
if [ -z "$private_key" ] || [ -z "$public_key" ]; then
echo "Private or public key is not set."
else
echo "$private_key" | base64 -d > module/private_key
echo "$public_key" | base64 -d > module/public_key
fi
- name: Build with Gradle
run: |

4
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "loader/src/external/lsplt"]
[submodule "LSPlt"]
path = loader/src/external/lsplt
url = https://github.com/JingMatrix/LSPlt
url = https://github.com/PerformanC/LSPlt

View File

@@ -6,13 +6,13 @@
- **es_ES** by [@LuchoModzzz](https://github.com/Lxchoooo)
- **es_MX** by [@LuchoModzzz](https://github.com/Lxchoooo)
- **fr_FR** by [@GhostFRR](https://github.com/GhostFRR)
- **ja_JP** by [@Fyphen1223](https://github.com/Fyphen1223)
- **ja_JP** by [@Fyphen1223](https://github.com/Fyphen1223) & [@reindex-ot](https://github.com/reindex-ot)
- **id_ID** by [@bpanca05](https://github.com/bpanca05) & [@LuckyKiddos](https://github.com/GuitarHeroStyles)
- **it_IT** by [@thasave14](https://github.com/thasave14)
- **pt_BR** by [@ThePedroo](https://github.com/ThePedroo)
- **ro_RO** by [@ExtremeXT](https://github.com/ExtremeXT)
- **ru_RU** by [@Emulond](https://github.com/Emulond) & [@AJleKcAHgP68](https://github.com/AJleKcAHgP68)
- **tr_TR** by [@dyingwillow](https://github.com/dyingwillow)
- **tr_TR** by [@witchfuneral](https://github.com/witchfuneral)
- **uk_UA** by [@Kittyskj](https://github.com/Kittyskj)
- **vi_VN** by [@RainyXeon](https://github.com/RainyXeon)
- **zh_CN** by [@Meltartica](https://github.com/Meltartica) & [@SheepChef](https://github.com/SheepChef)

View File

@@ -38,19 +38,18 @@ val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)
tasks.register("Delete", Delete::class) {
delete(rootProject.buildDir)
delete(layout.buildDirectory.get())
}
fun Project.configureBaseExtension() {
extensions.findByType(LibraryExtension::class)?.run {
namespace = "icu.nullptr.zygisk.next"
namespace = "com.performanc.org.rezygisk"
compileSdk = androidCompileSdkVersion
ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion
defaultConfig {
minSdk = androidMinSdkVersion
targetSdk = androidTargetSdkVersion
}
lint {

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]);
for (size_t i = 0; i < info->modules.modules_count; i++) {
free(info->modules.modules[i]);
}
free(info->modules->modules);
}
free(info->modules);
free(info->modules.modules);
info->modules.modules = NULL;
}
bool rezygiskd_read_modules(struct zygisk_modules *modules) {
@@ -237,14 +232,12 @@ 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);
}
}
int rezygiskd_connect_companion(size_t index) {
int fd = rezygiskd_connect(1);

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,45 +404,24 @@ 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) {
if (!bias_calculated) for (int i = 0; i < img->header->e_phnum; ++i) {
if (phdr[i].p_type != PT_LOAD) continue;
img->bias = phdr[i].p_vaddr - phdr[i].p_offset;
bias_calculated = true;
LOGI("Calculated bias %ld from first PT_LOAD segment %d (vaddr %lx, offset %lx)",
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);
break;
}
}
}
}
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;
}
}
}
if (!bias_calculated)
LOGE("Failed to calculate bias for %s. Assuming bias is 0.", elf);
@@ -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;
@@ -535,15 +515,17 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
((uintptr_t)1 << ((hash >> img->gnu_shift2_) % bloom_mask_bits));
if ((mask & bloom_word) != mask) {
LOGE("Symbol '%s' (hash %u) filtered out by GNU Bloom Filter (idx %zu, mask 0x%lx, word 0x%lx)",
/* INFO: Very loggy -- generates too much noise. GNU is rarely used for Zygisk context. */
/* LOGW("Symbol '%s' (hash %u) filtered out by GNU Bloom Filter (idx %zu, mask 0x%lx, word 0x%lx)",
name, hash, bloom_idx, (unsigned long)mask, (unsigned long)bloom_word);
*/
return 0;
}
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;
}
@@ -566,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++;
@@ -587,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;
@@ -603,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);
@@ -621,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++) {
@@ -631,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);
@@ -648,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);
@@ -664,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);
@@ -96,12 +140,12 @@ char *read_string(int fd) {
#define write_func(type) \
ssize_t write_## type(int fd, type val) { \
return write(fd, &val, sizeof(type)); \
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)); \
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,9 +1,13 @@
#ifndef MISC_H
#define MISC_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define IS_ISOLATED_SERVICE(uid) \
((uid) >= 90000 && (uid) < 1000000)
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.

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

@@ -0,0 +1,97 @@
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "logging.h"
static bool seccomp_filters_visible() {
FILE *status_file = fopen("/proc/self/status", "r");
if (!status_file) {
return true;
}
const char *needle = "Seccomp_filters:";
char line[256];
while (fgets(line, sizeof(line), status_file)) {
if (strncmp(line, needle, strlen(needle)) == 0) {
fclose(status_file);
return true;
}
}
fclose(status_file);
return false;
}
void send_seccomp_event() {
if (seccomp_filters_visible()) {
return;
}
__u32 args[4] = {0};
int rnd_fd = open("/dev/urandom", O_RDONLY);
if (rnd_fd == -1) {
PLOGE("send_seccomp_event: open(/dev/urandom)");
return;
}
if (read(rnd_fd, &args, sizeof(args)) != sizeof(args)) {
PLOGE("send_seccomp_event: read(rnd_fd)");
close(rnd_fd);
return;
}
close(rnd_fd);
args[0] |= 0x10000;
struct sock_filter filter[] = {
/* INFO: Check syscall number */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit_group, 0, 9),
/* INFO: Load and check arg0 (lower 32 bits) */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, args[0], 0, 7),
/* INFO: Load and check arg1 (lower 32 bits) */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, args[1], 0, 5),
/* INFO: Load and check arg2 (lower 32 bits) */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[2])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, args[2], 0, 3),
/* INFO: Load and check arg3 (lower 32 bits) */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[3])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, args[3], 0, 1),
/* INFO: All match: return TRACE => will trigger PTRACE_EVENT_SECCOMP */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
/* INFO: Default: allow */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
PLOGE("send_seccomp_event: prctl(SECCOMP)");
return;
}
/* INFO: This will trigger a ptrace event, syscall will not execute due to tracee_skip_syscall */
syscall(__NR_exit_group, args[0], args[1], args[2], args[3]);
}

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;
@@ -22,5 +23,11 @@ void entry(void* addr, size_t size, const char* path) {
LOGD("start plt hooking");
hook_functions();
clean_trace(path, 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;
@@ -98,6 +98,7 @@ struct ZygiskContext {
~ZygiskContext();
/* Zygisksu changed: Load module fds */
bool load_modules_only();
void run_modules_pre();
void run_modules_post();
DCL_PRE_POST(fork)
@@ -123,8 +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;
std::vector<lsplt::MapInfo> cached_map_infos = {};
bool enable_unloader = false;
bool hooked_unloader = false;
} // namespace
@@ -134,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();
}
@@ -173,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;
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;
}
old_unshare(CLONE_NEWNS);
}
/* 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,
@@ -215,13 +237,21 @@ DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) {
int res = old_pthread_attr_setstacksize((pthread_attr_t *)target, size);
LOGV("Call pthread_attr_setstacksize in [tid, pid]: %d, %d", gettid(), getpid());
if (!enable_unloader)
return res;
// Only perform unloading on the main thread
if (gettid() != getpid())
return res;
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,
@@ -242,13 +272,32 @@ 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);
}
/*
* INFO: Our goal is to get called after libart.so is loaded, but before ART actually starts running.
* If we are too early, we won't find libart.so in maps, and if we are too late, we could make other
* threads crash if they try to use the PLT while we are in the process of hooking it.
* For this task, hooking property_get was chosen as there are lots of calls to this, so it's
* relatively unlikely to break.
*
* The line where libart.so is loaded is:
* https://github.com/aosp-mirror/platform_frameworks_base/blob/1cdfff555f4a21f71ccc978290e2e212e2f8b168/core/jni/AndroidRuntime.cpp#L1266
*
* And shortly after that, in the startVm method that is called right after, there are many calls to property_get:
* https://github.com/aosp-mirror/platform_frameworks_base/blob/1cdfff555f4a21f71ccc978290e2e212e2f8b168/core/jni/AndroidRuntime.cpp#L791
*
* After we succeed in getting called at a point where libart.so is already loaded, we will ignore
* the rest of the property_get calls.
*/
DCL_HOOK_FUNC(int, property_get, const char *key, char *value, const char *default_value) {
hook_unloader();
return old_property_get(key, value, default_value);
}
#undef DCL_HOOK_FUNC
// -----------------------------------------------------------------
@@ -283,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
@@ -307,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;
@@ -318,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;
@@ -331,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;
}
@@ -350,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;
@@ -424,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;
@@ -439,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() {
@@ -454,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();
}
// -----------------------------------------------------------------
@@ -563,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;
};
@@ -601,9 +694,11 @@ void ZygiskContext::sanitize_fds() {
struct dirent *entry;
while ((entry = readdir(dir))) {
int fd = parse_int(entry->d_name);
if (fd < 0 || fd < MAX_FD_SIZE || fd == dfd || allowed_fds[fd]) continue;
if (fd < 0 || fd > MAX_FD_SIZE || fd == dfd || allowed_fds[fd]) continue;
close(fd);
LOGW("Closed leaked fd: %d", fd);
}
closedir(dir);
@@ -615,13 +710,21 @@ void ZygiskContext::fork_post() {
g_ctx = nullptr;
}
/* Zygisksu changed: Load module fds */
void ZygiskContext::run_modules_pre() {
bool ZygiskContext::load_modules_only() {
struct zygisk_modules ms;
if (rezygiskd_read_modules(&ms) == false) {
LOGE("Failed to read modules from zygiskd");
return;
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++) {
@@ -643,16 +746,30 @@ void ZygiskContext::run_modules_pre() {
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);
for (auto &m : modules) {
m.onLoad(env);
return true;
}
if (flags[APP_SPECIALIZE]) m.preAppSpecialize(args.app);
else if (flags[SERVER_FORK_AND_SPECIALIZE]) m.preServerSpecialize(args.server);
/* Zygisksu changed: Load module fds */
void ZygiskContext::run_modules_pre() {
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);
}
}
@@ -660,16 +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());
clean_trace("/data/adb", modules.size(), modules_unloaded, true);
if (zygisk_module_length > 0) {
LOGD("Modules unloaded: %zu/%zu", modules_unloaded, zygisk_module_length);
solist_reset_counters(zygisk_module_length, modules_unloaded);
LOGD("Returned global counters to their original values");
}
}
@@ -677,7 +809,48 @@ void ZygiskContext::run_modules_post() {
void ZygiskContext::app_specialize_pre() {
flags[APP_SPECIALIZE] = true;
info_flags = rezygiskd_get_process_flags(g_ctx->args.app->uid, (const char *const)process);
/* INFO: Isolated services have different UIDs than the main apps. Because
numerous root implementations base themselves in the UID of the
app, we need to ensure that the UID sent to ReZygiskd to search
is the app's and not the isolated service, or else it will be
able to bypass DenyList.
All apps, and isolated processes, of *third-party* applications will
have their app_data_dir set. The system applications might not have
one, however it is unlikely they will create an isolated process,
and even if so, it should not impact in detections, performance or
any area.
*/
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);
if (!data_dir) {
LOGE("Failed to get app data directory");
return;
}
struct stat st;
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);
return;
}
uid = st.st_uid;
LOGD("Isolated service being related to UID %d, app data dir: %s", uid, data_dir);
env->ReleaseStringUTFChars(*args.app->app_data_dir, data_dir);
}
info_flags = rezygiskd_get_process_flags(uid, (const char *const)process);
if (info_flags & PROCESS_IS_FIRST_STARTED) {
/* INFO: To ensure we are really using a clean mount namespace, we use
the first process it as reference for clean mount namespace,
@@ -687,24 +860,36 @@ void ZygiskContext::app_specialize_pre() {
update_mnt_ns(Clean, true);
}
if ((info_flags & (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) == (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) {
if ((info_flags & PROCESS_IS_MANAGER) == PROCESS_IS_MANAGER) {
LOGD("Manager process detected. Notifying that Zygisk has been enabled.");
/* INFO: This environment variable is related to Magisk Zygisk/Manager. It
it used by Magisk's Zygisk to communicate to Magisk Manager whether
Zygisk is working or not.
Zygisk is working or not, allowing Zygisk modules to both work properly
and for the manager to mark Zygisk as enabled.
To allow Zygisk modules to both work properly and for the manager to
identify Zygisk, being it not built-in, as working, we also set it. */
setenv("ZYGISK_ENABLED", "1", 1);
} else {
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
postSpecialize. While preSpecialie in fact runs with Zygote (not superuser)
privileges, in postSpecialize it will now be with lower permission, in
the app's sandbox and therefore can move to a clean mount namespace after
executing the modules preSpecialize.
However, to enhance capabilities of root managers, it is also set for
any other supported manager, so that, if they wish, they can recognize
if Zygisk is enabled.
*/
if ((info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST) {
setenv("ZYGISK_ENABLED", "1", 1);
}
/* 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);
@@ -714,7 +899,17 @@ void ZygiskContext::app_specialize_pre() {
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);
}
@@ -722,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;
}
@@ -738,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;
@@ -755,6 +950,11 @@ 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;
@@ -774,12 +974,14 @@ 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)
if (!is_child())
return;
app_specialize_pre();
sanitize_fds();
@@ -806,28 +1008,31 @@ 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) {
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]));
}
hook_unloader();
enable_unloader = true;
}
} // 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");
@@ -836,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;
}
@@ -849,40 +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)
void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps) {
LOGD("cleaning trace for path %s", path);
if (load > 0 || unload > 0) solist_reset_counters(load, unload);
LOGD("Dropping solist record for %s", path);
bool path_found = solist_drop_so_path(path);
if (!path_found || !spoof_maps) return;
LOGD("spoofing virtual maps for %s", path);
// spoofing map names is futile in Android, we do it simply
// to avoid Zygisk detections based on string comparison
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);
mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
mprotect(addr, size, map.perms);
}
}
}
void hook_functions() {
plt_hook_list = new vector<tuple<dev_t, ino_t, const char *, void **>>();
jni_hook_list = new map<string, vector<JNINativeMethod>>();
@@ -890,20 +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")) {
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, "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);
hook_commit();
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, property_get);
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(
@@ -913,36 +1097,61 @@ void hook_functions() {
}
static void hook_unloader() {
if (hooked_unloader) return;
hooked_unloader = true;
ino_t art_inode = 0;
dev_t art_dev = 0;
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;
}
}
if (art_dev == 0 || art_inode == 0) {
/*
* INFO: If we are here, it means we are too early and libart.so hasn't loaded yet when
* property_get was called. This doesn't normally happen, but we try again next time
* just to be safe.
*/
LOGE("virtual map for libart.so is not cached");
return;
hooked_unloader = false;
} 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();
hook_commit(map_infos);
}
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,239 +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; }
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,28 +1,30 @@
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <dlfcn.h>
#include <linux/limits.h>
#include "elf_util.h"
#include "logging.h"
#include "solist.h"
/* TODO: Is offset for realpath necessary? It seems to have the function
available anywhere. */
#ifdef __LP64__
size_t solist_size_offset = 0x18;
size_t solist_next_offset = 0x30;
size_t solist_realpath_offset = 0x1a8;
#else
size_t solist_size_offset = 0x90;
size_t solist_next_offset = 0xa4;
size_t solist_realpath_offset = 0x174;
#endif
static const char *(*get_realpath_sym)(SoInfo *) = NULL;
static void (*soinfo_free)(SoInfo *) = NULL;
static inline SoInfo *get_next(SoInfo *self) {
return *(SoInfo **)((uintptr_t)self + solist_next_offset);
}
static 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)
@@ -35,11 +37,7 @@ static inline void set_size(SoInfo *self, size_t size) {
*(size_t *) ((uintptr_t)self + solist_size_offset) = size;
}
static inline size_t get_size(SoInfo *self) {
return *(size_t *) ((uintptr_t)self + solist_size_offset);
}
struct pdg ppdg;
struct pdg ppdg = { 0 };
static bool pdg_setup(ElfImg *img) {
ppdg.ctor = (void *(*)())getSymbAddress(img, "__dl__ZN18ProtectedDataGuardC2Ev");
@@ -48,22 +46,20 @@ static bool pdg_setup(ElfImg *img) {
return ppdg.ctor != NULL && ppdg.dtor != NULL;
}
static void pdg_protect() {
if (ppdg.ctor != NULL)
(*(ppdg.ctor))();
}
/* INFO: Allow data to be written to the areas. */
static void pdg_unprotect() {
if (ppdg.dtor != NULL)
(*(ppdg.dtor))();
(*ppdg.ctor)();
}
static SoInfo *solist = NULL;
static SoInfo *somain = NULL;
static SoInfo **sonext = NULL;
/* INFO: Block write and only allow read access to the areas. */
static void pdg_protect() {
(*ppdg.dtor)();
}
static uint64_t *g_module_load_counter = NULL;
static uint64_t *g_module_unload_counter = NULL;
static SoInfo *somain = NULL;
static size_t *g_module_load_counter = NULL;
static size_t *g_module_unload_counter = NULL;
static bool solist_init() {
#ifdef __LP64__
@@ -77,10 +73,6 @@ static bool solist_init() {
return false;
}
ppdg = (struct pdg) {
.ctor = NULL,
.dtor = NULL
};
if (!pdg_setup(linker)) {
LOGE("Failed to setup pdg");
@@ -96,17 +88,6 @@ static bool solist_init() {
See #63 for more information.
*/
solist = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL6solist");
if ((void *)solist == NULL) {
LOGE("Failed to find solist __dl__ZL6solist*");
ElfImg_destroy(linker);
return false;
}
LOGD("%p is solist", (void *)solist);
somain = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL6somain");
if (somain == NULL) {
LOGE("Failed to find somain __dl__ZL6somain*");
@@ -118,26 +99,14 @@ static bool solist_init() {
LOGD("%p is somain", (void *)somain);
sonext = (SoInfo **)getSymbAddressByPrefix(linker, "__dl__ZL6sonext");
if (sonext == NULL) {
LOGE("Failed to find sonext __dl__ZL6sonext*");
ElfImg_destroy(linker);
return false;
}
LOGD("%p is sonext", (void *)sonext);
SoInfo *vdso = (SoInfo *)getSymbValueByPrefix(linker, "__dl__ZL4vdso");
if (vdso != NULL) LOGD("%p is vdso", (void *)vdso);
get_realpath_sym = (const char *(*)(SoInfo *))getSymbAddress(linker, "__dl__ZNK6soinfo12get_realpathEv");
if (get_realpath_sym == NULL) {
LOGE("Failed to find get_realpath __dl__ZNK6soinfo12get_realpathEv");
ElfImg_destroy(linker);
somain = NULL;
return false;
}
@@ -149,30 +118,63 @@ static bool solist_init() {
ElfImg_destroy(linker);
somain = NULL;
return false;
}
LOGD("%p is soinfo_free", (void *)soinfo_free);
g_module_load_counter = (uint64_t *)getSymbAddress(linker, "__dl__ZL21g_module_load_counter");
find_containing_library = (SoInfo *(*)(const void *))getSymbAddress(linker, "__dl__Z23find_containing_libraryPKv");
if (find_containing_library == NULL) {
LOGE("Failed to find find_containing_library __dl__Z23find_containing_libraryPKv");
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;
}
g_module_load_counter = (size_t *)getSymbAddress(linker, "__dl__ZL21g_module_load_counter");
if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter");
g_module_unload_counter = (uint64_t *)getSymbAddress(linker, "__dl__ZL23g_module_unload_counter");
g_module_unload_counter = (size_t *)getSymbAddress(linker, "__dl__ZL23g_module_unload_counter");
if (g_module_unload_counter != NULL) LOGD("found symbol g_module_unload_counter");
for (size_t i = 0; i < 1024 / sizeof(void *); i++) {
uintptr_t possible_field = (uintptr_t)solist + i * sizeof(void *);
size_t possible_size_of_somain = *(size_t *)((uintptr_t)somain + i * sizeof(void *));
if (possible_size_of_somain < 0x100000 && possible_size_of_somain > 0x100) {
solist_size_offset = i * sizeof(void *);
LOGD("solist_size_offset is %zu * %zu = %p", i, sizeof(void *), (void *)solist_size_offset);
}
if (*(void **)possible_field == somain || (vdso != NULL && *(void **)possible_field == vdso)) {
solist_next_offset = i * sizeof(void *);
LOGD("solist_next_offset is %zu * %zu = %p", i, sizeof(void *), (void *)solist_next_offset);
break;
}
@@ -183,36 +185,144 @@ static bool solist_init() {
return true;
}
bool solist_drop_so_path(const char *target_path) {
if (solist == NULL && !solist_init()) {
/* 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 unload) {
if (somain == NULL && !solist_init()) {
LOGE("Failed to initialize solist");
return false;
}
for (SoInfo *iter = solist; iter; iter = get_next(iter)) {
if (get_path(iter) && strstr(get_path(iter), target_path)) {
pdg_protect();
SoInfo *found = (*find_containing_library)(lib_memory);
if (found == NULL) {
LOGD("Could not find containing library for %p", lib_memory);
LOGV("dropping solist record loaded at %s with size %zu", get_path(iter), get_size(iter));
if (get_size(iter) > 0) {
set_size(iter, 0);
soinfo_free(iter);
pdg_unprotect();
return true;
}
pdg_unprotect();
}
}
purge_unused_memory();
return false;
}
LOGD("Found so path for %p: %s", lib_memory, get_path(found));
char path[PATH_MAX];
if (get_path(found) == NULL) {
LOGE("Failed to get path for %p", found);
return false;
}
strncpy(path, get_path(found), sizeof(path) - 1);
/* INFO: This area is guarded. Must unprotect first. */
pdg_unprotect();
set_size(found, 0);
if (unload) pdg_protect();
LOGD("Set size of %p to 0", (void *)found);
/* 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);
return true;
}
void solist_reset_counters(size_t load, size_t unload) {
if (solist == NULL && !solist_init()) {
if (somain == NULL && !solist_init()) {
LOGE("Failed to initialize solist");
return;
@@ -224,18 +334,18 @@ void solist_reset_counters(size_t load, size_t unload) {
return;
}
uint64_t loaded_modules = *g_module_load_counter;
uint64_t unloaded_modules = *g_module_unload_counter;
size_t loaded_modules = *g_module_load_counter;
size_t unloaded_modules = *g_module_unload_counter;
if (loaded_modules >= load) {
*g_module_load_counter = loaded_modules - load;
*g_module_load_counter -= load;
LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter);
LOGD("reset g_module_load_counter to %zu", *g_module_load_counter);
}
if (unloaded_modules >= unload) {
*g_module_unload_counter = unloaded_modules - unload;
*g_module_unload_counter -= unload;
LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter);
LOGD("reset g_module_unload_counter to %zu", *g_module_unload_counter);
}
}

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(const char *target_path);
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,4 +7,4 @@ extern size_t block_size;
void hook_functions();
void clean_trace(const char* path, size_t load = 1, size_t unload = 0, bool spoof_maps = false);
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();
((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 };
size_t nread;
again:
nread = read(monitor_sock_fd, &msg, sizeof(msg));
if ((int)nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) goto again;
uint8_t cmd;
ssize_t nread = TEMP_FAILURE_RETRY(read(monitor_sock_fd, &cmd, sizeof(cmd)));
if (nread == -1) {
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;
}
}
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;
}
}
@@ -448,17 +479,14 @@ static bool ensure_daemon_created(bool is_64bit) {
status##abi.daemon_running = false; \
\
if (!status##abi.daemon_error_info) { \
status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \
status##abi.daemon_error_info = strdup(status_str); \
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; \
}
@@ -467,19 +495,20 @@ static bool ensure_daemon_created(bool is_64bit) {
tracer = "./bin/zygisk-ptrace" # abi; \
\
if (should_stop_inject ## abi()) { \
LOGW("zygote" # abi " restart too much times, stop injecting"); \
LOGW("Zygote" # abi " restart too much times, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "zygote crashed", sizeof("zygote crashed")); \
strcpy(monitor_stop_reason, "Zygote crashed"); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
\
if (!ensure_daemon_created(is_64)) { \
LOGW("daemon" #abi " not running, stop injecting"); \
LOGW("ReZygiskd " #abi "-bit not running, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "daemon not running", sizeof("daemon not running")); \
strcpy(monitor_stop_reason, "ReZygiskd not running"); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
@@ -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

@@ -159,17 +159,47 @@ bool inject_on_main(int pid, const char *lib_path) {
void *libc_return_addr = find_module_return_addr(map, "libc.so");
LOGD("libc return addr %p", libc_return_addr);
const char *libdl_path = NULL;
const char *libc_path = NULL;
for (size_t i = 0; i < map->size; i++) {
if (map->maps[i].path == NULL) continue;
const char *filename = position_after(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;
continue;
}
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;
continue;
}
}
/* call dlopen */
#ifdef __LP64__
void *dlopen_addr = find_func_addr(local_map, map, "/apex/com.android.runtime/lib64/bionic/libdl.so", "dlopen");
#else
void *dlopen_addr = find_func_addr(local_map, map, "/apex/com.android.runtime/lib/bionic/libdl.so", "dlopen");
#endif
if (dlopen_addr == NULL) {
void *dlopen_addr = NULL;
if (!libdl_path || (dlopen_addr = find_func_addr(local_map, map, libdl_path, "dlopen")) == NULL) {
/* INFO: Android 7.1 and below doesn't have libdl.so loaded in Zygote */
LOGW("Failed to find dlopen from libdl.so, will load from linker");
#ifdef __LP64__
dlopen_addr = find_func_addr(local_map, map, "/system/bin/linker64", "__dl_dlopen");
#else
dlopen_addr = find_func_addr(local_map, map, "/system/bin/linker", "__dl_dlopen");
#endif
if (dlopen_addr == NULL) {
PLOGE("Find __dl_dlopen");
@@ -198,16 +228,16 @@ bool inject_on_main(int pid, const char *lib_path) {
LOGE("handle is null");
/* call dlerror */
#ifdef __LP64__
void *dlerror_addr = find_func_addr(local_map, map, "/apex/com.android.runtime/lib64/bionic/libdl.so", "dlerror");
#else
void *dlerror_addr = find_func_addr(local_map, map, "/apex/com.android.runtime/lib/bionic/libdl.so", "dlerror");
#endif
if (dlerror_addr == NULL) {
void *dlerror_addr = NULL;
if (!libdl_path || (dlerror_addr = find_func_addr(local_map, map, libdl_path, "dlerror")) == NULL) {
/* INFO: Android 7.1 and below doesn't have libdl.so loaded in Zygote */
LOGW("Failed to find dlerror from libdl.so, will load from linker");
#ifdef __LP64__
dlerror_addr = find_func_addr(local_map, map, "/system/bin/linker64", "__dl_dlerror");
#else
dlerror_addr = find_func_addr(local_map, map, "/system/bin/linker", "__dl_dlerror");
#endif
if (dlerror_addr == NULL) {
LOGE("Find __dl_dlerror");
@@ -225,19 +255,19 @@ bool inject_on_main(int pid, const char *lib_path) {
LOGE("dlerror str is null");
free(args);
free_maps(local_map);
free_maps(map);
return false;
}
#ifdef __LP64__
void *strlen_addr = find_func_addr(local_map, map, "/system/lib64/libc.so", "strlen");
#else
void *strlen_addr = find_func_addr(local_map, map, "/system/lib/libc.so", "strlen");
#endif
void *strlen_addr = find_func_addr(local_map, map, libc_path, "strlen");
if (strlen_addr == NULL) {
LOGE("find strlen");
free(args);
free_maps(local_map);
free_maps(map);
return false;
}
@@ -249,6 +279,8 @@ bool inject_on_main(int pid, const char *lib_path) {
LOGE("dlerror len <= 0");
free(args);
free_maps(local_map);
free_maps(map);
return false;
}
@@ -258,6 +290,8 @@ bool inject_on_main(int pid, const char *lib_path) {
LOGE("malloc err");
free(args);
free_maps(local_map);
free_maps(map);
return false;
}
@@ -269,20 +303,23 @@ bool inject_on_main(int pid, const char *lib_path) {
free(err);
free(args);
free_maps(local_map);
free_maps(map);
return false;
}
/* call dlsym(handle, "entry") */
#ifdef __LP64__
void *dlsym_addr = find_func_addr(local_map, map, "/apex/com.android.runtime/lib64/bionic/libdl.so", "dlsym");
#else
void *dlsym_addr = find_func_addr(local_map, map, "/apex/com.android.runtime/lib/bionic/libdl.so", "dlsym");
#endif
if (dlsym_addr == NULL) {
void *dlsym_addr = NULL;
if (!libdl_path || (dlsym_addr = find_func_addr(local_map, map, libdl_path, "dlsym")) == NULL) {
/* INFO: Android 7.1 and below doesn't have libdl.so loaded in Zygote */
LOGW("Failed to find dlsym from libdl.so, will load from linker");
#ifdef __LP64__
dlsym_addr = find_func_addr(local_map, map, "/system/bin/linker64", "__dl_dlsym");
#else
dlsym_addr = find_func_addr(local_map, map, "/system/bin/linker", "__dl_dlsym");
#endif
if (dlsym_addr == NULL) {
LOGE("find __dl_dlsym");
@@ -331,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);
@@ -370,7 +405,7 @@ bool trace_zygote(int pid) {
int status;
if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_EXITKILL) == -1) {
if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_EXITKILL | PTRACE_O_TRACESECCOMP) == -1) {
PLOGE("seize");
return false;
@@ -405,6 +440,16 @@ bool trace_zygote(int pid) {
if (STOPPED_WITH(SIGCONT, 0)) {
LOGD("received SIGCONT");
/* INFO: Due to kernel bugs, fixed in 5.16+, ptrace_message (msg of
PTRACE_GETEVENTMSG) may not represent the current state of
the process. Because we set some options, which alters the
ptrace_message, we need to call PTRACE_SYSCALL to reset the
ptrace_message to 0, the default/normal state.
*/
ptrace(PTRACE_SYSCALL, pid, 0, 0);
WAIT_OR_DIE
ptrace(PTRACE_DETACH, pid, 0, SIGCONT);
}
} else {

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;
@@ -313,20 +314,12 @@ void *find_module_return_addr(struct maps *map, const char *suffix) {
}
void *find_module_base(struct maps *map, const char *file) {
const char *suffix = position_after(file, '/');
if (!suffix) {
LOGE("failed to find suffix in %s", file);
return NULL;
}
for (size_t i = 0; i < map->size; i++) {
if (map->maps[i].path == NULL) continue;
const char *file_name = position_after(map->maps[i].path, '/');
if (!file_name) continue;
const char *file_path = map->maps[i].path;
if (strlen(file_name) < strlen(suffix) || map->maps[i].offset != 0 || strncmp(file_name, suffix, strlen(suffix)) != 0) continue;
if (strlen(file_path) != strlen(file) || map->maps[i].offset != 0 || strncmp(file_path, file, strlen(file)) != 0) continue;
return (void *)map->maps[i].start;
}
@@ -530,6 +523,32 @@ int fork_dont_care() {
return pid;
}
void tracee_skip_syscall(int pid) {
struct user_regs_struct regs;
if (!get_regs(pid, &regs)) {
LOGE("failed to get seccomp regs");
exit(1);
}
regs.REG_SYSNR = -1;
if (!set_regs(pid, &regs)) {
LOGE("failed to set seccomp regs");
exit(1);
}
/* INFO: It might not work, don't check for error */
#if defined(__aarch64__)
int sysnr = -1;
struct iovec iov = {
.iov_base = &sysnr,
.iov_len = sizeof (int),
};
ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov);
#elif defined(__arm__)
ptrace(PTRACE_SET_SYSCALL, pid, 0, (void*) -1);
#endif
}
void wait_for_trace(int pid, int *status, int flags) {
while (1) {
pid_t result = waitpid(pid, status, flags);
@@ -540,7 +559,21 @@ void wait_for_trace(int pid, int *status, int flags) {
exit(1);
}
if (!WIFSTOPPED(*status)) {
/* 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);
continue;
} else if (!WIFSTOPPED(*status)) {
char status_str[64];
parse_status(*status, status_str, sizeof(status_str));

View File

@@ -37,18 +37,22 @@ void free_maps(struct maps *maps);
#define REG_SP rsp
#define REG_IP rip
#define REG_RET rax
#define REG_SYSNR orig_rax
#elif defined(__i386__)
#define REG_SP esp
#define REG_IP eip
#define REG_RET eax
#define REG_SYSNR orig_eax
#elif defined(__aarch64__)
#define REG_SP sp
#define REG_IP pc
#define REG_RET regs[0]
#define REG_SYSNR regs[8]
#elif defined(__arm__)
#define REG_SP uregs[13]
#define REG_IP uregs[15]
#define REG_RET uregs[0]
#define REG_SYSNR uregs[7]
#define user_regs_struct user_regs
#endif
@@ -62,6 +66,8 @@ bool set_regs(int pid, struct user_regs_struct *regs);
void get_addr_mem_region(struct maps *map, uintptr_t addr, char *buf, size_t buf_size);
const char *position_after(const char *str, const char needle);
void *find_module_return_addr(struct maps *map, const char *suffix);
void *find_func_addr(struct maps *local_info, struct maps *remote_info, const char *module, const char *func);

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,23 +91,31 @@ 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">'
rootCss.style.setProperty('--bright', '#766000')
/* INFO: Hide zygote divs */
zygote_divs.forEach((zygote_div) => {
zygote_div.style.display = 'none'
})
loading_screen.style.display = 'none'
bottom_nav.style.display = 'flex'
return;
}
if (catCmd.errno === 0) {
/* INFO: Just ensure that they won't appear unless there's info */
zygote_divs.forEach((zygote_div) => {
zygote_div.style.display = 'none'
@@ -115,78 +123,75 @@ async function getModuleNames(modules) {
version.innerHTML = catCmd.stdout.split('\n').find((line) => line.startsWith('version=')).substring('version='.length).trim()
let moduleInfo = catCmd.stdout.split('\n').find((line) => line.startsWith('description=')).substring('description='.length).split('[')[1].split(']')[0]
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)
const daemonModules = []
moduleInfo.match(/\(([^)]+)\)/g).forEach((area) => {
moduleInfo = moduleInfo.replace(area, ',')
rezygisk_state.innerHTML = translations.page.home.status.notWorking
rezygisk_icon_state.innerHTML = '<img class="dimc" src="assets/cross.svg">'
const info = area.substring(1, area.length - 1).split(', ')
if (info.length === 1) return; /* INFO: undefined as object */
rootCss.style.setProperty('--bright', '#766000')
const rootImpl = info[0].substring('Root: '.length)
info[1] = info[1].substring('Modules: '.length)
const modules = info.slice(1, info.length)
ReZygiskInfo.rootImpl = rootImpl
if (modules[0] !== 'None') daemonModules.push(modules)
/* 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'
return;
}
if (info.startsWith('zygote')) {
ReZygiskInfo.zygotes.push({
bits: info.substring('zygote'.length, 'zygote'.length + 'XX'.length),
state: info.substring('zygoteXX: X '.length).trim()
})
}
const ReZygiskState = JSON.parse(stateCmd.stdout)
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] || []
})
}
})
root_impl.innerHTML = ReZygiskState.root
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;
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;
}
expectedWorking = ReZygiskInfo.zygotes.length
const expectedWorking = (ReZygiskState.zygote['64'] !== undefined ? 1 : 0) + (ReZygiskState.zygote['32'] !== undefined ? 1 : 0)
let actuallyWorking = 0
for (let i = 0; i < ReZygiskInfo.zygotes.length; i++) {
const zygote = ReZygiskInfo.zygotes[i]
/* INFO: Not used ATM */
/* const daemon = ReZygiskInfo.daemons[i] */
if (ReZygiskState.zygote['64'] !== undefined) {
const zygote64 = ReZygiskState.zygote['64']
const zygoteDiv = zygote_divs[zygote.bits === '64' ? 0 : 1]
const zygoteStatusDiv = zygote_status_divs[zygote.bits === '64' ? 0 : 1]
zygote_divs[0].style.display = 'block'
zygoteDiv.style.display = 'block'
switch (zygote.state) {
case 'injected': {
zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.injected;
switch (zygote64) {
case 1: {
zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.injected
actuallyWorking++
break;
break
}
case 'not injected': zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.notInjected; break;
default: zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.unknown;
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
}
}
if (ReZygiskState.zygote['32'] !== undefined) {
const zygote32 = ReZygiskState.zygote['32']
zygote_divs[1].style.display = 'block'
switch (zygote32) {
case 1: {
zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.injected
actuallyWorking++
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
}
}
if (expectedWorking === 0 || actuallyWorking === 0) {
@@ -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) => {
Object.keys(ReZygiskState.rezygiskd).forEach((daemon_bit) => {
const daemon = ReZygiskState.rezygiskd[daemon_bit]
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.bits)
module.bitsUsed.push(daemon_bit)
} else {
all_modules.push({
id: module_id,
name: null,
bitsUsed: [ daemon.bits ]
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')

93
webroot/lang/ja_JP.json Normal file
View File

@@ -0,0 +1,93 @@
{
"langName": "日本語",
"global": {
"unknown": "不明"
},
"smallPage": {
"language": {
"header": "言語を選択してください"
},
"theme": {
"header": "テーマを選択してください",
"dark": "ダーク",
"light": "ライト",
"system": "システムベース"
},
"errorh": {
"buttons": {
"copy": "コピー",
"clear": "すべてのログを消去"
},
"header": "エラーの履歴",
"placeholder": "エラーログは記録されていません!"
}
},
"page": {
"home": {
"header": "ホーム",
"status": {
"notWorking": "動作していません",
"ok": "動作中",
"partially": "部分的に動作中"
},
"info": {
"version": "バージョン",
"root": "Root の実装",
"zygote": {
"injected": "インジェクト済み",
"notInjected": "未インジェクト",
"unknown": "不明"
}
}
},
"modules": {
"header": "モジュール",
"notAvaliable": "Zygisk を使用するモジュールはありません。",
"arch": "アーキテクチャ: "
},
"actions": {
"header": "アクション",
"monitorButton": {
"start": "開始",
"stop": "停止",
"pause": "一時停止"
},
"monitor": "監視",
"status": {
"tracing": "トレース中",
"stopping": "停止中",
"stopped": "停止済み",
"exiting": "終了中",
"unknown": "不明"
}
},
"settings": {
"header": "設定",
"font": {
"header": "システムフォントを有効化",
"description": "現在の WebUI でシステムフォントの使用を有効化します。注意: FlipFont との互換性がない場合があります"
},
"theme": {
"header": "システムテーマ",
"description": "WebUI のシステムテーマを変更します"
},
"language": {
"header": "言語の変更",
"description": "言語を変更します"
},
"logs": {
"header": "エラーの履歴",
"description": "すべてのエラーログを表示します"
},
"credits": {
"module": "モジュールの開発者",
"original": "オリジナルの開発者",
"web": "WebUI の開発者"
},
"license": {
"module": "モジュールのライセンス",
"web": "WebUI のライセンス"
}
}
}
}

View File

@@ -121,11 +121,7 @@ void companion_entry(int fd) {
ASSURE_SIZE_WRITE("ZygiskdCompanion", "module_entry", ret, sizeof(uint8_t));
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
struct sigaction sa = { .sa_handler = SIG_IGN };
sigaction(SIGPIPE, &sa, NULL);
while (1) {
@@ -157,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

@@ -62,6 +62,7 @@ void apatch_get_existence(struct root_impl_state *state) {
}
struct package_config {
char *process;
uid_t uid;
bool root_granted;
bool umount_needed;
@@ -72,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;
@@ -95,16 +104,18 @@ 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;
strtok(line, ",");
config->configs[config->size].process = strdup(strtok(line, ","));
char *exclude_str = strtok(NULL, ",");
if (exclude_str == NULL) continue;
@@ -127,17 +138,9 @@ bool _apatch_get_package_config(struct packages_config *restrict config) {
return true;
}
void _apatch_free_package_config(struct packages_config *restrict config) {
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;
@@ -155,13 +158,9 @@ bool apatch_uid_granted_root(uid_t uid) {
return false;
}
bool apatch_uid_should_umount(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;
@@ -174,6 +173,29 @@ bool apatch_uid_should_umount(uid_t uid) {
return umount_needed;
}
/* INFO: Isolated services have different UIDs than the main app, and
while libzygisk.so has code to send the UID of the app related
to the isolated service, we add this so that in case it fails,
this should avoid it pass through as Mounted.
*/
if (IS_ISOLATED_SERVICE(uid)) {
size_t targeted_process_length = strlen(process);
for (size_t i = 0; i < config.size; i++) {
size_t config_process_length = strlen(config.configs[i].process);
size_t smallest_process_length = targeted_process_length < config_process_length ? targeted_process_length : config_process_length;
if (strncmp(config.configs[i].process, process, smallest_process_length) != 0) continue;
/* INFO: This allow us to copy the information to avoid use-after-free */
bool umount_needed = config.configs[i].umount_needed;
_apatch_free_package_config(&config);
return umount_needed;
}
}
_apatch_free_package_config(&config);
return false;

View File

@@ -7,7 +7,7 @@ void apatch_get_existence(struct root_impl_state *state);
bool apatch_uid_granted_root(uid_t uid);
bool apatch_uid_should_umount(uid_t uid);
bool apatch_uid_should_umount(uid_t uid, const char *const process);
bool apatch_uid_is_manager(uid_t uid);

View File

@@ -30,6 +30,7 @@ void root_impls_setup(void) {
impl.impl = Multiple;
} else if (state_ksu.state == Supported) {
impl.impl = KernelSU;
impl.variant = state_ksu.variant;
} else if (state_apatch.state == Supported) {
impl.impl = APatch;
} else if (state_magisk.state == Supported) {
@@ -73,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) {
@@ -100,7 +100,7 @@ bool uid_should_umount(uid_t uid, const char *const process) {
return ksu_uid_should_umount(uid);
}
case APatch: {
return apatch_uid_should_umount(uid);
return apatch_uid_should_umount(uid, process);
}
case Magisk: {
return magisk_uid_should_umount(process);

View File

@@ -19,10 +19,18 @@
#define CMD_GET_VERSION 2
#define CMD_UID_GRANTED_ROOT 12
#define CMD_UID_SHOULD_UMOUNT 13
#define CMD_GET_MANAGER_UID 16
#define CMD_HOOK_MODE 0xC0DEAD1A
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) {
@@ -42,6 +50,25 @@ 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, NULL, &reply_ok);
if (mode[0] != '\0') state->variant = KNext;
else state->variant = KOfficial;
variant = state->variant;
/* 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. */
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");
supports_manager_uid_retrieval = true;
}
}
else if (version >= 1 && version <= MIN_KSU_VERSION - 1) state->state = TooOld;
else state->state = Abnormal;
@@ -68,8 +95,26 @@ bool ksu_uid_should_umount(uid_t uid) {
}
bool ksu_uid_is_manager(uid_t uid) {
/* INFO: If the manager UID is set, we can use it to check if the UID
is the manager UID, which is more reliable than checking
the KSU manager data directory, as spoofed builds of
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, &reply_ok);
return uid == manager_uid;
}
const char *manager_path = NULL;
if (variant == KOfficial) manager_path = "/data/user_de/0/me.weishu.kernelsu";
else if (variant == KNext) manager_path = "/data/user_de/0/com.rifsxd.ksunext";
struct stat s;
if (stat("/data/user_de/0/me.weishu.kernelsu", &s) == -1) {
if (stat(manager_path, &s) == -1) {
if (errno != ENOENT) {
LOGE("Failed to stat KSU manager data directory: %s\n", strerror(errno));
}

View File

@@ -3,6 +3,11 @@
#include "../constants.h"
enum kernelsu_variants {
KOfficial,
KNext
};
void ksu_get_existence(struct root_impl_state *state);
bool ksu_uid_granted_root(uid_t uid);

View File

@@ -28,7 +28,7 @@ char *magisk_managers[] = {
#define DEBUG_RAMDISK_MAGISK lp_select("/debug_ramdisk/magisk32", "/debug_ramdisk/magisk64")
#define BITLESS_DEBUG_RAMDISK_MAGISK "/debug_ramdisk/magisk"
enum magisk_variants variant = Official;
static enum magisk_variants variant = MOfficial;
/* INFO: Longest path */
static char path_to_magisk[sizeof(DEBUG_RAMDISK_MAGISK)] = { 0 };
bool is_using_sulist = false;
@@ -74,7 +74,7 @@ void magisk_get_existence(struct root_impl_state *state) {
return;
}
state->variant = (uint8_t)Official;
state->variant = (uint8_t)MOfficial;
for (unsigned long i = 0; i < sizeof(supported_variants) / sizeof(supported_variants[0]); i++) {
if (strstr(magisk_info, supported_variants[i])) {

View File

@@ -4,8 +4,8 @@
#include "../constants.h"
enum magisk_variants {
Official,
Kitsune
MOfficial,
MKitsune
};
void magisk_get_existence(struct root_impl_state *state);

View File

@@ -20,6 +20,7 @@
#include "utils.h"
#include "root_impl/common.h"
#include "root_impl/kernelsu.h"
#include "root_impl/magisk.h"
int clean_namespace_fd = 0;
@@ -108,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);
}
@@ -436,7 +434,8 @@ void stringify_root_impl_name(struct root_impl impl, char *restrict output) {
break;
}
case KernelSU: {
strcpy(output, "KernelSU");
if (impl.variant == KOfficial) strcpy(output, "KernelSU");
else strcpy(output, "KernelSU Next");
break;
}
@@ -446,11 +445,8 @@ void stringify_root_impl_name(struct root_impl impl, char *restrict output) {
break;
}
case Magisk: {
if (impl.variant == 0) {
strcpy(output, "Magisk Official");
} else {
strcpy(output, "Magisk Kitsune");
}
if (impl.variant == MOfficial) strcpy(output, "Magisk Official");
else strcpy(output, "Magisk Kitsune");
break;
}
@@ -544,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;
@@ -573,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);
@@ -603,15 +645,11 @@ bool umount_root(struct root_impl impl) {
return false;
}
switch (impl.impl) {
case None: { break; }
case Multiple: { break; }
case KernelSU:
case APatch: {
char source_name[LONGEST_ROOT_IMPL_NAME];
/* INFO: "Magisk" is the longest word that will ever be put in source_name */
char source_name[sizeof("magisk")];
if (impl.impl == KernelSU) strcpy(source_name, "KSU");
else strcpy(source_name, "APatch");
else if (impl.impl == APatch) strcpy(source_name, "APatch");
else strcpy(source_name, "magisk");
LOGI("[%s] Unmounting root", source_name);
@@ -622,16 +660,16 @@ bool umount_root(struct root_impl impl) {
struct mountinfo mount = mounts.mounts[i];
bool should_unmount = false;
/* INFO: KernelSU has its own /system mounts, so we only skip the mount
if they are from a module, not KSU itself.
/* INFO: The root implementations have their own /system mounts, so we
only skip the mount if they are from a module, not Magisk itself.
*/
if (strncmp(mount.target, "/system/", strlen("/system/")) == 0 &&
strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0) continue;
strncmp(mount.root, "/adb/modules/", strlen("/adb/modules/")) == 0 &&
strncmp(mount.target, "/system/etc/", strlen("/system/etc/")) != 0) continue;
if (strcmp(mount.source, source_name) == 0) should_unmount = true;
if (strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0) should_unmount = true;
if (strncmp(mount.target, "/data/adb/modules", strlen("/data/adb/modules")) == 0) should_unmount = true;
if (strncmp(mount.root, "/adb/modules/", strlen("/adb/modules/")) == 0) should_unmount = true;
if (!should_unmount) continue;
@@ -651,7 +689,6 @@ bool umount_root(struct root_impl impl) {
for (size_t i = num_targets; i > 0; i--) {
const char *target = targets_to_unmount[i - 1];
if (umount2(target, MNT_DETACH) == -1) {
LOGE("[%s] Failed to unmount %s: %s\n", source_name, target, strerror(errno));
} else {
@@ -660,59 +697,6 @@ bool umount_root(struct root_impl impl) {
}
free(targets_to_unmount);
break;
}
case Magisk: {
LOGI("[Magisk] Unmounting root");
const char **targets_to_unmount = NULL;
size_t num_targets = 0;
for (size_t i = 0; i < mounts.length; i++) {
struct mountinfo mount = mounts.mounts[i];
bool should_unmount = false;
/* INFO: Magisk has its own /system mounts, so we only skip the mount
if they are from a module, not Magisk itself.
*/
if (strncmp(mount.target, "/system/", strlen("/system/")) == 0 &&
strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0) continue;
if (strcmp(mount.source, "magisk") == 0) should_unmount = true;
if (strncmp(mount.target, "/debug_ramdisk", strlen("/debug_ramdisk")) == 0) should_unmount = true;
if (strncmp(mount.target, "/data/adb/modules", strlen("/data/adb/modules")) == 0) should_unmount = true;
if (strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0) should_unmount = true;
if (!should_unmount) continue;
num_targets++;
targets_to_unmount = realloc(targets_to_unmount, num_targets * sizeof(char*));
if (targets_to_unmount == NULL) {
LOGE("[Magisk] Failed to allocate memory for targets_to_unmount\n");
free(targets_to_unmount);
free_mounts(&mounts);
return false;
}
targets_to_unmount[num_targets - 1] = mount.target;
}
for (size_t i = num_targets; i > 0; i--) {
const char *target = targets_to_unmount[i - 1];
if (umount2(target, MNT_DETACH) == -1) {
LOGE("[Magisk] Failed to unmount %s: %s\n", target, strerror(errno));
} else {
LOGI("[Magisk] Unmounted %s\n", target);
}
}
free(targets_to_unmount);
break;
}
}
free_mounts(&mounts);
return true;
@@ -844,7 +828,7 @@ int save_mns_fd(int pid, enum MountNamespaceState mns_state, struct root_impl im
return -1;
}
if (impl.impl == Magisk && impl.variant == Kitsune && mns_state == Clean) {
if (impl.impl == Magisk && impl.variant == MKitsune && mns_state == Clean) {
LOGI("[Magisk] Magisk Kitsune detected, will skip cache first.");
/* INFO: MagiskSU of Kitsune has a special behavior: It is only mounted

View File

@@ -65,6 +65,9 @@
return -1; \
}
#define IS_ISOLATED_SERVICE(uid) \
((uid) >= 90000 && (uid) < 1000000)
#define write_func_def(type) \
ssize_t write_## type(int fd, type val)

View File

@@ -48,11 +48,17 @@ enum Architecture {
#define ZYGISKD_PATH "/data/adb/modules/rezygisk/bin/zygiskd" lp_select("32", "64")
static enum Architecture get_arch(void) {
char system_arch[32];
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);
if (strstr(system_arch, "arm") != NULL) return lp_select(ARM32, ARM64);
/* INFO: "PC" architectures should have priority because in an emulator
the native architecture should have priority over the emulated
architecture for "native" reasons. */
if (strstr(system_arch, "x86") != NULL) return lp_select(X86, X86_64);
if (strstr(system_arch, "arm") != NULL) return lp_select(ARM32, ARM64);
LOGE("Unsupported system architecture: %s\n", system_arch);
exit(1);
@@ -117,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");
@@ -130,13 +135,17 @@ static void load_modules(enum Architecture arch, struct Context *restrict contex
context->modules[context->len].companion = -1;
context->len++;
}
closedir(dir);
}
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) {
@@ -252,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
@@ -267,101 +271,63 @@ 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;
}
struct sigaction sa = { .sa_handler = SIG_IGN };
sigaction(SIGPIPE, &sa, NULL);
bool first_process = true;
while (1) {
int client_fd = accept(socket_fd, NULL, NULL);
if (client_fd == -1) {
LOGE("accept: %s\n", strerror(errno));
return;
break;
}
uint8_t action8 = 0;
@@ -369,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) {
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;
}
@@ -555,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);
@@ -566,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) {
@@ -586,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) {
@@ -618,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);