New way to unload zygisk (#51)

* use old binder_rs

* New way to unload zygisk (3a4fe53)

Co-authored-by: LoveSy <shana@zju.edu.cn>
Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>

---------

Co-authored-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>
This commit is contained in:
LoveSy
2023-10-15 11:11:05 +08:00
committed by GitHub
parent 42503e7cfe
commit db47f03728
4 changed files with 85 additions and 44 deletions

View File

@@ -43,7 +43,7 @@ val androidMinSdkVersion by extra(29)
val androidTargetSdkVersion by extra(34) val androidTargetSdkVersion by extra(34)
val androidCompileSdkVersion by extra(34) val androidCompileSdkVersion by extra(34)
val androidBuildToolsVersion by extra("34.0.0") val androidBuildToolsVersion by extra("34.0.0")
val androidCompileNdkVersion by extra("25.2.9519653") val androidCompileNdkVersion by extra("26.0.10792818")
val androidSourceCompatibility by extra(JavaVersion.VERSION_11) val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
val androidTargetCompatibility by extra(JavaVersion.VERSION_11) val androidTargetCompatibility by extra(JavaVersion.VERSION_11)

View File

@@ -7,15 +7,6 @@ using namespace std;
void *self_handle = nullptr; void *self_handle = nullptr;
[[gnu::destructor]] [[maybe_unused]]
static void zygisk_cleanup_wait() {
if (self_handle) {
// Wait 10us to make sure none of our code is executing
timespec ts = { .tv_sec = 0, .tv_nsec = 10000L };
nanosleep(&ts, nullptr);
}
}
extern "C" [[gnu::visibility("default")]] extern "C" [[gnu::visibility("default")]]
void entry(void *handle) { void entry(void *handle) {
#ifdef NDEBUG #ifdef NDEBUG

View File

@@ -24,7 +24,8 @@ using jni_hook::hash_map;
using jni_hook::tree_map; using jni_hook::tree_map;
using xstring = jni_hook::string; using xstring = jni_hook::string;
static bool unhook_functions(); static void hook_unloader();
static void unhook_functions();
namespace { namespace {
@@ -46,6 +47,11 @@ void name##_post();
#define MAX_FD_SIZE 1024 #define MAX_FD_SIZE 1024
struct HookContext;
// Current context
HookContext *g_ctx;
struct HookContext { struct HookContext {
JNIEnv *env; JNIEnv *env;
union { union {
@@ -80,7 +86,8 @@ struct HookContext {
vector<IgnoreInfo> ignore_info; vector<IgnoreInfo> ignore_info;
HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0), HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0),
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {} hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; }
~HookContext();
/* Zygisksu changed: Load module fds */ /* Zygisksu changed: Load module fds */
void run_modules_pre(); void run_modules_pre();
@@ -94,6 +101,7 @@ struct HookContext {
void unload_zygisk(); void unload_zygisk();
void sanitize_fds(); void sanitize_fds();
bool exempt_fd(int fd); bool exempt_fd(int fd);
bool is_child() const { return pid <= 0; }
// Compatibility shim // Compatibility shim
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup); void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
@@ -109,9 +117,8 @@ struct HookContext {
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list; vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list; map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map; hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
bool should_unmap_zygisk = false;
// Current context
HookContext *g_ctx;
const JNINativeInterface *old_functions = nullptr; const JNINativeInterface *old_functions = nullptr;
JNINativeInterface *new_functions = nullptr; JNINativeInterface *new_functions = nullptr;
@@ -246,13 +253,28 @@ DCL_HOOK_FUNC(void, android_log_close) {
old_android_log_close(); old_android_log_close();
} }
// Last point before process secontext changes // We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,
DCL_HOOK_FUNC(int, selinux_android_setcontext, // it will return to our code which has been unmapped, causing segmentation fault.
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) { // Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start.
if (g_ctx) { DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) {
g_ctx->flags[CAN_UNLOAD_ZYGISK] = unhook_functions(); int res = old_pthread_attr_destroy((pthread_attr_t *)target);
// Only perform unloading on the main thread
if (gettid() != getpid())
return res;
LOGD("pthread_attr_destroy\n");
if (should_unmap_zygisk) {
unhook_functions();
if (should_unmap_zygisk) {
// Because both `pthread_attr_destroy` and `dlclose` have the same function signature,
// we can use `musttail` to let the compiler reuse our stack frame and thus
// `dlclose` will directly return to the caller of `pthread_attr_destroy`.
[[clang::musttail]] return dlclose(self_handle);
}
} }
return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
return res;
} }
#undef DCL_HOOK_FUNC #undef DCL_HOOK_FUNC
@@ -449,7 +471,6 @@ int sigmask(int how, int signum) {
} }
void HookContext::fork_pre() { void HookContext::fork_pre() {
g_ctx = this;
// Do our own fork before loading any 3rd party code // Do our own fork before loading any 3rd party code
// First block SIGCHLD, unblock after original fork is done // First block SIGCHLD, unblock after original fork is done
sigmask(SIG_BLOCK, SIGCHLD); sigmask(SIG_BLOCK, SIGCHLD);
@@ -615,7 +636,6 @@ bool HookContext::exempt_fd(int fd) {
void HookContext::nativeSpecializeAppProcess_pre() { void HookContext::nativeSpecializeAppProcess_pre() {
process = env->GetStringUTFChars(args.app->nice_name, nullptr); process = env->GetStringUTFChars(args.app->nice_name, nullptr);
LOGV("pre specialize [%s]\n", process); LOGV("pre specialize [%s]\n", process);
g_ctx = this;
// App specialize does not check FD // App specialize does not check FD
flags[SKIP_FD_SANITIZATION] = true; flags[SKIP_FD_SANITIZATION] = true;
app_specialize_pre(); app_specialize_pre();
@@ -674,6 +694,43 @@ void HookContext::nativeForkAndSpecialize_post() {
fork_post(); fork_post();
} }
HookContext::~HookContext() {
// 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.
g_ctx = nullptr;
if (!is_child())
return;
should_unmap_zygisk = true;
// Restore JNIEnv
if (env->functions == new_functions) {
env->functions = old_functions;
delete new_functions;
}
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && env->RegisterNatives(
env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
should_unmap_zygisk = false;
}
}
delete jni_hook_list;
jni_hook_list = nullptr;
// Strip out all API function pointers
for (auto &m : modules) {
m.clearApi();
}
hook_unloader();
}
} // namespace } // namespace
static bool hook_commit() { static bool hook_commit() {
@@ -716,7 +773,6 @@ void hook_functions() {
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork); 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, unshare);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext);
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc);
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
hook_commit(); hook_commit();
@@ -728,38 +784,32 @@ void hook_functions() {
plt_hook_list->end()); plt_hook_list->end());
} }
static bool unhook_functions() { static void hook_unloader() {
bool success = true; ino_t art_inode = 0;
dev_t art_dev = 0;
// Restore JNIEnv for (auto &map : lsplt::MapInfo::Scan()) {
if (g_ctx->env->functions == new_functions) { if (map.path.ends_with("/libart.so")) {
g_ctx->env->functions = old_functions; art_inode = map.inode;
delete new_functions; art_dev = map.dev;
} break;
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && g_ctx->env->RegisterNatives(
g_ctx->env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
success = false;
} }
} }
delete jni_hook_list;
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy);
hook_commit();
}
static void unhook_functions() {
// Unhook plt_hook // Unhook plt_hook
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) { for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) { if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
LOGE("Failed to register plt_hook [%s]\n", sym); LOGE("Failed to register plt_hook [%s]\n", sym);
success = false;
} }
} }
delete plt_hook_list; delete plt_hook_list;
if (!hook_commit()) { if (!hook_commit()) {
LOGE("Failed to restore plt_hook\n"); LOGE("Failed to restore plt_hook\n");
success = false; should_unmap_zygisk = false;
} }
return success;
} }

View File

@@ -23,7 +23,7 @@ passfd = "0.1"
rand = "0.8" rand = "0.8"
tokio = { version = "1.28", features = ["full"] } tokio = { version = "1.28", features = ["full"] }
binder = { git = "https://github.com/Kernel-SU/binder_rs" } binder = { git = "https://github.com/Kernel-SU/binder_rs", rev = "c9f2b62d6a744fd2264056c638c1b061a6a2932d" }
[profile.release] [profile.release]
strip = true strip = true