33 Commits

Author SHA1 Message Date
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
32 changed files with 715 additions and 284 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: |
echo "$private_key" | base64 -d > module/private_key
echo "$public_key" | base64 -d > module/public_key
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

@@ -535,8 +535,10 @@ 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)",
name, hash, bloom_idx, (unsigned long)mask, (unsigned long)bloom_word);
/* 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;
}

View File

@@ -4,6 +4,9 @@
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

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

@@ -22,5 +22,8 @@ void entry(void* addr, size_t size, const char* path) {
LOGD("start plt hooking");
hook_functions();
clean_trace(path, 1, 0, false);
void *module_addrs[1] = { addr };
clean_trace(path, module_addrs, 1, 1, 0, false);
send_seccomp_event();
}

View File

@@ -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)
@@ -124,6 +125,8 @@ struct ZygiskContext {
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>> *jni_hook_list;
bool should_unmap_zygisk = false;
bool enable_unloader = false;
bool hooked_unloader = false;
std::vector<lsplt::MapInfo> cached_map_infos = {};
} // namespace
@@ -215,6 +218,9 @@ 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;
@@ -249,6 +255,27 @@ DCL_HOOK_FUNC(char *, strdup, const char *s) {
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
// -----------------------------------------------------------------
@@ -601,9 +628,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 +644,12 @@ 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;
}
for (size_t i = 0; i < ms.modules_count; i++) {
@@ -648,6 +676,11 @@ void ZygiskContext::run_modules_pre() {
free_modules(&ms);
return true;
}
/* Zygisksu changed: Load module fds */
void ZygiskContext::run_modules_pre() {
for (auto &m : modules) {
m.onLoad(env);
@@ -669,7 +702,19 @@ void ZygiskContext::run_modules_post() {
if (modules.size() > 0) {
LOGD("modules unloaded: %zu/%zu", modules_unloaded, modules.size());
clean_trace("/data/adb", modules.size(), modules_unloaded, true);
/* INFO: While Variable Length Arrays (VLAs) aren't usually
recommended due to the ease of using too much of the
stack, this should be fine since it should not be
possible to exhaust the stack with only a few addresses. */
void *module_addrs[modules.size() * sizeof(void *)];
size_t i = 0;
for (const auto &m : modules) {
module_addrs[i++] = m.getEntry();
}
clean_trace("/data/adb", module_addrs, modules.size(), modules.size(), modules_unloaded, true);
}
}
@@ -677,7 +722,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,33 +773,64 @@ 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. */
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.
*/
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.
/* INFO: Because we load directly from the file, we need to do it before we umount
the mounts, or else it won't have access to /data/adb anymore.
*/
if ((info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST) {
flags[DO_REVERT_UNMOUNT] = true;
if (!load_modules_only()) {
LOGE("Failed to load modules");
update_mnt_ns(Clean, false);
return;
}
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
postSpecialize. In preSpecialize, the process still has privileged
permissions, and therefore can execute mount/umount/setns functions.
If we update the mount namespace AFTER executing them, any mounts made
will be lost, and the process will not have access to them anymore.
In postSpecialize, while still could have its mounts modified with the
assistance of a Zygisk companion, it will already have the mount
namespace switched by then, so there won't be issues.
Knowing this, we update the mns before execution, so that they can still
make changes to mounts in DenyListed processes without being reverted.
*/
bool in_denylist = (info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST;
if (in_denylist) {
flags[DO_REVERT_UNMOUNT] = true;
update_mnt_ns(Clean, false);
}
/* INFO: Executed after setns to ensure a module can update the mounts of an
application without worrying about it being overwritten by setns.
*/
run_modules_pre();
/* INFO: The modules may request that although the process is NOT in
the DenyList, it has its mount namespace switched to the clean
one.
So to ensure this behavior happens, we must also check after the
modules are loaded and executed, so that the modules can have
the chance to request it.
*/
if (!in_denylist && flags[DO_REVERT_UNMOUNT])
update_mnt_ns(Clean, false);
}
}
@@ -759,6 +876,7 @@ void ZygiskContext::nativeForkSystemServer_pre() {
if (!is_child())
return;
load_modules_only();
run_modules_pre();
rezygiskd_system_server_started();
@@ -821,7 +939,7 @@ ZygiskContext::~ZygiskContext() {
m.clearApi();
}
hook_unloader();
enable_unloader = true;
}
} // namespace
@@ -849,19 +967,35 @@ 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) {
/* INFO: module_addrs_length is always the same as "load" */
void clean_trace(const char *path, void **module_addrs, size_t module_addrs_length, size_t load, size_t unload, bool spoof_maps) {
LOGD("cleaning trace for path %s", path);
if (load > 0 || unload > 0) solist_reset_counters(load, unload);
LOGD("Dropping solist record for %s", path);
bool path_found = solist_drop_so_path(path);
if (!path_found || !spoof_maps) return;
bool any_dropped = false;
for (size_t i = 0; i < module_addrs_length; i++) {
bool local_any_dropped = solist_drop_so_path(module_addrs[i]);
if (!local_any_dropped) continue;
any_dropped = true;
LOGD("Dropped solist record for %p", module_addrs[i]);
}
if (!any_dropped || !spoof_maps) return;
LOGD("spoofing virtual maps for %s", path);
// spoofing map names is futile in Android, we do it simply
// to avoid Zygisk detections based on string comparison
/* INFO: Spoofing maps names is futile, after all it will
still show up in /proc/self/(s)maps but with a
different name, however still detectable by
checking the permissions. This, however, avoids
just checking for "zygisk". */
/* TODO: Use SoList to map through libraries to avoid open /proc/self/maps here */
for (auto &map : lsplt::MapInfo::Scan()) {
if (strstr(map.path.c_str(), path) && strstr(map.path.c_str(), "libzygisk") == 0)
{
@@ -877,8 +1011,8 @@ void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps)
mprotect(addr, size, PROT_READ);
}
memcpy(copy, addr, size);
mprotect(copy, size, map.perms);
mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
mprotect(addr, size, map.perms);
}
}
}
@@ -903,6 +1037,7 @@ void hook_functions() {
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, property_get);
hook_commit();
// Remove unhooked methods
@@ -913,9 +1048,13 @@ void hook_functions() {
}
static void hook_unloader() {
if (hooked_unloader) return;
hooked_unloader = true;
ino_t art_inode = 0;
dev_t art_dev = 0;
cached_map_infos = lsplt::MapInfo::Scan();
for (auto &map : cached_map_infos) {
if (map.path.ends_with("/libart.so")) {
art_inode = map.inode;
@@ -925,7 +1064,16 @@ static void hook_unloader() {
}
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");
hooked_unloader = false;
return;
} else {
LOGD("hook_unloader called with libart.so [%zu:%lu]", art_dev, art_inode);

View File

@@ -213,6 +213,7 @@ case 5: \
bool tryUnload() const { return unload && dlclose(handle) == 0; };
void clearApi() { memset(&api, 0, sizeof(api)); }
int getId() const { return id; }
void *getEntry() const { return entry.ptr; }
ZygiskModule(int id, void *handle, void *entry);

View File

@@ -2,27 +2,28 @@
#include <stdbool.h>
#include <string.h>
#include <android/dlext.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 inline const char *get_path(SoInfo *self) {
if (get_realpath_sym)
@@ -35,11 +36,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 +45,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 +72,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 +87,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,20 +98,6 @@ 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");
@@ -154,25 +120,28 @@ static bool solist_init() {
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);
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 +152,49 @@ static bool solist_init() {
return true;
}
bool solist_drop_so_path(const char *target_path) {
if (solist == NULL && !solist_init()) {
/* 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) {
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();
}
return false;
}
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;
}
strcpy(path, get_path(found));
pdg_unprotect();
set_size(found, 0);
soinfo_free(found);
pdg_protect();
LOGD("Successfully dropped so path for: %s", path);
/* 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 +206,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

@@ -35,7 +35,7 @@ struct pdg {
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);
/*
INFO: When dlopen'ing a library, the system will increment 1 to a global

View File

@@ -7,4 +7,6 @@ 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);
void clean_trace(const char *path, void **module_addrs, size_t module_addrs_length, size_t load, size_t unload, bool spoof_maps);
extern "C" void send_seccomp_event();

View File

@@ -159,17 +159,43 @@ 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 < local_map->size; i++) {
if (local_map->maps[i].path == NULL) continue;
const char *filename = position_after(local_map->maps[i].path, '/');
if (strcmp(filename, "libdl.so") == 0) {
libdl_path = local_map->maps[i].path;
/* INFO: If we had found libc.so too, no need to continue searching */
if (libc_path) break;
continue;
}
if (strcmp(filename, "libc.so") == 0) {
libc_path = local_map->maps[i].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");
dlopen_addr = find_func_addr(local_map, map, "/system/bin/linker", "__dl_dlopen");
#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 +224,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");
dlerror_addr = find_func_addr(local_map, map, "/system/bin/linker", "__dl_dlerror");
#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 +251,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 +275,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 +286,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 +299,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");
dlsym_addr = find_func_addr(local_map, map, "/system/bin/linker", "__dl_dlsym");
#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");
@@ -370,7 +403,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 +438,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

@@ -313,20 +313,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 +522,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 +558,13 @@ void wait_for_trace(int pid, int *status, int flags) {
exit(1);
}
if (!WIFSTOPPED(*status)) {
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);

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

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;
@@ -104,7 +105,7 @@ bool _apatch_get_package_config(struct packages_config *restrict config) {
return false;
}
strtok(line, ",");
config->configs[config->size].process = strdup(strtok(line, ","));
char *exclude_str = strtok(NULL, ",");
if (exclude_str == NULL) continue;
@@ -128,6 +129,10 @@ bool _apatch_get_package_config(struct packages_config *restrict config) {
}
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);
}
@@ -155,7 +160,7 @@ 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);
@@ -174,6 +179,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) {
@@ -100,7 +101,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,6 +19,12 @@
#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 version = 0;
@@ -42,6 +48,26 @@ void ksu_get_existence(struct root_impl_state *state) {
}
state->state = Supported;
char mode[16] = { 0 };
prctl((signed int)KERNEL_SU_OPTION, CMD_HOOK_MODE, mode, 0, 0);
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. */
int reply_ok = 0;
prctl((signed int)KERNEL_SU_OPTION, CMD_GET_MANAGER_UID, 0, 0, &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 +94,24 @@ 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) {
uid_t manager_uid = 0;
prctl(KERNEL_SU_OPTION, CMD_GET_MANAGER_UID, &manager_uid, NULL, NULL);
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;
@@ -436,7 +437,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 +448,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;
}
@@ -603,115 +602,57 @@ bool umount_root(struct root_impl impl) {
return false;
}
switch (impl.impl) {
case None: { break; }
case Multiple: { break; }
/* 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 if (impl.impl == APatch) strcpy(source_name, "APatch");
else strcpy(source_name, "magisk");
case KernelSU:
case APatch: {
char source_name[LONGEST_ROOT_IMPL_NAME];
if (impl.impl == KernelSU) strcpy(source_name, "KSU");
else strcpy(source_name, "APatch");
LOGI("[%s] Unmounting root", source_name);
LOGI("[%s] Unmounting root", source_name);
const char **targets_to_unmount = NULL;
size_t num_targets = 0;
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];
for (size_t i = 0; i < mounts.length; i++) {
struct mountinfo mount = mounts.mounts[i];
bool should_unmount = false;
/* 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 &&
strncmp(mount.target, "/system/etc/", strlen("/system/etc/")) != 0) continue;
bool should_unmount = false;
if (strcmp(mount.source, source_name) == 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;
/* INFO: KernelSU has its own /system mounts, so we only skip the mount
if they are from a module, not KSU itself.
*/
if (strncmp(mount.target, "/system/", strlen("/system/")) == 0 &&
strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0) continue;
if (!should_unmount) 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;
num_targets++;
targets_to_unmount = realloc(targets_to_unmount, num_targets * sizeof(char*));
if (targets_to_unmount == NULL) {
LOGE("[%s] Failed to allocate memory for targets_to_unmount\n", source_name);
if (!should_unmount) continue;
num_targets++;
targets_to_unmount = realloc(targets_to_unmount, num_targets * sizeof(char*));
if (targets_to_unmount == NULL) {
LOGE("[%s] Failed to allocate memory for targets_to_unmount\n", source_name);
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("[%s] Failed to unmount %s: %s\n", source_name, target, strerror(errno));
} else {
LOGI("[%s] Unmounted %s\n", source_name, target);
}
}
free(targets_to_unmount);
free_mounts(&mounts);
break;
return false;
}
case Magisk: {
LOGI("[Magisk] Unmounting root");
const char **targets_to_unmount = NULL;
size_t num_targets = 0;
targets_to_unmount[num_targets - 1] = mount.target;
}
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;
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 {
LOGI("[%s] Unmounted %s\n", source_name, target);
}
}
free(targets_to_unmount);
free_mounts(&mounts);
@@ -844,7 +785,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,14 @@ 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];
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);
@@ -130,6 +133,8 @@ 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) {
@@ -355,6 +360,9 @@ void zygiskd_start(char *restrict argv[]) {
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);