Introduce zygisk loader

Use a separate library for 1st stage
This commit is contained in:
topjohnwu 2022-08-19 04:49:19 -07:00
parent 6bfe34e5a8
commit 9806b38d8e
11 changed files with 113 additions and 97 deletions

View File

@ -203,16 +203,6 @@ def clean_elf():
execv(args)
def binary_dump(src, var_name):
out_str = f'constexpr unsigned char {var_name}[] = {{'
for i, c in enumerate(xz(src.read())):
if i % 16 == 0:
out_str += '\n'
out_str += f'0x{c:02X},'
out_str += '\n};\n'
return out_str
def run_ndk_build(flags):
os.chdir('native')
flags = 'NDK_PROJECT_PATH=. NDK_APPLICATION_MK=src/Application.mk ' + flags
@ -221,7 +211,7 @@ def run_ndk_build(flags):
error('Build binary failed!')
os.chdir('..')
for arch in archs:
for tgt in support_targets + ['libpreload.so']:
for tgt in support_targets + ['libinit-ld.so', 'libzygisk-ld.so']:
source = op.join('native', 'libs', arch, tgt)
target = op.join('native', 'out', arch, tgt)
mv(source, target)
@ -306,6 +296,16 @@ def write_if_diff(file_name, text):
f.write(text)
def binary_dump(src, var_name, compressor=xz):
out_str = f'constexpr unsigned char {var_name}[] = {{'
for i, c in enumerate(compressor(src.read())):
if i % 16 == 0:
out_str += '\n'
out_str += f'0x{c:02X},'
out_str += '\n};\n'
return out_str
def dump_bin_header(args):
stub = op.join(config['outdir'], f'stub-{"release" if args.release else "debug"}.apk')
if not op.exists(stub):
@ -315,10 +315,13 @@ def dump_bin_header(args):
text = binary_dump(src, 'manager_xz')
write_if_diff(op.join(native_gen_path, 'binaries.h'), text)
for arch in archs:
preload = op.join('native', 'out', arch, 'libpreload.so')
preload = op.join('native', 'out', arch, 'libinit-ld.so')
with open(preload, 'rb') as src:
text = binary_dump(src, 'preload_xz')
write_if_diff(op.join(native_gen_path, f'{arch}_binaries.h'), text)
text = binary_dump(src, 'init_ld_xz')
preload = op.join('native', 'out', arch, 'libzygisk-ld.so')
with open(preload, 'rb') as src:
text += binary_dump(src, 'zygisk_ld', compressor=lambda x: x)
write_if_diff(op.join(native_gen_path, f'{arch}_binaries.h'), text)
def dump_flag_header():
@ -363,8 +366,8 @@ def build_binary(args):
flag = ''
if 'magisk' in args.target:
flag += ' B_MAGISK=1'
if 'magisk' in args.target or 'magiskinit' in args.target:
flag += ' B_PRELOAD=1'
if 'magiskpolicy' in args.target:
flag += ' B_POLICY=1'
@ -383,13 +386,23 @@ def build_binary(args):
if flag:
run_ndk_build(flag)
clean_elf()
# magiskinit and busybox has to be built separately
# magiskinit and magisk embeds preload.so
flag = ''
if 'magisk' in args.target:
flag += ' B_MAGISK=1'
if 'magiskinit' in args.target:
flag += ' B_INIT=1'
if flag:
dump_bin_header(args)
run_ndk_build('B_INIT=1')
run_ndk_build(flag)
clean_elf()
# BusyBox is built with different libc
if 'busybox' in args.target:
run_ndk_build('B_BB=1')

View File

@ -47,6 +47,7 @@ LOCAL_SRC_FILES := \
zygisk/deny/revert.cpp
LOCAL_LDLIBS := -llog
LOCAL_LDFLAGS := -Wl,--dynamic-list=src/exported_sym.txt
include $(BUILD_EXECUTABLE)
@ -55,10 +56,15 @@ endif
ifdef B_PRELOAD
include $(CLEAR_VARS)
LOCAL_MODULE := preload
LOCAL_MODULE := init-ld
LOCAL_SRC_FILES := init/preload.c
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zygisk-ld
LOCAL_SRC_FILES := zygisk/loader.c
include $(BUILD_SHARED_LIBRARY)
endif
ifdef B_INIT

View File

@ -28,5 +28,5 @@ LOCAL_MODULE := libcompat
LOCAL_SRC_FILES := compat/compat.cpp
# Fix static variables' ctor/dtor when using LTO
# See: https://github.com/android/ndk/issues/1461
LOCAL_EXPORT_LDLIBS := -static -T src/lto_fix.lds
LOCAL_EXPORT_LDFLAGS := -static -T src/lto_fix.lds
include $(BUILD_STATIC_LIBRARY)

View File

@ -0,0 +1,13 @@
#include <binaries.h>
#if defined(__arm__)
#include <armeabi-v7a_binaries.h>
#elif defined(__aarch64__)
#include <arm64-v8a_binaries.h>
#elif defined(__i386__)
#include <x86_binaries.h>
#elif defined(__x86_64__)
#include <x86_64_binaries.h>
#else
#error Unsupported ABI
#endif

View File

@ -567,19 +567,13 @@ int app_process_64 = -1;
if (access("/system/bin/app_process" #bit, F_OK) == 0) { \
app_process_##bit = xopen("/system/bin/app_process" #bit, O_RDONLY | O_CLOEXEC); \
string zbin = zygisk_bin + "/app_process" #bit; \
string dbin = zygisk_bin + "/magisk" #bit; \
string mbin = MAGISKTMP + "/magisk" #bit; \
int src = xopen(mbin.data(), O_RDONLY | O_CLOEXEC); \
int out = xopen(zbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \
xsendfile(out, src, nullptr, INT_MAX); \
close(out); \
out = xopen(dbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \
lseek(src, 0, SEEK_SET); \
xsendfile(out, src, nullptr, INT_MAX); \
close(out); \
close(src); \
clone_attr("/system/bin/app_process" #bit, zbin.data()); \
clone_attr("/system/bin/app_process" #bit, dbin.data()); \
bind_mount(zbin.data(), "/system/bin/app_process" #bit); \
}

View File

@ -0,0 +1,3 @@
{
zygisk_inject_entry;
};

View File

@ -6,19 +6,7 @@
#include <xz.h>
#include <base.hpp>
#include <binaries.h>
#if defined(__arm__)
#include <armeabi-v7a_binaries.h>
#elif defined(__aarch64__)
#include <arm64-v8a_binaries.h>
#elif defined(__i386__)
#include <x86_binaries.h>
#elif defined(__x86_64__)
#include <x86_64_binaries.h>
#else
#error Unsupported ABI
#endif
#include <embed.hpp>
#include "init.hpp"
@ -78,7 +66,7 @@ int dump_manager(const char *path, mode_t mode) {
}
int dump_preload(const char *path, mode_t mode) {
return dump_bin(preload_xz, sizeof(preload_xz), path, mode);
return dump_bin(init_ld_xz, sizeof(init_ld_xz), path, mode);
}
class RecoveryInit : public BaseInit {

View File

@ -8,6 +8,7 @@
#include <base.hpp>
#include <daemon.hpp>
#include <magisk.hpp>
#include <selinux.hpp>
#include "zygisk.hpp"
#include "module.hpp"
@ -51,30 +52,9 @@ static void *unload_first_stage(void *) {
return nullptr;
}
#if defined(__LP64__)
// Use symlink to workaround linker bug on old broken Android
// https://issuetracker.google.com/issues/36914295
#define SECOND_STAGE_PATH "/system/bin/app_process"
#else
#define SECOND_STAGE_PATH "/system/bin/app_process32"
#endif
static void second_stage_entry() {
extern "C" void zygisk_inject_entry(void *handle) {
zygisk_logging();
ZLOGD("inject 2nd stage\n");
MAGISKTMP = getenv(MAGISKTMP_ENV);
self_handle = dlopen(SECOND_STAGE_PATH, RTLD_NOLOAD);
dlclose(self_handle);
unsetenv(MAGISKTMP_ENV);
sanitize_environ();
hook_functions();
new_daemon_thread(&unload_first_stage, nullptr);
}
static void first_stage_entry() {
ZLOGD("inject 1st stage\n");
ZLOGD("load success\n");
char *ld = getenv("LD_PRELOAD");
if (char *c = strrchr(ld, ':')) {
@ -84,31 +64,13 @@ static void first_stage_entry() {
unsetenv("LD_PRELOAD");
}
// Load second stage
android_dlextinfo info {
.flags = ANDROID_DLEXT_FORCE_LOAD
};
setenv(INJECT_ENV_2, "1", 1);
if (android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info) == nullptr) {
// Android 5.x doesn't support ANDROID_DLEXT_FORCE_LOAD
ZLOGI("ANDROID_DLEXT_FORCE_LOAD is not supported, fallback to dlopen\n");
if (dlopen(SECOND_STAGE_PATH, RTLD_LAZY) == nullptr) {
ZLOGE("Cannot load the second stage\n");
unsetenv(INJECT_ENV_2);
}
}
}
MAGISKTMP = getenv(MAGISKTMP_ENV);
self_handle = handle;
[[gnu::constructor]] [[maybe_unused]]
static void zygisk_init() {
android_logging();
if (getenv(INJECT_ENV_1)) {
unsetenv(INJECT_ENV_1);
first_stage_entry();
} else if (getenv(INJECT_ENV_2)) {
unsetenv(INJECT_ENV_2);
second_stage_entry();
}
unsetenv(MAGISKTMP_ENV);
sanitize_environ();
hook_functions();
new_daemon_thread(&unload_first_stage, nullptr);
}
// The following code runs in zygote/app process
@ -239,7 +201,7 @@ static int zygote_start_counts[] = { 0, 0 };
static void setup_files(int client, const sock_cred *cred) {
LOGD("zygisk: setup files for pid=[%d]\n", cred->pid);
char buf[256];
char buf[4096];
if (!get_exe(cred->pid, buf, sizeof(buf))) {
write_int(client, 1);
return;
@ -278,22 +240,31 @@ static void setup_files(int client, const sock_cred *cred) {
}
}
// Hijack some binary in /system/bin to host 1st stage
// Ack
write_int(client, 0);
// Hijack some binary in /system/bin to host loader
const char *hbin;
string mbin;
int app_fd;
if (is_64_bit) {
hbin = HIJACK_BIN64;
mbin = MAGISKTMP + "/" ZYGISKBIN "/magisk64";
mbin = MAGISKTMP + "/" ZYGISKBIN "/loader64.so";
app_fd = app_process_64;
} else {
hbin = HIJACK_BIN32;
mbin = MAGISKTMP + "/" ZYGISKBIN "/magisk32";
mbin = MAGISKTMP + "/" ZYGISKBIN "/loader32.so";
app_fd = app_process_32;
}
// Receive and bind mount loader
int ld_fd = xopen(mbin.data(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0755);
string ld_data = read_string(client);
xwrite(ld_fd, ld_data.data(), ld_data.size());
close(ld_fd);
setfilecon(mbin.data(), "u:object_r:" SEPOL_FILE_TYPE ":s0");
xmount(mbin.data(), hbin, nullptr, MS_BIND, nullptr);
write_int(client, 0);
send_fd(client, app_fd);
write_string(client, MAGISKTMP);
}

View File

@ -0,0 +1,27 @@
#include <dlfcn.h>
#include <android/dlext.h>
#if defined(__LP64__)
// Use symlink to workaround linker bug on old broken Android
// https://issuetracker.google.com/issues/36914295
#define SECOND_STAGE_PATH "/system/bin/app_process"
#else
#define SECOND_STAGE_PATH "/system/bin/app_process32"
#endif
__attribute__((constructor))
static void zygisk_loader() {
android_dlextinfo info = {
.flags = ANDROID_DLEXT_FORCE_LOAD
};
// Android 5.x doesn't support ANDROID_DLEXT_FORCE_LOAD
void *handle =
android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info) ?:
dlopen(SECOND_STAGE_PATH, RTLD_LAZY);
if (handle) {
void(*entry)(void*) = dlsym(handle, "zygisk_inject_entry");
if (entry) {
entry(handle);
}
}
}

View File

@ -7,6 +7,7 @@
#include <socket.hpp>
#include <daemon.hpp>
#include <selinux.hpp>
#include <embed.hpp>
#include "zygisk.hpp"
@ -73,6 +74,10 @@ int app_process_main(int argc, char *argv[]) {
if (read_int(socket) != 0)
break;
// Send over zygisk loader
write_int(socket, sizeof(zygisk_ld));
xwrite(socket, zygisk_ld, sizeof(zygisk_ld));
int app_proc_fd = recv_fd(socket);
if (app_proc_fd < 0)
break;
@ -86,7 +91,6 @@ int app_process_main(int argc, char *argv[]) {
} else {
setenv("LD_PRELOAD", HIJACK_BIN, 1);
}
setenv(INJECT_ENV_1, "1", 1);
setenv(MAGISKTMP_ENV, tmp.data(), 1);
close(socket);

View File

@ -5,8 +5,6 @@
#include <vector>
#include <daemon.hpp>
#define INJECT_ENV_1 "MAGISK_INJ_1"
#define INJECT_ENV_2 "MAGISK_INJ_2"
#define MAGISKTMP_ENV "MAGISKTMP"
#define HIJACK_BIN64 "/system/bin/appwidget"
@ -55,7 +53,6 @@ extern void *self_handle;
void hook_functions();
int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector<int> &fds);
int remote_request_unmount();
inline int zygisk_request(int req) {
int fd = connect_daemon(MainRequest::ZYGISK);