You've already forked ZygiskNext
mirror of
https://github.com/Dr-TSNG/ZygiskNext.git
synced 2025-08-27 23:46:34 +00:00
init-sigstop-based ptrace zygisk
This commit is contained in:
@@ -13,4 +13,9 @@ 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(libptracer.so ${PTRACER_SRC_LIST})
|
||||
target_include_directories(libptracer.so PRIVATE include)
|
||||
target_link_libraries(libptracer.so cxx::cxx log common)
|
||||
|
||||
add_subdirectory(external)
|
||||
|
||||
@@ -284,6 +284,14 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) {
|
||||
return res;
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(char *, strdup, const char *s) {
|
||||
if (s == "com.android.internal.os.ZygoteInit"sv) {
|
||||
LOGD("strdup %s\n", s);
|
||||
replace_jni_methods();
|
||||
}
|
||||
return old_strdup(s);
|
||||
}
|
||||
|
||||
#undef DCL_HOOK_FUNC
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
@@ -762,6 +770,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 +779,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() {
|
||||
|
||||
22
loader/src/ptracer/main.cpp
Normal file
22
loader/src/ptracer/main.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "main.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc >= 2 && argv[1] == "prop_monitor"sv) {
|
||||
if (access("/system/lib" LP_SELECT("", "64"), R_OK) == 0) prop_monitor_main();
|
||||
} else if (argc >= 3 && argv[1] == "trace-zygote"sv) {
|
||||
auto pid = strtol(argv[2], nullptr, 0);
|
||||
trace_zygote_main(pid);
|
||||
} else {
|
||||
if (argc >= 2) LOGE("unknown command %s", argv[1]);
|
||||
else LOGE("no command specified");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
4
loader/src/ptracer/main.hpp
Normal file
4
loader/src/ptracer/main.hpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void prop_monitor_main();
|
||||
void trace_zygote_main(int pid);
|
||||
37
loader/src/ptracer/monitor.cpp
Normal file
37
loader/src/ptracer/monitor.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <sys/system_properties.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "main.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
void prop_monitor_main() {
|
||||
LOGI("prop monitor started");
|
||||
// if service is not running, pid = ""
|
||||
auto name = "init.svc_debug_pid." LP_SELECT("zygote_secondary", "zygote"); // argv[1];
|
||||
LOGI("start monitoring %s", name);
|
||||
auto prop = __system_property_find(name);
|
||||
if (prop == nullptr) {
|
||||
__system_property_set(name, "");
|
||||
prop = __system_property_find(name);
|
||||
if (prop == nullptr) {
|
||||
LOGE("failed to create prop");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
char val[PROP_VALUE_MAX];
|
||||
uint32_t new_serial = 0;
|
||||
while (true) {
|
||||
__system_property_wait(prop, new_serial, &new_serial, nullptr);
|
||||
__system_property_get(name, val);
|
||||
LOGD("%s(%u): %s\n", name, new_serial, val);
|
||||
auto pid = strtol(val, nullptr, 0);
|
||||
if (pid != 0) {
|
||||
LOGD("start ptrace %ld", pid);
|
||||
if (fork_dont_care() == 0) {
|
||||
execl("/proc/self/exe", "zygisk-ptracer", "trace-zygote", val, nullptr);
|
||||
PLOGE("failed to exec");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
200
loader/src/ptracer/ptracer.cpp
Normal file
200
loader/src/ptracer/ptracer.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#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) {
|
||||
// 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;
|
||||
if (waitpid(pid, &status, __WALL) == -1) {
|
||||
PLOGE("wait");
|
||||
return false;
|
||||
}
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
if (regs.REG_IP != break_addr) {
|
||||
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_base = find_module_base(map, "libc.so");
|
||||
|
||||
// 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_base, args);
|
||||
LOGD("remote handle %p", (void *) remote_handle);
|
||||
if (remote_handle == 0) {
|
||||
LOGE("handle is null");
|
||||
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_base, args);
|
||||
LOGD("injector entry %p", (void*) injector_entry);
|
||||
if (injector_entry == 0) {
|
||||
LOGE("injector entry is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// call injector entry(handle)
|
||||
args.clear();
|
||||
args.push_back(remote_handle);
|
||||
remote_call(pid, regs, injector_entry, (uintptr_t) libc_base, 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;
|
||||
|
||||
/*
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
waitpid(pid, &status, __WALL);
|
||||
if (WIFSTOPPED(status)) {
|
||||
siginfo_t siginfo;
|
||||
ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo);
|
||||
LOGD("process stopped by signal %d %s si_code=%d si_addr=%p", WSTOPSIG(status),
|
||||
strsignal(WSTOPSIG(status)), siginfo.si_code, siginfo.si_addr);
|
||||
pause();
|
||||
} else {
|
||||
LOGD("other reason %d", status);
|
||||
}*/
|
||||
} else {
|
||||
LOGE("stopped by other reason: %d", status);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define STOPPED_WITH(sig, event) (WIFSTOPPED(status) && WSTOPSIG(status) == (sig) && (status >> 16) == (event))
|
||||
|
||||
void trace_zygote_main(int pid) {
|
||||
int status;
|
||||
LOGI("tracing %d (tracer %d)", pid, getpid());
|
||||
if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC) == -1) {
|
||||
PLOGE("seize");
|
||||
return;
|
||||
}
|
||||
wait_pid(pid, &status, __WALL);
|
||||
if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) {
|
||||
// if SIGSTOP is delivered before we seized it
|
||||
LOGD("process is already stopped");
|
||||
kill(pid, SIGCONT);
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
wait_pid(pid, &status, __WALL);
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP)) {
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
wait_pid(pid, &status, __WALL);
|
||||
if (STOPPED_WITH(SIGCONT, 0)) {
|
||||
LOGD("received SIGCONT");
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
}
|
||||
} else {
|
||||
LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", parse_status(status).c_str());
|
||||
}
|
||||
} else if (STOPPED_WITH(SIGSTOP, 0)) {
|
||||
// if SIGSTOP is delivered after we seized it
|
||||
LOGD("process received SIGSTOP, suppress");
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
} else {
|
||||
LOGE("unknown state %s, neither EVENT_STOP nor SIGSTOP", parse_status(status).c_str());
|
||||
exit(1);
|
||||
}
|
||||
wait_pid(pid, &status, __WALL);
|
||||
// enter the app_process
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
|
||||
LOGD("app_process exec-ed");
|
||||
if (!inject_on_main(pid, "/dev/zygisk/lib" LP_SELECT("", "64") "/libzygisk.so")) {
|
||||
LOGE("failed to inject");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
LOGE("unknown status %d", status);
|
||||
exit(1);
|
||||
}
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
}
|
||||
362
loader/src/ptracer/utils.cpp
Normal file
362
loader/src/ptracer/utils.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
#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 "utils.hpp"
|
||||
#include "logging.h"
|
||||
|
||||
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_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;
|
||||
if (waitpid(pid, &status, __WALL) == -1) {
|
||||
PLOGE("wait");
|
||||
}
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
|
||||
if (!get_regs(pid, regs)) {
|
||||
LOGE("failed to get regs after call");
|
||||
return 0;
|
||||
}
|
||||
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 %d", status);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
int wait_pid(int pid, int* status, int flags) {
|
||||
while (true) {
|
||||
auto result = waitpid(pid, status, flags);
|
||||
if (result == -1 && errno == EINTR) continue;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::string parse_status(int status) {
|
||||
std::ostringstream os;
|
||||
os << "status " << std::hex << status;
|
||||
os << 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();
|
||||
}
|
||||
114
loader/src/ptracer/utils.hpp
Normal file
114
loader/src/ptracer/utils.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include "daemon.h"
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LOG_TAG "zygisk-ptracer64"
|
||||
#else
|
||||
#define LOG_TAG "zygisk-ptracer32"
|
||||
#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.
|
||||
[[maybe_unused, gnu::visibility("default")]] 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();
|
||||
|
||||
int wait_pid(int pid, int* status, int flags);
|
||||
|
||||
std::string parse_status(int status);
|
||||
|
||||
#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)";
|
||||
}
|
||||
@@ -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 30 ]; then
|
||||
ui_print "! Unsupported sdk: $API"
|
||||
abort "! Minimal supported sdk is 29 (Android 10)"
|
||||
abort "! Minimal supported sdk is 30 (Android 11)"
|
||||
else
|
||||
ui_print "- Device sdk: $API"
|
||||
fi
|
||||
@@ -104,52 +104,53 @@ mv "$TMPDIR/sepolicy.rule" "$MODPATH"
|
||||
HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true
|
||||
|
||||
mkdir "$MODPATH/bin"
|
||||
mkdir "$MODPATH/system"
|
||||
mkdir "$MODPATH/system/lib64"
|
||||
[ "$HAS32BIT" = true ] && mkdir "$MODPATH/system/lib"
|
||||
mkdir "$MODPATH/lib"
|
||||
mkdir "$MODPATH/lib64"
|
||||
|
||||
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/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer32"
|
||||
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
|
||||
extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/lib64" true
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-wd"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-fuse"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-cp64"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-ptrace64"
|
||||
extract "$ZIPFILE" 'lib/x86_64/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer64"
|
||||
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/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer32"
|
||||
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
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/lib64" true
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-wd"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-fuse"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-cp64"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-ptrace64"
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer64"
|
||||
fi
|
||||
|
||||
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)
|
||||
|
||||
@@ -20,5 +20,27 @@ if [ "$(which magisk)" ]; then
|
||||
done
|
||||
fi
|
||||
|
||||
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
|
||||
unshare -m sh -c "bin/zygisk-fuse &"
|
||||
create_sys_perm() {
|
||||
mkdir -p $1
|
||||
chmod 555 $1
|
||||
chcon u:object_r:system_file:s0 $1
|
||||
}
|
||||
|
||||
create_sys_perm /dev/zygisk
|
||||
|
||||
if [ -f $MODDIR/lib64/libzygisk.so ];then
|
||||
create_sys_perm /dev/zygisk/lib64
|
||||
cp $MODDIR/lib64/libzygisk.so /dev/zygisk/lib64/libzygisk.so
|
||||
chcon u:object_r:system_lib_file:s0 /dev/zygisk/lib64/libzygisk.so
|
||||
setprop ctl.sigstop_on zygote
|
||||
unshare -m sh -c "./bin/zygisk-ptracer64 prop_monitor &"
|
||||
fi
|
||||
|
||||
if [ -f $MODDIR/lib/libzygisk.so ];then
|
||||
create_sys_perm /dev/zygisk/lib
|
||||
cp $MODDIR/lib/libzygisk.so /dev/zygisk/lib/libzygisk.so
|
||||
chcon u:object_r:system_lib_file:s0 /dev/zygisk/lib/libzygisk.so
|
||||
setprop ctl.sigstop_on zygote_secondary
|
||||
unshare -m sh -c "./bin/zygisk-ptracer32 prop_monitor &"
|
||||
fi
|
||||
|
||||
|
||||
@@ -23,10 +23,6 @@ proc-maps = "0.3"
|
||||
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.release]
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
|
||||
@@ -22,8 +22,6 @@ pub const PATH_ZYGISK_LIB: &str = concatcp!(lp_select!("/system/lib", "/system/l
|
||||
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_MODULES_DIR: &str = "..";
|
||||
pub const PATH_MODULE_PROP: &str = "module.prop";
|
||||
|
||||
@@ -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,7 +1,5 @@
|
||||
mod constants;
|
||||
mod dl;
|
||||
mod fuse;
|
||||
mod ptrace;
|
||||
mod root_impl;
|
||||
mod utils;
|
||||
mod watchdog;
|
||||
@@ -28,9 +26,7 @@ fn start(name: &str) -> Result<()> {
|
||||
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"),
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -4,8 +4,6 @@ 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};
|
||||
@@ -128,27 +126,6 @@ async fn spawn_daemon() -> Result<()> {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user