Remove all non-Magisk hiding code

Magisk no longer interferes with any signals/info that were not created
or caused by Magisk itself.
This commit is contained in:
topjohnwu 2021-08-18 02:01:54 -07:00
parent 2b17c77195
commit 003fea52b1
9 changed files with 110 additions and 636 deletions

View File

@ -298,10 +298,10 @@ void post_fs_data(int client) {
safe_mode = true;
// Disable all modules and magiskhide so next boot will be clean
disable_modules();
stop_magiskhide();
disable_hide();
} else {
exec_common_scripts("post-fs-data");
auto_start_magiskhide(false);
check_enable_hide();
handle_modules();
}
@ -350,7 +350,7 @@ void boot_complete(int client) {
if (access(SECURE_DIR, F_OK) != 0)
xmkdir(SECURE_DIR, 0700);
auto_start_magiskhide(true);
check_enable_hide();
if (!check_manager()) {
if (access(MANAGERAPK, F_OK) == 0) {

View File

@ -55,5 +55,5 @@ void magiskhide_handler(int client, ucred *cred);
void su_daemon_handler(int client, ucred *credential);
// MagiskHide
void auto_start_magiskhide(bool late_props);
int stop_magiskhide();
void check_enable_hide();
int disable_hide();

View File

@ -9,69 +9,6 @@
using namespace std;
static const char *prop_key[] =
{ "ro.boot.vbmeta.device_state", "ro.boot.verifiedbootstate", "ro.boot.flash.locked",
"ro.boot.veritymode", "ro.boot.warranty_bit", "ro.warranty_bit",
"ro.debuggable", "ro.secure", "ro.build.type", "ro.build.tags",
"ro.vendor.boot.warranty_bit", "ro.vendor.warranty_bit",
"vendor.boot.vbmeta.device_state", nullptr };
static const char *prop_val[] =
{ "locked", "green", "1",
"enforcing", "0", "0",
"0", "1", "user", "release-keys",
"0", "0",
"locked", nullptr };
static const char *late_prop_key[] =
{ "vendor.boot.verifiedbootstate", nullptr };
static const char *late_prop_val[] =
{ "green", nullptr };
void hide_sensitive_props() {
LOGI("hide: Hiding sensitive props\n");
for (int i = 0; prop_key[i]; ++i) {
auto value = getprop(prop_key[i]);
if (!value.empty() && value != prop_val[i])
setprop(prop_key[i], prop_val[i], false);
}
// Hide that we booted from recovery when magisk is in recovery mode
auto bootmode = getprop("ro.bootmode");
if (!bootmode.empty() && str_contains(bootmode, "recovery"))
setprop("ro.bootmode", "unknown", false);
bootmode = getprop("ro.boot.mode");
if (!bootmode.empty() && str_contains(bootmode, "recovery"))
setprop("ro.boot.mode", "unknown", false);
bootmode = getprop("vendor.boot.mode");
if (!bootmode.empty() && str_contains(bootmode, "recovery"))
setprop("vendor.boot.mode", "unknown", false);
// Xiaomi cross region flash
auto hwc = getprop("ro.boot.hwc");
if (!hwc.empty() && str_contains(hwc, "CN"))
setprop("ro.boot.hwc", "GLOBAL", false);
auto hwcountry = getprop("ro.boot.hwcountry");
if (!hwcountry.empty() && str_contains(hwcountry, "China"))
setprop("ro.boot.hwcountry", "GLOBAL", false);
auto selinux = getprop("ro.build.selinux");
if (!selinux.empty())
delprop("ro.build.selinux");
}
void hide_late_sensitive_props() {
LOGI("hide: Hiding sensitive props (late)\n");
for (int i = 0; late_prop_key[i]; ++i) {
auto value = getprop(late_prop_key[i]);
if (!value.empty() && value != late_prop_val[i])
setprop(late_prop_key[i], late_prop_val[i], false);
}
}
static void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1)
LOGD("hide: Unmounted (%s)\n", mountpoint);
@ -86,8 +23,7 @@ void hide_daemon(int pid) {
}
}
#define TMPFS_MNT(dir) (mentry->mnt_type == "tmpfs"sv && \
strncmp(mentry->mnt_dir, "/" #dir, sizeof("/" #dir) - 1) == 0)
#define TMPFS_MNT(dir) (mentry->mnt_type == "tmpfs"sv && str_starts(mentry->mnt_dir, "/" #dir))
void hide_unmount(int pid) {
if (pid > 0 && switch_mnt_ns(pid))
@ -95,16 +31,6 @@ void hide_unmount(int pid) {
LOGD("hide: handling PID=[%d]\n", pid);
char val;
int fd = xopen(SELINUX_ENFORCE, O_RDONLY);
xxread(fd, &val, sizeof(val));
close(fd);
// Permissive
if (val == '0') {
chmod(SELINUX_ENFORCE, 0640);
chmod(SELINUX_POLICY, 0440);
}
vector<string> targets;
// Unmount dummy skeletons and /sbin links
@ -121,7 +47,7 @@ void hide_unmount(int pid) {
// Unmount all Magisk created mounts
parse_mnt("/proc/self/mounts", [&](mntent *mentry) {
if (strstr(mentry->mnt_fsname, BLOCKDIR))
if (str_contains(mentry->mnt_fsname, BLOCKDIR))
targets.emplace_back(mentry->mnt_dir);
return true;
});
@ -129,4 +55,3 @@ void hide_unmount(int pid) {
for (auto &s : reversed(targets))
lazy_unmount(s.data());
}

View File

@ -13,16 +13,16 @@
using namespace std;
static bool hide_state = false;
static set<pair<string, string>> hide_set; /* set of <pkg, process> pair */
map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
static set<pair<string, string>> *hide_set; /* set of <pkg, process> pair */
static map<int, vector<string_view>> *uid_proc_map; /* uid -> list of process */
// Locks the variables above
pthread_mutex_t hide_state_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t hide_data_lock = PTHREAD_MUTEX_INITIALIZER;
void update_uid_map() {
mutex_guard lock(hide_state_lock);
uid_proc_map.clear();
static atomic<bool> hide_state = false;
static void rebuild_uid_map() {
uid_proc_map->clear();
string data_path(APP_DATA_DIR);
size_t len = data_path.length();
auto dir = open_dir(APP_DATA_DIR);
@ -34,17 +34,17 @@ void update_uid_map() {
data_path += '/';
size_t user_len = data_path.length();
struct stat st;
for (auto &hide : hide_set) {
for (const auto &hide : *hide_set) {
if (hide.first == ISOLATED_MAGIC) {
if (!first_iter) continue;
// Setup isolated processes
uid_proc_map[-1].emplace_back(hide.second);
(*uid_proc_map)[-1].emplace_back(hide.second);
}
data_path.resize(user_len);
data_path += hide.first;
if (stat(data_path.data(), &st))
continue;
uid_proc_map[st.st_uid].emplace_back(hide.second);
(*uid_proc_map)[st.st_uid].emplace_back(hide.second);
}
first_iter = false;
}
@ -52,26 +52,19 @@ void update_uid_map() {
// Leave /proc fd opened as we're going to read from it repeatedly
static DIR *procfp;
void crawl_procfs(const function<bool(int)> &fn) {
rewinddir(procfp);
crawl_procfs(procfp, fn);
}
void crawl_procfs(DIR *dir, const function<bool(int)> &fn) {
struct dirent *dp;
template<class F>
static void crawl_procfs(F &&fn) {
rewinddir(procfp);
dirent *dp;
int pid;
while ((dp = readdir(dir))) {
while ((dp = readdir(procfp))) {
pid = parse_int(dp->d_name);
if (pid > 0 && !fn(pid))
break;
}
}
bool hide_enabled() {
mutex_guard g(hide_state_lock);
return hide_state;
}
template <bool str_op(string_view, string_view)>
static bool proc_name_match(int pid, const char *name) {
char buf[4019];
@ -86,12 +79,12 @@ static bool proc_name_match(int pid, const char *name) {
return false;
}
static inline bool str_eql(string_view s, string_view ss) { return s == ss; }
static inline bool str_eql(string_view a, string_view b) { return a == b; }
static void kill_process(const char *name, bool multi = false,
bool (*filter)(int, const char *) = proc_name_match<&str_eql>) {
template<bool str_op(string_view, string_view) = &str_eql>
static void kill_process(const char *name, bool multi = false) {
crawl_procfs([=](int pid) -> bool {
if (filter(pid, name)) {
if (proc_name_match<str_op>(pid, name)) {
kill(pid, SIGKILL);
return multi;
}
@ -137,10 +130,10 @@ static bool validate(const char *pkg, const char *proc) {
static void add_hide_set(const char *pkg, const char *proc) {
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
hide_set.emplace(pkg, proc);
hide_set->emplace(pkg, proc);
if (strcmp(pkg, ISOLATED_MAGIC) == 0) {
// Kill all matching isolated processes
kill_process(proc, true, proc_name_match<&str_starts>);
kill_process<&str_starts>(proc, true);
} else {
kill_process(proc);
}
@ -153,9 +146,15 @@ static int add_list(const char *pkg, const char *proc) {
if (!validate(pkg, proc))
return HIDE_INVALID_PKG;
for (auto &hide : hide_set)
if (hide.first == pkg && hide.second == proc)
return HIDE_ITEM_EXIST;
{
// Critical region
mutex_guard lock(hide_data_lock);
for (const auto &hide : *hide_set)
if (hide.first == pkg && hide.second == proc)
return HIDE_ITEM_EXIST;
add_hide_set(pkg, proc);
rebuild_uid_map();
}
// Add to database
char sql[4096];
@ -163,42 +162,33 @@ static int add_list(const char *pkg, const char *proc) {
"INSERT INTO hidelist (package_name, process) VALUES('%s', '%s')", pkg, proc);
char *err = db_exec(sql);
db_err_cmd(err, return DAEMON_ERROR);
{
// Critical region
mutex_guard lock(hide_state_lock);
add_hide_set(pkg, proc);
}
return DAEMON_SUCCESS;
}
int add_list(int client) {
string pkg = read_string(client);
string proc = read_string(client);
int ret = add_list(pkg.data(), proc.data());
if (ret == DAEMON_SUCCESS)
update_uid_map();
return ret;
return add_list(pkg.data(), proc.data());
}
static int rm_list(const char *pkg, const char *proc) {
bool remove = false;
{
// Critical region
mutex_guard lock(hide_state_lock);
for (auto it = hide_set.begin(); it != hide_set.end();) {
mutex_guard lock(hide_data_lock);
for (auto it = hide_set->begin(); it != hide_set->end();) {
if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) {
remove = true;
LOGI("hide_list rm: [%s/%s]\n", it->first.data(), it->second.data());
it = hide_set.erase(it);
it = hide_set->erase(it);
} else {
++it;
}
}
if (!remove)
return HIDE_ITEM_NOT_EXIST;
rebuild_uid_map();
}
if (!remove)
return HIDE_ITEM_NOT_EXIST;
char sql[4096];
if (proc[0] == '\0')
@ -207,17 +197,14 @@ static int rm_list(const char *pkg, const char *proc) {
snprintf(sql, sizeof(sql),
"DELETE FROM hidelist WHERE package_name='%s' AND process='%s'", pkg, proc);
char *err = db_exec(sql);
db_err(err);
db_err_cmd(err, return DAEMON_ERROR);
return DAEMON_SUCCESS;
}
int rm_list(int client) {
string pkg = read_string(client);
string proc = read_string(client);
int ret = rm_list(pkg.data(), proc.data());
if (ret == DAEMON_SUCCESS)
update_uid_map();
return ret;
return rm_list(pkg.data(), proc.data());
}
static bool str_ends_safe(string_view s, string_view ss) {
@ -227,9 +214,6 @@ static bool str_ends_safe(string_view s, string_view ss) {
return str_ends(s, ss);
}
#define SNET_PROC "com.google.android.gms.unstable"
#define GMS_PKG "com.google.android.gms"
static bool init_list() {
LOGD("hide: initialize\n");
@ -243,27 +227,22 @@ static bool init_list() {
if (SDK_INT >= 29) {
kill_process("usap32", true);
kill_process("usap64", true);
kill_process("_zygote", true, proc_name_match<&str_ends_safe>);
kill_process<&str_ends_safe>("_zygote", true);
}
// Add SafetyNet by default
add_hide_set(GMS_PKG, SNET_PROC);
// We also need to hide the default GMS process if MAGISKTMP != /sbin
// The snet process communicates with the main process and get additional info
if (MAGISKTMP != "/sbin")
add_hide_set(GMS_PKG, GMS_PKG);
return true;
}
void ls_list(int client) {
write_int(client, DAEMON_SUCCESS);
for (auto &hide : hide_set) {
write_int(client, hide.first.size() + hide.second.size() + 1);
xwrite(client, hide.first.data(), hide.first.size());
xwrite(client, "|", 1);
xwrite(client, hide.second.data(), hide.second.size());
{
mutex_guard lock(hide_data_lock);
for (const auto &hide : *hide_set) {
write_int(client, hide.first.size() + hide.second.size() + 1);
xwrite(client, hide.first.data(), hide.first.size());
xwrite(client, "|", 1);
xwrite(client, hide.second.data(), hide.second.size());
}
}
write_int(client, 0);
close(client);
@ -272,14 +251,12 @@ void ls_list(int client) {
static void update_hide_config() {
char sql[64];
sprintf(sql, "REPLACE INTO settings (key,value) VALUES('%s',%d)",
DB_SETTING_KEYS[HIDE_CONFIG], hide_state);
DB_SETTING_KEYS[HIDE_CONFIG], hide_state.load());
char *err = db_exec(sql);
db_err(err);
}
int launch_magiskhide(bool late_props) {
mutex_guard lock(hide_state_lock);
int enable_hide() {
if (hide_state)
return HIDE_IS_ENABLED;
@ -291,31 +268,30 @@ int launch_magiskhide(bool late_props) {
LOGI("* Enable MagiskHide\n");
mutex_guard lock(hide_data_lock);
default_new(hide_set);
default_new(uid_proc_map);
// Initialize the hide list
if (!init_list())
return DAEMON_ERROR;
hide_sensitive_props();
if (late_props)
hide_late_sensitive_props();
hide_state = true;
update_hide_config();
// Unlock here or else we'll be stuck in deadlock
lock.unlock();
update_uid_map();
rebuild_uid_map();
return DAEMON_SUCCESS;
}
int stop_magiskhide() {
mutex_guard g(hide_state_lock);
int disable_hide() {
mutex_guard lock(hide_data_lock);
if (hide_state) {
LOGI("* Disable MagiskHide\n");
uid_proc_map.clear();
hide_set.clear();
delete uid_proc_map;
delete hide_set;
uid_proc_map = nullptr;
hide_set = nullptr;
}
hide_state = false;
@ -323,43 +299,41 @@ int stop_magiskhide() {
return DAEMON_SUCCESS;
}
void auto_start_magiskhide(bool late_props) {
if (hide_enabled()) {
hide_late_sensitive_props();
} else {
void check_enable_hide() {
if (!hide_state) {
db_settings dbs;
get_db_settings(dbs, HIDE_CONFIG);
if (dbs[HIDE_CONFIG])
launch_magiskhide(late_props);
enable_hide();
}
}
bool is_hide_target(int uid, string_view process, int max_len) {
mutex_guard lock(hide_state_lock);
bool is_hide_target(int uid, string_view process) {
mutex_guard lock(hide_data_lock);
if (uid % 100000 >= 90000) {
// Isolated processes
auto it = uid_proc_map.find(-1);
if (it == uid_proc_map.end())
auto it = uid_proc_map->find(-1);
if (it == uid_proc_map->end())
return false;
for (auto &s : it->second) {
if (s.length() > max_len && process.length() > max_len && str_starts(s, process))
return true;
if (str_starts(process, s))
return true;
}
} else {
auto it = uid_proc_map.find(uid);
if (it == uid_proc_map.end())
auto it = uid_proc_map->find(uid);
if (it == uid_proc_map->end())
return false;
for (auto &s : it->second) {
if (s.length() > max_len && process.length() > max_len && str_starts(s, process))
return true;
if (s == process)
return true;
}
}
return false;
}
bool hide_enabled() {
return hide_state;
}

View File

@ -20,9 +20,6 @@ using namespace std;
" ls Print the current hide list\n"
" exec CMDs... Execute commands in isolated mount\n"
" namespace and do all hide unmounts\n"
#ifdef MAGISK_DEBUG
" test Run process monitor test\n"
#endif
, arg0);
exit(1);
}
@ -32,10 +29,10 @@ void magiskhide_handler(int client, ucred *cred) {
int res = DAEMON_ERROR;
switch (req) {
case STOP_MAGISKHIDE:
case ADD_HIDELIST:
case RM_HIDELIST:
case LS_HIDELIST:
case DISABLE_HIDE:
case ADD_LIST:
case RM_LIST:
case LS_LIST:
if (!hide_enabled()) {
write_int(client, HIDE_NOT_ENABLED);
close(client);
@ -44,19 +41,19 @@ void magiskhide_handler(int client, ucred *cred) {
}
switch (req) {
case LAUNCH_MAGISKHIDE:
res = launch_magiskhide(true);
case ENABLE_HIDE:
res = enable_hide();
break;
case STOP_MAGISKHIDE:
res = stop_magiskhide();
case DISABLE_HIDE:
res = disable_hide();
break;
case ADD_HIDELIST:
case ADD_LIST:
res = add_list(client);
break;
case RM_HIDELIST:
case RM_LIST:
res = rm_list(client);
break;
case LS_HIDELIST:
case LS_LIST:
ls_list(client);
return;
case HIDE_STATUS:
@ -79,15 +76,15 @@ int magiskhide_main(int argc, char *argv[]) {
int req;
if (opt == "enable"sv)
req = LAUNCH_MAGISKHIDE;
req = ENABLE_HIDE;
else if (opt == "disable"sv)
req = STOP_MAGISKHIDE;
req = DISABLE_HIDE;
else if (opt == "add"sv)
req = ADD_HIDELIST;
req = ADD_LIST;
else if (opt == "rm"sv)
req = RM_HIDELIST;
req = RM_LIST;
else if (opt == "ls"sv)
req = LS_HIDELIST;
req = LS_LIST;
else if (opt == "status"sv)
req = HIDE_STATUS;
else if (opt == "exec"sv && argc > 2) {
@ -104,7 +101,7 @@ int magiskhide_main(int argc, char *argv[]) {
int fd = connect_daemon();
write_int(fd, MAGISKHIDE);
write_int(fd, req);
if (req == ADD_HIDELIST || req == RM_HIDELIST) {
if (req == ADD_LIST || req == RM_LIST) {
write_string(fd, argv[2]);
write_string(fd, argv[3] ? argv[3] : "");
}
@ -141,7 +138,7 @@ int magiskhide_main(int argc, char *argv[]) {
return DAEMON_ERROR;
}
if (req == LS_HIDELIST) {
if (req == LS_LIST) {
string res;
for (;;) {
read_string(fd, res);

View File

@ -1,48 +1,35 @@
#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <unistd.h>
#include <dirent.h>
#include <string_view>
#include <functional>
#include <map>
#include <daemon.hpp>
#define SIGTERMTHRD SIGUSR1
#define ISOLATED_MAGIC "isolated"
// CLI entries
int launch_magiskhide(bool late_props);
int stop_magiskhide();
int enable_hide();
int disable_hide();
int add_list(int client);
int rm_list(int client);
void ls_list(int client);
// Utility functions
void crawl_procfs(const std::function<bool (int)> &fn);
void crawl_procfs(DIR *dir, const std::function<bool (int)> &fn);
bool hide_enabled();
void update_uid_map();
bool is_hide_target(int uid, std::string_view process, int max_len = 1024);
bool is_hide_target(int uid, std::string_view process);
// Hide policies
void hide_daemon(int pid);
void hide_unmount(int pid = -1);
void hide_sensitive_props();
void hide_late_sensitive_props();
extern pthread_mutex_t hide_state_lock;
extern std::map<int, std::vector<std::string_view>> uid_proc_map;
enum {
LAUNCH_MAGISKHIDE,
STOP_MAGISKHIDE,
ADD_HIDELIST,
RM_HIDELIST,
LS_HIDELIST,
ENABLE_HIDE,
DISABLE_HIDE,
ADD_LIST,
RM_LIST,
LS_LIST,
HIDE_STATUS,
};

View File

@ -1,409 +0,0 @@
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <sys/ptrace.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <vector>
#include <bitset>
#include <utils.hpp>
#include "magiskhide.hpp"
using namespace std;
static int inotify_fd = -1;
static void new_zygote(int pid);
pthread_t monitor_thread;
/******************
* Data structures
******************/
#define PID_MAX 32768
struct pid_set {
bitset<PID_MAX>::const_reference operator[](size_t pos) const { return set[pos - 1]; }
bitset<PID_MAX>::reference operator[](size_t pos) { return set[pos - 1]; }
void reset() { set.reset(); }
private:
bitset<PID_MAX> set;
};
// true if pid is monitored
static pid_set attaches;
// zygote pid -> mnt ns
static map<int, struct stat> zygote_map;
/********
* Utils
********/
static inline int read_ns(const int pid, struct stat *st) {
char path[32];
sprintf(path, "/proc/%d/ns/mnt", pid);
return stat(path, st);
}
static int parse_ppid(int pid) {
char path[32];
int ppid;
sprintf(path, "/proc/%d/stat", pid);
auto stat = open_file(path, "re");
if (!stat)
return -1;
// PID COMM STATE PPID .....
fscanf(stat.get(), "%*d %*s %*c %d", &ppid);
return ppid;
}
static bool is_zygote_done() {
#ifdef __LP64__
return zygote_map.size() >= 2;
#else
return zygote_map.size() >= 1;
#endif
}
static void check_zygote() {
crawl_procfs([](int pid) -> bool {
char buf[512];
snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid);
if (FILE *f = fopen(buf, "re")) {
fgets(buf, sizeof(buf), f);
if (strncmp(buf, "zygote", 6) == 0 && parse_ppid(pid) == 1)
new_zygote(pid);
fclose(f);
}
return true;
});
if (is_zygote_done()) {
// Stop periodic scanning
timeval val { .tv_sec = 0, .tv_usec = 0 };
itimerval interval { .it_interval = val, .it_value = val };
setitimer(ITIMER_REAL, &interval, nullptr);
}
}
#define APP_PROC "/system/bin/app_process"
static void setup_inotify() {
inotify_fd = xinotify_init1(IN_CLOEXEC);
if (inotify_fd < 0)
return;
// Setup inotify asynchronous I/O
fcntl(inotify_fd, F_SETFL, O_ASYNC);
struct f_owner_ex ex = {
.type = F_OWNER_TID,
.pid = gettid()
};
fcntl(inotify_fd, F_SETOWN_EX, &ex);
// Monitor packages.xml
inotify_add_watch(inotify_fd, "/data/system", IN_CLOSE_WRITE);
// Monitor app_process
if (access(APP_PROC "32", F_OK) == 0) {
inotify_add_watch(inotify_fd, APP_PROC "32", IN_ACCESS);
if (access(APP_PROC "64", F_OK) == 0)
inotify_add_watch(inotify_fd, APP_PROC "64", IN_ACCESS);
} else {
inotify_add_watch(inotify_fd, APP_PROC, IN_ACCESS);
}
}
/************************
* Async signal handlers
************************/
static void inotify_event(int) {
// Make sure we can actually read stuffs
// or else the whole thread will be blocked.
struct pollfd pfd = {
.fd = inotify_fd,
.events = POLLIN,
.revents = 0
};
if (poll(&pfd, 1, 0) <= 0)
return; // Nothing to read
char buf[512];
auto event = reinterpret_cast<struct inotify_event *>(buf);
read(inotify_fd, buf, sizeof(buf));
if ((event->mask & IN_CLOSE_WRITE) && event->name == "packages.xml"sv)
update_uid_map();
check_zygote();
}
static void term_thread(int) {
LOGD("proc_monitor: cleaning up\n");
zygote_map.clear();
attaches.reset();
close(inotify_fd);
inotify_fd = -1;
// Restore all signal handlers that was set
sigset_t set;
sigfillset(&set);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
struct sigaction act{};
act.sa_handler = SIG_DFL;
sigaction(SIGTERMTHRD, &act, nullptr);
sigaction(SIGIO, &act, nullptr);
sigaction(SIGALRM, &act, nullptr);
LOGD("proc_monitor: terminate\n");
pthread_exit(nullptr);
}
/******************
* Ptrace Madness
******************/
// Ptrace is super tricky, preserve all excessive logging in code
// but disable when actually building for usage (you won't want
// your logcat spammed with new thread events from all apps)
//#define PTRACE_LOG(fmt, args...) LOGD("PID=[%d] " fmt, pid, ##args)
#define PTRACE_LOG(...)
static void detach_pid(int pid, int signal = 0) {
attaches[pid] = false;
ptrace(PTRACE_DETACH, pid, 0, signal);
PTRACE_LOG("detach\n");
}
static bool check_pid(int pid) {
char path[128];
char cmdline[1024];
struct stat st;
sprintf(path, "/proc/%d", pid);
if (stat(path, &st)) {
// Process died unexpectedly, ignore
detach_pid(pid);
return true;
}
int uid = st.st_uid;
// UID hasn't changed
if (uid == 0)
return false;
sprintf(path, "/proc/%d/cmdline", pid);
if (auto f = open_file(path, "re")) {
fgets(cmdline, sizeof(cmdline), f.get());
} else {
// Process died unexpectedly, ignore
detach_pid(pid);
return true;
}
if (cmdline == "zygote"sv || cmdline == "zygote32"sv || cmdline == "zygote64"sv ||
cmdline == "usap32"sv || cmdline == "usap64"sv)
return false;
if (!is_hide_target(uid, cmdline, 95))
goto not_target;
// Ensure ns is separated
read_ns(pid, &st);
for (auto &zit : zygote_map) {
if (zit.second.st_ino == st.st_ino &&
zit.second.st_dev == st.st_dev) {
// ns not separated, abort
LOGW("proc_monitor: skip [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
goto not_target;
}
}
// Detach but the process should still remain stopped
// The hide daemon will resume the process after hiding it
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
detach_pid(pid, SIGSTOP);
hide_daemon(pid);
return true;
not_target:
PTRACE_LOG("[%s] is not our target\n", cmdline);
detach_pid(pid);
return true;
}
static bool is_process(int pid) {
char buf[128];
char key[32];
int tgid;
sprintf(buf, "/proc/%d/status", pid);
auto fp = open_file(buf, "re");
// PID is dead
if (!fp)
return false;
while (fgets(buf, sizeof(buf), fp.get())) {
sscanf(buf, "%s", key);
if (key == "Tgid:"sv) {
sscanf(buf, "%*s %d", &tgid);
return tgid == pid;
}
}
return false;
}
static void new_zygote(int pid) {
struct stat st;
if (read_ns(pid, &st))
return;
auto it = zygote_map.find(pid);
if (it != zygote_map.end()) {
// Update namespace info
it->second = st;
return;
}
LOGD("proc_monitor: ptrace zygote PID=[%d]\n", pid);
zygote_map[pid] = st;
xptrace(PTRACE_ATTACH, pid);
waitpid(pid, nullptr, __WALL | __WNOTHREAD);
xptrace(PTRACE_SETOPTIONS, pid, nullptr,
PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXIT);
xptrace(PTRACE_CONT, pid);
}
#define DETACH_AND_CONT { detach_pid(pid); continue; }
void proc_monitor() {
monitor_thread = pthread_self();
// Backup original mask
sigset_t orig_mask;
pthread_sigmask(SIG_SETMASK, nullptr, &orig_mask);
sigset_t unblock_set;
sigemptyset(&unblock_set);
sigaddset(&unblock_set, SIGTERMTHRD);
sigaddset(&unblock_set, SIGIO);
sigaddset(&unblock_set, SIGALRM);
struct sigaction act{};
sigfillset(&act.sa_mask);
act.sa_handler = SIG_IGN;
sigaction(SIGTERMTHRD, &act, nullptr);
sigaction(SIGIO, &act, nullptr);
sigaction(SIGALRM, &act, nullptr);
// Temporary unblock to clear pending signals
pthread_sigmask(SIG_UNBLOCK, &unblock_set, nullptr);
pthread_sigmask(SIG_SETMASK, &orig_mask, nullptr);
act.sa_handler = term_thread;
sigaction(SIGTERMTHRD, &act, nullptr);
act.sa_handler = inotify_event;
sigaction(SIGIO, &act, nullptr);
act.sa_handler = [](int){ check_zygote(); };
sigaction(SIGALRM, &act, nullptr);
setup_inotify();
// First try find existing zygotes
check_zygote();
if (!is_zygote_done()) {
// Periodic scan every 250ms
timeval val { .tv_sec = 0, .tv_usec = 250000 };
itimerval interval { .it_interval = val, .it_value = val };
setitimer(ITIMER_REAL, &interval, nullptr);
}
for (int status;;) {
pthread_sigmask(SIG_UNBLOCK, &unblock_set, nullptr);
const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
if (pid < 0) {
if (errno == ECHILD) {
// Nothing to wait yet, sleep and wait till signal interruption
LOGD("proc_monitor: nothing to monitor, wait for signal\n");
struct timespec ts = {
.tv_sec = INT_MAX,
.tv_nsec = 0
};
nanosleep(&ts, nullptr);
}
continue;
}
pthread_sigmask(SIG_SETMASK, &orig_mask, nullptr);
if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */)
DETACH_AND_CONT;
int event = WEVENT(status);
int signal = WSTOPSIG(status);
if (signal == SIGTRAP && event) {
unsigned long msg;
xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg);
if (zygote_map.count(pid)) {
// Zygote event
switch (event) {
case PTRACE_EVENT_FORK:
case PTRACE_EVENT_VFORK:
PTRACE_LOG("zygote forked: [%lu]\n", msg);
attaches[msg] = true;
break;
case PTRACE_EVENT_EXIT:
PTRACE_LOG("zygote exited with status: [%lu]\n", msg);
[[fallthrough]];
default:
zygote_map.erase(pid);
DETACH_AND_CONT;
}
} else {
switch (event) {
case PTRACE_EVENT_CLONE:
PTRACE_LOG("create new threads: [%lu]\n", msg);
if (attaches[pid] && check_pid(pid))
continue;
break;
case PTRACE_EVENT_EXEC:
case PTRACE_EVENT_EXIT:
PTRACE_LOG("exit or execve\n");
[[fallthrough]];
default:
DETACH_AND_CONT;
}
}
xptrace(PTRACE_CONT, pid);
} else if (signal == SIGSTOP) {
if (!attaches[pid]) {
// Double check if this is actually a process
attaches[pid] = is_process(pid);
}
if (attaches[pid]) {
// This is a process, continue monitoring
PTRACE_LOG("SIGSTOP from child\n");
xptrace(PTRACE_SETOPTIONS, pid, nullptr,
PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT);
xptrace(PTRACE_CONT, pid);
} else {
// This is a thread, do NOT monitor
PTRACE_LOG("SIGSTOP from thread\n");
DETACH_AND_CONT;
}
} else {
// Not caused by us, resend signal
xptrace(PTRACE_CONT, pid, nullptr, signal);
PTRACE_LOG("signal [%d]\n", signal);
}
}
}

View File

@ -58,6 +58,9 @@ reversed_container<T> reversed(T &base) {
return reversed_container<T>(base);
}
template<class T>
static inline void default_new(T *&p) { p = new T(); }
template<typename T, typename Impl>
class stateless_allocator {
public:

View File

@ -326,9 +326,6 @@ int hook_register(const char *path, const char *symbol, void *new_func, void **o
} // namespace
template<class T>
static inline void default_new(T *&p) { p = new T(); }
#define XHOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \
hook_register(PATH_REGEX, SYM, (void*) new_##NAME, (void **) &old_##NAME)