Merge pull request #73 from 5ec1cff/master

This commit is contained in:
Nullptr
2023-12-12 16:01:56 +08:00
committed by GitHub
55 changed files with 3814 additions and 1134 deletions

25
.github/scripts/telegram_url.py vendored Normal file
View File

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

View File

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

3
.gitmodules vendored
View File

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

View File

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

View File

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

View File

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

View File

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

23
loader/src/CMakeLists.txt Normal file
View File

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

View File

@@ -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<struct sockaddr*>(&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");
}
}
}

View File

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

View File

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

View File

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

8
loader/src/external/CMakeLists.txt vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,82 @@
#pragma once
#include "jni_helper.hpp"
template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
inline static constexpr auto kPointerSize = sizeof(void *);
namespace lsplant::art {
class ArtMethod {
public:
void *GetData() {
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + data_offset);
}
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
if (art_method_field) [[likely]] {
return reinterpret_cast<art::ArtMethod *>(
JNI_GetLongField(env, method, art_method_field));
} else {
return reinterpret_cast<art::ArtMethod *>(env->FromReflectedMethod(method));
}
}
static bool Init(JNIEnv *env) {
ScopedLocalRef<jclass> executable{env, nullptr};
executable = JNI_FindClass(env, "java/lang/reflect/Executable");
if (!executable) {
LOGE("Failed to found Executable");
return false;
}
if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J");
!art_method_field) {
LOGE("Failed to find artMethod field");
return false;
}
auto throwable = JNI_FindClass(env, "java/lang/Throwable");
if (!throwable) {
LOGE("Failed to found Executable");
return false;
}
auto clazz = JNI_FindClass(env, "java/lang/Class");
static_assert(std::is_same_v<decltype(clazz)::BaseType, jclass>);
jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors",
"()[Ljava/lang/reflect/Constructor;");
const auto constructors =
JNI_Cast<jobjectArray>(JNI_CallObjectMethod(env, throwable, get_declared_constructors));
if (constructors.size() < 2) {
LOGE("Throwable has less than 2 constructors");
return false;
}
auto &first_ctor = constructors[0];
auto &second_ctor = constructors[1];
auto *first = FromReflectedMethod(env, first_ctor.get());
auto *second = FromReflectedMethod(env, second_ctor.get());
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
LOGD("ArtMethod size: %zu", art_method_size);
if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] {
LOGW("ArtMethod size exceeds maximum assume. There may be something wrong.");
}
entry_point_offset = art_method_size - kPointerSize;
data_offset = entry_point_offset - kPointerSize;
LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset);
LOGD("ArtMethod::data offset: %zu", data_offset);
return true;
}
private:
inline static jfieldID art_method_field = nullptr;
inline static size_t art_method_size = 0;
inline static size_t entry_point_offset = 0;
inline static size_t data_offset = 0;
};
} // namespace lsplant::art

View File

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

View File

@@ -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<decltype(&{self.name})>({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<JNINativeMethod> 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));
}
""")

View File

@@ -4,29 +4,30 @@
#include <regex.h>
#include <bitset>
#include <list>
#include <map>
#include <array>
#include <lsplt.hpp>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#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<RegisterInfo> register_info;
vector<IgnoreInfo> 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<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *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<jint (*)(JavaVM **, jsize, jsize *)>(
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<decltype(get_created_java_vms)>(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<void **>(&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<void (*)(JNIEnv *)>(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<JNINativeMethod> 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;
}
// No matching method found, set fnPtr to null
methods[i].fnPtr = nullptr;
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;
old_functions->RegisterNatives(env, env->FindClass(clz), hooks.data(), static_cast<int>(hooks.size()));
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<jint (*)(JavaVM **, jsize, jsize *)>(
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<decltype(get_created_java_vms)>(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<void **>(&env), JNI_VERSION_1_6);
if (res != JNI_OK || env == nullptr) return;
auto classMember = lsplant::JNI_FindClass(env, "java/lang/reflect/Member");
if (classMember != nullptr) member_getModifiers = lsplant::JNI_GetMethodID(env, classMember, "getModifiers", "()I");
auto classModifier = lsplant::JNI_FindClass(env, "java/lang/reflect/Modifier");
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;
}
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() {

File diff suppressed because it is too large Load Diff

View File

@@ -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<decltype(&nativeForkAndSpecialize_l)>(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<decltype(&nativeForkAndSpecialize_o)>(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<decltype(&nativeForkAndSpecialize_p)>(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<decltype(&nativeForkAndSpecialize_q_alt)>(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<decltype(&nativeForkAndSpecialize_r)>(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<decltype(&nativeForkAndSpecialize_u)>(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<decltype(&nativeForkAndSpecialize_samsung_m)>(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<decltype(&nativeForkAndSpecialize_samsung_n)>(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<decltype(&nativeForkAndSpecialize_samsung_o)>(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<decltype(&nativeForkAndSpecialize_samsung_p)>(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<decltype(&nativeSpecializeAppProcess_q)>(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<decltype(&nativeSpecializeAppProcess_q_alt)>(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<decltype(&nativeSpecializeAppProcess_r)>(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<decltype(&nativeSpecializeAppProcess_u)>(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<decltype(&nativeSpecializeAppProcess_samsung_q)>(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<decltype(&nativeForkSystemServer_l)>(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<decltype(&nativeForkSystemServer_samsung_q)>(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<JNINativeMethod[]> hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {
unique_ptr<JNINativeMethod[]> newMethods;
int clz_id = -1;
int hook_cnt = 0;
do {
if (className == "com/android/internal/os/Zygote"sv) {
clz_id = 0;
hook_cnt = 3;
break;
}
} while (false);
if (hook_cnt) {
newMethods = make_unique<JNINativeMethod[]>(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)
}
class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;
}
return newMethods;
}
} // namespace
static void do_hook_zygote(JNIEnv *env) {
vector<JNINativeMethod> 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));
}

View File

@@ -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<uint8_t *> _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<uint8_t *>(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

View File

@@ -1,44 +0,0 @@
#pragma once
#include <map>
#include <sys/mman.h>
#pragma clang diagnostic push
#include <parallel_hashmap/phmap.h>
#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<class T>
using allocator = stateless_allocator<T, memory_block>;
using string = std::basic_string<char, std::char_traits<char>, allocator<char>>;
// Use node_hash_map since it will use less memory because we are using a monotonic allocator
template<class K, class V>
using hash_map = phmap::node_hash_map<K, V,
phmap::priv::hash_default_hash<K>,
phmap::priv::hash_default_eq<K>,
allocator<std::pair<const K, V>>
>;
template<class K, class V>
using tree_map = std::map<K, V,
std::less<K>,
allocator<std::pair<const K, V>>
>;
} // namespace jni_hook
// Provide heterogeneous lookup for jni_hook::string
namespace phmap::priv {
template <> struct HashEq<jni_hook::string> : StringHashEqT<char> {};
} // namespace phmap::priv

View File

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

View File

@@ -0,0 +1,55 @@
#include <cstdio>
#include <cstdlib>
#include <string_view>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/system_properties.h>
#include "main.hpp"
#include "utils.hpp"
#include "daemon.h"
#include <sys/mount.h>
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 <pid> | ctl <start|stop|exit> | version\n", argv[0]);
return 1;
}
}

View File

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

View File

@@ -0,0 +1,631 @@
#include <sys/system_properties.h>
#include <unistd.h>
#include <sys/stat.h>
#include <map>
#include <set>
#include <syscall.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/signalfd.h>
#include <err.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <time.h>
#include <fcntl.h>
#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<EventHandler *>(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<uint8_t> buf;
buf.resize(sizeof(MsgHead), 0);
MsgHead &msg = *reinterpret_cast<MsgHead*>(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<size_t>(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<pid_t> 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");
}

View File

@@ -0,0 +1,214 @@
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <vector>
#include <string>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include <signal.h>
#include <sys/system_properties.h>
#include <string>
#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<uintptr_t *>(regs.REG_SP);
LOGD("kernel argument %p %s", arg, get_addr_mem_region(map, arg).c_str());
int argc;
auto argv = reinterpret_cast<char **>(reinterpret_cast<uintptr_t *>(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<ElfW(auxv_t) *>(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<void *>(buf.a_un.a_val);
addr_of_entry_addr = reinterpret_cast<char *>(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, &regs, 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<long> 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;
}

View File

@@ -0,0 +1,435 @@
#include <vector>
#include <sys/mman.h>
#include <sys/sysmacros.h>
#include <array>
#include <cinttypes>
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <vector>
#include <string>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include <signal.h>
#include <sstream>
#include <ios>
#include <cstring>
#include <sys/stat.h>
#include "utils.hpp"
#include "logging.h"
#include <sched.h>
#include <fcntl.h>
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> MapInfo::Scan(const std::string& pid) {
constexpr static auto kPermLength = 5;
constexpr static auto kMapEntry = 7;
std::vector<MapInfo> info;
std::string file_name = std::string("/proc/") + pid + "/maps";
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{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<char, kPermLength> 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<dev_t>(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 &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_GETREGS, pid, 0, &regs) == -1) {
PLOGE("getregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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 &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_SETREGS, pid, 0, &regs) == -1) {
PLOGE("setregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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<MapInfo> &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 "<unknown>";
}
void *find_module_return_addr(std::vector<MapInfo> &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<MapInfo> &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<MapInfo> &local_info,
std::vector<MapInfo> &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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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 &regs, long preserve) {
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
}
void *push_string(int pid, struct user_regs_struct &regs, const char *str) {
auto len = strlen(str) + 1;
regs.REG_SP -= len;
align_stack(regs);
auto addr = reinterpret_cast<uintptr_t *>(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 &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<long> &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;
}

View File

@@ -0,0 +1,124 @@
#pragma once
#include <string>
#include <sys/ptrace.h>
#include <map>
#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<MapInfo> 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 &regs);
bool set_regs(int pid, struct user_regs_struct &regs);
std::string get_addr_mem_region(std::vector<MapInfo> &info, void *addr);
void *find_module_base(std::vector<MapInfo> &info, std::string_view suffix);
void *find_func_addr(
std::vector<MapInfo> &local_info,
std::vector<MapInfo> &remote_info,
std::string_view module,
std::string_view func);
void align_stack(struct user_regs_struct &regs, long preserve = 0);
void *push_string(int pid, struct user_regs_struct &regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<long> &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<MapInfo> &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);

View File

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

View File

@@ -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' </dev/urandom | head -c 18)
echo -n "$MAGIC" > "$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)

View File

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

View File

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

View File

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

3
module/src/zygisk-ctl.sh Normal file
View File

@@ -0,0 +1,3 @@
MODDIR=${0%/*}/..
export MAGIC=$(cat $MODDIR/magic)
exec $MODDIR/bin/zygisk-ptrace64 ctl $*

View File

@@ -1,2 +1,3 @@
[build]
target-dir = "build/intermediates/rust"
target = "aarch64-linux-android"

View File

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

View File

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

72
zygiskd/src/companion.rs Normal file
View File

@@ -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<Option<ZygiskCompanionEntryFn>> {
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))
}
}

View File

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

View File

@@ -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<FileAttr> = LateInit::new();
static PCL_CONTENT: LateInit<Vec<u8>> = 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<u32> = 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<u32> = 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<u64>, 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"),
}
}

View File

@@ -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<F: Future>(future: F) -> F::Output {
let async_runtime = tokio::runtime::Runtime::new().unwrap();
async_runtime.block_on(future)
}
fn start(name: &str) -> Result<()> {
utils::switch_mount_namespace(1)?;
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"),
fn start() {
let args: Vec<String> = 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;
}
Ok(())
utils::switch_mount_namespace(1).expect("switch mnt ns");
root_impl::setup();
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();
}

View File

@@ -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<MapRange> {
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<usize> {
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<u8> = 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::<libc::siginfo_t>::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::<libc::siginfo_t>::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<String> = env::args().collect();
let pid = args[1].parse::<u32>().unwrap();
info!("ptracing {} pid {}", lp_select!("zygote32", "zygote64"), pid);
ptrace_zygote(pid)?;
Ok(())
}

View File

@@ -23,10 +23,11 @@ pub fn get_kernel_su() -> Option<Version> {
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),
}
}

View File

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

View File

@@ -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<String> {
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<u32> {
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<u32> {
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<UnixListener> {
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;
}

View File

@@ -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<bool> {
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::<Pin<Box<dyn Future<Output=Result<()>>>>>::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")?;
}
}

View File

@@ -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<ZygiskCompanionEntryFn>,
companion: Mutex<Option<Option<UnixStream>>>,
}
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::<u8>::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<Vec<Module>> {
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<OwnedFd> {
fn create_daemon_socket() -> Result<UnixListener> {
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<Option<ZygiskCompanionEntryFn>> {
fn spawn_companion(name: &str, lib_fd: RawFd) -> Result<Option<UnixStream>> {
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(())
}