Add preliminary zygote code injection support

Prototyping the injection setup and a clean "self unloading" mechanism.
This commit is contained in:
topjohnwu 2021-01-06 22:21:17 -08:00
parent cd23d27048
commit 4060c2107c
13 changed files with 281 additions and 102 deletions

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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