Add preliminary zygote code injection support
Prototyping the injection setup and a clean "self unloading" mechanism.
This commit is contained in:
parent
cd23d27048
commit
4060c2107c
@ -30,7 +30,9 @@ LOCAL_SRC_FILES := \
|
||||
su/su.cpp \
|
||||
su/connect.cpp \
|
||||
su/pts.cpp \
|
||||
su/su_daemon.cpp
|
||||
su/su_daemon.cpp \
|
||||
inject/entry.cpp \
|
||||
inject/utils.cpp
|
||||
|
||||
LOCAL_LDLIBS := -llog
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
@ -8,21 +8,26 @@
|
||||
#include <selinux.hpp>
|
||||
#include <utils.hpp>
|
||||
|
||||
using namespace std::literals;
|
||||
using namespace std;
|
||||
|
||||
using main_fun = int (*)(int, char *[]);
|
||||
|
||||
static main_fun applet_main[] = { su_client_main, resetprop_main, magiskhide_main, nullptr };
|
||||
|
||||
[[noreturn]] static void call_applet(int argc, char **argv) {
|
||||
static int call_applet(int argc, char *argv[]) {
|
||||
// Applets
|
||||
string_view base = basename(argv[0]);
|
||||
for (int i = 0; applet_names[i]; ++i) {
|
||||
if (strcmp(basename(argv[0]), applet_names[i]) == 0) {
|
||||
exit((*applet_main[i])(argc, argv));
|
||||
if (base == applet_names[i]) {
|
||||
return (*applet_main[i])(argc, argv);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s: applet not found\n", basename(argv[0]));
|
||||
exit(1);
|
||||
if (str_starts(base, "app_process")) {
|
||||
return app_process_main(argc, argv);
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: applet not found\n", base.data());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@ -41,6 +46,6 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
call_applet(argc, argv);
|
||||
return call_applet(argc, argv);
|
||||
}
|
||||
|
||||
|
@ -189,21 +189,22 @@ static int magisk_log(int prio, const char *fmt, va_list ap) {
|
||||
return vfprintf(local_log_file.get(), buf, args);
|
||||
}
|
||||
|
||||
static void android_logging() {
|
||||
#define mlog(prio) [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_##prio, fmt, ap); }
|
||||
static void magisk_logging() {
|
||||
auto in_mem_file = make_stream_fp<byte_stream>(log_buf, log_buf_len);
|
||||
log_file.reset(in_mem_file.release(), [](FILE *) {
|
||||
free(log_buf);
|
||||
log_buf = nullptr;
|
||||
});
|
||||
log_cb.d = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_DEBUG, fmt, ap); };
|
||||
log_cb.i = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_INFO, fmt, ap); };
|
||||
log_cb.w = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_WARN, fmt, ap); };
|
||||
log_cb.e = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_ERROR, fmt, ap); };
|
||||
log_cb.d = mlog(DEBUG);
|
||||
log_cb.i = mlog(INFO);
|
||||
log_cb.w = mlog(WARN);
|
||||
log_cb.e = mlog(ERROR);
|
||||
log_cb.ex = nop_ex;
|
||||
}
|
||||
|
||||
static void daemon_entry(int ppid) {
|
||||
android_logging();
|
||||
magisk_logging();
|
||||
|
||||
int fd = xopen("/dev/null", O_WRONLY);
|
||||
xdup2(fd, STDOUT_FILENO);
|
||||
|
@ -36,3 +36,4 @@ int magiskhide_main(int argc, char *argv[]);
|
||||
int magiskpolicy_main(int argc, char *argv[]);
|
||||
int su_client_main(int argc, char *argv[]);
|
||||
int resetprop_main(int argc, char *argv[]);
|
||||
int app_process_main(int argc, char *argv[]);
|
||||
|
124
native/jni/inject/entry.cpp
Normal file
124
native/jni/inject/entry.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include <libgen.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <android/log.h>
|
||||
#include <atomic>
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void *self_handle = nullptr;
|
||||
static atomic<int> active_threads = -1;
|
||||
|
||||
#define alog(prio) [](auto fmt, auto ap){ \
|
||||
return __android_log_vprint(ANDROID_LOG_##prio, "Magisk", fmt, ap); }
|
||||
static void inject_logging() {
|
||||
log_cb.d = alog(DEBUG);
|
||||
log_cb.i = alog(INFO);
|
||||
log_cb.w = alog(WARN);
|
||||
log_cb.e = alog(ERROR);
|
||||
log_cb.ex = nop_ex;
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void inject_cleanup() {
|
||||
if (active_threads < 0)
|
||||
return;
|
||||
|
||||
// Setup 1ms
|
||||
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
|
||||
|
||||
// Check flag in busy loop
|
||||
while (active_threads)
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
// Wait another 1ms to make sure all threads left our code
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
|
||||
static inline void self_unload() {
|
||||
new_daemon_thread(reinterpret_cast<void *(*)(void *)>(&dlclose), self_handle);
|
||||
active_threads--;
|
||||
}
|
||||
|
||||
static void *unload_first_stage(void *) {
|
||||
// Setup 1ms
|
||||
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
|
||||
|
||||
while (getenv(INJECT_ENV_2))
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
// Wait another 1ms to make sure all threads left our code
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
unmap_all(INJECT_LIB_1);
|
||||
active_threads--;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void inject_init() {
|
||||
inject_logging();
|
||||
if (char *env = getenv(INJECT_ENV_1)) {
|
||||
LOGD("zygote: inject 1st stage\n");
|
||||
|
||||
if (env[0] == '1')
|
||||
unsetenv("LD_PRELOAD");
|
||||
else
|
||||
setenv("LD_PRELOAD", env, 1); // Restore original LD_PRELOAD
|
||||
unsetenv(INJECT_ENV_1);
|
||||
|
||||
// Setup second stage
|
||||
setenv(INJECT_ENV_2, "1", 1);
|
||||
cp_afc(INJECT_LIB_1, INJECT_LIB_2);
|
||||
dlopen(INJECT_LIB_2, RTLD_LAZY);
|
||||
} else if (getenv(INJECT_ENV_2)) {
|
||||
LOGD("zygote: inject 2nd stage\n");
|
||||
|
||||
active_threads = 1;
|
||||
|
||||
// Get our own handle
|
||||
self_handle = dlopen(INJECT_LIB_2, RTLD_LAZY);
|
||||
dlclose(self_handle);
|
||||
|
||||
// Cleanup 1st stage maps
|
||||
active_threads++;
|
||||
new_daemon_thread(&unload_first_stage);
|
||||
unsetenv(INJECT_ENV_2);
|
||||
|
||||
// TODO: actually inject stuffs, for now we demonstrate clean self unloading
|
||||
self_unload();
|
||||
}
|
||||
}
|
||||
|
||||
int app_process_main(int argc, char *argv[]) {
|
||||
inject_logging();
|
||||
char buf[4096];
|
||||
if (realpath("/proc/self/exe", buf) == nullptr)
|
||||
return 1;
|
||||
|
||||
int in = xopen(buf, O_RDONLY);
|
||||
int out = xopen(INJECT_LIB_1, O_CREAT | O_WRONLY | O_TRUNC, 0777);
|
||||
sendfile(out, in, nullptr, INT_MAX);
|
||||
close(in);
|
||||
close(out);
|
||||
|
||||
if (char *ld = getenv("LD_PRELOAD")) {
|
||||
char env[128];
|
||||
sprintf(env, "%s:" INJECT_LIB_1, ld);
|
||||
setenv("LD_PRELOAD", env, 1);
|
||||
setenv(INJECT_ENV_1, ld, 1); // Backup original LD_PRELOAD
|
||||
} else {
|
||||
setenv("LD_PRELOAD", INJECT_LIB_1, 1);
|
||||
setenv(INJECT_ENV_1, "1", 1);
|
||||
}
|
||||
|
||||
// Execute real app_process
|
||||
xumount2(buf, MNT_DETACH);
|
||||
execve(buf, argv, environ);
|
||||
exit(1);
|
||||
}
|
17
native/jni/inject/inject.hpp
Normal file
17
native/jni/inject/inject.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define INJECT_LIB_1 "/dev/tmp/magisk.1.so"
|
||||
#define INJECT_LIB_2 "/dev/tmp/magisk.2.so"
|
||||
#define INJECT_ENV_1 "MAGISK_INJ_1"
|
||||
#define INJECT_ENV_2 "MAGISK_INJ_2"
|
||||
|
||||
// Unmap all pages matching the name
|
||||
void unmap_all(const char *name);
|
||||
|
||||
// Get library name and base address that contains the function
|
||||
uintptr_t get_function_lib(uintptr_t addr, char *lib);
|
||||
|
||||
// Get library base address with name
|
||||
uintptr_t get_remote_lib(int pid, const char *lib);
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
#include "ptrace.hpp"
|
||||
|
||||
using namespace std;
|
||||
@ -43,80 +44,6 @@ using namespace std;
|
||||
#define ARM_r0 regs[0]
|
||||
#endif
|
||||
|
||||
struct map_info {
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
int perms;
|
||||
char *path;
|
||||
|
||||
map_info() : start(0), end(0), perms(0), path(nullptr) {}
|
||||
|
||||
enum {
|
||||
EXEC = (1 << 0),
|
||||
WRITE = (1 << 1),
|
||||
READ = (1 << 2),
|
||||
};
|
||||
};
|
||||
|
||||
// Func signature: bool(map_info&)
|
||||
template <typename Func>
|
||||
static void parse_maps(int pid, Func fn) {
|
||||
char file[32];
|
||||
|
||||
// format: start-end perms offset dev inode path
|
||||
sprintf(file, "/proc/%d/maps", pid);
|
||||
file_readline(true, file, [=](string_view l) -> bool {
|
||||
char *pos = (char *) l.data();
|
||||
map_info info;
|
||||
|
||||
// Parse address hex strings
|
||||
info.start = strtoul(pos, &pos, 16);
|
||||
info.end = strtoul(++pos, &pos, 16);
|
||||
|
||||
// Parse permissions
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::READ;
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::WRITE;
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::EXEC;
|
||||
pos += 3;
|
||||
|
||||
// Skip everything except path
|
||||
int path_off;
|
||||
sscanf(pos, "%*s %*s %*s %n%*s", &path_off);
|
||||
pos += path_off;
|
||||
info.path = pos;
|
||||
|
||||
return fn(info);
|
||||
});
|
||||
}
|
||||
|
||||
uintptr_t get_function_lib(uintptr_t addr, char *lib) {
|
||||
uintptr_t base = 0;
|
||||
parse_maps(getpid(), [=, &base](map_info &info) -> bool {
|
||||
if (addr >= info.start && addr < info.end) {
|
||||
strcpy(lib, info.path);
|
||||
base = info.start;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return base;
|
||||
}
|
||||
|
||||
uintptr_t get_remote_lib(int pid, const char *lib) {
|
||||
uintptr_t base = 0;
|
||||
parse_maps(pid, [=, &base](map_info &info) -> bool {
|
||||
if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) {
|
||||
base = info.start;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return base;
|
||||
}
|
||||
|
||||
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long data = xptrace(PTRACE_PEEKTEXT, pid, reinterpret_cast<void*>(addr + i));
|
||||
@ -165,7 +92,7 @@ static void _remote_setregs(int pid, pt_regs *regs) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) {
|
||||
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) {
|
||||
pt_regs regs, regs_bak;
|
||||
|
||||
// Get registers and save a backup
|
||||
@ -300,7 +227,11 @@ static uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_lis
|
||||
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) {
|
||||
char lib_name[4096];
|
||||
auto local = get_function_lib(addr, lib_name);
|
||||
if (local == 0)
|
||||
return 0;
|
||||
auto remote = get_remote_lib(pid, lib_name);
|
||||
if (remote == 0)
|
||||
return 0;
|
||||
addr = addr - local + remote;
|
||||
va_list va;
|
||||
va_start(va, nargs);
|
||||
|
@ -2,12 +2,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Get library name and base address that contains the function
|
||||
uintptr_t get_function_lib(uintptr_t addr, char *lib);
|
||||
|
||||
// Get library base address with name
|
||||
uintptr_t get_remote_lib(int pid, const char *lib);
|
||||
|
||||
// Write bytes to the remote process at addr
|
||||
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len);
|
||||
#define remote_write(...) _remote_write(pid, __VA_ARGS__)
|
||||
@ -19,6 +13,9 @@ bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len);
|
||||
// Call a remote function
|
||||
// Arguments are expected to be only integer-like or pointer types
|
||||
// as other more complex C ABIs are not implemented.
|
||||
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va);
|
||||
|
||||
// Find remote offset and invoke function
|
||||
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...);
|
||||
|
||||
// C++ wrapper for auto argument counting and casting function pointers
|
||||
|
101
native/jni/inject/utils.cpp
Normal file
101
native/jni/inject/utils.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
struct map_info {
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
int perms;
|
||||
char *path;
|
||||
|
||||
map_info() : start(0), end(0), perms(0), path(nullptr) {}
|
||||
|
||||
enum {
|
||||
EXEC = (1 << 0),
|
||||
WRITE = (1 << 1),
|
||||
READ = (1 << 2),
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template<typename Func>
|
||||
static void parse_maps(int pid, Func fn) {
|
||||
char file[32];
|
||||
|
||||
// format: start-end perms offset dev inode path
|
||||
sprintf(file, "/proc/%d/maps", pid);
|
||||
file_readline(true, file, [=](string_view l) -> bool {
|
||||
char *pos = (char *) l.data();
|
||||
map_info info;
|
||||
|
||||
// Parse address hex strings
|
||||
info.start = strtoul(pos, &pos, 16);
|
||||
info.end = strtoul(++pos, &pos, 16);
|
||||
|
||||
// Parse permissions
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::READ;
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::WRITE;
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::EXEC;
|
||||
pos += 3;
|
||||
|
||||
// Skip everything except path
|
||||
int path_off;
|
||||
sscanf(pos, "%*s %*s %*s %n%*s", &path_off);
|
||||
pos += path_off;
|
||||
info.path = pos;
|
||||
|
||||
return fn(info);
|
||||
});
|
||||
}
|
||||
|
||||
void unmap_all(const char *name) {
|
||||
vector<map_info> unmaps;
|
||||
parse_maps(getpid(), [=, &unmaps](map_info &info) -> bool {
|
||||
if (strcmp(info.path, name) == 0)
|
||||
unmaps.emplace_back(info);
|
||||
return true;
|
||||
});
|
||||
for (map_info &info : unmaps) {
|
||||
void *addr = reinterpret_cast<void *>(info.start);
|
||||
size_t size = info.end - info.start;
|
||||
munmap(addr, size);
|
||||
if (info.perms & map_info::READ) {
|
||||
// Make sure readable pages are still readable
|
||||
xmmap(addr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t get_function_lib(uintptr_t addr, char *lib) {
|
||||
uintptr_t base = 0;
|
||||
parse_maps(getpid(), [=, &base](map_info &info) -> bool {
|
||||
if (addr >= info.start && addr < info.end) {
|
||||
if (lib)
|
||||
strcpy(lib, info.path);
|
||||
base = info.start;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return base;
|
||||
}
|
||||
|
||||
uintptr_t get_remote_lib(int pid, const char *lib) {
|
||||
uintptr_t base = 0;
|
||||
parse_maps(pid, [=, &base](map_info &info) -> bool {
|
||||
if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) {
|
||||
base = info.start;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return base;
|
||||
}
|
@ -339,7 +339,6 @@ static void new_zygote(int pid) {
|
||||
xptrace(PTRACE_CONT, pid);
|
||||
}
|
||||
|
||||
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
||||
#define DETACH_AND_CONT { detach_pid(pid); continue; }
|
||||
|
||||
void proc_monitor() {
|
||||
|
@ -113,12 +113,12 @@ int exec_command_sync(exec_t &exec) {
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int new_daemon_thread(thread_entry entry, void *arg, const pthread_attr_t *attr) {
|
||||
int new_daemon_thread(thread_entry entry, void *arg) {
|
||||
pthread_t thread;
|
||||
int ret = xpthread_create(&thread, attr, entry, arg);
|
||||
if (ret == 0)
|
||||
pthread_detach(thread);
|
||||
return ret;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
return xpthread_create(&thread, &attr, entry, arg);
|
||||
}
|
||||
|
||||
static void *proxy_routine(void *fp) {
|
||||
|
@ -59,7 +59,7 @@ static inline int parse_int(const std::string &s) { return parse_int(s.data());
|
||||
static inline int parse_int(std::string_view s) { return parse_int(s.data()); }
|
||||
|
||||
using thread_entry = void *(*)(void *);
|
||||
int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr);
|
||||
int new_daemon_thread(thread_entry entry, void *arg = nullptr);
|
||||
int new_daemon_thread(std::function<void()> &&entry);
|
||||
|
||||
static inline bool str_contains(std::string_view s, std::string_view ss) {
|
||||
|
@ -64,3 +64,4 @@ long xptrace(int request, pid_t pid, void *addr = nullptr, void *data = nullptr)
|
||||
static inline long xptrace(int request, pid_t pid, void *addr, uintptr_t data) {
|
||||
return xptrace(request, pid, addr, reinterpret_cast<void *>(data));
|
||||
}
|
||||
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
||||
|
Loading…
Reference in New Issue
Block a user