diff --git a/.github/scripts/telegram_url.py b/.github/scripts/telegram_url.py new file mode 100644 index 00000000..e869690a --- /dev/null +++ b/.github/scripts/telegram_url.py @@ -0,0 +1,25 @@ +import json +import os +import urllib.parse + +url = f'https://api.telegram.org/bot{os.environ["BOT_TOKEN"]}' +url += f'/sendMediaGroup?chat_id={urllib.parse.quote(os.environ["CHANNEL_ID"])}&media=' + +# https://core.telegram.org/bots/api#markdownv2-style +msg = os.environ["COMMIT_MESSAGE"] +for c in ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']: + msg = msg.replace(c, f'\\{c}') +commit_url = os.environ["COMMIT_URL"] +commit_id = os.environ["COMMIT_ID"][:7] + +caption = f"[{commit_id}]({commit_url})\n{msg}"[:1024] + +data = json.dumps([ + {"type": "document", "media": "attach://Release"}, + {"type": "document", "media":"attach://Debug"}, + {"type": "document", "media": "attach://ReleaseSymbol"}, + {"type": "document", "media": "attach://DebugSymbol","caption": caption,"parse_mode":"MarkdownV2"} + ]) + +url += urllib.parse.quote(data) +print(url) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ad99e23..b1a1ee6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,11 @@ name: CI on: workflow_dispatch: + inputs: + post_telegram: + description: 'Post to Telegram' + required: true + type: boolean push: branches: [ master ] tags: [ v* ] @@ -75,6 +80,12 @@ jobs: debugName=`ls module/build/outputs/release/Zygisk-Next-v*-debug.zip | awk -F '(/|.zip)' '{print $5}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT unzip module/build/outputs/release/Zygisk-Next-v*-release.zip -d zksu-release unzip module/build/outputs/release/Zygisk-Next-v*-debug.zip -d zksu-debug + releaseSymbolName="SYMBOLS-$releaseName.zip" + debugSymbolName="SYMBOLS-$debugName.zip" + echo "releaseSymbolName=$releaseSymbolName" >> $GITHUB_OUTPUT + echo "debugSymbolName=$debugSymbolName" >> $GITHUB_OUTPUT + zip -r $releaseSymbolName zygiskd/build/symbols/release + zip -r $debugSymbolName zygiskd/build/symbols/debug - name: Upload release uses: actions/upload-artifact@v3 @@ -87,3 +98,34 @@ jobs: with: name: ${{ steps.prepareArtifact.outputs.debugName }} path: "./zksu-debug/*" + + - name: Upload release symbols + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.prepareArtifact.outputs.releaseName }}-symbols + path: "zygiskd/build/symbols/release" + + - name: Upload debug symbols + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.prepareArtifact.outputs.debugName }}-symbols + path: "zygiskd/build/symbols/debug" + + - name: Post to channel + if: ${{ success() && github.event_name != 'pull_request' && github.ref == 'refs/heads/master' && github.ref_type != 'tag' && inputs.post_telegram != 'false' }} + env: + CHANNEL_ID: ${{ secrets.CHANNEL_ID }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + COMMIT_URL: ${{ github.event.head_commit.url }} + COMMIT_ID: ${{ github.event.head_commit.id }} + run: | + if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then + OUTPUT="module/build/outputs/release" + export Release=$(find $OUTPUT -name "Zygisk-Next-v*-release.zip") + export Debug=$(find $OUTPUT -name "Zygisk-Next-v*-debug.zip") + export ReleaseSymbol="${{ steps.prepareArtifact.outputs.releaseSymbolName }}" + export DebugSymbol="${{ steps.prepareArtifact.outputs.debugSymbolName }}" + URL=$(python3 .github/scripts/telegram_url.py) + curl -v "$URL" -F Release="@$Release" -F Debug="@$Debug" -F ReleaseSymbol="@$ReleaseSymbol" -F DebugSymbol="@$DebugSymbol" + fi diff --git a/.gitmodules b/.gitmodules index c226d7f2..340c7329 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "loader/src/external/lsplt"] path = loader/src/external/lsplt url = https://github.com/LSPosed/lsplt -[submodule "loader/src/external/parallel-hashmap"] - path = loader/src/external/parallel-hashmap - url = https://github.com/greg7mdp/parallel-hashmap diff --git a/build.gradle.kts b/build.gradle.kts index 28fbafad..e98b9c11 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ val minKsudVersion by extra(10942) val maxKsuVersion by extra(20000) val minMagiskVersion by extra(26300) -val androidMinSdkVersion by extra(29) +val androidMinSdkVersion by extra(26) val androidTargetSdkVersion by extra(34) val androidCompileSdkVersion by extra(34) val androidBuildToolsVersion by extra("34.0.0") diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index 7e0b3655..aa352b4e 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -5,6 +5,10 @@ plugins { alias(libs.plugins.agp.lib) } +val verCode: Int by rootProject.extra +val verName: String by rootProject.extra +val commitHash: String by rootProject.extra + fun Project.findInPath(executable: String, property: String): String? { val pathEnv = System.getenv("PATH") return pathEnv.split(File.pathSeparator).map { folder -> @@ -15,12 +19,27 @@ fun Project.findInPath(executable: String, property: String): String? { }?.absolutePath ?: properties.getOrDefault(property, null) as? String? } -val ccachePatch by lazy { +val ccachePath by lazy { project.findInPath("ccache", "ccache.path")?.also { println("loader: Use ccache: $it") } } +val defaultCFlags = arrayOf( + "-Wall", "-Wextra", + "-fno-rtti", "-fno-exceptions", + "-fno-stack-protector", "-fomit-frame-pointer", + "-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__" +) + +val releaseFlags = arrayOf( + "-Oz", "-flto", + "-Wno-unused", "-Wno-unused-parameter", + "-fvisibility=hidden", "-fvisibility-inlines-hidden", + "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", + "-Wl,--exclude-libs,ALL", "-Wl,--gc-sections", "-Wl,--strip-all" +) + android { buildFeatures { androidResources = false @@ -28,16 +47,33 @@ android { prefab = true } - externalNativeBuild.ndkBuild { - path("src/Android.mk") + externalNativeBuild.cmake { + path("src/CMakeLists.txt") } defaultConfig { - externalNativeBuild { - ndkBuild { - ccachePatch?.let { - arguments += "NDK_CCACHE=$it" - } + externalNativeBuild.cmake { + arguments += "-DANDROID_STL=none" + arguments += "-DLSPLT_STANDALONE=ON" + cFlags("-std=c18", *defaultCFlags) + cppFlags("-std=c++20", *defaultCFlags) + ccachePath?.let { + arguments += "-DNDK_CCACHE=$it" + } + } + } + + buildTypes { + debug { + externalNativeBuild.cmake { + arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-debug" + } + } + release { + externalNativeBuild.cmake { + cFlags += releaseFlags + cppFlags += releaseFlags + arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-release" } } } diff --git a/loader/src/Android.mk b/loader/src/Android.mk deleted file mode 100644 index 2846d1f9..00000000 --- a/loader/src/Android.mk +++ /dev/null @@ -1,26 +0,0 @@ -LOCAL_PATH := $(call my-dir) -define walk - $(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e))) -endef - -include $(CLEAR_VARS) -LOCAL_MODULE := common -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/common)) -LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) -LOCAL_STATIC_LIBRARIES := cxx -LOCAL_LDLIBS := -llog -include $(BUILD_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := zygisk -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector)) -LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) -LOCAL_STATIC_LIBRARIES := cxx common liblsplt libphmap -LOCAL_LDLIBS := -llog -include $(BUILD_SHARED_LIBRARY) - -$(call import-module,prefab/cxx) - -include src/external/Android.mk diff --git a/loader/src/Application.mk b/loader/src/Application.mk deleted file mode 100644 index 9ac9783e..00000000 --- a/loader/src/Application.mk +++ /dev/null @@ -1,14 +0,0 @@ -APP_CFLAGS := -Wall -Wextra -APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer -APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__ -APP_CPPFLAGS := -std=c++20 -APP_CONLYFLAGS := -std=c18 -APP_STL := none - -ifneq ($(NDK_DEBUG),1) -APP_CFLAGS += -Oz -flto -APP_CFLAGS += -Wno-unused -Wno-unused-parameter -APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden -APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables -APP_LDFLAGS += -Wl,--exclude-libs,ALL -flto -Wl,--gc-sections -Wl,--strip-all -endif diff --git a/loader/src/CMakeLists.txt b/loader/src/CMakeLists.txt new file mode 100644 index 00000000..c8dc73a9 --- /dev/null +++ b/loader/src/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.22.1) +project("loader") + +find_package(cxx REQUIRED CONFIG) + +add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\") + +aux_source_directory(common COMMON_SRC_LIST) +add_library(common STATIC ${COMMON_SRC_LIST}) +target_include_directories(common PRIVATE include) +target_link_libraries(common cxx::cxx log) + +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) + +aux_source_directory(ptracer PTRACER_SRC_LIST) +add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST}) +target_include_directories(libzygisk_ptrace.so PRIVATE include) +target_link_libraries(libzygisk_ptrace.so cxx::cxx log common) + +add_subdirectory(external) diff --git a/loader/src/common/daemon.cpp b/loader/src/common/daemon.cpp index cc25fd00..b0965739 100644 --- a/loader/src/common/daemon.cpp +++ b/loader/src/common/daemon.cpp @@ -7,6 +7,11 @@ #include "socket_utils.h" namespace zygiskd { + static std::string zygisk_path; + void Init(const char *path) { + LOGI("zygisk path set to %s", path); + zygisk_path = path; + } int Connect(uint8_t retry) { int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); @@ -14,14 +19,17 @@ namespace zygiskd { .sun_family = AF_UNIX, .sun_path={0}, }; - strcpy(addr.sun_path, kCPSocketPath); + auto socket_path = zygisk_path + kCPSocketName; + strcpy(addr.sun_path, socket_path.c_str()); socklen_t socklen = sizeof(addr); while (retry--) { int r = connect(fd, reinterpret_cast(&addr), socklen); if (r == 0) return fd; - LOGW("Retrying to connect to zygiskd, sleep 1s"); - sleep(1); + if (retry) { + PLOGE("Retrying to connect to zygiskd, sleep 1s"); + sleep(1); + } } close(fd); @@ -93,7 +101,7 @@ namespace zygiskd { } int GetModuleDir(size_t index) { - int fd = Connect(1); + UniqueFd fd = Connect(1); if (fd == -1) { PLOGE("GetModuleDir"); return -1; @@ -102,4 +110,19 @@ namespace zygiskd { socket_utils::write_usize(fd, index); return socket_utils::recv_fd(fd); } + + void ZygoteRestart() { + UniqueFd fd = Connect(1); + if (fd == -1) { + if (errno == ENOENT) { + LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)"); + } else { + PLOGE("Could not notify ZygoteRestart"); + } + return; + } + if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) { + PLOGE("Failed to request ZygoteRestart"); + } + } } diff --git a/loader/src/common/dl.cpp b/loader/src/common/dl.cpp index eb5f7d23..2acd0a81 100644 --- a/loader/src/common/dl.cpp +++ b/loader/src/common/dl.cpp @@ -52,7 +52,7 @@ void* DlopenMem(int fd, int flags) { auto* handle = android_dlopen_ext("/jit-cache", flags, &info); if (handle) { - LOGD("dlopen fd %d: %p", fd, handle); + LOGV("dlopen fd %d: %p", fd, handle); } else { LOGE("dlopen fd %d: %s", fd, dlerror()); } diff --git a/loader/src/injector/files.cpp b/loader/src/common/files.cpp similarity index 100% rename from loader/src/injector/files.cpp rename to loader/src/common/files.cpp diff --git a/loader/src/injector/misc.cpp b/loader/src/common/misc.cpp similarity index 100% rename from loader/src/injector/misc.cpp rename to loader/src/common/misc.cpp diff --git a/loader/src/common/socket_utils.cpp b/loader/src/common/socket_utils.cpp index 73dd1d24..d4a9f1d5 100644 --- a/loader/src/common/socket_utils.cpp +++ b/loader/src/common/socket_utils.cpp @@ -19,12 +19,12 @@ namespace socket_utils { read_sz += ret; } while (read_sz != count && ret != 0); if (read_sz != count) { - PLOGE("read (%d != %d)", count, read_sz); + PLOGE("read (%zu != %zu)", count, read_sz); } return read_sz; } - ssize_t xwrite(int fd, const void* buf, size_t count) { + size_t xwrite(int fd, const void* buf, size_t count) { size_t write_sz = 0; ssize_t ret; do { @@ -32,12 +32,12 @@ namespace socket_utils { if (ret < 0) { if (errno == EINTR) continue; PLOGE("write"); - return ret; + return write_sz; } write_sz += ret; } while (write_sz != count && ret != 0); if (write_sz != count) { - PLOGE("write (%d != %d)", count, write_sz); + PLOGE("write (%zu != %zu)", count, write_sz); } return write_sz; } diff --git a/loader/src/external/Android.mk b/loader/src/external/Android.mk deleted file mode 100644 index 43e76d11..00000000 --- a/loader/src/external/Android.mk +++ /dev/null @@ -1,21 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -# liblsplt.a -include $(CLEAR_VARS) -LOCAL_MODULE:= liblsplt -LOCAL_C_INCLUDES := $(LOCAL_PATH)/lsplt/lsplt/src/main/jni/include -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) -LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -DLOG_DISABLED -LOCAL_CPPFLAGS := -std=c++20 -LOCAL_STATIC_LIBRARIES := libcxx -LOCAL_SRC_FILES := \ - lsplt/lsplt/src/main/jni/elf_util.cc \ - lsplt/lsplt/src/main/jni/lsplt.cc -include $(BUILD_STATIC_LIBRARY) - -# Header only library -include $(CLEAR_VARS) -LOCAL_MODULE:= libphmap -LOCAL_CFLAGS := -Wno-unused-value -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap -include $(BUILD_STATIC_LIBRARY) diff --git a/loader/src/external/CMakeLists.txt b/loader/src/external/CMakeLists.txt new file mode 100644 index 00000000..8f8d2e33 --- /dev/null +++ b/loader/src/external/CMakeLists.txt @@ -0,0 +1,8 @@ +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) diff --git a/loader/src/external/parallel-hashmap b/loader/src/external/parallel-hashmap deleted file mode 160000 index 55725dbe..00000000 --- a/loader/src/external/parallel-hashmap +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 55725dbe7047d54e3cbeccf7c4fb27f6603e0f2e diff --git a/loader/src/include/api.hpp b/loader/src/include/api.hpp index ab616a97..e4a00bd3 100644 --- a/loader/src/include/api.hpp +++ b/loader/src/include/api.hpp @@ -161,6 +161,7 @@ namespace zygisk { jobjectArray *const whitelisted_data_info_list; jboolean *const mount_data_dirs; jboolean *const mount_storage_dirs; + jboolean *const mount_sysprop_overrides; AppSpecializeArgs() = delete; }; diff --git a/loader/src/include/daemon.h b/loader/src/include/daemon.h index 2a177f3e..f44eb891 100644 --- a/loader/src/include/daemon.h +++ b/loader/src/include/daemon.h @@ -11,7 +11,9 @@ # define LP_SELECT(lp32, lp64) lp32 #endif -constexpr auto kCPSocketPath = "/dev/zygisk/" LP_SELECT("cp32", "cp64") ".sock"; +constexpr auto kCPSocketName = "/" LP_SELECT("cp32", "cp64") ".sock"; +constexpr const auto MAGIC_PATH_ENV = "MAGIC_PATH"; +constexpr const auto MAGIC_ENV = "MAGIC"; class UniqueFd { using Fd = int; @@ -58,8 +60,11 @@ namespace zygiskd { ReadModules, RequestCompanionSocket, GetModuleDir, + ZygoteRestart, }; + void Init(const char *path); + bool PingHeartbeat(); int RequestLogcatFd(); @@ -71,4 +76,6 @@ namespace zygiskd { int ConnectCompanion(size_t index); int GetModuleDir(size_t index); + + void ZygoteRestart(); } diff --git a/loader/src/injector/files.hpp b/loader/src/include/files.hpp similarity index 100% rename from loader/src/injector/files.hpp rename to loader/src/include/files.hpp diff --git a/loader/src/injector/misc.hpp b/loader/src/include/misc.hpp similarity index 100% rename from loader/src/injector/misc.hpp rename to loader/src/include/misc.hpp diff --git a/loader/src/include/socket_utils.h b/loader/src/include/socket_utils.h index b2775d79..09330de9 100644 --- a/loader/src/include/socket_utils.h +++ b/loader/src/include/socket_utils.h @@ -9,7 +9,7 @@ namespace socket_utils { ssize_t xread(int fd, void *buf, size_t count); - ssize_t xwrite(int fd, const void *buf, size_t count); + size_t xwrite(int fd, const void *buf, size_t count); uint8_t read_u8(int fd); diff --git a/loader/src/injector/art_method.hpp b/loader/src/injector/art_method.hpp new file mode 100644 index 00000000..461d9dc0 --- /dev/null +++ b/loader/src/injector/art_method.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "jni_helper.hpp" + +template +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(reinterpret_cast(this) + data_offset); + } + + static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { + if (art_method_field) [[likely]] { + return reinterpret_cast( + JNI_GetLongField(env, method, art_method_field)); + } else { + return reinterpret_cast(env->FromReflectedMethod(method)); + } + } + + static bool Init(JNIEnv *env) { + ScopedLocalRef 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); + jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors", + "()[Ljava/lang/reflect/Constructor;"); + const auto constructors = + JNI_Cast(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(second) - reinterpret_cast(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 diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp index e87bf8ef..0abbffa2 100644 --- a/loader/src/injector/entry.cpp +++ b/loader/src/injector/entry.cpp @@ -8,10 +8,11 @@ using namespace std; void *self_handle = nullptr; extern "C" [[gnu::visibility("default")]] -void entry(void* handle) { - LOGI("Zygisk library injected"); +void entry(void* handle, const char* path) { + LOGI("Zygisk library injected, version %s", ZKSU_VERSION); self_handle = handle; + zygiskd::Init(path); if (!zygiskd::PingHeartbeat()) { LOGE("Zygisk daemon is not running"); return; @@ -21,6 +22,6 @@ void entry(void* handle) { logging::setfd(zygiskd::RequestLogcatFd()); #endif - LOGD("Start hooking"); + LOGI("Start hooking"); hook_functions(); } diff --git a/loader/src/injector/gen_jni_hooks.py b/loader/src/injector/gen_jni_hooks.py new file mode 100644 index 00000000..ac0544ae --- /dev/null +++ b/loader/src/injector/gen_jni_hooks.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 + +# keep sync with https://github.com/topjohnwu/Magisk/blob/master/native/src/core/zygisk/gen_jni_hooks.py + +primitives = ['jint', 'jboolean', 'jlong'] + +class JType: + def __init__(self, cpp, jni): + self.cpp = cpp + self.jni = jni + + +class JArray(JType): + def __init__(self, type): + if type.cpp in primitives: + name = type.cpp + 'Array' + else: + name = 'jobjectArray' + super().__init__(name, '[' + type.jni) + + +class Argument: + def __init__(self, name, type, set_arg = False): + self.name = name + self.type = type + self.set_arg = set_arg + + def cpp(self): + return f'{self.type.cpp} {self.name}' + +# Args we don't care, give it an auto generated name +class Anon(Argument): + cnt = 0 + def __init__(self, type): + super().__init__(f'_{Anon.cnt}', type) + Anon.cnt += 1 + +class Return: + def __init__(self, value, type): + self.value = value + self.type = type + +class Method: + def __init__(self, name, ret, args): + self.name = name + self.ret = ret + self.args = args + + def cpp(self): + return ', '.join(map(lambda x: x.cpp(), self.args)) + + def name_list(self): + return ', '.join(map(lambda x: x.name, self.args)) + + def jni(self): + args = ''.join(map(lambda x: x.type.jni, self.args)) + return f'({args}){self.ret.type.jni}' + + def body(self): + return '' + +class JNIHook(Method): + def __init__(self, ver, ret, args): + name = f'{self.base_name()}_{ver}' + super().__init__(name, ret, args) + + def base_name(self): + return '' + + def orig_method(self): + return f'reinterpret_cast({self.base_name()}_orig)' + +def ind(i): + return '\n' + ' ' * i + +# Common types +jint = JType('jint', 'I') +jintArray = JArray(jint) +jstring = JType('jstring', 'Ljava/lang/String;') +jboolean = JType('jboolean', 'Z') +jlong = JType('jlong', 'J') +void = JType('void', 'V') + +class ForkAndSpec(JNIHook): + def __init__(self, ver, args): + super().__init__(ver, Return('ctx.pid', jint), args) + + def base_name(self): + return 'nativeForkAndSpecialize' + + def init_args(self): + return 'AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);' + + def body(self): + decl = '' + decl += ind(1) + self.init_args() + for a in self.args: + if a.set_arg: + decl += ind(1) + f'args.{a.name} = &{a.name};' + decl += ind(1) + 'ZygiskContext ctx(env, &args);' + decl += ind(1) + f'ctx.{self.base_name()}_pre();' + decl += ind(1) + self.orig_method() + '(' + decl += ind(2) + f'env, clazz, {self.name_list()}' + decl += ind(1) + ');' + decl += ind(1) + f'ctx.{self.base_name()}_post();' + return decl + +class SpecApp(ForkAndSpec): + def __init__(self, ver, args): + super().__init__(ver, args) + self.ret = Return('', void) + + def base_name(self): + return 'nativeSpecializeAppProcess' + +class ForkServer(ForkAndSpec): + def base_name(self): + return 'nativeForkSystemServer' + + def init_args(self): + return 'ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);' + +# Common args +uid = Argument('uid', jint) +gid = Argument('gid', jint) +gids = Argument('gids', jintArray) +runtime_flags = Argument('runtime_flags', jint) +rlimits = Argument('rlimits', JArray(jintArray)) +mount_external = Argument('mount_external', jint) +se_info = Argument('se_info', jstring) +nice_name = Argument('nice_name', jstring) +fds_to_close = Argument('fds_to_close', jintArray) +instruction_set = Argument('instruction_set', jstring) +app_data_dir = Argument('app_data_dir', jstring) + +# o +fds_to_ignore = Argument('fds_to_ignore', jintArray, True) + +# p +is_child_zygote = Argument('is_child_zygote', jboolean, True) + +# q_alt +is_top_app = Argument('is_top_app', jboolean, True) + +# r +pkg_data_info_list = Argument('pkg_data_info_list', JArray(jstring), True) +whitelisted_data_info_list = Argument('whitelisted_data_info_list', JArray(jstring), True) +mount_data_dirs = Argument('mount_data_dirs', jboolean, True) +mount_storage_dirs = Argument('mount_storage_dirs', jboolean, True) + +# u +mount_sysprop_overrides = Argument('mount_sysprop_overrides', jboolean, True) + +# server +permitted_capabilities = Argument('permitted_capabilities', jlong) +effective_capabilities = Argument('effective_capabilities', jlong) + +# Method definitions +fas_l = ForkAndSpec('l', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, nice_name, fds_to_close, instruction_set, app_data_dir]) + +fas_o = ForkAndSpec('o', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) + +fas_p = ForkAndSpec('p', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir]) + +fas_q_alt = ForkAndSpec('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app]) + +fas_r = ForkAndSpec('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, + pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs]) + +fas_u = ForkAndSpec('u', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, + pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides]) + +fas_samsung_m = ForkAndSpec('samsung_m', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir]) + +fas_samsung_n = ForkAndSpec('samsung_n', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir, Anon(jint)]) + +fas_samsung_o = ForkAndSpec('samsung_o', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) + +fas_samsung_p = ForkAndSpec('samsung_p', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, is_child_zygote, + instruction_set, app_data_dir]) + +spec_q = SpecApp('q', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, is_child_zygote, instruction_set, app_data_dir]) + +spec_q_alt = SpecApp('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app]) + +spec_r = SpecApp('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, + is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, + whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs]) + +spec_u = SpecApp('u', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, + is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, + whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides]) + +spec_samsung_q = SpecApp('samsung_q', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, is_child_zygote, instruction_set, app_data_dir]) + +server_l = ForkServer('l', [uid, gid, gids, runtime_flags, rlimits, + permitted_capabilities, effective_capabilities]) + +server_samsung_q = ForkServer('samsung_q', [uid, gid, gids, runtime_flags, Anon(jint), Anon(jint), rlimits, + permitted_capabilities, effective_capabilities]) + +hook_map = {} + +def gen_jni_def(clz, methods): + if clz not in hook_map: + hook_map[clz] = [] + + decl = '' + for m in methods: + decl += ind(0) + f'[[clang::no_stack_protector]] {m.ret.type.cpp} {m.name}(JNIEnv *env, jclass clazz, {m.cpp()}) {{' + decl += m.body() + if m.ret.value: + decl += ind(1) + f'return {m.ret.value};' + decl += ind(0) + '}' + + decl += ind(0) + f'std::array {m.base_name()}_methods = {{' + for m in methods: + decl += ind(1) + 'JNINativeMethod {' + decl += ind(2) + f'"{m.base_name()}",' + decl += ind(2) + f'"{m.jni()}",' + decl += ind(2) + f'(void *) &{m.name}' + decl += ind(1) + '},' + decl += ind(0) + '};' + decl = ind(0) + f'void *{m.base_name()}_orig = nullptr;' + decl + decl += ind(0) + + hook_map[clz].append(m.base_name()) + + return decl + +with open('jni_hooks.hpp', 'w') as f: + f.write('// Generated by gen_jni_hooks.py\n') + f.write('\nnamespace {\n') + + zygote = 'com/android/internal/os/Zygote' + + methods = [fas_l, fas_o, fas_p, fas_q_alt, fas_r, fas_u, fas_samsung_m, fas_samsung_n, fas_samsung_o, fas_samsung_p] + f.write(gen_jni_def(zygote, methods)) + + methods = [spec_q, spec_q_alt, spec_r, spec_u, spec_samsung_q] + f.write(gen_jni_def(zygote, methods)) + + methods = [server_l, server_samsung_q] + f.write(gen_jni_def(zygote, methods)) + + f.write('\n} // namespace\n') + + f.write(""" +static void do_hook_zygote(JNIEnv *env) { + vector hooks; + const char *clz; + clz = "com/android/internal/os/Zygote"; + hookJniNativeMethods(env, clz, nativeForkAndSpecialize_methods.data(), nativeForkAndSpecialize_methods.size()); + for (auto &method : nativeForkAndSpecialize_methods) { + if (method.fnPtr) { + nativeForkAndSpecialize_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + hookJniNativeMethods(env, clz, nativeSpecializeAppProcess_methods.data(), nativeSpecializeAppProcess_methods.size()); + for (auto &method : nativeSpecializeAppProcess_methods) { + if (method.fnPtr) { + nativeSpecializeAppProcess_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + hookJniNativeMethods(env, clz, nativeForkSystemServer_methods.data(), nativeForkSystemServer_methods.size()); + for (auto &method : nativeForkSystemServer_methods) { + if (method.fnPtr) { + nativeForkSystemServer_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + jni_hook_list->emplace(clz, std::move(hooks)); +} +""") diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 382dbf9c..ddf3ea86 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -4,29 +4,30 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include "dl.h" #include "daemon.h" #include "zygisk.hpp" -#include "memory.hpp" #include "module.hpp" #include "files.hpp" +#include "misc.hpp" + +#include "art_method.hpp" using namespace std; -using jni_hook::hash_map; -using jni_hook::tree_map; -using xstring = jni_hook::string; static void hook_unloader(); static void unhook_functions(); -static void restore_jni_env(JNIEnv *env); namespace { @@ -47,12 +48,12 @@ void name##_post(); #define MAX_FD_SIZE 1024 -struct HookContext; +struct ZygiskContext; // Current context -HookContext *g_ctx; +ZygiskContext *g_ctx; -struct HookContext { +struct ZygiskContext { JNIEnv *env; union { void *ptr; @@ -85,17 +86,12 @@ struct HookContext { vector register_info; vector ignore_info; - HookContext(JNIEnv *env, void *args) : + ZygiskContext(JNIEnv *env, void *args) : env(env), args{args}, process(nullptr), pid(-1), info_flags(0), hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { - static bool restored_env = false; - if (!restored_env) { - restore_jni_env(env); - restored_env = true; - } g_ctx = this; } - ~HookContext(); + ~ZygiskContext(); /* Zygisksu changed: Load module fds */ void run_modules_pre(); @@ -123,98 +119,12 @@ struct HookContext { // Global variables vector> *plt_hook_list; map, StringCmp> *jni_hook_list; -hash_map>> *jni_method_map; bool should_unmap_zygisk = false; -const JNINativeInterface *old_functions = nullptr; -JNINativeInterface *new_functions = nullptr; - } // namespace -#define HOOK_JNI(method) \ -if (methods[i].name == #method##sv) { \ - int j = 0; \ - for (; j < method##_methods_num; ++j) { \ - if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \ - jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \ - method##_orig = methods[i].fnPtr; \ - newMethods[i] = method##_methods[j]; \ - LOGI("replaced %s#" #method "\n", className); \ - --hook_cnt; \ - break; \ - } \ - } \ - if (j == method##_methods_num) { \ - LOGE("unknown signature of %s#" #method ": %s\n", className, methods[i].signature); \ - } \ - continue; \ -} - -// JNI method hook definitions, auto generated -#include "jni_hooks.hpp" - -#undef HOOK_JNI - namespace { -string get_class_name(JNIEnv *env, jclass clazz) { - static auto class_getName = env->GetMethodID(env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;"); - auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName); - const char *name = env->GetStringUTFChars(nameRef, nullptr); - string className(name); - env->ReleaseStringUTFChars(nameRef, name); - std::replace(className.begin(), className.end(), '.', '/'); - return className; -} - -jint env_RegisterNatives( - JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { - auto className = get_class_name(env, clazz); - LOGV("JNIEnv->RegisterNatives [%s]\n", className.data()); - auto newMethods = hookAndSaveJNIMethods(className.data(), methods, numMethods); - return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods); -} - -void replace_jni_methods() { - auto get_created_java_vms = reinterpret_cast( - dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); - if (!get_created_java_vms) { - for (auto &map: lsplt::MapInfo::Scan()) { - if (!map.path.ends_with("/libnativehelper.so")) continue; - void *h = dlopen(map.path.data(), RTLD_LAZY); - if (!h) { - LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror()); - break; - } - get_created_java_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); - dlclose(h); - break; - } - if (!get_created_java_vms) { - LOGW("JNI_GetCreatedJavaVMs not found\n"); - return; - } - } - JavaVM *vm = nullptr; - jsize num = 0; - jint res = get_created_java_vms(&vm, 1, &num); - if (res != JNI_OK || vm == nullptr) return; - JNIEnv *env = nullptr; - res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (res != JNI_OK || env == nullptr) return; - default_new(new_functions); - memcpy(new_functions, env->functions, sizeof(*new_functions)); - new_functions->RegisterNatives = &env_RegisterNatives; - - // Replace the function table in JNIEnv to hook RegisterNatives - old_functions = env->functions; - env->functions = new_functions; - - // Re-run register_com_android_internal_os_Zygote to hook JNI methods - auto register_zygote = dlsym(RTLD_DEFAULT, "_ZN7android39register_com_android_internal_os_ZygoteEP7_JNIEnv"); - reinterpret_cast(register_zygote)(env); -} - #define DCL_HOOK_FUNC(ret, func, ...) \ ret (*old_##func)(__VA_ARGS__); \ ret new_##func(__VA_ARGS__) @@ -270,7 +180,7 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { if (gettid() != getpid()) return res; - LOGD("pthread_attr_destroy\n"); + LOGV("pthread_attr_destroy\n"); if (should_unmap_zygisk) { unhook_functions(); if (should_unmap_zygisk) { @@ -284,44 +194,119 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { return res; } +void initialize_jni_hook(); + +DCL_HOOK_FUNC(char *, strdup, const char *s) { + if (s == "com.android.internal.os.ZygoteInit"sv) { + LOGV("strdup %s\n", s); + initialize_jni_hook(); + } + return old_strdup(s); +} + #undef DCL_HOOK_FUNC // ----------------------------------------------------------------- +static bool can_hook_jni = false; +static jint MODIFIER_NATIVE = 0; +static jmethodID member_getModifiers = nullptr; + void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { - auto class_map = jni_method_map->find(clz); - if (class_map == jni_method_map->end()) { - for (int i = 0; i < numMethods; ++i) { + if (!can_hook_jni) return; + auto clazz = env->FindClass(clz); + if (clazz == nullptr) { + env->ExceptionClear(); + for (int i = 0; i < numMethods; i++) { methods[i].fnPtr = nullptr; } return; } vector hooks; - for (int i = 0; i < numMethods; ++i) { - auto method_map = class_map->second.find(methods[i].name); - if (method_map != class_map->second.end()) { - auto it = method_map->second.find(methods[i].signature); - if (it != method_map->second.end()) { - // Copy the JNINativeMethod - hooks.push_back(methods[i]); - // Save the original function pointer - methods[i].fnPtr = it->second; - // Do not allow double hook, remove method from map - method_map->second.erase(it); - continue; + for (int i = 0; i < numMethods; i++) { + auto &nm = methods[i]; + auto mid = env->GetMethodID(clazz, nm.name, nm.signature); + bool is_static = false; + if (mid == nullptr) { + env->ExceptionClear(); + mid = env->GetStaticMethodID(clazz, nm.name, nm.signature); + is_static = true; + } + if (mid == nullptr) { + env->ExceptionClear(); + nm.fnPtr = nullptr; + LOGE("Could not found jni method in class %s: %s(%s)", clz, nm.name, nm.signature); + continue; + } + auto method = lsplant::JNI_ToReflectedMethod(env, clazz, mid, is_static); + auto modifier = lsplant::JNI_CallIntMethod(env, method, member_getModifiers); + if ((modifier & MODIFIER_NATIVE) == 0) { + nm.fnPtr = nullptr; + LOGE("Don't hook method because it's not native method: %s: %s(%s)", clz, nm.name, nm.signature); + continue; + } + auto artMethod = lsplant::art::ArtMethod::FromReflectedMethod(env, method); + hooks.push_back(nm); + auto orig = artMethod->GetData(); + LOGV("replaced %s %s orig %p", clz, nm.name, orig); + nm.fnPtr = orig; + } + + if (hooks.empty()) return; + env->RegisterNatives(clazz, hooks.data(), hooks.size()); +} + +// JNI method hook definitions, auto generated +#include "jni_hooks.hpp" + +void initialize_jni_hook() { + auto get_created_java_vms = reinterpret_cast( + dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); + if (!get_created_java_vms) { + for (auto &map: lsplt::MapInfo::Scan()) { + if (!map.path.ends_with("/libnativehelper.so")) continue; + void *h = dlopen(map.path.data(), RTLD_LAZY); + if (!h) { + LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror()); + break; } + get_created_java_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); + dlclose(h); + break; + } + if (!get_created_java_vms) { + LOGW("JNI_GetCreatedJavaVMs not found\n"); + return; } - // No matching method found, set fnPtr to null - methods[i].fnPtr = nullptr; } + JavaVM *vm = nullptr; + jsize num = 0; + jint res = get_created_java_vms(&vm, 1, &num); + if (res != JNI_OK || vm == nullptr) return; + JNIEnv *env = nullptr; + res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (res != JNI_OK || env == nullptr) return; - if (hooks.empty()) + 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"); + if (classModifier != nullptr) { + auto fieldId = lsplant::JNI_GetStaticFieldID(env, classModifier, "NATIVE", "I"); + if (fieldId != nullptr) MODIFIER_NATIVE = lsplant::JNI_GetStaticIntField(env, classModifier, fieldId); + } + if (member_getModifiers == nullptr || MODIFIER_NATIVE == 0) return; + if (!lsplant::art::ArtMethod::Init(env)) { + LOGE("failed to init ArtMethod"); return; + } - old_functions->RegisterNatives(env, env->FindClass(clz), hooks.data(), static_cast(hooks.size())); + can_hook_jni = true; + do_hook_zygote(env); } +// ----------------------------------------------------------------- + ZygiskModule::ZygiskModule(int id, void *handle, void *entry) : id(id), handle(handle), entry{entry}, api{}, mod{nullptr} { // Make sure all pointers are null @@ -372,7 +357,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { return true; } -void HookContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) { +void ZygiskContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) { if (regex == nullptr || symbol == nullptr || fn == nullptr) return; regex_t re; @@ -382,7 +367,7 @@ void HookContext::plt_hook_register(const char *regex, const char *symbol, void register_info.emplace_back(RegisterInfo{re, symbol, fn, backup}); } -void HookContext::plt_hook_exclude(const char *regex, const char *symbol) { +void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) { if (!regex) return; regex_t re; if (regcomp(&re, regex, REG_NOSUB) != 0) @@ -391,7 +376,7 @@ void HookContext::plt_hook_exclude(const char *regex, const char *symbol) { ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""}); } -void HookContext::plt_hook_process_regex() { +void ZygiskContext::plt_hook_process_regex() { if (register_info.empty()) return; for (auto &map : lsplt::MapInfo::Scan()) { @@ -415,7 +400,7 @@ void HookContext::plt_hook_process_regex() { } } -bool HookContext::plt_hook_commit() { +bool ZygiskContext::plt_hook_commit() { { mutex_guard lock(hook_info_lock); plt_hook_process_regex(); @@ -477,7 +462,7 @@ int sigmask(int how, int signum) { return sigprocmask(how, &set, nullptr); } -void HookContext::fork_pre() { +void ZygiskContext::fork_pre() { // Do our own fork before loading any 3rd party code // First block SIGCHLD, unblock after original fork is done sigmask(SIG_BLOCK, SIGCHLD); @@ -499,7 +484,7 @@ void HookContext::fork_pre() { allowed_fds[dirfd(dir.get())] = false; } -void HookContext::sanitize_fds() { +void ZygiskContext::sanitize_fds() { if (flags[SKIP_FD_SANITIZATION]) return; @@ -555,14 +540,14 @@ void HookContext::sanitize_fds() { } } -void HookContext::fork_post() { +void ZygiskContext::fork_post() { // Unblock SIGCHLD in case the original method didn't sigmask(SIG_UNBLOCK, SIGCHLD); g_ctx = nullptr; } /* Zygisksu changed: Load module fds */ -void HookContext::run_modules_pre() { +void ZygiskContext::run_modules_pre() { auto ms = zygiskd::ReadModules(); auto size = ms.size(); for (size_t i = 0; i < size; i++) { @@ -583,7 +568,7 @@ void HookContext::run_modules_pre() { } } -void HookContext::run_modules_post() { +void ZygiskContext::run_modules_post() { flags[POST_SPECIALIZE] = true; for (const auto &m : modules) { if (flags[APP_SPECIALIZE]) { @@ -596,14 +581,14 @@ void HookContext::run_modules_post() { } /* Zygisksu changed: Load module fds */ -void HookContext::app_specialize_pre() { +void ZygiskContext::app_specialize_pre() { flags[APP_SPECIALIZE] = true; info_flags = zygiskd::GetProcessFlags(g_ctx->args.app->uid); run_modules_pre(); } -void HookContext::app_specialize_post() { +void ZygiskContext::app_specialize_post() { run_modules_post(); // Cleanups @@ -612,7 +597,7 @@ void HookContext::app_specialize_post() { logging::setfd(-1); } -bool HookContext::exempt_fd(int fd) { +bool ZygiskContext::exempt_fd(int fd) { if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION]) return true; if (!flags[APP_FORK_AND_SPECIALIZE]) @@ -623,7 +608,7 @@ bool HookContext::exempt_fd(int fd) { // ----------------------------------------------------------------- -void HookContext::nativeSpecializeAppProcess_pre() { +void ZygiskContext::nativeSpecializeAppProcess_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); LOGV("pre specialize [%s]\n", process); // App specialize does not check FD @@ -631,13 +616,13 @@ void HookContext::nativeSpecializeAppProcess_pre() { app_specialize_pre(); } -void HookContext::nativeSpecializeAppProcess_post() { +void ZygiskContext::nativeSpecializeAppProcess_post() { LOGV("post specialize [%s]\n", process); app_specialize_post(); } /* Zygisksu changed: No system_server status write back */ -void HookContext::nativeForkSystemServer_pre() { +void ZygiskContext::nativeForkSystemServer_pre() { LOGV("pre forkSystemServer\n"); flags[SERVER_FORK_AND_SPECIALIZE] = true; @@ -650,7 +635,7 @@ void HookContext::nativeForkSystemServer_pre() { sanitize_fds(); } -void HookContext::nativeForkSystemServer_post() { +void ZygiskContext::nativeForkSystemServer_post() { if (pid == 0) { LOGV("post forkSystemServer\n"); run_modules_post(); @@ -658,7 +643,7 @@ void HookContext::nativeForkSystemServer_post() { fork_post(); } -void HookContext::nativeForkAndSpecialize_pre() { +void ZygiskContext::nativeForkAndSpecialize_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); LOGV("pre forkAndSpecialize [%s]\n", process); @@ -675,7 +660,7 @@ void HookContext::nativeForkAndSpecialize_pre() { sanitize_fds(); } -void HookContext::nativeForkAndSpecialize_post() { +void ZygiskContext::nativeForkAndSpecialize_post() { if (pid == 0) { LOGV("post forkAndSpecialize [%s]\n", process); app_specialize_post(); @@ -683,7 +668,7 @@ void HookContext::nativeForkAndSpecialize_post() { fork_post(); } -HookContext::~HookContext() { +ZygiskContext::~ZygiskContext() { // This global pointer points to a variable on the stack. // Set this to nullptr to prevent leaking local variable. // This also disables most plt hooked functions. @@ -716,12 +701,6 @@ HookContext::~HookContext() { } // namespace -static void restore_jni_env(JNIEnv *env) { - env->functions = old_functions; - delete new_functions; - new_functions = nullptr; -} - static bool hook_commit() { if (lsplt::CommitHook()) { return true; @@ -748,7 +727,6 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ void hook_functions() { default_new(plt_hook_list); default_new(jni_hook_list); - default_new(jni_method_map); ino_t android_runtime_inode = 0; dev_t android_runtime_dev = 0; @@ -762,6 +740,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_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); hook_commit(); @@ -770,8 +749,6 @@ void hook_functions() { std::remove_if(plt_hook_list->begin(), plt_hook_list->end(), [](auto &t) { return *std::get<3>(t) == nullptr;}), plt_hook_list->end()); - - replace_jni_methods(); } static void hook_unloader() { diff --git a/loader/src/injector/jni_helper.hpp b/loader/src/injector/jni_helper.hpp new file mode 100644 index 00000000..fef32b98 --- /dev/null +++ b/loader/src/injector/jni_helper.hpp @@ -0,0 +1,1116 @@ +#pragma once + +#include +#include + +#include +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-partial-specialization" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection" + +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &) = delete; \ + void operator=(const TypeName &) = delete + +namespace lsplant { + template class> + struct is_instance : public std::false_type {}; + + template class U> + struct is_instance, U> : public std::true_type {}; + + template class U> + inline constexpr bool is_instance_v = is_instance::value; + + template + concept JObject = std::is_base_of_v, std::remove_pointer_t>; + + template + class ScopedLocalRef { + public: + using BaseType [[maybe_unused]] = T; + + ScopedLocalRef(JNIEnv *env, T local_ref) : env_(env), local_ref_(nullptr) { reset(local_ref); } + + ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, s.release()) {} + + template + ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} + + explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{nullptr}) {} + + ~ScopedLocalRef() { reset(); } + + void reset(T ptr = nullptr) { + if (ptr != local_ref_) { + if (local_ref_ != nullptr) { + env_->DeleteLocalRef(local_ref_); + } + local_ref_ = ptr; + } + } + + [[nodiscard]] T release() { + T localRef = local_ref_; + local_ref_ = nullptr; + return localRef; + } + + T get() const { return local_ref_; } + + operator T() const { return local_ref_; } + + // We do not expose an empty constructor as it can easily lead to errors + // using common idioms, e.g.: + // ScopedLocalRef<...> ref; + // ref.reset(...); + // Move assignment operator. + ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept { + reset(s.release()); + env_ = s.env_; + return *this; + } + + operator bool() const { return local_ref_; } + + template + friend class ScopedLocalRef; + + friend class JUTFString; + + private: + JNIEnv *env_; + T local_ref_; + DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); + }; + + template + concept JArray = std::is_base_of_v, std::remove_pointer_t>; + + template + class ScopedLocalRef; + + class JNIScopeFrame { + JNIEnv *env_; + + DISALLOW_COPY_AND_ASSIGN(JNIScopeFrame); + + public: + JNIScopeFrame(JNIEnv *env, jint size) : env_(env) { env_->PushLocalFrame(size); } + + ~JNIScopeFrame() { env_->PopLocalFrame(nullptr); } + }; + + class JNIMonitor { + JNIEnv *env_; + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JNIMonitor); + + public: + JNIMonitor(JNIEnv *env, jobject obj) : env_(env), obj_(obj) { env_->MonitorEnter(obj_); } + + ~JNIMonitor() { env_->MonitorExit(obj_); } + }; + + template + concept ScopeOrRaw = std::is_convertible_v || + (is_instance_v, ScopedLocalRef> + &&std::is_convertible_v::BaseType, U>); + + template + concept ScopeOrClass = ScopeOrRaw; + + template + concept ScopeOrObject = ScopeOrRaw; + + inline ScopedLocalRef ClearException(JNIEnv *env) { + if (auto exception = env->ExceptionOccurred()) { + env->ExceptionClear(); + static jclass log = (jclass)env->NewGlobalRef(env->FindClass("android/util/Log")); + static jmethodID toString = env->GetStaticMethodID( + log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception); + env->DeleteLocalRef(exception); + return {env, str}; + } + return {env, nullptr}; + } + + template + [[maybe_unused]] inline auto UnwrapScope(T &&x) { + if constexpr (std::is_same_v, std::string_view>) + return x.data(); + else if constexpr (is_instance_v, ScopedLocalRef>) + return x.get(); + else + return std::forward(x); + } + + template + [[maybe_unused]] inline auto WrapScope(JNIEnv *env, T &&x) { + if constexpr (std::is_convertible_v) { + return ScopedLocalRef(env, std::forward(x)); + } else + return x; + } + + template + [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple &&x, + std::index_sequence) { + return std::make_tuple(WrapScope(env, std::forward(std::get(x)))...); + } + + template + [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple &&x) { + return WrapScope(env, std::forward>(x), + std::make_index_sequence()); + } + + inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) { + return ScopedLocalRef(env, env->NewStringUTF(sv.data())); + } + + class JUTFString { + public: + inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {} + + inline JUTFString(const ScopedLocalRef &jstr) + : JUTFString(jstr.env_, jstr.local_ref_, nullptr) {} + + inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) + : env_(env), jstr_(jstr) { + if (env_ && jstr_) + cstr_ = env_->GetStringUTFChars(jstr, nullptr); + else + cstr_ = default_cstr; + } + + inline operator const char *() const { return cstr_; } + + inline operator const std::string() const { return cstr_; } + + inline operator const bool() const { return cstr_ != nullptr; } + + inline auto get() const { return cstr_; } + + inline ~JUTFString() { + if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_); + } + + JUTFString(JUTFString &&other) + : env_(std::move(other.env_)), + jstr_(std::move(other.jstr_)), + cstr_(std::move(other.cstr_)) { + other.cstr_ = nullptr; + } + + JUTFString &operator=(JUTFString &&other) { + if (&other != this) { + env_ = std::move(other.env_); + jstr_ = std::move(other.jstr_); + cstr_ = std::move(other.cstr_); + other.cstr_ = nullptr; + } + return *this; + } + + private: + JNIEnv *env_; + jstring jstr_; + const char *cstr_; + + JUTFString(const JUTFString &) = delete; + + JUTFString &operator=(const JUTFString &) = delete; + }; + + template + requires(std::is_function_v) + [[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) { + struct finally { + finally(JNIEnv *env) : env_(env) {} + + ~finally() { + if (auto exception = ClearException(env_)) { + __android_log_print(ANDROID_LOG_ERROR, +#ifdef LOG_TAG + LOG_TAG, +#else + "JNIHelper", +#endif + "%s", JUTFString(env_, exception.get()).get()); + } + } + + JNIEnv *env_; + } _(env); + + if constexpr (!std::is_same_v(args)))...>>) + return WrapScope(env, (env->*f)(UnwrapScope(std::forward(args))...)); + else + (env->*f)(UnwrapScope(std::forward(args))...); + } + +// functions to class + + [[maybe_unused]] inline auto JNI_FindClass(JNIEnv *env, std::string_view name) { + return JNI_SafeInvoke(env, &JNIEnv::FindClass, name); + } + + template + [[maybe_unused]] inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) { + return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj); + } + +// functions to field + + template + [[maybe_unused]] inline auto JNI_GetFieldID(JNIEnv *env, Class &&clazz, std::string_view name, + std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, std::forward(clazz), name, sig); + } + +// getters + + template + [[maybe_unused]] inline auto JNI_GetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetBooleanField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetBooleanField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetByteField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetByteField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetCharField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetCharField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetShortField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetShortField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetIntField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetIntField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetLongField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetLongField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetFloatField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetFloatField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetDoubleField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetDoubleField, std::forward(obj), fieldId); + } + +// setters + + template + [[maybe_unused]] inline auto JNI_SetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId, + const Value &value) { + return JNI_SafeInvoke(env, &JNIEnv::SetObjectField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetBooleanField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jboolean value) { + return JNI_SafeInvoke(env, &JNIEnv::SetBooleanField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetByteField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jbyte value) { + return JNI_SafeInvoke(env, &JNIEnv::SetByteField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetCharField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jchar value) { + return JNI_SafeInvoke(env, &JNIEnv::SetCharField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetShortField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jshort value) { + return JNI_SafeInvoke(env, &JNIEnv::SetShortField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetIntField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jint value) { + return JNI_SafeInvoke(env, &JNIEnv::SetIntField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetLongField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jlong value) { + return JNI_SafeInvoke(env, &JNIEnv::SetLongField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetFloatField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jfloat value) { + return JNI_SafeInvoke(env, &JNIEnv::SetFloatField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetDoubleField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jdouble value) { + return JNI_SafeInvoke(env, &JNIEnv::SetDoubleField, std::forward(obj), fieldId, value); + } + +// functions to static field + + template + [[maybe_unused]] inline auto JNI_GetStaticFieldID(JNIEnv *env, Class &&clazz, std::string_view name, + std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticFieldID, std::forward(clazz), name, sig); + } + +// getters + + template + [[maybe_unused]] inline auto JNI_GetStaticObjectField(JNIEnv *env, Class &&clazz, + jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticBooleanField(JNIEnv *env, Class &&clazz, + jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticBooleanField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticByteField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticByteField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticCharField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticCharField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticShortField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticShortField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticIntField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticLongField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticLongField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticFloatField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticFloatField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticDoubleField(JNIEnv *env, Class &&clazz, + jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticDoubleField, std::forward(clazz), fieldId); + } + +// setters + + template + [[maybe_unused]] inline auto JNI_SetStaticObjectField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + const Object &value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticObjectField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticBooleanField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jboolean value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticBooleanField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticByteField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jbyte value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticByteField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticCharField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jchar value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticCharField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticShortField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jshort value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticShortField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticIntField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jint value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticIntField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticLongField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jlong value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticLongField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticFloatField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jfloat value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticFloatField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticDoubleField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jdouble value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticDoubleField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_ToReflectedMethod(JNIEnv *env, Class &&clazz, jmethodID method, + jboolean isStatic = JNI_FALSE) { + return JNI_SafeInvoke(env, &JNIEnv::ToReflectedMethod, std::forward(clazz), method, + isStatic); + } + +// functions to method + +// virtual methods + + template + [[maybe_unused]] inline auto JNI_GetMethodID(JNIEnv *env, Class &&clazz, std::string_view name, + std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetMethodID, std::forward(clazz), name, sig); + } + + template + [[maybe_unused]] inline auto JNI_CallVoidMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallVoidMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallObjectMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallBooleanMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallBooleanMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallByteMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallByteMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCharMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallCharMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallShortMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallShortMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallIntMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallLongMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallLongMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallFloatMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallFloatMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallDoubleMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallDoubleMethod, std::forward(obj), method, + std::forward(args)...); + } + +// static methods + + template + [[maybe_unused]] inline auto JNI_GetStaticMethodID(JNIEnv *env, Class &&clazz, + std::string_view name, std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticMethodID, std::forward(clazz), name, sig); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticVoidMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticVoidMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticObjectMethod(JNIEnv *env, Class &&clazz, + jmethodID method, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticObjectMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, Class &&clazz, + jmethodID method, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticByteMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticByteMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticCharMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticCharMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticShortMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticShortMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticIntMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticLongMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticLongMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticFloatMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticFloatMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticDoubleMethod(JNIEnv *env, Class &&clazz, + jmethodID method, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticDoubleMethod, std::forward(clazz), method, + std::forward(args)...); + } + +// non-vritual methods + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualObjectMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualByteMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualCharMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualShortMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualIntMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualLongMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_NewObject(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::NewObject, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_RegisterNatives(JNIEnv *env, Class &&clazz, + const JNINativeMethod *methods, jint size) { + return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, std::forward(clazz), methods, size); + } + + template + [[maybe_unused]] inline auto JNI_IsInstanceOf(JNIEnv *env, Object &&obj, Class &&clazz) { + return JNI_SafeInvoke(env, &JNIEnv::IsInstanceOf, std::forward(obj), + std::forward(clazz)); + } + + template + [[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) { + return (decltype(UnwrapScope(std::forward(x))))env->NewGlobalRef( + UnwrapScope(std::forward(x))); + } + + template + [[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef &&x) requires( + std::is_convertible_v) { + return ScopedLocalRef(std::move(x)); + } + + [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void *address, jlong capacity) { + return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity); + } + + template + struct JArrayUnderlyingTypeHelper; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = ScopedLocalRef; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jboolean; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jbyte; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jchar; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jshort; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jint; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jlong; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jfloat; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jdouble; + }; + + template + using JArrayUnderlyingType = typename JArrayUnderlyingTypeHelper::Type; + + template + class ScopedLocalRef { + ScopedLocalRef(JNIEnv *env, T local_ref, size_t size, JArrayUnderlyingType *elements, + bool modified) noexcept + : env_(env), local_ref_(local_ref), size_(size), elements_(elements), modified_(modified) {} + + public: + class Iterator { + friend class ScopedLocalRef; + Iterator(JArrayUnderlyingType *e) : e_(e) {} + JArrayUnderlyingType *e_; + + public: + auto &operator*() { return *e_; } + auto *operator->() { return e_; } + Iterator &operator++() { return ++e_, *this; } + Iterator &operator--() { return --e_, *this; } + Iterator operator++(int) { return Iterator(e_++); } + Iterator operator--(int) { return Iterator(e_--); } + bool operator==(const Iterator &other) const { return other.e_ == e_; } + bool operator!=(const Iterator &other) const { return other.e_ != e_; } + }; + + class ConstIterator { + friend class ScopedLocalRef; + ConstIterator(const JArrayUnderlyingType *e) : e_(e) {} + const JArrayUnderlyingType *e_; + + public: + const auto &operator*() { return *e_; } + const auto *operator->() { return e_; } + ConstIterator &operator++() { return ++e_, *this; } + ConstIterator &operator--() { return --e_, *this; } + ConstIterator operator++(int) { return ConstIterator(e_++); } + ConstIterator operator--(int) { return ConstIterator(e_--); } + bool operator==(const ConstIterator &other) const { return other.e_ == e_; } + bool operator!=(const ConstIterator &other) const { return other.e_ != e_; } + }; + + auto begin() { + modified_ = true; + return Iterator(elements_); + } + + auto end() { + modified_ = true; + return Iterator(elements_ + size_); + } + + auto begin() const { return ConstIterator(elements_); } + + auto end() const { return ConstIterator(elements_ + size_); } + + auto cbegin() const { return ConstIterator(elements_); } + + auto cend() const { return ConstIterator(elements_ + size_); } + + using BaseType [[maybe_unused]] = T; + + ScopedLocalRef(JNIEnv *env, T local_ref) noexcept : env_(env), local_ref_(nullptr) { + reset(local_ref); + } + + ScopedLocalRef(ScopedLocalRef &&s) noexcept + : ScopedLocalRef(s.env_, s.local_ref_, s.size_, s.elements_, s.modified_) { + s.local_ref_ = nullptr; + s.size_ = 0; + s.elements_ = nullptr; + s.modified_ = false; + } + + template + ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} + + explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{nullptr}) {} + + ~ScopedLocalRef() { env_->DeleteLocalRef(release()); } + + void reset(T ptr = nullptr) { + if (ptr != local_ref_) { + if (local_ref_ != nullptr) { + ReleaseElements(modified_ ? 0 : JNI_ABORT); + env_->DeleteLocalRef(local_ref_); + if constexpr (std::is_same_v) { + for (size_t i = 0; i < size_; ++i) { + elements_[i].~ScopedLocalRef(); + } + operator delete[](elements_); + } + elements_ = nullptr; + } + local_ref_ = ptr; + size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0; + if (!local_ref_) return; + if constexpr (std::is_same_v) { + elements_ = static_cast *>(operator new[]( + sizeof(ScopedLocalRef) * size_)); + for (size_t i = 0; i < size_; ++i) { + new (&elements_[i]) ScopedLocalRef( + JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, i)); + } + } else if constexpr (std::is_same_v) { + elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetByteArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetCharArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetShortArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetIntArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetLongArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetFloatArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetDoubleArrayElements(local_ref_, nullptr); + } + } + } + + [[nodiscard]] T release() { + T localRef = local_ref_; + size_ = 0; + local_ref_ = nullptr; + ReleaseElements(modified_ ? 0 : JNI_ABORT); + if constexpr (std::is_same_v) { + for (size_t i = 0; i < size_; ++i) { + elements_[i].~ScopedLocalRef(); + } + operator delete[](elements_); + } + elements_ = nullptr; + return localRef; + } + + T get() const { return local_ref_; } + + explicit operator T() const { return local_ref_; } + + JArrayUnderlyingType &operator[](size_t index) { + modified_ = true; + return elements_[index]; + } + + const JArrayUnderlyingType &operator[](size_t index) const { return elements_[index]; } + + void commit() { + ReleaseElements(JNI_COMMIT); + modified_ = false; + } + + // We do not expose an empty constructor as it can easily lead to errors + // using common idioms, e.g.: + // ScopedLocalRef<...> ref; + // ref.reset(...); + // Move assignment operator. + ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept { + env_ = s.env_; + local_ref_ = s.local_ref_; + size_ = s.size_; + elements_ = s.elements_; + modified_ = s.modified_; + s.elements_ = nullptr; + s.size_ = 0; + s.modified_ = false; + s.local_ref_ = nullptr; + return *this; + } + + size_t size() const { return size_; } + + operator bool() const { return local_ref_; } + + template + friend class ScopedLocalRef; + + friend class JUTFString; + + private: + void ReleaseElements(jint mode) { + if (!local_ref_ || !elements_) return; + if constexpr (std::is_same_v) { + for (size_t i = 0; i < size_; ++i) { + JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, local_ref_, i, elements_[i]); + } + } else if constexpr (std::is_same_v) { + env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseByteArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseCharArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseShortArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseIntArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseLongArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseFloatArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseDoubleArrayElements(local_ref_, elements_, mode); + } + } + + JNIEnv *env_; + T local_ref_; + size_t size_; + JArrayUnderlyingType *elements_{nullptr}; + bool modified_ = false; + DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); + }; + +// functions to array + + template Array> + [[maybe_unused]] inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) { + return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array); + } + +// newers + + template + [[maybe_unused]] inline auto JNI_NewObjectArray(JNIEnv *env, jsize len, Class &&clazz, + const Object &init) { + return JNI_SafeInvoke(env, &JNIEnv::NewObjectArray, len, std::forward(clazz), init); + } + + [[maybe_unused]] inline auto JNI_NewBooleanArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewBooleanArray, len); + } + + [[maybe_unused]] inline auto JNI_NewByteArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewByteArray, len); + } + + [[maybe_unused]] inline auto JNI_NewCharArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewCharArray, len); + } + + [[maybe_unused]] inline auto JNI_NewShortArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewShortArray, len); + } + + [[maybe_unused]] inline auto JNI_NewIntArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewIntArray, len); + } + + [[maybe_unused]] inline auto JNI_NewLongArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewLongArray, len); + } + + [[maybe_unused]] inline auto JNI_NewFloatArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewFloatArray, len); + } + + [[maybe_unused]] inline auto JNI_NewDoubleArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewDoubleArray, len); + } + + template + [[maybe_unused]] inline auto JNI_GetObjectFieldOf(JNIEnv *env, Object &&object, + std::string_view field_name, + std::string_view field_class) { + auto &&o = std::forward(object); + return JNI_GetObjectField( + env, o, JNI_GetFieldID(env, JNI_GetObjectClass(env, o), field_name, field_class)); + } + +} // namespace lsplant + +#undef DISALLOW_COPY_AND_ASSIGN + +#pragma clang diagnostic pop diff --git a/loader/src/injector/jni_hooks.hpp b/loader/src/injector/jni_hooks.hpp index 6ec6a958..3180cbed 100644 --- a/loader/src/injector/jni_hooks.hpp +++ b/loader/src/injector/jni_hooks.hpp @@ -5,7 +5,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_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir @@ -16,7 +16,7 @@ void *nativeForkAndSpecialize_orig = nullptr; [[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_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir @@ -28,7 +28,7 @@ void *nativeForkAndSpecialize_orig = nullptr; AppSpecializeArgs_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir @@ -41,7 +41,7 @@ void *nativeForkAndSpecialize_orig = nullptr; args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app @@ -58,7 +58,7 @@ void *nativeForkAndSpecialize_orig = nullptr; args.whitelisted_data_info_list = &whitelisted_data_info_list; args.mount_data_dirs = &mount_data_dirs; args.mount_storage_dirs = &mount_storage_dirs; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs @@ -66,9 +66,27 @@ void *nativeForkAndSpecialize_orig = nullptr; ctx.nativeForkAndSpecialize_post(); 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_v3 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; + args.pkg_data_info_list = &pkg_data_info_list; + args.whitelisted_data_info_list = &whitelisted_data_info_list; + args.mount_data_dirs = &mount_data_dirs; + args.mount_storage_dirs = &mount_storage_dirs; + args.mount_sysprop_overrides = &mount_sysprop_overrides; + ZygiskContext ctx(env, &args); + ctx.nativeForkAndSpecialize_pre(); + reinterpret_cast(nativeForkAndSpecialize_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides + ); + ctx.nativeForkAndSpecialize_post(); + 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_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir @@ -78,7 +96,7 @@ void *nativeForkAndSpecialize_orig = nullptr; } [[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_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4 @@ -89,7 +107,7 @@ void *nativeForkAndSpecialize_orig = nullptr; [[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_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir @@ -101,7 +119,7 @@ void *nativeForkAndSpecialize_orig = nullptr; AppSpecializeArgs_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir @@ -109,60 +127,64 @@ void *nativeForkAndSpecialize_orig = nullptr; ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -const JNINativeMethod nativeForkAndSpecialize_methods[] = { - { +std::array nativeForkAndSpecialize_methods = { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_l }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_p }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I", (void *) &nativeForkAndSpecialize_q_alt }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I", (void *) &nativeForkAndSpecialize_r }, - { + JNINativeMethod { + "nativeForkAndSpecialize", + "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)I", + (void *) &nativeForkAndSpecialize_u + }, + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_m }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I", (void *) &nativeForkAndSpecialize_samsung_n }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_p }, }; -constexpr int nativeForkAndSpecialize_methods_num = std::size(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_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir @@ -173,7 +195,7 @@ void *nativeSpecializeAppProcess_orig = nullptr; AppSpecializeArgs_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app @@ -188,51 +210,71 @@ void *nativeSpecializeAppProcess_orig = nullptr; args.whitelisted_data_info_list = &whitelisted_data_info_list; args.mount_data_dirs = &mount_data_dirs; args.mount_storage_dirs = &mount_storage_dirs; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs ); 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_v3 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; + args.whitelisted_data_info_list = &whitelisted_data_info_list; + args.mount_data_dirs = &mount_data_dirs; + args.mount_storage_dirs = &mount_storage_dirs; + args.mount_sysprop_overrides = &mount_sysprop_overrides; + ZygiskContext ctx(env, &args); + ctx.nativeSpecializeAppProcess_pre(); + reinterpret_cast(nativeSpecializeAppProcess_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides + ); + 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_v3 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; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _9, _10, nice_name, is_child_zygote, instruction_set, app_data_dir ); ctx.nativeSpecializeAppProcess_post(); } -const JNINativeMethod nativeSpecializeAppProcess_methods[] = { - { +std::array nativeSpecializeAppProcess_methods = { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_q }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V", (void *) &nativeSpecializeAppProcess_q_alt }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", (void *) &nativeSpecializeAppProcess_r }, - { + JNINativeMethod { + "nativeSpecializeAppProcess", + "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)V", + (void *) &nativeSpecializeAppProcess_u + }, + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_samsung_q }, }; -constexpr int nativeSpecializeAppProcess_methods_num = std::size(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); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkSystemServer_pre(); reinterpret_cast(nativeForkSystemServer_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities @@ -242,7 +284,7 @@ void *nativeForkSystemServer_orig = nullptr; } [[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); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkSystemServer_pre(); reinterpret_cast(nativeForkSystemServer_orig)( env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities @@ -250,45 +292,48 @@ void *nativeForkSystemServer_orig = nullptr; ctx.nativeForkSystemServer_post(); return ctx.pid; } -const JNINativeMethod nativeForkSystemServer_methods[] = { - { +std::array nativeForkSystemServer_methods = { + JNINativeMethod { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) &nativeForkSystemServer_l }, - { + JNINativeMethod { "nativeForkSystemServer", "(II[IIII[[IJJ)I", (void *) &nativeForkSystemServer_samsung_q }, }; -constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods); -unique_ptr hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) { - unique_ptr newMethods; - int clz_id = -1; - int hook_cnt = 0; - do { - if (className == "com/android/internal/os/Zygote"sv) { - clz_id = 0; - hook_cnt = 3; +} // namespace + +static void do_hook_zygote(JNIEnv *env) { + vector hooks; + const char *clz; + clz = "com/android/internal/os/Zygote"; + hookJniNativeMethods(env, clz, nativeForkAndSpecialize_methods.data(), nativeForkAndSpecialize_methods.size()); + for (auto &method : nativeForkAndSpecialize_methods) { + if (method.fnPtr) { + nativeForkAndSpecialize_orig = method.fnPtr; + hooks.emplace_back(method); break; } - } while (false); - if (hook_cnt) { - newMethods = make_unique(numMethods); - memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods); } - auto &class_map = (*jni_method_map)[className]; - for (int i = 0; i < numMethods; ++i) { - if (hook_cnt && clz_id == 0) { - HOOK_JNI(nativeForkAndSpecialize) - HOOK_JNI(nativeSpecializeAppProcess) - HOOK_JNI(nativeForkSystemServer) + hookJniNativeMethods(env, clz, nativeSpecializeAppProcess_methods.data(), nativeSpecializeAppProcess_methods.size()); + for (auto &method : nativeSpecializeAppProcess_methods) { + if (method.fnPtr) { + nativeSpecializeAppProcess_orig = method.fnPtr; + hooks.emplace_back(method); + break; } - class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr; } - return newMethods; + hookJniNativeMethods(env, clz, nativeForkSystemServer_methods.data(), nativeForkSystemServer_methods.size()); + for (auto &method : nativeForkSystemServer_methods) { + if (method.fnPtr) { + nativeForkSystemServer_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + jni_hook_list->emplace(clz, std::move(hooks)); } - -} // namespace diff --git a/loader/src/injector/memory.cpp b/loader/src/injector/memory.cpp deleted file mode 100644 index 1ebcf520..00000000 --- a/loader/src/injector/memory.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "memory.hpp" - -namespace jni_hook { - -// We know our minimum alignment is WORD size (size of pointer) -static constexpr size_t ALIGN = sizeof(long); - -// 4MB is more than enough -static constexpr size_t CAPACITY = (1 << 22); - -// No need to be thread safe as the initial mmap always happens on the main thread -static uint8_t *_area = nullptr; - -static std::atomic _curr = nullptr; - -void *memory_block::allocate(size_t sz) { - if (!_area) { - // Memory will not actually be allocated because physical pages are mapped in on-demand - _area = static_cast(mmap( - nullptr, CAPACITY, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - _curr = _area; - } - return _curr.fetch_add(align_to(sz, ALIGN)); -} - -void memory_block::release() { - if (_area) - munmap(_area, CAPACITY); -} - -} // namespace jni_hook diff --git a/loader/src/injector/memory.hpp b/loader/src/injector/memory.hpp deleted file mode 100644 index a48c44a0..00000000 --- a/loader/src/injector/memory.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include - -#pragma clang diagnostic push -#include -#pragma clang diagnostic pop - -#include "misc.hpp" - -namespace jni_hook { - -struct memory_block { - static void *allocate(size_t sz); - static void deallocate(void *, size_t) { /* Monotonic increase */ } - static void release(); -}; - -template -using allocator = stateless_allocator; - -using string = std::basic_string, allocator>; - -// Use node_hash_map since it will use less memory because we are using a monotonic allocator -template -using hash_map = phmap::node_hash_map, - phmap::priv::hash_default_eq, - allocator> ->; - -template -using tree_map = std::map, - allocator> ->; - -} // namespace jni_hook - -// Provide heterogeneous lookup for jni_hook::string -namespace phmap::priv { -template <> struct HashEq : StringHashEqT {}; -} // namespace phmap::priv diff --git a/loader/src/injector/module.hpp b/loader/src/injector/module.hpp index 60b2fa00..bc2ec1a8 100644 --- a/loader/src/injector/module.hpp +++ b/loader/src/injector/module.hpp @@ -5,7 +5,7 @@ namespace { - struct HookContext; + struct ZygiskContext; struct ZygiskModule; struct AppSpecializeArgs_v1; @@ -44,6 +44,7 @@ namespace { jobjectArray *whitelisted_data_info_list = nullptr; jboolean *mount_data_dirs = nullptr; jboolean *mount_storage_dirs = nullptr; + jboolean *mount_sysprop_overrides = nullptr; AppSpecializeArgs_v3( jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, diff --git a/loader/src/ptracer/main.cpp b/loader/src/ptracer/main.cpp new file mode 100644 index 00000000..bbb51854 --- /dev/null +++ b/loader/src/ptracer/main.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "main.hpp" +#include "utils.hpp" +#include "daemon.h" +#include + +using namespace std::string_view_literals; + +int main(int argc, char **argv) { + if (argc >= 2 && argv[1] == "monitor"sv) { + init_monitor(); + return 0; + } else if (argc >= 3 && argv[1] == "trace"sv) { + if (argc >= 4 && argv[3] == "--restart"sv) { + zygiskd::Init(getenv(MAGIC_PATH_ENV)); + zygiskd::ZygoteRestart(); + } + auto pid = strtol(argv[2], 0, 0); + if (!trace_zygote(pid)) { + kill(pid, SIGKILL); + return 1; + } + return 0; + } else if (argc >= 2 && argv[1] == "ctl"sv) { + if (argc == 3) { + if (argv[2] == "start"sv) { + send_control_command(START); + return 0; + } else if (argv[2] == "stop"sv) { + send_control_command(STOP); + return 0; + } else if (argv[2] == "exit"sv) { + send_control_command(EXIT); + return 0; + } + } + printf("Zygisk Next Tracer %s\n", ZKSU_VERSION); + printf("Usage: %s ctl start|stop|exit\n", argv[0]); + return 1; + } else if (argc >= 2 && argv[1] == "version"sv) { + printf("Zygisk Next Tracer %s\n", ZKSU_VERSION); + return 0; + } else { + printf("Zygisk Next Tracer %s\n", ZKSU_VERSION); + printf("usage: %s monitor | trace | ctl | version\n", argv[0]); + return 1; + } +} diff --git a/loader/src/ptracer/main.hpp b/loader/src/ptracer/main.hpp new file mode 100644 index 00000000..35de0c97 --- /dev/null +++ b/loader/src/ptracer/main.hpp @@ -0,0 +1,20 @@ +#pragma once + + +void init_monitor(); +bool trace_zygote(int pid); + +enum Command { + START = 1, + STOP = 2, + EXIT = 3, + // sent from daemon + ZYGOTE64_INJECTED = 4, + ZYGOTE32_INJECTED = 5, + DAEMON64_SET_INFO = 6, + DAEMON32_SET_INFO = 7, + DAEMON64_SET_ERROR_INFO = 8, + DAEMON32_SET_ERROR_INFO = 9, +}; + +void send_control_command(Command cmd); diff --git a/loader/src/ptracer/monitor.cpp b/loader/src/ptracer/monitor.cpp new file mode 100644 index 00000000..e919c6c6 --- /dev/null +++ b/loader/src/ptracer/monitor.cpp @@ -0,0 +1,631 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.hpp" +#include "utils.hpp" +#include "files.hpp" +#include "misc.hpp" + +using namespace std::string_view_literals; + + +#define STOPPED_WITH(sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8))) + +static void updateStatus(); + +enum TracingState { + TRACING = 1, + STOPPING, + STOPPED, + EXITING +}; + +std::string monitor_stop_reason; + +constexpr char SOCKET_NAME[] = "init_monitor"; + +std::string GetControlSocketName() { + auto env = getenv(MAGIC_ENV); + if (env == nullptr) return SOCKET_NAME; + return std::string(SOCKET_NAME) + env; +} + +struct EventLoop; + +struct EventHandler { + virtual int GetFd() = 0; + virtual void HandleEvent(EventLoop& loop, uint32_t event) = 0; +}; + +struct EventLoop { +private: + int epoll_fd_; + bool running = false; +public: + bool Init() { + epoll_fd_ = epoll_create(1); + if (epoll_fd_ == -1) { + PLOGE("failed to create"); + return false; + } + return true; + } + + void Stop() { + running = false; + } + + void Loop() { + running = true; + constexpr auto MAX_EVENTS = 2; + struct epoll_event events[MAX_EVENTS]; + while (running) { + int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1); + if (nfds == -1) { + PLOGE("epoll_wait"); + continue; + } + for (int i = 0; i < nfds; i++) { + reinterpret_cast(events[i].data.ptr)->HandleEvent(*this, + events[i].events); + if (!running) break; + } + } + } + + bool RegisterHandler(EventHandler &handler, uint32_t events) { + struct epoll_event ev{}; + ev.events = events; + ev.data.ptr = &handler; + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, handler.GetFd(), &ev) == -1) { + PLOGE("failed to add event handler"); + return false; + } + return true; + } + + bool UnregisterHandler(EventHandler &handler) { + if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, handler.GetFd(), nullptr) == -1) { + PLOGE("failed to del event handler"); + return false; + } + return true; + } + + ~EventLoop() { + if (epoll_fd_ >= 0) close(epoll_fd_); + } +}; + +static TracingState tracing_state = TRACING; + + +struct Status { + bool supported = false; + bool zygote_injected = false; + bool daemon_running = false; + pid_t daemon_pid = -1; + std::string daemon_info; + std::string daemon_error_info; +}; + +static Status status64; +static Status status32; + +struct SocketHandler : public EventHandler { + int sock_fd_; + + bool Init() { + sock_fd_ = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sock_fd_ == -1) { + PLOGE("socket create"); + return false; + } + struct sockaddr_un addr{ + .sun_family = AF_UNIX, + .sun_path={0}, + }; + auto socket_name = GetControlSocketName(); + strcpy(addr.sun_path + 1, socket_name.c_str()); + socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1; + if (bind(sock_fd_, (struct sockaddr *) &addr, socklen) == -1) { + PLOGE("bind socket"); + return false; + } + return true; + } + + int GetFd() override { + return sock_fd_; + } + + void HandleEvent(EventLoop &loop, uint32_t event) override { + struct [[gnu::packed]] MsgHead { + Command cmd; + int length; + char data[0]; + }; + for (;;) { + std::vector buf; + buf.resize(sizeof(MsgHead), 0); + MsgHead &msg = *reinterpret_cast(buf.data()); + ssize_t real_size; + auto nread = recv(sock_fd_, &msg, sizeof(msg), MSG_PEEK); + if (nread == -1) { + if (errno == EAGAIN) { + break; + } + PLOGE("read socket"); + } + if (static_cast(nread) < sizeof(Command)) { + LOGE("read %zu < %zu", nread, sizeof(Command)); + continue; + } + if (msg.cmd >= Command::DAEMON64_SET_INFO) { + if (nread != sizeof(msg)) { + LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(MsgHead)); + continue; + } + real_size = sizeof(MsgHead) + msg.length; + } else { + if (nread != sizeof(Command)) { + LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(Command)); + continue; + } + real_size = sizeof(Command); + } + buf.resize(real_size); + nread = recv(sock_fd_, &msg, real_size, 0); + if (nread == -1) { + if (errno == EAGAIN) { + break; + } + PLOGE("recv"); + continue; + } + if (nread != real_size) { + LOGE("real size %zu != %zu", real_size, nread); + continue; + } + switch (msg.cmd) { + case START: + if (tracing_state == STOPPING) { + tracing_state = TRACING; + } else if (tracing_state == STOPPED) { + ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK); + LOGI("start tracing init"); + tracing_state = TRACING; + } + updateStatus(); + break; + case STOP: + if (tracing_state == TRACING) { + LOGI("stop tracing requested"); + tracing_state = STOPPING; + monitor_stop_reason = "user requested"; + ptrace(PTRACE_INTERRUPT, 1, 0, 0); + updateStatus(); + } + break; + case EXIT: + LOGI("prepare for exit ..."); + tracing_state = EXITING; + monitor_stop_reason = "user requested"; + updateStatus(); + loop.Stop(); + break; + case ZYGOTE64_INJECTED: + status64.zygote_injected = true; + updateStatus(); + break; + case ZYGOTE32_INJECTED: + status32.zygote_injected = true; + updateStatus(); + break; + case DAEMON64_SET_INFO: + LOGD("received daemon64 info %s", msg.data); + status64.daemon_info = std::string(msg.data); + updateStatus(); + break; + case DAEMON32_SET_INFO: + LOGD("received daemon32 info %s", msg.data); + status32.daemon_info = std::string(msg.data); + updateStatus(); + break; + case DAEMON64_SET_ERROR_INFO: + LOGD("received daemon64 error info %s", msg.data); + status64.daemon_running = false; + status64.daemon_error_info = std::string(msg.data); + updateStatus(); + break; + case DAEMON32_SET_ERROR_INFO: + LOGD("received daemon32 error info %s", msg.data); + status32.daemon_running = false; + status32.daemon_error_info = std::string(msg.data); + updateStatus(); + break; + } + } + } + + ~SocketHandler() { + if (sock_fd_ >= 0) close(sock_fd_); + } +}; + +constexpr auto MAX_RETRY_COUNT = 5; + +#define CREATE_ZYGOTE_START_COUNTER(abi) \ +struct timespec last_zygote##abi{.tv_sec = 0, .tv_nsec = 0}; \ +int count_zygote##abi = 0; \ +bool should_stop_inject##abi() { \ + struct timespec now{}; \ + clock_gettime(CLOCK_MONOTONIC, &now); \ + if (now.tv_sec - last_zygote##abi.tv_sec < 30) { \ + count_zygote##abi++; \ + } else { \ + count_zygote##abi = 0; \ + } \ + last_zygote##abi = now; \ + return count_zygote##abi >= MAX_RETRY_COUNT; \ +} + +CREATE_ZYGOTE_START_COUNTER(64) +CREATE_ZYGOTE_START_COUNTER(32) + + +static bool ensure_daemon_created(bool is_64bit) { + auto &status = is_64bit ? status64 : status32; + status.zygote_injected = false; + if (status.daemon_pid == -1) { + auto pid = fork(); + if (pid < 0) { + PLOGE("create daemon (64=%s)", is_64bit ? "true" : "false"); + return false; + } else if (pid == 0) { + std::string daemon_name = "./bin/zygisk-cp"; + daemon_name += is_64bit ? "64" : "32"; + execl(daemon_name.c_str(), daemon_name.c_str(), nullptr); + PLOGE("exec daemon %s failed", daemon_name.c_str()); + exit(1); + } else { + status.supported = true; + status.daemon_pid = pid; + status.daemon_running = true; + return true; + } + } else { + return status.daemon_running; + } +} + +struct SigChldHandler : public EventHandler { +private: + int signal_fd_; + struct signalfd_siginfo fdsi; + int status; + std::set process; +public: + bool Init() { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) { + PLOGE("set sigprocmask"); + return false; + } + signal_fd_ = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (signal_fd_ == -1) { + PLOGE("create signalfd"); + return false; + } + ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK); + return true; + } + + int GetFd() override { + return signal_fd_; + } + + void HandleEvent(EventLoop &loop, uint32_t event) override { + for (;;) { + ssize_t s = read(signal_fd_, &fdsi, sizeof(fdsi)); + if (s == -1) { + if (errno == EAGAIN) break; + PLOGE("read signalfd"); + continue; + } + if (s != sizeof(fdsi)) { + LOGW("read %zu != %zu", s, sizeof(fdsi)); + continue; + } + if (fdsi.ssi_signo != SIGCHLD) { + LOGW("no sigchld received"); + continue; + } + int pid; + while ((pid = waitpid(-1, &status, __WALL | WNOHANG)) != 0) { + if (pid == -1) { + if (tracing_state == STOPPED && errno == ECHILD) break; + PLOGE("waitpid"); + } + if (pid == 1) { + if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) { + long child_pid; + ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid); + LOGV("forked %ld", child_pid); + } else if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP) && + tracing_state == STOPPING) { + if (ptrace(PTRACE_DETACH, 1, 0, 0) == -1) + PLOGE("failed to detach init"); + tracing_state = STOPPED; + LOGI("stop tracing init"); + continue; + } + if (WIFSTOPPED(status)) { + if (WPTEVENT(status) == 0) { + if (WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTTOU) { + LOGW("inject signal sent to init: %s %d", + sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); + ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)); + } else { + LOGW("suppress stopping signal sent to init: %s %d", + sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); + } + continue; + } + ptrace(PTRACE_CONT, pid, 0, 0); + } + continue; + } +#define CHECK_DAEMON_EXIT(abi) \ + if (status##abi.supported && pid == status64.daemon_pid) { \ + auto status_str = parse_status(status); \ + LOGW("daemon" #abi "pid %d exited: %s", pid, status_str.c_str()); \ + status##abi.daemon_running = false; \ + if (status##abi.daemon_error_info.empty()) { \ + status##abi.daemon_error_info = status_str; \ + } \ + updateStatus(); \ + continue; \ + } + CHECK_DAEMON_EXIT(64) + CHECK_DAEMON_EXIT(32) + auto state = process.find(pid); + if (state == process.end()) { + LOGV("new process %d attached", pid); + process.emplace(pid); + ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC); + ptrace(PTRACE_CONT, pid, 0, 0); + continue; + } else { + if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) { + auto program = get_program(pid); + LOGV("%d program %s", pid, program.c_str()); + const char* tracer = nullptr; + do { + if (tracing_state != TRACING) { + LOGW("stop injecting %d because not tracing", pid); + break; + } +#define PRE_INJECT(abi, is_64) \ + if (program == "/system/bin/app_process"#abi) { \ + tracer = "./bin/zygisk-ptrace"#abi; \ + if (should_stop_inject##abi()) { \ + LOGW("zygote" #abi " restart too much times, stop injecting"); \ + tracing_state = STOPPING; \ + 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"); \ + tracing_state = STOPPING; \ + monitor_stop_reason = "daemon not running"; \ + ptrace(PTRACE_INTERRUPT, 1, 0, 0); \ + break; \ + } \ + } + PRE_INJECT(64, true) + PRE_INJECT(32, false) + if (tracer != nullptr) { + LOGD("stopping %d", pid); + kill(pid, SIGSTOP); + ptrace(PTRACE_CONT, pid, 0, 0); + waitpid(pid, &status, __WALL); + if (STOPPED_WITH(SIGSTOP, 0)) { + LOGD("detaching %d", pid); + ptrace(PTRACE_DETACH, pid, 0, SIGSTOP); + status = 0; + auto p = fork_dont_care(); + if (p == 0) { + execl(tracer, basename(tracer), "trace", + std::to_string(pid).c_str(), "--restart", nullptr); + PLOGE("failed to exec, kill"); + kill(pid, SIGKILL); + exit(1); + } else if (p == -1) { + PLOGE("failed to fork, kill"); + kill(pid, SIGKILL); + } + } + } + } while (false); + updateStatus(); + } else { + LOGE("process %d received unknown status %s", pid, + parse_status(status).c_str()); + } + process.erase(state); + if (WIFSTOPPED(status)) { + LOGV("detach process %d", pid); + ptrace(PTRACE_DETACH, pid, 0, 0); + } + } + } + } + } + + ~SigChldHandler() { + if (signal_fd_ >= 0) close(signal_fd_); + } +}; + +static std::string prop_path; +static std::string pre_section; +static std::string post_section; + +static void updateStatus() { + auto prop = xopen_file(prop_path.c_str(), "w"); + std::string status_text = "monitor:"; + switch (tracing_state) { + case TRACING: + status_text += "😋tracing"; + break; + case STOPPING: + [[fallthrough]]; + case STOPPED: + status_text += "❌stopped"; + break; + case EXITING: + status_text += "❌exited"; + break; + } + if (tracing_state != TRACING && !monitor_stop_reason.empty()) { + status_text += "("; + status_text += monitor_stop_reason; + status_text += ")"; + } + status_text += ","; +#define WRITE_STATUS_ABI(suffix) \ + if (status##suffix.supported) { \ + status_text += " zygote" #suffix ":"; \ + if (tracing_state != TRACING) status_text += "❓unknown,"; \ + else if (status##suffix.zygote_injected) status_text += "😋injected,"; \ + else status_text += "❌not injected,"; \ + status_text += " daemon" #suffix ":"; \ + if (status##suffix.daemon_running) { \ + status_text += "😋running"; \ + if (!status##suffix.daemon_info.empty()) { \ + status_text += "("; \ + status_text += status##suffix.daemon_info; \ + status_text += ")"; \ + } \ + } else { \ + status_text += "❌crashed"; \ + if (!status##suffix.daemon_error_info.empty()) { \ + status_text += "("; \ + status_text += status##suffix.daemon_error_info; \ + status_text += ")"; \ + } \ + } \ + } + WRITE_STATUS_ABI(64) + WRITE_STATUS_ABI(32) + fprintf(prop.get(), "%s[%s] %s", pre_section.c_str(), status_text.c_str(), post_section.c_str()); +} + +static bool prepare_environment() { + auto path = getenv(MAGIC_PATH_ENV); + if (path == nullptr) { + LOGE("path is null, is MAGIC_PATH_ENV specified?"); + return false; + } + prop_path = std::string(path) + "/module.prop"; + close(open(prop_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)); + auto orig_prop = xopen_file("./module.prop", "r"); + if (orig_prop == nullptr) { + PLOGE("failed to open orig prop"); + return false; + } + bool post = false; + file_readline(false, orig_prop.get(), [&](std::string_view line) -> bool { + if (line.starts_with("description=")) { + post = true; + pre_section += "description="; + post_section += line.substr(sizeof("description")); + } else { + if (post) { + post_section += line; + } else { + pre_section += line; + } + } + return true; + }); + int old_ns; + char wd[128]; + if (getcwd(wd, sizeof(wd)) == nullptr) { + PLOGE("get cwd"); + return false; + } + if (!switch_mnt_ns(1, &old_ns)) return false; + if (chdir(wd) == -1) { + PLOGE("chdir %s", wd); + return false; + } + if (mount(prop_path.c_str(), "/data/adb/modules/zygisksu/module.prop", nullptr, MS_BIND, nullptr) == -1) { + PLOGE("failed to mount prop"); + return false; + } + if (!switch_mnt_ns(0, &old_ns)) return false; + if (chdir(wd) == -1) { + PLOGE("chdir %s", wd); + return false; + } + updateStatus(); + return true; +} + +void init_monitor() { + LOGI("Zygisk Next %s", ZKSU_VERSION); + LOGI("init monitor started"); + if (!prepare_environment()) { + exit(1); + } + SocketHandler socketHandler{}; + socketHandler.Init(); + SigChldHandler ptraceHandler{}; + ptraceHandler.Init(); + EventLoop looper; + looper.Init(); + looper.RegisterHandler(socketHandler, EPOLLIN | EPOLLET); + looper.RegisterHandler(ptraceHandler, EPOLLIN | EPOLLET); + looper.Loop(); + LOGI("exit"); +} + +void send_control_command(Command cmd) { + int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sockfd == -1) err(EXIT_FAILURE, "socket"); + struct sockaddr_un addr{ + .sun_family = AF_UNIX, + .sun_path={0}, + }; + auto socket_name = GetControlSocketName(); + strcpy(addr.sun_path + 1, socket_name.c_str()); + socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1; + auto nsend = sendto(sockfd, (void *) &cmd, sizeof(cmd), 0, (sockaddr *) &addr, socklen); + if (nsend == -1) { + err(EXIT_FAILURE, "send"); + } else if (nsend != sizeof(cmd)) { + printf("send %ld != %ld\n", nsend, sizeof(cmd)); + exit(1); + } + printf("command sent\n"); +} diff --git a/loader/src/ptracer/ptracer.cpp b/loader/src/ptracer/ptracer.cpp new file mode 100644 index 00000000..2c960614 --- /dev/null +++ b/loader/src/ptracer/ptracer.cpp @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +bool inject_on_main(int pid, const char *lib_path, const char* magic_path) { + LOGI("injecting %s to zygote %d", lib_path, pid); + // parsing KernelArgumentBlock + // https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/private/KernelArgumentBlock.h;l=30;drc=6d1ee77ee32220e4202c3066f7e1f69572967ad8 + struct user_regs_struct regs{}, backup{}; + auto map = MapInfo::Scan(std::to_string(pid)); + if (!get_regs(pid, regs)) return false; + auto arg = reinterpret_cast(regs.REG_SP); + LOGD("kernel argument %p %s", arg, get_addr_mem_region(map, arg).c_str()); + int argc; + auto argv = reinterpret_cast(reinterpret_cast(arg) + 1); + LOGD("argv %p", argv); + read_proc(pid, arg, &argc, sizeof(argc)); + LOGD("argc %d", argc); + auto envp = argv + argc + 1; + LOGD("envp %p", envp); + auto p = envp; + while (true) { + uintptr_t *buf; + read_proc(pid, (uintptr_t *) p, &buf, sizeof(buf)); + if (buf != nullptr) ++p; + else break; + } + ++p; + auto auxv = reinterpret_cast(p); + LOGD("auxv %p %s", auxv, get_addr_mem_region(map, auxv).c_str()); + auto v = auxv; + void *entry_addr = nullptr; + void *addr_of_entry_addr = nullptr; + while (true) { + ElfW(auxv_t) buf; + read_proc(pid, (uintptr_t *) v, &buf, sizeof(buf)); + if (buf.a_type == AT_ENTRY) { + entry_addr = reinterpret_cast(buf.a_un.a_val); + addr_of_entry_addr = reinterpret_cast(v) + offsetof(ElfW(auxv_t), a_un); + LOGD("entry address %p %s (v=%p, entry_addr=%p)", entry_addr, + get_addr_mem_region(map, entry_addr).c_str(), v, addr_of_entry_addr); + break; + } + if (buf.a_type == AT_NULL) break; + v++; + } + if (entry_addr == nullptr) { + LOGE("failed to get entry"); + return false; + } + + // Replace the program entry with an invalid address + // For arm32 compatibility, we set the last bit to the same as the entry address + uintptr_t break_addr = (-0x05ec1cff & ~1) | ((uintptr_t) entry_addr & 1); + if (!write_proc(pid, (uintptr_t *) addr_of_entry_addr, &break_addr, sizeof(break_addr))) return false; + ptrace(PTRACE_CONT, pid, 0, 0); + int status; + wait_for_trace(pid, &status, __WALL); + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) { + if (!get_regs(pid, regs)) return false; + if ((regs.REG_IP & ~1) != (break_addr & ~1)) { + LOGE("stopped at unknown addr %p", (void *) regs.REG_IP); + return false; + } + // The linker has been initialized now, we can do dlopen + LOGD("stopped at entry"); + + // restore entry address + if (!write_proc(pid, (uintptr_t *) addr_of_entry_addr, &entry_addr, sizeof(entry_addr))) return false; + + // backup registers + memcpy(&backup, ®s, sizeof(regs)); + map = MapInfo::Scan(std::to_string(pid)); + auto local_map = MapInfo::Scan(); + auto libc_return_addr = find_module_return_addr(map, "libc.so"); + LOGD("libc return addr %p", libc_return_addr); + + // call dlopen + auto dlopen_addr = find_func_addr(local_map, map, "libdl.so", "dlopen"); + if (dlopen_addr == nullptr) return false; + std::vector args; + auto str = push_string(pid, regs, lib_path); + args.clear(); + args.push_back((long) str); + args.push_back((long) RTLD_NOW); + auto remote_handle = remote_call(pid, regs, (uintptr_t) dlopen_addr, (uintptr_t) libc_return_addr, args); + LOGD("remote handle %p", (void *) remote_handle); + if (remote_handle == 0) { + LOGE("handle is null"); + // call dlerror + auto dlerror_addr = find_func_addr(local_map, map, "libdl.so", "dlerror"); + if (dlerror_addr == nullptr) { + LOGE("find dlerror"); + return false; + } + args.clear(); + auto dlerror_str_addr = remote_call(pid, regs, (uintptr_t) dlerror_addr, (uintptr_t) libc_return_addr, args); + LOGD("dlerror str %p", (void*) dlerror_str_addr); + if (dlerror_str_addr == 0) return false; + auto strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen"); + if (strlen_addr == nullptr) { + LOGE("find strlen"); + return false; + } + args.clear(); + args.push_back(dlerror_str_addr); + auto dlerror_len = remote_call(pid, regs, (uintptr_t) strlen_addr, (uintptr_t) libc_return_addr, args); + LOGD("dlerror len %ld", dlerror_len); + if (dlerror_len <= 0) return false; + std::string err; + err.resize(dlerror_len + 1, 0); + read_proc(pid, (uintptr_t*) dlerror_str_addr, err.data(), dlerror_len); + LOGE("dlerror info %s", err.c_str()); + return false; + } + + // call dlsym(handle, "entry") + auto dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym"); + if (dlsym_addr == nullptr) return false; + args.clear(); + str = push_string(pid, regs, "entry"); + args.push_back(remote_handle); + args.push_back((long) str); + auto injector_entry = remote_call(pid, regs, (uintptr_t) dlsym_addr, (uintptr_t) libc_return_addr, args); + LOGD("injector entry %p", (void*) injector_entry); + if (injector_entry == 0) { + LOGE("injector entry is null"); + return false; + } + + // call injector entry(handle, magic) + args.clear(); + args.push_back(remote_handle); + str = push_string(pid, regs, magic_path); + args.push_back((long) str); + remote_call(pid, regs, injector_entry, (uintptr_t) libc_return_addr, args); + + // reset pc to entry + backup.REG_IP = (long) entry_addr; + LOGD("invoke entry"); + // restore registers + if (!set_regs(pid, backup)) return false; + + return true; + } else { + LOGE("stopped by other reason: %s", parse_status(status).c_str()); + } + return false; +} + +#define STOPPED_WITH(sig, event) (WIFSTOPPED(status) && WSTOPSIG(status) == (sig) && (status >> 16) == (event)) + +bool trace_zygote(int pid) { + LOGI("start tracing %d", pid); +#define WAIT_OR_DIE wait_for_trace(pid, &status, __WALL); +#define CONT_OR_DIE \ + if (ptrace(PTRACE_CONT, pid, 0, 0) == -1) { \ + PLOGE("cont"); \ + return false; \ + } + int status; + LOGI("tracing %d (tracer %d)", pid, getpid()); + if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_EXITKILL) == -1) { + PLOGE("seize"); + return false; + } + WAIT_OR_DIE + if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) { + std::string magic_path = getenv(MAGIC_PATH_ENV); + std::string lib_path = magic_path + "/lib" LP_SELECT("", "64") "/libzygisk.so"; + if (!inject_on_main(pid, lib_path.c_str(), magic_path.c_str())) { + LOGE("failed to inject"); + return false; + } + LOGD("inject done, continue process"); + if (kill(pid, SIGCONT)) { + PLOGE("kill"); + return false; + } + CONT_OR_DIE + WAIT_OR_DIE + if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP)) { + CONT_OR_DIE + WAIT_OR_DIE + if (STOPPED_WITH(SIGCONT, 0)) { + LOGD("received SIGCONT"); + ptrace(PTRACE_DETACH, pid, 0, SIGCONT); + } + } else { + LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", parse_status(status).c_str()); + ptrace(PTRACE_DETACH, pid, 0, 0); + return false; + } + } else { + LOGE("unknown state %s, not SIGSTOP + EVENT_STOP", parse_status(status).c_str()); + ptrace(PTRACE_DETACH, pid, 0, 0); + return false; + } + return true; +} diff --git a/loader/src/ptracer/utils.cpp b/loader/src/ptracer/utils.cpp new file mode 100644 index 00000000..fdbc2ea1 --- /dev/null +++ b/loader/src/ptracer/utils.cpp @@ -0,0 +1,435 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" +#include "logging.h" +#include +#include + +bool switch_mnt_ns(int pid, int *fd) { + int nsfd, old_nsfd = -1; + std::string path; + if (pid == 0) { + if (fd != nullptr) { + nsfd = *fd; + *fd = -1; + } else return false; + path = "/proc/self/fd/"; + path += std::to_string(nsfd); + } else { + if (fd != nullptr) { + old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (old_nsfd == -1) { + PLOGE("get old nsfd"); + return false; + } + *fd = old_nsfd; + } + path = std::string("/proc/") + std::to_string(pid) + "/ns/mnt"; + nsfd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + if (nsfd == -1) { + PLOGE("open nsfd %s", path.c_str()); + close(old_nsfd); + return false; + } + } + if (setns(nsfd, CLONE_NEWNS) == -1) { + PLOGE("set ns to %s", path.c_str()); + close(nsfd); + close(old_nsfd); + return false; + } + close(nsfd); + return true; +} + +std::vector MapInfo::Scan(const std::string& pid) { + constexpr static auto kPermLength = 5; + constexpr static auto kMapEntry = 7; + std::vector info; + std::string file_name = std::string("/proc/") + pid + "/maps"; + auto maps = std::unique_ptr{fopen(file_name.c_str(), "r"), &fclose}; + if (maps) { + char *line = nullptr; + size_t len = 0; + ssize_t read; + while ((read = getline(&line, &len, maps.get())) > 0) { + line[read - 1] = '\0'; + uintptr_t start = 0; + uintptr_t end = 0; + uintptr_t off = 0; + ino_t inode = 0; + unsigned int dev_major = 0; + unsigned int dev_minor = 0; + std::array perm{'\0'}; + int path_off; + if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", &start, + &end, perm.data(), &off, &dev_major, &dev_minor, &inode, + &path_off) != kMapEntry) { + continue; + } + while (path_off < read && isspace(line[path_off])) path_off++; + auto &ref = info.emplace_back(MapInfo{start, end, 0, perm[3] == 'p', off, + static_cast(makedev(dev_major, dev_minor)), + inode, line + path_off}); + if (perm[0] == 'r') ref.perms |= PROT_READ; + if (perm[1] == 'w') ref.perms |= PROT_WRITE; + if (perm[2] == 'x') ref.perms |= PROT_EXEC; + } + free(line); + } + return info; +} + +ssize_t write_proc(int pid, uintptr_t *remote_addr, const void *buf, size_t len) { + LOGD("write to remote addr %p size %zu", remote_addr, len); + struct iovec local{ + .iov_base = (void *) buf, + .iov_len = len + }; + struct iovec remote{ + .iov_base = (void *) remote_addr, + .iov_len = len + }; + auto l = process_vm_writev(pid, &local, 1, &remote, 1, 0); + if (l == -1) { + PLOGE("process_vm_writev"); + } else if (l != len) { + LOGW("not fully written: %zu, excepted %zu", l, len); + } + return l; +} + +ssize_t read_proc(int pid, uintptr_t *remote_addr, void *buf, size_t len) { + struct iovec local{ + .iov_base = (void *) buf, + .iov_len = len + }; + struct iovec remote{ + .iov_base = (void *) remote_addr, + .iov_len = len + }; + auto l = process_vm_readv(pid, &local, 1, &remote, 1, 0); + if (l == -1) { + PLOGE("process_vm_readv"); + } else if (l != len) { + LOGW("not fully read: %zu, excepted %zu", l, len); + } + return l; +} + +bool get_regs(int pid, struct user_regs_struct ®s) { +#if defined(__x86_64__) || defined(__i386__) + if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) { + PLOGE("getregs"); + return false; + } +#elif defined(__aarch64__) || defined(__arm__) + struct iovec iov = { + .iov_base = ®s, + .iov_len = sizeof(struct user_regs_struct), + }; + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) { + PLOGE("getregs"); + return false; + } +#endif + return true; +} + +bool set_regs(int pid, struct user_regs_struct ®s) { +#if defined(__x86_64__) || defined(__i386__) + if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) { + PLOGE("setregs"); + return false; + } +#elif defined(__aarch64__) || defined(__arm__) + struct iovec iov = { + .iov_base = ®s, + .iov_len = sizeof(struct user_regs_struct), + }; + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) { + PLOGE("setregs"); + return false; + } +#endif + return true; +} + +std::string get_addr_mem_region(std::vector &info, void *addr) { + for (auto &map: info) { + if (map.start <= (uintptr_t) addr && map.end > (uintptr_t) addr) { + auto s = std::string(map.path); + s += ' '; + s += map.perms & PROT_READ ? 'r' : '-'; + s += map.perms & PROT_WRITE ? 'w' : '-'; + s += map.perms & PROT_EXEC ? 'x' : '-'; + return s; + } + } + return ""; +} + + +void *find_module_return_addr(std::vector &info, std::string_view suffix) { + for (auto &map: info) { + if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) { + return (void *) map.start; + } + } + return nullptr; +} + +void *find_module_base(std::vector &info, std::string_view suffix) { + for (auto &map: info) { + if (map.offset == 0 && map.path.ends_with(suffix)) { + return (void *) map.start; + } + } + return nullptr; +} + +void *find_func_addr( + std::vector &local_info, + std::vector &remote_info, + std::string_view module, + std::string_view func) { + auto lib = dlopen(module.data(), RTLD_NOW); + if (lib == nullptr) { + LOGE("failed to open lib %s: %s", module.data(), dlerror()); + return nullptr; + } + auto sym = reinterpret_cast(dlsym(lib, func.data())); + if (sym == nullptr) { + LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror()); + dlclose(lib); + return nullptr; + } + LOGD("sym %s: %p", func.data(), sym); + dlclose(lib); + auto local_base = reinterpret_cast(find_module_base(local_info, module)); + if (local_base == nullptr) { + LOGE("failed to find local base for module %s", module.data()); + return nullptr; + } + auto remote_base = reinterpret_cast(find_module_base(remote_info, module)); + if (remote_base == nullptr) { + LOGE("failed to find remote base for module %s", module.data()); + return nullptr; + } + LOGD("found local base %p remote base %p", local_base, remote_base); + auto addr = (sym - local_base) + remote_base; + LOGD("addr %p", addr); + return addr; +} + +void align_stack(struct user_regs_struct ®s, long preserve) { + regs.REG_SP = (regs.REG_SP - preserve) & ~0xf; +} + +void *push_string(int pid, struct user_regs_struct ®s, const char *str) { + auto len = strlen(str) + 1; + regs.REG_SP -= len; + align_stack(regs); + auto addr = reinterpret_cast(regs.REG_SP); + if (!write_proc(pid, addr, str, len)) { + LOGE("failed to write string %s", str); + } + LOGD("pushed string %p", addr); + return addr; +} + +uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, + std::vector &args) { + align_stack(regs); + LOGD("call %d args", args.size()); + for (auto &a: args) { + LOGD("arg %p", (void *) a); + } +#if defined(__x86_64__) + if (args.size() >= 1) { + regs.rdi = args[0]; + } + if (args.size() >= 2) { + regs.rsi = args[1]; + } + if (args.size() >= 3) { + regs.rdx = args[2]; + } + if (args.size() >= 4) { + regs.rcx = args[3]; + } + if (args.size() >= 5) { + regs.r8 = args[4]; + } + if (args.size() >= 6) { + regs.r9 = args[5]; + } + if (args.size() > 6) { + auto remain = (args.size() - 6) * sizeof(long); + align_stack(regs, remain); + if (!write_proc(pid, (uintptr_t *) regs.REG_SP, args.data(), remain)) { + LOGE("failed to push arguments"); + } + } + regs.REG_SP -= sizeof(long); + if (!write_proc(pid, (uintptr_t *) regs.REG_SP, &return_addr, sizeof(return_addr))) { + LOGE("failed to write return addr"); + } + regs.REG_IP = func_addr; +#elif defined(__i386__) + if (args.size() > 0) { + auto remain = (args.size()) * sizeof(long); + align_stack(regs, remain); + if (!write_proc(pid, (uintptr_t *)regs.REG_SP, args.data(), remain)) { + LOGE("failed to push arguments"); + } + } + regs.REG_SP -= sizeof(long); + if (!write_proc(pid, (uintptr_t*) regs.REG_SP, &return_addr, sizeof(return_addr))) { + LOGE("failed to write return addr"); + } + regs.REG_IP = func_addr; +#elif defined(__aarch64__) + for (int i = 0; i < args.size() && i < 8; i++) { + regs.regs[i] = args[i]; + } + if (args.size() > 8) { + auto remain = (args.size() - 8) * sizeof(long); + align_stack(regs, remain); + write_proc(pid, (uintptr_t *)regs.REG_SP, args.data(), remain); + } + regs.regs[30] = return_addr; + regs.REG_IP = func_addr; +#elif defined(__arm__) + for (int i = 0; i < args.size() && i < 4; i++) { + regs.uregs[i] = args[i]; + } + if (args.size() > 4) { + auto remain = (args.size() - 4) * sizeof(long); + align_stack(regs, remain); + write_proc(pid, (uintptr_t *)regs.REG_SP, args.data(), remain); + } + regs.uregs[14] = return_addr; + regs.REG_IP = func_addr; + constexpr auto CPSR_T_MASK = 1lu << 5; + if ((regs.REG_IP & 1) != 0) { + regs.REG_IP = regs.REG_IP & ~1; + regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK; + } else { + regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK; + } +#endif + if (!set_regs(pid, regs)) { + LOGE("failed to set regs"); + return 0; + } + ptrace(PTRACE_CONT, pid, 0, 0); + int status; + wait_for_trace(pid, &status, __WALL); + if (!get_regs(pid, regs)) { + LOGE("failed to get regs after call"); + return 0; + } + if (WSTOPSIG(status) == SIGSEGV) { + if (regs.REG_IP != return_addr) { + LOGE("wrong return addr %p", (void *) regs.REG_IP); + return 0; + } + return regs.REG_RET; + } else { + LOGE("stopped by other reason %s at addr %p", parse_status(status).c_str(), (void*) regs.REG_IP); + } + return 0; +} + +int fork_dont_care() { + auto pid = fork(); + if (pid < 0) { + PLOGE("fork 1"); + } else if (pid == 0) { + pid = fork(); + if (pid < 0) { + PLOGE("fork 2"); + } else if (pid > 0) { + exit(0); + } + } else { + int status; + waitpid(pid, &status, __WALL); + } + return pid; +} + +void wait_for_trace(int pid, int* status, int flags) { + while (true) { + auto result = waitpid(pid, status, flags); + if (result == -1) { + if (errno == EINTR) { + continue; + } else { + PLOGE("wait %d failed", pid); + exit(1); + } + } + if (!WIFSTOPPED(*status)) { + LOGE("process %d not stopped for trace: %s, exit", pid, parse_status(*status).c_str()); + exit(1); + } + return; + } +} + +std::string parse_status(int status) { + std::ostringstream os; + os << "0x" << std::hex << status << std::dec << " "; + if (WIFEXITED(status)) { + os << "exited with " << WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + os << "signaled with " << sigabbrev_np(WTERMSIG(status)) << "(" << WTERMSIG(status) << ")"; + } else if (WIFSTOPPED(status)) { + os << "stopped by "; + auto stop_sig = WSTOPSIG(status); + os << "signal=" << sigabbrev_np(stop_sig) << "(" << stop_sig << "),"; + os << "event=" << parse_ptrace_event(status); + } else { + os << "unknown"; + } + return os.str(); +} + +std::string get_program(int pid) { + std::string path = "/proc/"; + path += std::to_string(pid); + path += "/exe"; + constexpr const auto SIZE = 256; + char buf[SIZE + 1]; + auto sz = readlink(path.c_str(), buf, SIZE); + if (sz == -1) { + PLOGE("readlink /proc/%d/exe", pid); + return ""; + } + buf[sz] = 0; + return buf; +} diff --git a/loader/src/ptracer/utils.hpp b/loader/src/ptracer/utils.hpp new file mode 100644 index 00000000..14f9109a --- /dev/null +++ b/loader/src/ptracer/utils.hpp @@ -0,0 +1,124 @@ +#pragma once +#include +#include +#include + +#include "daemon.h" + +#ifdef __LP64__ +#define LOG_TAG "zygisk-ptrace64" +#else +#define LOG_TAG "zygisk-ptrace32" +#endif +#include "logging.h" + +struct MapInfo { + /// \brief The start address of the memory region. + uintptr_t start; + /// \brief The end address of the memory region. + uintptr_t end; + /// \brief The permissions of the memory region. This is a bit mask of the following values: + /// - PROT_READ + /// - PROT_WRITE + /// - PROT_EXEC + uint8_t perms; + /// \brief Whether the memory region is private. + bool is_private; + /// \brief The offset of the memory region. + uintptr_t offset; + /// \brief The device number of the memory region. + /// Major can be obtained by #major() + /// Minor can be obtained by #minor() + dev_t dev; + /// \brief The inode number of the memory region. + ino_t inode; + /// \brief The path of the memory region. + std::string path; + + /// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries. + /// This is useful to find out the inode of the library to hook. + /// \return A list of \ref MapInfo entries. + static std::vector Scan(const std::string& pid = "self"); +}; + +#if defined(__x86_64__) +#define REG_SP rsp +#define REG_IP rip +#define REG_RET rax +#elif defined(__i386__) +#define REG_SP esp +#define REG_IP eip +#define REG_RET eax +#elif defined(__aarch64__) +#define REG_SP sp +#define REG_IP pc +#define REG_RET regs[0] +#elif defined(__arm__) +#define REG_SP uregs[13] +#define REG_IP uregs[15] +#define REG_RET uregs[0] +#define user_regs_struct user_regs +#endif + +ssize_t write_proc(int pid, uintptr_t *remote_addr, const void *buf, size_t len); + +ssize_t read_proc(int pid, uintptr_t *remote_addr, void *buf, size_t len); + +bool get_regs(int pid, struct user_regs_struct ®s); + +bool set_regs(int pid, struct user_regs_struct ®s); + +std::string get_addr_mem_region(std::vector &info, void *addr); + +void *find_module_base(std::vector &info, std::string_view suffix); + +void *find_func_addr( + std::vector &local_info, + std::vector &remote_info, + std::string_view module, + std::string_view func); + +void align_stack(struct user_regs_struct ®s, long preserve = 0); + +void *push_string(int pid, struct user_regs_struct ®s, const char *str); + +uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, + std::vector &args); + +int fork_dont_care(); + +void wait_for_trace(int pid, int* status, int flags); + +std::string parse_status(int status); + +#define WPTEVENT(x) (x >> 16) + +#define CASE_CONST_RETURN(x) case x: return #x; + +inline const char* parse_ptrace_event(int status) { + status = status >> 16; + switch (status) { + CASE_CONST_RETURN(PTRACE_EVENT_FORK) + CASE_CONST_RETURN(PTRACE_EVENT_VFORK) + CASE_CONST_RETURN(PTRACE_EVENT_CLONE) + CASE_CONST_RETURN(PTRACE_EVENT_EXEC) + CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE) + CASE_CONST_RETURN(PTRACE_EVENT_EXIT) + CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP) + CASE_CONST_RETURN(PTRACE_EVENT_STOP) + default: + return "(no event)"; + } +} + +inline const char* sigabbrev_np(int sig) { + if (sig > 0 && sig < NSIG) return sys_signame[sig]; + return "(unknown)"; +} + +std::string get_program(int pid); +void *find_module_return_addr(std::vector &info, std::string_view suffix); + +// pid = 0, fd != nullptr -> set to fd +// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd +bool switch_mnt_ns(int pid, int *fd); diff --git a/module/build.gradle.kts b/module/build.gradle.kts index 48b81f0d..41d77785 100644 --- a/module/build.gradle.kts +++ b/module/build.gradle.kts @@ -34,12 +34,12 @@ androidComponents.onVariants { variant -> group = "module" dependsOn( ":loader:assemble$variantCapped", - ":zygiskd:cargoBuild", + ":zygiskd:buildAndStrip", ) into(moduleDir) from("${rootProject.projectDir}/README.md") from("$projectDir/src") { - exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh") + exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh") filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) } from("$projectDir/src") { @@ -52,7 +52,7 @@ androidComponents.onVariants { variant -> ) } from("$projectDir/src") { - include("customize.sh", "post-fs-data.sh", "service.sh") + include("customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh") val tokens = mapOf( "DEBUG" to if (buildTypeLowered == "debug") "true" else "false", "MIN_KSU_VERSION" to "$minKsuVersion", @@ -65,6 +65,7 @@ androidComponents.onVariants { variant -> } into("bin") { from(project(":zygiskd").buildDir.path + "/rustJniLibs/android") + include("**/zygiskd") } into("lib") { from("${project(":loader").buildDir}/intermediates/stripped_native_libs/$variantLowered/out/lib") diff --git a/module/src/customize.sh b/module/src/customize.sh index fb341ed9..78efb9bd 100644 --- a/module/src/customize.sh +++ b/module/src/customize.sh @@ -53,9 +53,9 @@ VERSION=$(grep_prop version "${TMPDIR}/module.prop") ui_print "- Installing Zygisk Next $VERSION" # check android -if [ "$API" -lt 29 ]; then +if [ "$API" -lt 26 ]; then ui_print "! Unsupported sdk: $API" - abort "! Minimal supported sdk is 29 (Android 10)" + abort "! Minimal supported sdk is 26 (Android 8.0)" else ui_print "- Device sdk: $API" fi @@ -99,57 +99,62 @@ ui_print "- Extracting module files" extract "$ZIPFILE" 'module.prop' "$MODPATH" extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" extract "$ZIPFILE" 'service.sh' "$MODPATH" +extract "$ZIPFILE" 'zygisk-ctl.sh' "$MODPATH" mv "$TMPDIR/sepolicy.rule" "$MODPATH" -HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true +HAS32BIT=false && [ $(getprop ro.product.cpu.abilist32) ] && HAS32BIT=true mkdir "$MODPATH/bin" -mkdir "$MODPATH/system" -mkdir "$MODPATH/system/lib64" -[ "$HAS32BIT" = true ] && mkdir "$MODPATH/system/lib" +mkdir "$MODPATH/lib" +mkdir "$MODPATH/lib64" +mv "$MODPATH/zygisk-ctl.sh" "$MODPATH/bin/zygisk-ctl" if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then if [ "$HAS32BIT" = true ]; then ui_print "- Extracting x86 libraries" extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32" - extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/system/lib" true + extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/lib" true ln -sf "zygiskd32" "$MODPATH/bin/zygisk-cp32" - ln -sf "zygiskd32" "$MODPATH/bin/zygisk-ptrace32" + extract "$ZIPFILE" 'lib/x86/libzygisk_ptrace.so' "$MODPATH/bin" true + mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32" fi ui_print "- Extracting x64 libraries" extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64" - extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/system/lib64" true - ln -sf "zygiskd64" "$MODPATH/bin/zygisk-wd" - ln -sf "zygiskd64" "$MODPATH/bin/zygisk-fuse" + extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/lib64" true ln -sf "zygiskd64" "$MODPATH/bin/zygisk-cp64" - ln -sf "zygiskd64" "$MODPATH/bin/zygisk-ptrace64" + extract "$ZIPFILE" 'lib/x86_64/libzygisk_ptrace.so' "$MODPATH/bin" true + mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64" else if [ "$HAS32BIT" = true ]; then ui_print "- Extracting arm libraries" extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32" - extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/system/lib" true + extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/lib" true ln -sf "zygiskd32" "$MODPATH/bin/zygisk-cp32" - ln -sf "zygiskd32" "$MODPATH/bin/zygisk-ptrace32" + extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_ptrace.so' "$MODPATH/bin" true + mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32" fi ui_print "- Extracting arm64 libraries" extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64" - extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/system/lib64" true - ln -sf "zygiskd64" "$MODPATH/bin/zygisk-wd" - ln -sf "zygiskd64" "$MODPATH/bin/zygisk-fuse" + extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/lib64" true ln -sf "zygiskd64" "$MODPATH/bin/zygisk-cp64" - ln -sf "zygiskd64" "$MODPATH/bin/zygisk-ptrace64" + extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_ptrace.so' "$MODPATH/bin" true + mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64" fi +ui_print "- Generating magic" +MAGIC=$(tr -dc 'a-f0-9' "$MODPATH/magic" + ui_print "- Setting permissions" set_perm_recursive "$MODPATH/bin" 0 0 0755 0755 -set_perm_recursive "$MODPATH/system/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0 -set_perm_recursive "$MODPATH/system/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0 +set_perm_recursive "$MODPATH/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0 +set_perm_recursive "$MODPATH/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0 # If Huawei's Maple is enabled, system_server is created with a special way which is out of Zygisk's control HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable) diff --git a/module/src/post-fs-data.sh b/module/src/post-fs-data.sh index 1981d285..829063a9 100644 --- a/module/src/post-fs-data.sh +++ b/module/src/post-fs-data.sh @@ -7,6 +7,11 @@ fi cd "$MODDIR" +MAGIC=$(cat ./magic) +MAGIC_PATH=/dev/zygisk_$MAGIC +export MAGIC +export MAGIC_PATH + if [ "$(which magisk)" ]; then for file in ../*; do if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then @@ -20,5 +25,25 @@ if [ "$(which magisk)" ]; then done fi +create_sys_perm() { + mkdir -p $1 + chmod 555 $1 + chcon u:object_r:system_file:s0 $1 +} + +create_sys_perm $MAGIC_PATH + +if [ -f $MODDIR/lib64/libzygisk.so ];then + create_sys_perm $MAGIC_PATH/lib64 + cp $MODDIR/lib64/libzygisk.so $MAGIC_PATH/lib64/libzygisk.so + chcon u:object_r:system_file:s0 $MAGIC_PATH/lib64/libzygisk.so +fi + +if [ -f $MODDIR/lib/libzygisk.so ];then + create_sys_perm $MAGIC_PATH/lib + cp $MODDIR/lib/libzygisk.so $MAGIC_PATH/lib/libzygisk.so + chcon u:object_r:system_file:s0 $MAGIC_PATH/lib/libzygisk.so +fi + [ "$DEBUG" = true ] && export RUST_BACKTRACE=1 -unshare -m sh -c "bin/zygisk-fuse &" +unshare -m sh -c "./bin/zygisk-ptrace64 monitor &" diff --git a/module/src/sepolicy.rule b/module/src/sepolicy.rule index 6f16b889..a99f9ef2 100644 --- a/module/src/sepolicy.rule +++ b/module/src/sepolicy.rule @@ -1,5 +1,3 @@ -deny vold fusectlfs file write - allow * tmpfs * * allow zygote appdomain_tmpfs dir * allow zygote appdomain_tmpfs file * @@ -19,3 +17,4 @@ allow zygote mnt_vendor_file dir search allow zygote system_file dir mounton allow zygote labeledfs filesystem mount allow zygote fs_type filesystem unmount +allow zygote zygote process execmem diff --git a/module/src/service.sh b/module/src/service.sh index 012a291e..12107c23 100644 --- a/module/src/service.sh +++ b/module/src/service.sh @@ -9,6 +9,9 @@ fi cd "$MODDIR" +MAGIC_PATH=/dev/zygisk_$(cat ./magic) +export MAGIC_PATH + if [ "$(which magisk)" ]; then for file in ../*; do if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then @@ -21,6 +24,3 @@ if [ "$(which magisk)" ]; then fi done fi - -[ "$DEBUG" = true ] && export RUST_BACKTRACE=1 -unshare -m sh -c "bin/zygisk-wd &" diff --git a/module/src/zygisk-ctl.sh b/module/src/zygisk-ctl.sh new file mode 100644 index 00000000..c351490d --- /dev/null +++ b/module/src/zygisk-ctl.sh @@ -0,0 +1,3 @@ +MODDIR=${0%/*}/.. +export MAGIC=$(cat $MODDIR/magic) +exec $MODDIR/bin/zygisk-ptrace64 ctl $* diff --git a/zygiskd/.cargo/config.toml b/zygiskd/.cargo/config.toml index 70d0e56a..10816b18 100644 --- a/zygiskd/.cargo/config.toml +++ b/zygiskd/.cargo/config.toml @@ -1,2 +1,3 @@ [build] target-dir = "build/intermediates/rust" +target = "aarch64-linux-android" diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index 8d7877d1..0afd2d2b 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -20,14 +20,16 @@ num_enum = "0.5" passfd = "0.1" proc-maps = "0.3" -rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread"] } +rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] } tokio = { version = "1.28", features = ["full"] } -binder = { git = "https://github.com/Kernel-SU/binder_rs", rev = "c9f2b62d6a744fd2264056c638c1b061a6a2932d" } -fuser = { git = "https://github.com/Dr-TSNG/fuser", default-features = false } -ptrace-do = { git = "https://github.com/5ec1cff/ptrace-do" } +[profile.dev] +strip = false +panic = "abort" [profile.release] -strip = true +strip = false +debug = true +panic = "abort" opt-level = "z" lto = true diff --git a/zygiskd/build.gradle.kts b/zygiskd/build.gradle.kts index 081cfc71..8d33c3f7 100644 --- a/zygiskd/build.gradle.kts +++ b/zygiskd/build.gradle.kts @@ -6,6 +6,9 @@ plugins { val minKsuVersion: Int by rootProject.extra val maxKsuVersion: Int by rootProject.extra val minMagiskVersion: Int by rootProject.extra +val verCode: Int by rootProject.extra +val verName: String by rootProject.extra +val commitHash: String by rootProject.extra android.buildFeatures { androidResources = false @@ -25,5 +28,40 @@ cargo { spec.environment("MIN_KSU_VERSION", minKsuVersion) spec.environment("MAX_KSU_VERSION", maxKsuVersion) spec.environment("MIN_MAGISK_VERSION", minMagiskVersion) + spec.environment("ZKSU_VERSION", "$verName-$verCode-$commitHash-$profile") } } + +afterEvaluate { + task("buildAndStrip") { + dependsOn(":zygiskd:cargoBuild") + val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } + doLast { + val dir = File(buildDir, "rustJniLibs/android") + val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first() + val binDir = File(prebuilt, "bin") + val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}") + symbolDir.mkdirs() + val suffix = if (prebuilt.name.contains("windows")) ".exe" else "" + val strip = File(binDir, "llvm-strip$suffix") + val objcopy = File(binDir, "llvm-objcopy$suffix") + dir.listFiles()!!.forEach { + if (!it.isDirectory) return@forEach + val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug") + symbolPath.parentFile.mkdirs() + exec { + workingDir = it + commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath) + } + exec { + workingDir = it + commandLine(strip, "--strip-all", "zygiskd") + } + exec { + workingDir = it + commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd") + } + } + } + } +} \ No newline at end of file diff --git a/zygiskd/src/companion.rs b/zygiskd/src/companion.rs new file mode 100644 index 00000000..74dc45ac --- /dev/null +++ b/zygiskd/src/companion.rs @@ -0,0 +1,72 @@ +use std::ffi::c_void; +use std::os::fd::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; +use std::thread; +use anyhow::Result; +use passfd::FdPassingExt; +use rustix::fs::fstat; +use tokio::io::AsyncWriteExt; +use crate::utils::{check_unix_socket, UnixStreamExt}; +use crate::dl; + +type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); + +pub fn entry(fd: i32) { + log::info!("companion entry fd={}", fd); + let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; + let name = stream.read_string().expect("read name"); + let library = stream.recv_fd().expect("receive library fd"); + let entry = load_module(library).expect("load module"); + unsafe { libc::close(library) }; + + let entry = match entry { + Some(entry) => { + log::debug!("Companion process created for `{name}`"); + stream.write_u8(1).expect("reply 1"); + entry + } + None => { + log::debug!("No companion entry for `{name}`"); + stream.write_u8(0).expect("reply 0"); + return (); + } + }; + + loop { + if !check_unix_socket(&stream, true) { + log::info!("Something bad happened in zygiskd, terminate companion"); + std::process::exit(0); + } + let fd = stream.recv_fd().expect("recv fd"); + log::trace!("New companion request from module `{name}` fd=`{fd}`"); + let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; + stream.write_u8(1).expect("reply success"); + thread::spawn(move || { + let st0 = fstat(&stream).expect("failed to stat stream"); + unsafe { entry(stream.as_raw_fd()); } + // Only close client if it is the same file so we don't + // accidentally close a re-used file descriptor. + // This check is required because the module companion + // handler could've closed the file descriptor already. + if let Ok(st1) = fstat(&stream) { + if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino { + std::mem::forget(stream); + } + } + }); + } +} + +fn load_module(fd: RawFd) -> Result> { + unsafe { + let path = format!("/proc/self/fd/{fd}"); + let handle = dl::dlopen(&path, libc::RTLD_NOW)?; + let symbol = std::ffi::CString::new("zygisk_companion_entry")?; + let entry = libc::dlsym(handle, symbol.as_ptr()); + if entry.is_null() { + return Ok(None); + } + let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry); + Ok(Some(fnptr)) + } +} diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index f367e90a..524e1dc0 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -9,36 +9,27 @@ use crate::lp_select; pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION"))); pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION"))); pub const MIN_MAGISK_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_MAGISK_VERSION"))); +pub const ZKSU_VERSION: &'static str = env!("ZKSU_VERSION"); #[cfg(debug_assertions)] pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace; #[cfg(not(debug_assertions))] pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info; -pub const PROP_CTL_RESTART: &str = "ctl.restart"; -pub const PATH_PCL: &str = "/system/etc/preloaded-classes"; -pub const PATH_ZYGISK_LIB: &str = concatcp!(lp_select!("/system/lib", "/system/lib64"), "/libzygisk.so"); -pub const PATH_WORK_DIR: &str = "/dev/zygisk"; // TODO: Replace with /debug_ramdisk/zygisk -pub const PATH_PROP_OVERLAY: &str = concatcp!(PATH_WORK_DIR, "/module.prop"); -pub const PATH_CP_SOCKET: &str = concatcp!(PATH_WORK_DIR, lp_select!("/cp32.sock", "/cp64.sock")); -pub const PATH_FUSE_DIR: &str = concatcp!(PATH_WORK_DIR, "/fuse"); -pub const PATH_FUSE_PCL: &str = concatcp!(PATH_FUSE_DIR, "/preloaded-classes"); +pub const PATH_CP_NAME: &str = lp_select!("/cp32.sock", "/cp64.sock"); pub const PATH_MODULES_DIR: &str = ".."; pub const PATH_MODULE_PROP: &str = "module.prop"; pub const PATH_CP_BIN32: &str = "bin/zygisk-cp32"; pub const PATH_CP_BIN64: &str = "bin/zygisk-cp64"; -pub const PATH_PTRACE_BIN32: &str = "bin/zygisk-ptrace32"; -pub const PATH_PTRACE_BIN64: &str = "bin/zygisk-ptrace64"; +pub const PATH_PT_BIN32: &str = "bin/zygisk-ptracer32"; +pub const PATH_PT_BIN64: &str = "bin/zygisk-ptracer64"; +pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4); +pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6); +pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8); - -pub const STATUS_LOADED: &str = "😋 Zygisk Next is loaded"; -pub const STATUS_CRASHED: &str = "❌ Zygisk Next has crashed"; -pub const STATUS_ROOT_IMPL_NONE: &str = "❌ Unknown root implementation"; -pub const STATUS_ROOT_IMPL_TOO_OLD: &str = "❌ Root implementation version too old"; -pub const STATUS_ROOT_IMPL_ABNORMAL: &str = "❌ Abnormal root implementation version"; -pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations installed"; +pub const MAX_RESTART_COUNT: i32 = 5; #[derive(Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] @@ -49,6 +40,7 @@ pub enum DaemonSocketAction { ReadModules, RequestCompanionSocket, GetModuleDir, + ZygoteRestart, } // Zygisk process flags diff --git a/zygiskd/src/fuse.rs b/zygiskd/src/fuse.rs deleted file mode 100644 index 5862a83b..00000000 --- a/zygiskd/src/fuse.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::cmp::min; -use anyhow::{bail, Result}; -use std::ffi::OsStr; -use std::{fs, thread}; -use std::io::Read; -use std::process::{Command, Stdio}; -use std::sync::{mpsc, Mutex}; -use std::time::{Duration, SystemTime}; -use fuser::{FileAttr, Filesystem, FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyOpen, Request}; -use libc::ENOENT; -use log::{error, info}; -use rustix::fs::UnmountFlags; -use rustix::mount::{mount_bind, unmount}; -use rustix::path::Arg; -use crate::constants; -use crate::utils::LateInit; - -pub struct DelegateFilesystem; - -const fn attr(inode: u64, size: u64, kind: FileType) -> FileAttr { - FileAttr { - ino: inode, - size, - blocks: 0, - atime: SystemTime::UNIX_EPOCH, - mtime: SystemTime::UNIX_EPOCH, - ctime: SystemTime::UNIX_EPOCH, - crtime: SystemTime::UNIX_EPOCH, - kind, - perm: 0o644, - nlink: 0, - uid: 0, - gid: 0, - rdev: 0, - blksize: 0, - flags: 0, - } -} - -const INO_DIR: u64 = 1; -const INO_PCL: u64 = 2; - -static ATTR_DIR: FileAttr = attr(INO_DIR, 0, FileType::Directory); -static ATTR_PCL: LateInit = LateInit::new(); - -static PCL_CONTENT: LateInit> = LateInit::new(); - -const ENTRIES: &[(u64, FileType, &str)] = &[ - (INO_DIR, FileType::Directory, "."), - (INO_DIR, FileType::Directory, ".."), - (INO_PCL, FileType::RegularFile, "preloaded-classes"), -]; - -const TTL: Duration = Duration::from_secs(1); - -fn ptrace_zygote64(pid: u32) -> Result<()> { - static LAST: Mutex = Mutex::new(0); - - let mut last = LAST.lock().unwrap(); - if *last == pid { - return Ok(()); - } - *last = pid; - let (sender, receiver) = mpsc::channel::<()>(); - - let worker = move || -> Result<()> { - let mut child = Command::new(constants::PATH_PTRACE_BIN64).stdout(Stdio::piped()).arg(format!("{}", pid)).spawn()?; - child.stdout.as_mut().unwrap().read_exact(&mut [0u8; 1])?; - info!("child attached"); - sender.send(())?; - let result = child.wait()?; - info!("ptrace64 process status {}", result); - Ok(()) - }; - - thread::spawn(move || { - if let Err(e) = worker() { - error!("Crashed: {:?}", e); - } - }); - - receiver.recv()?; - Ok(()) -} - -fn ptrace_zygote32(pid: u32) -> Result<()> { - static LAST: Mutex = Mutex::new(0); - - let mut last = LAST.lock().unwrap(); - if *last == pid { - return Ok(()); - } - *last = pid; - let (sender, receiver) = mpsc::channel::<()>(); - - let worker = move || -> Result<()> { - let mut child = Command::new(constants::PATH_PTRACE_BIN32).stdout(Stdio::piped()).arg(format!("{}", pid)).spawn()?; - child.stdout.as_mut().unwrap().read_exact(&mut [0u8; 1])?; - info!("child attached"); - sender.send(())?; - let result = child.wait()?; - info!("ptrace32 process status {}", result); - Ok(()) - }; - - thread::spawn(move || { - if let Err(e) = worker() { - error!("Crashed: {:?}", e); - } - }); - - receiver.recv()?; - Ok(()) -} - -impl Filesystem for DelegateFilesystem { - fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { - if parent != INO_DIR { - reply.error(ENOENT); - return; - } - match name.as_str().unwrap() { - "preloaded-classes" => reply.entry(&TTL, &ATTR_PCL, 0), - _ => reply.error(ENOENT), - } - } - - fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) { - match ino { - INO_DIR => reply.attr(&TTL, &ATTR_DIR), - INO_PCL => reply.attr(&TTL, &ATTR_PCL), - _ => reply.error(ENOENT), - } - } - - fn open(&mut self, req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) { - if ino == INO_PCL { - let pid = req.pid(); - let process = format!("/proc/{}/cmdline", pid); - let process = fs::read_to_string(process).unwrap(); - let process = &process[..process.find('\0').unwrap()]; - info!("Process {} is reading preloaded-classes", process); - match process { - "zygote64" => ptrace_zygote64(pid).unwrap(), - "zygote" => ptrace_zygote32(pid).unwrap(), - _ => (), - } - } - reply.opened(0, 0); - } - - fn read(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, size: u32, _flags: i32, _lock_owner: Option, reply: ReplyData) { - let offset = offset as usize; - let size = size as usize; - if ino == INO_PCL { - let len = PCL_CONTENT.len(); - if offset >= len { - reply.data(&[]); - } else { - let end = min(offset + size, len); - reply.data(&PCL_CONTENT[offset..end]); - } - } else { - reply.error(ENOENT); - } - } - - fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) { - if ino != INO_DIR { - reply.error(ENOENT); - return; - } - for (i, entry) in ENTRIES.iter().enumerate().skip(offset as usize) { - if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { - break; - } - } - reply.ok(); - } -} - -pub fn main() -> Result<()> { - info!("Start zygisk fuse"); - fs::create_dir(constants::PATH_WORK_DIR)?; - fs::create_dir(constants::PATH_FUSE_DIR)?; - PCL_CONTENT.init(fs::read(constants::PATH_PCL)?); - ATTR_PCL.init(attr(INO_PCL, PCL_CONTENT.len() as u64, FileType::RegularFile)); - let options = [ - fuser::MountOption::FSName(String::from("zygisk")), - fuser::MountOption::AllowOther, - fuser::MountOption::RO, - ]; - let session = fuser::spawn_mount2( - DelegateFilesystem, - constants::PATH_FUSE_DIR, - &options, - )?; - mount_bind(constants::PATH_FUSE_PCL, constants::PATH_PCL)?; - let crash = session.guard.join(); - unmount(constants::PATH_PCL, UnmountFlags::DETACH)?; - match crash { - Err(e) => bail!("Fuse mount crashed: {:?}", e), - _ => bail!("Fuse mount exited unexpectedly"), - } -} diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs index 4bd032d6..7692a71d 100644 --- a/zygiskd/src/main.rs +++ b/zygiskd/src/main.rs @@ -1,14 +1,12 @@ mod constants; mod dl; -mod fuse; -mod ptrace; mod root_impl; mod utils; -mod watchdog; mod zygiskd; +mod companion; use std::future::Future; -use anyhow::Result; +use crate::constants::ZKSU_VERSION; fn init_android_logger(tag: &str) { android_logger::init_once( @@ -18,22 +16,25 @@ fn init_android_logger(tag: &str) { ); } -fn async_start(future: F) -> F::Output { - let async_runtime = tokio::runtime::Runtime::new().unwrap(); - async_runtime.block_on(future) -} +fn start() { + let args: Vec = std::env::args().collect(); + if args.len() == 3 && args[1] == "companion" { + let fd: i32 = args[2].parse().unwrap(); + companion::entry(fd); + return; + } else if args.len() == 2 && args[1] == "version" { + println!("Zygisk Next daemon {}", ZKSU_VERSION); + return; + } else if args.len() == 2 && args[1] == "root" { + root_impl::setup(); + println!("root impl: {:?}", root_impl::get_impl()); + return; + } -fn start(name: &str) -> Result<()> { - utils::switch_mount_namespace(1)?; + utils::switch_mount_namespace(1).expect("switch mnt ns"); root_impl::setup(); - match name.trim_start_matches("zygisk-") { - "wd" => async_start(watchdog::main())?, - "fuse" => fuse::main()?, - lp_select!("cp32", "cp64") => zygiskd::main()?, - lp_select!("ptrace32", "ptrace64") => ptrace::main()?, - _ => println!("Available commands: wd, fuse, cp, ptrace"), - } - Ok(()) + log::info!("current root impl: {:?}", root_impl::get_impl()); + zygiskd::main().expect("zygiskd main"); } fn main() { @@ -41,7 +42,5 @@ fn main() { let nice_name = process.split('/').last().unwrap(); init_android_logger(nice_name); - if let Err(e) = start(nice_name) { - log::error!("Crashed: {}\n{}", e, e.backtrace()); - } + start(); } diff --git a/zygiskd/src/ptrace.rs b/zygiskd/src/ptrace.rs deleted file mode 100644 index d91fb9f0..00000000 --- a/zygiskd/src/ptrace.rs +++ /dev/null @@ -1,233 +0,0 @@ -use log::{debug, info}; -use std::ffi::CString; -use std::env; -use std::io::Write; -use rustix::path::Arg; -use proc_maps::{get_process_maps, MapRange, Pid}; -use ptrace_do::{RawProcess, TracedProcess}; -use rustix::process::getpid; -use crate::{constants, lp_select}; -use anyhow::{bail, Result}; - -const ANDROID_LIBC: &str = "bionic/libc.so"; -const ANDROID_LIBDL: &str = "bionic/libdl.so"; - -fn find_module_for_pid(pid: Pid, library: &str) -> Result { - let maps = get_process_maps(pid)?; - for map in maps.into_iter() { - if let Some(p) = map.filename() { - if p.as_str()?.contains(library) { - return Ok(map); - } - } - } - bail!("Cannot find module {library} for pid {pid}"); -} - -fn find_remote_procedure( - pid: Pid, - library: &str, - local_addr: usize, -) -> Result { - let local_module = find_module_for_pid(getpid().as_raw_nonzero().get(), library)?; - debug!( - "Identifed local range {library} ({:?}) at {:x}", - local_module.filename(), - local_module.start() - ); - - let remote_module = find_module_for_pid(pid, library)?; - debug!( - "Identifed remote range {library} ({:?}) at {:x}", - remote_module.filename(), - remote_module.start() - ); - - Ok(local_addr - local_module.start() + remote_module.start()) -} - -fn ptrace_zygote(pid: u32) -> Result<()> { - info!("Injecting into pid {}", pid); - let zygisk_lib = CString::new(constants::PATH_ZYGISK_LIB)?; - let libc_base = find_module_for_pid(pid as i32, ANDROID_LIBC)?.start(); - let mmap_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBC, - libc::mmap as usize, - )?; - let munmap_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBC, - libc::munmap as usize, - )?; - let dlopen_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBDL, - libc::dlopen as usize, - )?; - let dlsym_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBDL, - libc::dlsym as usize, - )?; - let errno_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBC, - libc::__errno as usize, - )?; - let dlerror_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBDL, - libc::dlerror as usize, - )?; - let strlen_remote = find_remote_procedure( - pid as i32, - ANDROID_LIBC, - libc::strlen as usize, - )?; - - let tracer = TracedProcess::attach(RawProcess::new(pid as i32))?; - std::io::stdout().write(b"1")?; - info!("attached process {}", pid); - std::io::stdout().flush()?; - let frame = tracer.next_frame()?; - debug!("Waited for a frame"); - - // Map a buffer in the remote process - debug!("remote mmap addr {:x}", mmap_remote); - let mmap_params: [usize; 6] = [ - 0, - 0x1000, - (libc::PROT_READ | libc::PROT_WRITE) as usize, - (libc::MAP_ANONYMOUS | libc::MAP_PRIVATE) as usize, - 0, - 0, - ]; - let mut arr: Vec = Vec::new(); - for p in mmap_params { - arr.extend_from_slice(&p.to_le_bytes()); - } - arr.as_slice(); - let (regs, mut frame) = frame.invoke_remote( - mmap_remote, - libc_base, - &mmap_params, - )?; - let buf_addr = regs.return_value(); - debug!("remote stopped at addr {:x}", regs.program_counter()); - if regs.program_counter() != libc_base { - let data = std::mem::MaybeUninit::::uninit(); - let siginfo = unsafe { - libc::ptrace(libc::PTRACE_GETSIGINFO, pid, 0, &data); - data.assume_init() - }; - bail!( - "stopped at unexpected addr {:x} signo {} si_code {} si_addr {:?}", - regs.program_counter(), - siginfo.si_signo, - siginfo.si_code, - unsafe { siginfo.si_addr() }, - ); - } - if buf_addr == usize::MAX { - debug!("errno remote {:x}", errno_remote); - let (regs, frame) = frame.invoke_remote( - errno_remote, - libc_base, - &[], - )?; - debug!("errno called"); - if regs.program_counter() != libc_base { - bail!("stopped at unexpected addr {:x} when getting errno", regs.program_counter()); - } - let err_addr = regs.return_value(); - let mut buf = [0u8; 4]; - frame.read_memory_mut(err_addr, &mut buf)?; - let err = i32::from_le_bytes(buf); - bail!("remote failed with {}", err); - } - debug!("Buffer addr: {:x}", buf_addr); - - // Load zygisk into remote process - frame.write_memory(buf_addr, zygisk_lib.as_bytes_with_nul())?; - let (regs, mut frame) = frame.invoke_remote( - dlopen_remote, - libc_base, - &[buf_addr, libc::RTLD_NOW as usize], - )?; - let handle = regs.return_value(); - debug!("Load zygisk into remote process: {:x}", handle); - if regs.program_counter() != libc_base { - let data = std::mem::MaybeUninit::::uninit(); - let siginfo = unsafe { - libc::ptrace(libc::PTRACE_GETSIGINFO, pid, 0, &data); - data.assume_init() - }; - bail!( - "stopped at unexpected addr {:x} signo {} si_code {} si_addr {:?}", - regs.program_counter(), - siginfo.si_signo, - siginfo.si_code, - unsafe { siginfo.si_addr() }, - ); - } - if handle == 0 { - debug!("got handle 0"); - let (regs, frame) = frame.invoke_remote( - dlerror_remote, - libc_base, - &[], - )?; - let err_addr = regs.return_value(); - if err_addr == 0 { - bail!("dlerror err addr 0"); - } - debug!("err addr {:x}", err_addr); - let (regs, frame) = frame.invoke_remote( - strlen_remote, - libc_base, - &[err_addr], - )?; - let len = regs.return_value(); - if len == 0 { - bail!("dlerror len 0"); - } - debug!("err len {}", len); - let mut buf = vec![0u8; len]; - frame.read_memory_mut(err_addr, buf.as_mut_slice())?; - bail!("err {:?}", buf); - } - - let entry = CString::new("entry")?; - frame.write_memory(buf_addr, entry.as_bytes_with_nul())?; - let (regs, frame) = frame.invoke_remote( - dlsym_remote, - libc_base, - &[handle, buf_addr], - )?; - let entry = regs.return_value(); - debug!("Call zygisk entry: {:x}", entry); - let (_, frame) = frame.invoke_remote( - entry, - libc_base, - &[handle], - )?; - - // Cleanup - let _ = frame.invoke_remote( - munmap_remote, - libc_base, - &[buf_addr], - )?; - debug!("Cleaned up"); - Ok(()) -} - -pub fn main() -> Result<()> { - info!("Start zygisk ptrace"); - let args: Vec = env::args().collect(); - let pid = args[1].parse::().unwrap(); - info!("ptracing {} pid {}", lp_select!("zygote32", "zygote64"), pid); - ptrace_zygote(pid)?; - Ok(()) -} diff --git a/zygiskd/src/root_impl/kernelsu.rs b/zygiskd/src/root_impl/kernelsu.rs index e92163ca..d629b20e 100644 --- a/zygiskd/src/root_impl/kernelsu.rs +++ b/zygiskd/src/root_impl/kernelsu.rs @@ -23,10 +23,11 @@ pub fn get_kernel_su() -> Option { 0, ) }; + const MAX_OLD_VERSION: i32 = MIN_KSU_VERSION - 1; match version { 0 => None, MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported), - 1..=MIN_KSU_VERSION => Some(Version::TooOld), + 1..=MAX_OLD_VERSION => Some(Version::TooOld), _ => Some(Version::Abnormal), } } diff --git a/zygiskd/src/root_impl/mod.rs b/zygiskd/src/root_impl/mod.rs index 8d4723bd..4cb3976a 100644 --- a/zygiskd/src/root_impl/mod.rs +++ b/zygiskd/src/root_impl/mod.rs @@ -1,6 +1,7 @@ mod kernelsu; mod magisk; +#[derive(Debug)] pub enum RootImpl { None, TooOld, @@ -44,7 +45,7 @@ pub fn uid_granted_root(uid: i32) -> bool { match get_impl() { RootImpl::KernelSU => kernelsu::uid_granted_root(uid), RootImpl::Magisk => magisk::uid_granted_root(uid), - _ => unreachable!(), + _ => panic!("uid_granted_root: unknown root impl {:?}", get_impl()), } } @@ -52,6 +53,6 @@ pub fn uid_should_umount(uid: i32) -> bool { match get_impl() { RootImpl::KernelSU => kernelsu::uid_should_umount(uid), RootImpl::Magisk => magisk::uid_should_umount(uid), - _ => unreachable!(), + _ => panic!("uid_should_umount: unknown root impl {:?}", get_impl()), } } diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs index 481d36f1..7cd80d2d 100644 --- a/zygiskd/src/utils.rs +++ b/zygiskd/src/utils.rs @@ -1,11 +1,13 @@ use anyhow::Result; use std::{fs, io::{Read, Write}, os::unix::net::UnixStream}; -use std::ffi::{c_char, CStr, CString}; -use std::os::fd::AsFd; -use std::os::unix::net::UnixListener; +use std::ffi::{c_char, c_void, CStr, CString}; +use std::os::fd::{AsFd, AsRawFd}; +use std::os::unix::net::{UnixDatagram, UnixListener}; use std::process::Command; use std::sync::OnceLock; -use rustix::net::{AddressFamily, bind_unix, listen, socket, SocketAddrUnix, SocketType}; +use bitflags::Flags; +use rustix::net::{AddressFamily, bind_unix, connect_unix, listen, SendFlags, sendto_unix, socket, SocketAddrUnix, SocketType}; +use rustix::path::Arg; use rustix::thread::gettid; #[cfg(target_pointer_width = "64")] @@ -63,6 +65,11 @@ pub fn set_socket_create_context(context: &str) -> Result<()> { } } +pub fn get_current_attr() -> Result { + let s = fs::read("/proc/self/attr/current")?; + Ok(s.to_string_lossy().to_string()) +} + pub fn chcon(path: &str, context: &str) -> Result<()> { Command::new("chcon").arg(context).arg(path).status()?; Ok(()) @@ -96,6 +103,28 @@ pub fn set_property(name: &str, value: &str) -> Result<()> { Ok(()) } +pub fn wait_property(name: &str, serial: u32) -> Result { + let name = CString::new(name)?; + let info = unsafe { + __system_property_find(name.as_ptr()) + }; + let mut serial = serial; + unsafe { + __system_property_wait(info, serial, &mut serial, std::ptr::null()); + } + Ok(serial) +} + +pub fn get_property_serial(name: &str) -> Result { + let name = CString::new(name)?; + let info = unsafe { + __system_property_find(name.as_ptr()) + }; + Ok(unsafe { + __system_property_serial(info) + }) +} + pub fn switch_mount_namespace(pid: i32) -> Result<()> { let cwd = std::env::current_dir()?; let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?; @@ -173,8 +202,38 @@ pub fn unix_listener_from_path(path: &str) -> Result { Ok(UnixListener::from(socket)) } +pub fn unix_datagram_sendto_abstract(path: &str, buf: &[u8]) -> Result<()> { + // FIXME: shall we set create context every time? + set_socket_create_context(get_current_attr()?.as_str())?; + let addr = SocketAddrUnix::new_abstract_name(path.as_bytes())?; + let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?; + connect_unix(&socket, &addr)?; + sendto_unix(socket, buf, SendFlags::empty(), &addr)?; + set_socket_create_context("u:r:zygote:s0")?; + Ok(()) +} + +pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool { + unsafe { + let mut pfd = libc::pollfd { + fd: stream.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }; + let timeout = if block { -1 } else { 0 }; + libc::poll(&mut pfd, 1, timeout); + if pfd.revents & !libc::POLLIN != 0 { + return false; + } + } + return true; +} + extern "C" { fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32; fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32; fn __system_property_set(name: *const c_char, value: *const c_char) -> u32; + fn __system_property_find(name: *const c_char) -> *const c_void; + fn __system_property_wait(info: *const c_void, old_serial: u32, new_serial: *mut u32, timeout: *const libc::timespec) -> bool; + fn __system_property_serial(info: *const c_void) -> u32; } diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs deleted file mode 100644 index cd2f38e3..00000000 --- a/zygiskd/src/watchdog.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::{constants, root_impl, utils}; -use anyhow::{bail, Result}; -use std::fs; -use std::future::Future; -use std::io::{BufRead, BufReader, Write}; -use std::pin::Pin; -use std::time::Duration; -use binder::IBinder; -use futures::stream::FuturesUnordered; -use futures::StreamExt; -use log::{debug, error, info}; -use rustix::mount::mount_bind; -use rustix::process::{getgid, getuid, kill_process, Pid, Signal}; -use tokio::process::{Child, Command}; -use crate::utils::LateInit; - -static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new(); - -pub async fn main() -> Result<()> { - let result = run().await; - if result.is_err() { - set_prop_hint(constants::STATUS_CRASHED)?; - } - result -} - -async fn run() -> Result<()> { - info!("Start zygisk watchdog"); - check_permission()?; - mount_prop().await?; - if check_and_set_hint()? == false { - log::warn!("Requirements not met, exiting"); - return Ok(()); - } - spawn_daemon().await?; - Ok(()) -} - -fn check_permission() -> Result<()> { - info!("Check permission"); - let uid = getuid(); - if !uid.is_root() { - bail!("UID is not 0"); - } - - let gid = getgid(); - if !gid.is_root() { - bail!("GID is not 0"); - } - - let context = fs::read_to_string("/proc/self/attr/current")?; - let context = context.trim_end_matches('\0'); - if context != "u:r:su:s0" && context != "u:r:magisk:s0" { - bail!("SELinux context incorrect: {context}"); - } - - Ok(()) -} - -async fn mount_prop() -> Result<()> { - let module_prop_file = fs::File::open(constants::PATH_MODULE_PROP)?; - let mut section = 0; - let mut sections: [String; 2] = [String::new(), String::new()]; - let lines = BufReader::new(module_prop_file).lines(); - for line in lines { - let line = line?; - if line.starts_with("description=") { - sections[0].push_str("description="); - sections[1].push_str(line.trim_start_matches("description=")); - sections[1].push('\n'); - section = 1; - } else { - sections[section].push_str(&line); - sections[section].push('\n'); - } - } - PROP_SECTIONS.init(sections); - - info!("Mount {} -> {}", constants::PATH_PROP_OVERLAY, constants::PATH_MODULE_PROP); - fs::File::create(constants::PATH_PROP_OVERLAY)?; - mount_bind(constants::PATH_PROP_OVERLAY, constants::PATH_MODULE_PROP)?; - Ok(()) -} - -fn set_prop_hint(hint: &str) -> Result<()> { - let mut file = fs::File::create(constants::PATH_PROP_OVERLAY)?; - file.write_all(PROP_SECTIONS[0].as_bytes())?; - file.write_all(b"[")?; - file.write_all(hint.as_bytes())?; - file.write_all(b"] ")?; - file.write_all(PROP_SECTIONS[1].as_bytes())?; - Ok(()) -} - -fn check_and_set_hint() -> Result { - let root_impl = root_impl::get_impl(); - match root_impl { - root_impl::RootImpl::None => set_prop_hint(constants::STATUS_ROOT_IMPL_NONE)?, - root_impl::RootImpl::TooOld => set_prop_hint(constants::STATUS_ROOT_IMPL_TOO_OLD)?, - root_impl::RootImpl::Abnormal => set_prop_hint(constants::STATUS_ROOT_IMPL_ABNORMAL)?, - root_impl::RootImpl::Multiple => set_prop_hint(constants::STATUS_ROOT_IMPL_MULTIPLE)?, - _ => { - set_prop_hint(constants::STATUS_LOADED)?; - return Ok(true); - } - } - Ok(false) -} - -async fn spawn_daemon() -> Result<()> { - let mut lives = 5; - loop { - let mut futures = FuturesUnordered::>>>>::new(); - let mut child_ids = vec![]; - let daemon32 = Command::new(constants::PATH_CP_BIN32).arg("daemon").spawn(); - let daemon64 = Command::new(constants::PATH_CP_BIN64).arg("daemon").spawn(); - async fn spawn_daemon(mut daemon: Child) -> Result<()> { - let result = daemon.wait().await?; - log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result); - Ok(()) - } - if let Ok(it) = daemon32 { - child_ids.push(it.id().unwrap()); - futures.push(Box::pin(spawn_daemon(it))); - } - if let Ok(it) = daemon64 { - child_ids.push(it.id().unwrap()); - futures.push(Box::pin(spawn_daemon(it))); - } - - async fn binder_listener() -> Result<()> { - let mut binder = loop { - match binder::get_service("activity") { - Some(binder) => break binder, - None => { - log::trace!("System server not ready, wait for 1s..."); - tokio::time::sleep(Duration::from_secs(1)).await; - } - }; - }; - - info!("System server ready"); - - loop { - if binder.ping_binder().is_err() { break; } - tokio::time::sleep(Duration::from_secs(1)).await; - } - bail!("System server died"); - } - futures.push(Box::pin(binder_listener())); - - if let Err(e) = futures.next().await.unwrap() { - error!("{}", e); - } - - for child in child_ids { - debug!("Killing child process {}", child); - let _ = kill_process(Pid::from_raw(child as i32).unwrap(), Signal::Kill); - } - - lives -= 1; - if lives == 0 { - bail!("Too many crashes, abort"); - } - - error!("Restarting zygote..."); - utils::set_property(constants::PROP_CTL_RESTART, "zygote")?; - } -} diff --git a/zygiskd/src/zygiskd.rs b/zygiskd/src/zygiskd.rs index 2df1ef35..c95a12cc 100644 --- a/zygiskd/src/zygiskd.rs +++ b/zygiskd/src/zygiskd.rs @@ -1,27 +1,29 @@ -use std::ffi::c_void; use crate::constants::{DaemonSocketAction, ProcessFlags}; -use crate::utils::UnixStreamExt; +use crate::utils::{check_unix_socket, UnixStreamExt}; use crate::{constants, dl, lp_select, root_impl, utils}; use anyhow::{bail, Result}; use passfd::FdPassingExt; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::thread; use std::fs; -use std::os::fd::OwnedFd; +use std::io::Error; +use std::os::fd::{OwnedFd, RawFd}; use std::os::unix::{ net::{UnixListener, UnixStream}, prelude::AsRawFd, }; use std::path::PathBuf; -use rustix::fs::fstat; -use rustix::process::{set_parent_process_death_signal, Signal}; +use std::process::{Command, exit}; +use log::info; +use std::os::unix::process::CommandExt; +use bitflags::Flags; type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); struct Module { name: String, lib_fd: OwnedFd, - entry: Option, + companion: Mutex>>, } struct Context { @@ -29,32 +31,64 @@ struct Context { } pub fn main() -> Result<()> { - log::info!("Start zygisk companion"); - set_parent_process_death_signal(Some(Signal::Kill))?; + log::info!("Welcome to Zygisk Next ({}) !", constants::ZKSU_VERSION); + let magic_path = std::env::var("MAGIC")?; + let controller_path = format!("init_monitor{}", magic_path); + log::info!("socket path {}", controller_path); let arch = get_arch()?; log::debug!("Daemon architecture: {arch}"); - - log::info!("Load modules"); let modules = load_modules(arch)?; + { + let mut msg = Vec::::new(); + let info = match root_impl::get_impl() { + root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk => { + msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes()); + let module_names: Vec<_> = modules.iter() + .map(|m| m.name.as_str()).collect(); + format!("Root: {:?},module({}): {}", root_impl::get_impl(), modules.len(), module_names.join(",")) + } + _ => { + msg.extend_from_slice(&constants::DAEMON_SET_ERROR_INFO.to_le_bytes()); + format!("Invalid root implementation: {:?}", root_impl::get_impl()) + } + }; + msg.extend_from_slice(&(info.len() as u32 + 1).to_le_bytes()); + msg.extend_from_slice(info.as_bytes()); + msg.extend_from_slice(&[0u8]); + utils::unix_datagram_sendto_abstract(controller_path.as_str(), msg.as_slice()).expect("failed to send info"); + } + let context = Context { modules, }; let context = Arc::new(context); - - log::info!("Create socket"); let listener = create_daemon_socket()?; - - log::info!("Handle zygote connections"); for stream in listener.incoming() { - let stream = stream?; + let mut stream = stream?; let context = Arc::clone(&context); - thread::spawn(move || { - if let Err(e) = handle_daemon_action(stream, &context) { - log::warn!("Error handling daemon action: {}\n{}", e, e.backtrace()); + let action = stream.read_u8()?; + let action = DaemonSocketAction::try_from(action)?; + log::trace!("New daemon action {:?}", action); + match action { + DaemonSocketAction::PingHeartbeat => { + let value = constants::ZYGOTE_INJECTED; + utils::unix_datagram_sendto_abstract(controller_path.as_str(), &value.to_le_bytes())?; + } + DaemonSocketAction::ZygoteRestart => { + info!("Zygote restarted, clean up companions"); + for module in &context.modules { + let mut companion = module.companion.lock().unwrap(); + companion.take(); + } + } + _ => { + thread::spawn(move || { + handle_daemon_action(action, stream, &context); + }); } - }); + } } Ok(()) @@ -96,8 +130,8 @@ fn load_modules(arch: &str) -> Result> { continue; } }; - let entry = resolve_module(&so_path.to_string_lossy())?; - let module = Module { name, lib_fd, entry }; + let companion = Mutex::new(None); + let module = Module { name, lib_fd, companion }; modules.push(module); } @@ -130,45 +164,68 @@ fn create_library_fd(so_path: &PathBuf) -> Result { fn create_daemon_socket() -> Result { utils::set_socket_create_context("u:r:zygote:s0")?; - log::debug!("Daemon socket: {}", constants::PATH_CP_SOCKET); - let listener = utils::unix_listener_from_path(constants::PATH_CP_SOCKET)?; + let magic_path = std::env::var("MAGIC_PATH")?; + let socket_path = magic_path + constants::PATH_CP_NAME; + log::debug!("Daemon socket: {}", socket_path); + let listener = utils::unix_listener_from_path(&socket_path)?; Ok(listener) } -fn resolve_module(path: &str) -> Result> { +fn spawn_companion(name: &str, lib_fd: RawFd) -> Result> { + let (mut daemon, companion) = UnixStream::pair()?; + + // FIXME: avoid getting self path from arg0 + let process = std::env::args().next().unwrap(); + let nice_name = process.split('/').last().unwrap(); + unsafe { - let handle = dl::dlopen(path, libc::RTLD_NOW)?; - let symbol = std::ffi::CString::new("zygisk_companion_entry")?; - let entry = libc::dlsym(handle, symbol.as_ptr()); - if entry.is_null() { - return Ok(None); + let pid = libc::fork(); + if pid < 0 { + bail!(Error::last_os_error()); + } else if pid > 0 { + drop(companion); + let mut status: libc::c_int = 0; + libc::waitpid(pid, &mut status, 0); + if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 { + daemon.write_string(name)?; + daemon.send_fd(lib_fd)?; + return match daemon.read_u8()? { + 0 => Ok(None), + 1 => Ok(Some(daemon)), + _ => bail!("Invalid companion response"), + } + } else { + bail!("exited with status {}", status); + } + } else { + // Remove FD_CLOEXEC flag + unsafe { libc::fcntl(companion.as_raw_fd() as libc::c_int, libc::F_SETFD, 0i32); }; } - let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry); - Ok(Some(fnptr)) } + + Command::new(&process) + .arg0(format!("{}-{}", nice_name, name)) + .arg("companion") + .arg(format!("{}", companion.as_raw_fd())) + .spawn()?; + exit(0) } -fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> { - let action = stream.read_u8()?; - let action = DaemonSocketAction::try_from(action)?; - log::trace!("New daemon action {:?}", action); +fn handle_daemon_action(action: DaemonSocketAction, mut stream: UnixStream, context: &Context) { match action { - DaemonSocketAction::PingHeartbeat => { - // Do nothing - } DaemonSocketAction::RequestLogcatFd => { loop { let level = match stream.read_u8() { Ok(level) => level, Err(_) => break, }; - let tag = stream.read_string()?; - let message = stream.read_string()?; - utils::log_raw(level as i32, &tag, &message)?; + let tag = stream.read_string().unwrap(); + let message = stream.read_string().unwrap(); + utils::log_raw(level as i32, &tag, &message).unwrap(); } } DaemonSocketAction::GetProcessFlags => { - let uid = stream.read_u32()? as i32; + let uid = stream.read_u32().unwrap() as i32; let mut flags = ProcessFlags::empty(); if root_impl::uid_granted_root(uid) { flags |= ProcessFlags::PROCESS_GRANTED_ROOT; @@ -179,50 +236,64 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> match root_impl::get_impl() { root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU, root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK, - _ => unreachable!(), + _ => panic!("wrong root impl: {:?}", root_impl::get_impl()), } log::trace!("Uid {} granted root: {}", uid, flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT)); log::trace!("Uid {} on denylist: {}", uid, flags.contains(ProcessFlags::PROCESS_ON_DENYLIST)); - stream.write_u32(flags.bits())?; + stream.write_u32(flags.bits()).unwrap(); } DaemonSocketAction::ReadModules => { - stream.write_usize(context.modules.len())?; + stream.write_usize(context.modules.len()).unwrap(); for module in context.modules.iter() { - stream.write_string(&module.name)?; - stream.send_fd(module.lib_fd.as_raw_fd())?; + stream.write_string(&module.name).unwrap(); + stream.send_fd(module.lib_fd.as_raw_fd()).unwrap(); } } DaemonSocketAction::RequestCompanionSocket => { - let index = stream.read_usize()?; + let index = stream.read_usize().unwrap(); let module = &context.modules[index]; - match module.entry { - None => { - stream.write_u8(0)?; - return Ok(()); + let mut companion = module.companion.lock().unwrap(); + if let Some(Some(sock)) = companion.as_ref() { + if !check_unix_socket(sock, false) { + log::error!("Poll companion for module `{}` crashed", module.name); + companion.take(); } - Some(companion) => { - stream.write_u8(1)?; - let st0 = fstat(&stream)?; - unsafe { companion(stream.as_raw_fd()); } - // Only close client if it is the same file so we don't - // accidentally close a re-used file descriptor. - // This check is required because the module companion - // handler could've closed the file descriptor already. - if let Ok(st1) = fstat(&stream) { - if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino { - std::mem::forget(stream); + } + if companion.is_none() { + match spawn_companion(&module.name, module.lib_fd.as_raw_fd()) { + Ok(c) => { + if c.is_some() { + log::trace!(" Spawned companion for `{}`", module.name); + } else { + log::trace!(" No companion spawned for `{}` because it has not entry", module.name); } + *companion = Some(c); + }, + Err(e) => { + log::warn!(" Failed to spawn companion for `{}`: {}", module.name, e); + } + }; + } + match companion.as_ref() { + Some(Some(sock)) => { + if let Err(e) = sock.send_fd(stream.as_raw_fd()) { + log::error!("Failed to send companion fd socket of module `{}`: {}", module.name, e); + stream.write_u8(0).unwrap(); } + // Ok: Send by companion + } + _ => { + stream.write_u8(0).unwrap(); } } } DaemonSocketAction::GetModuleDir => { - let index = stream.read_usize()?; + let index = stream.read_usize().unwrap(); let module = &context.modules[index]; let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name); - let dir = fs::File::open(dir)?; - stream.send_fd(dir.as_raw_fd())?; + let dir = fs::File::open(dir).unwrap(); + stream.send_fd(dir.as_raw_fd()).unwrap(); } + _ => {} } - Ok(()) }