You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +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)";
|
||||
}
|
||||
Reference in New Issue
Block a user