You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
Merge pull request #73 from 5ec1cff/master
This commit is contained in:
25
.github/scripts/telegram_url.py
vendored
Normal file
25
.github/scripts/telegram_url.py
vendored
Normal 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)
|
||||
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
@@ -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
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
23
loader/src/CMakeLists.txt
Normal 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)
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
21
loader/src/external/Android.mk
vendored
21
loader/src/external/Android.mk
vendored
@@ -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
8
loader/src/external/CMakeLists.txt
vendored
Normal 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)
|
||||
1
loader/src/external/parallel-hashmap
vendored
1
loader/src/external/parallel-hashmap
vendored
Submodule loader/src/external/parallel-hashmap deleted from 55725dbe70
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
82
loader/src/injector/art_method.hpp
Normal file
82
loader/src/injector/art_method.hpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
292
loader/src/injector/gen_jni_hooks.py
Normal file
292
loader/src/injector/gen_jni_hooks.py
Normal 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));
|
||||
}
|
||||
""")
|
||||
@@ -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() {
|
||||
|
||||
1116
loader/src/injector/jni_helper.hpp
Normal file
1116
loader/src/injector/jni_helper.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
55
loader/src/ptracer/main.cpp
Normal file
55
loader/src/ptracer/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
20
loader/src/ptracer/main.hpp
Normal file
20
loader/src/ptracer/main.hpp
Normal 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);
|
||||
631
loader/src/ptracer/monitor.cpp
Normal file
631
loader/src/ptracer/monitor.cpp
Normal 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");
|
||||
}
|
||||
214
loader/src/ptracer/ptracer.cpp
Normal file
214
loader/src/ptracer/ptracer.cpp
Normal 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, ®s, sizeof(regs));
|
||||
map = MapInfo::Scan(std::to_string(pid));
|
||||
auto local_map = MapInfo::Scan();
|
||||
auto libc_return_addr = find_module_return_addr(map, "libc.so");
|
||||
LOGD("libc return addr %p", libc_return_addr);
|
||||
|
||||
// call dlopen
|
||||
auto dlopen_addr = find_func_addr(local_map, map, "libdl.so", "dlopen");
|
||||
if (dlopen_addr == nullptr) return false;
|
||||
std::vector<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;
|
||||
}
|
||||
435
loader/src/ptracer/utils.cpp
Normal file
435
loader/src/ptracer/utils.cpp
Normal 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 ®s) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("getregs");
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(struct user_regs_struct),
|
||||
};
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
PLOGE("getregs");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("setregs");
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(struct user_regs_struct),
|
||||
};
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
PLOGE("setregs");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_addr_mem_region(std::vector<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 ®s, long preserve) {
|
||||
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
|
||||
}
|
||||
|
||||
void *push_string(int pid, struct user_regs_struct ®s, const char *str) {
|
||||
auto len = strlen(str) + 1;
|
||||
regs.REG_SP -= len;
|
||||
align_stack(regs);
|
||||
auto addr = reinterpret_cast<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 ®s, 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;
|
||||
}
|
||||
124
loader/src/ptracer/utils.hpp
Normal file
124
loader/src/ptracer/utils.hpp
Normal 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 ®s);
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
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 ®s, long preserve = 0);
|
||||
|
||||
void *push_string(int pid, struct user_regs_struct ®s, const char *str);
|
||||
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<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);
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 &"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
3
module/src/zygisk-ctl.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
MODDIR=${0%/*}/..
|
||||
export MAGIC=$(cat $MODDIR/magic)
|
||||
exec $MODDIR/bin/zygisk-ptrace64 ctl $*
|
||||
@@ -1,2 +1,3 @@
|
||||
[build]
|
||||
target-dir = "build/intermediates/rust"
|
||||
target = "aarch64-linux-android"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
72
zygiskd/src/companion.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")?;
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user