You've already forked PlayIntegrityFork
mirror of
https://github.com/osm0sis/PlayIntegrityFork.git
synced 2025-09-06 06:37:06 +00:00
334 lines
11 KiB
C
334 lines
11 KiB
C
// Copyright (c) 2021-2022 ByteDance Inc.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
//
|
|
|
|
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
|
|
|
|
#include "sh_task.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <malloc.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/eventfd.h>
|
|
#include <unistd.h>
|
|
|
|
#include "queue.h"
|
|
#include "sh_config.h"
|
|
#include "sh_errno.h"
|
|
#include "sh_linker.h"
|
|
#include "sh_log.h"
|
|
#include "sh_recorder.h"
|
|
#include "sh_sig.h"
|
|
#include "sh_switch.h"
|
|
#include "sh_util.h"
|
|
#include "shadowhook.h"
|
|
#include "xdl.h"
|
|
|
|
#define SH_TASK_THREAD_NAME "shadowhook-task"
|
|
|
|
// task
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wpadded"
|
|
struct sh_task {
|
|
char *lib_name;
|
|
char *sym_name;
|
|
uintptr_t target_addr;
|
|
uintptr_t new_addr;
|
|
uintptr_t *orig_addr;
|
|
shadowhook_hooked_t hooked;
|
|
void *hooked_arg;
|
|
uintptr_t caller_addr;
|
|
bool finished;
|
|
bool error;
|
|
bool ignore_symbol_check;
|
|
TAILQ_ENTRY(sh_task, ) link;
|
|
};
|
|
#pragma clang diagnostic pop
|
|
|
|
// task queue
|
|
typedef TAILQ_HEAD(sh_task_queue, sh_task, ) sh_task_queue_t;
|
|
|
|
// task queue object
|
|
static sh_task_queue_t sh_tasks = TAILQ_HEAD_INITIALIZER(sh_tasks);
|
|
static pthread_rwlock_t sh_tasks_lock = PTHREAD_RWLOCK_INITIALIZER;
|
|
static int sh_tasks_unfinished_cnt = 0;
|
|
|
|
static int sh_task_eventfd;
|
|
static int thread_started_result = SHADOWHOOK_ERRNO_MONITOR_THREAD;
|
|
|
|
sh_task_t *sh_task_create_by_target_addr(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
|
|
bool ignore_symbol_check, uintptr_t caller_addr) {
|
|
sh_task_t *self = malloc(sizeof(sh_task_t));
|
|
if (NULL == self) return NULL;
|
|
self->lib_name = NULL;
|
|
self->sym_name = NULL;
|
|
self->target_addr = target_addr;
|
|
self->new_addr = new_addr;
|
|
self->orig_addr = orig_addr;
|
|
self->hooked = NULL;
|
|
self->hooked_arg = NULL;
|
|
self->caller_addr = caller_addr;
|
|
self->finished = false;
|
|
self->error = false;
|
|
self->ignore_symbol_check = ignore_symbol_check;
|
|
|
|
return self;
|
|
}
|
|
|
|
sh_task_t *sh_task_create_by_sym_name(const char *lib_name, const char *sym_name, uintptr_t new_addr,
|
|
uintptr_t *orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
|
|
uintptr_t caller_addr) {
|
|
sh_task_t *self = malloc(sizeof(sh_task_t));
|
|
if (NULL == self) return NULL;
|
|
|
|
if (NULL == (self->lib_name = strdup(lib_name))) goto err;
|
|
if (NULL == (self->sym_name = strdup(sym_name))) goto err;
|
|
self->target_addr = 0;
|
|
self->new_addr = new_addr;
|
|
self->orig_addr = orig_addr;
|
|
self->hooked = hooked;
|
|
self->hooked_arg = hooked_arg;
|
|
self->caller_addr = caller_addr;
|
|
self->finished = false;
|
|
self->error = false;
|
|
self->ignore_symbol_check = false;
|
|
|
|
return self;
|
|
|
|
err:
|
|
if (NULL != self->lib_name) free(self->lib_name);
|
|
if (NULL != self->sym_name) free(self->sym_name);
|
|
free(self);
|
|
return NULL;
|
|
}
|
|
|
|
void sh_task_destroy(sh_task_t *self) {
|
|
if (NULL != self->lib_name) free(self->lib_name);
|
|
if (NULL != self->sym_name) free(self->sym_name);
|
|
free(self);
|
|
}
|
|
|
|
static void sh_task_do_callback(sh_task_t *self, int error_number) {
|
|
if (NULL != self->hooked)
|
|
self->hooked(error_number, self->lib_name, self->sym_name, (void *)self->target_addr,
|
|
(void *)self->new_addr, self->orig_addr, self->hooked_arg);
|
|
}
|
|
|
|
static int sh_task_hook_pending(struct dl_phdr_info *info, size_t size, void *arg) {
|
|
(void)size, (void)arg;
|
|
|
|
pthread_rwlock_rdlock(&sh_tasks_lock);
|
|
|
|
sh_task_t *task;
|
|
TAILQ_FOREACH(task, &sh_tasks, link) {
|
|
if (task->finished) continue;
|
|
if ('/' == info->dlpi_name[0] && NULL == strstr(info->dlpi_name, task->lib_name)) continue;
|
|
if ('/' != info->dlpi_name[0] && NULL == strstr(task->lib_name, info->dlpi_name)) continue;
|
|
|
|
xdl_info_t dlinfo;
|
|
char real_lib_name[512];
|
|
int r = sh_linker_get_dlinfo_by_sym_name(task->lib_name, task->sym_name, &dlinfo, real_lib_name,
|
|
sizeof(real_lib_name));
|
|
task->target_addr = (uintptr_t)dlinfo.dli_saddr;
|
|
if (SHADOWHOOK_ERRNO_PENDING != r) {
|
|
size_t backup_len = 0;
|
|
if (0 == r) {
|
|
r = sh_switch_hook(task->target_addr, task->new_addr, task->orig_addr, &backup_len, &dlinfo);
|
|
if (0 != r) task->error = true;
|
|
} else {
|
|
strlcpy(real_lib_name, task->lib_name, sizeof(real_lib_name));
|
|
task->error = true;
|
|
}
|
|
sh_recorder_add_hook(r, false, task->target_addr, real_lib_name, task->sym_name, task->new_addr,
|
|
backup_len, (uintptr_t)task, task->caller_addr);
|
|
task->finished = true;
|
|
sh_task_do_callback(task, r);
|
|
if (0 == __atomic_sub_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST)) break;
|
|
}
|
|
}
|
|
|
|
pthread_rwlock_unlock(&sh_tasks_lock);
|
|
|
|
return __atomic_load_n(&sh_tasks_unfinished_cnt, __ATOMIC_SEQ_CST) > 0 ? 0 : 1;
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
|
|
|
|
static void sh_task_post_dlopen_callback(void *arg) {
|
|
(void)arg;
|
|
|
|
if (0 == thread_started_result && __atomic_load_n(&sh_tasks_unfinished_cnt, __ATOMIC_SEQ_CST) > 0) {
|
|
uint64_t ev_val = 1;
|
|
SH_UTIL_TEMP_FAILURE_RETRY(write(sh_task_eventfd, &ev_val, sizeof(ev_val)));
|
|
}
|
|
}
|
|
|
|
__noreturn static void *sh_task_thread_func(void *arg) {
|
|
(void)arg;
|
|
pthread_t thread = pthread_self();
|
|
pthread_setname_np(thread, SH_TASK_THREAD_NAME);
|
|
pthread_detach(thread);
|
|
|
|
struct pollfd ev = {.fd = sh_task_eventfd, .events = POLLIN, .revents = 0};
|
|
while (1) {
|
|
int n = SH_UTIL_TEMP_FAILURE_RETRY(poll(&ev, 1, -1));
|
|
if (n < 0) {
|
|
sleep(1);
|
|
continue;
|
|
} else if (n > 0) {
|
|
uint64_t ev_val;
|
|
SH_UTIL_TEMP_FAILURE_RETRY(read(sh_task_eventfd, &ev_val, sizeof(ev_val)));
|
|
|
|
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
|
|
xdl_iterate_phdr(sh_task_hook_pending, NULL, XDL_DEFAULT);
|
|
} else {
|
|
SH_SIG_TRY(SIGSEGV, SIGBUS) {
|
|
xdl_iterate_phdr(sh_task_hook_pending, NULL, XDL_DEFAULT);
|
|
}
|
|
SH_SIG_CATCH() {
|
|
SH_LOG_WARN("task: dliterate crashed");
|
|
}
|
|
SH_SIG_EXIT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
static int sh_task_start_monitor(bool start_thread) {
|
|
static bool thread_started = false;
|
|
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
|
pthread_t thread;
|
|
int r;
|
|
|
|
// hook linker dlopen()
|
|
if (0 != (r = sh_linker_hook_dlopen(sh_task_post_dlopen_callback, NULL))) return r;
|
|
|
|
if (!start_thread) return 0;
|
|
|
|
// start thread
|
|
if (thread_started) return thread_started_result;
|
|
pthread_mutex_lock(&lock);
|
|
if (thread_started) goto end;
|
|
|
|
if (0 > (sh_task_eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC))) goto end;
|
|
if (0 != pthread_create(&thread, NULL, &sh_task_thread_func, NULL)) goto end;
|
|
|
|
// OK
|
|
thread_started_result = 0;
|
|
|
|
end:
|
|
thread_started = true;
|
|
pthread_mutex_unlock(&lock);
|
|
SH_LOG_INFO("task: start monitor %s, return: %d", 0 == thread_started_result ? "OK" : "FAILED",
|
|
thread_started_result);
|
|
return thread_started_result;
|
|
}
|
|
|
|
int sh_task_hook(sh_task_t *self) {
|
|
int r;
|
|
bool is_hook_sym_addr = true;
|
|
char real_lib_name[512] = "unknown";
|
|
char real_sym_name[1024] = "unknown";
|
|
size_t backup_len = 0;
|
|
|
|
// find target-address by library-name and symbol-name
|
|
xdl_info_t dlinfo;
|
|
memset(&dlinfo, 0, sizeof(xdl_info_t));
|
|
if (0 == self->target_addr) {
|
|
is_hook_sym_addr = false;
|
|
strlcpy(real_lib_name, self->lib_name, sizeof(real_lib_name));
|
|
strlcpy(real_sym_name, self->sym_name, sizeof(real_sym_name));
|
|
r = sh_linker_get_dlinfo_by_sym_name(self->lib_name, self->sym_name, &dlinfo, real_lib_name,
|
|
sizeof(real_lib_name));
|
|
if (SHADOWHOOK_ERRNO_PENDING == r) {
|
|
// we need to start monitor linker dlopen for handle the pending task
|
|
if (0 != (r = sh_task_start_monitor(true))) goto end;
|
|
r = SHADOWHOOK_ERRNO_PENDING;
|
|
goto end;
|
|
}
|
|
if (0 != r) goto end; // error
|
|
self->target_addr = (uintptr_t)dlinfo.dli_saddr; // OK
|
|
} else {
|
|
r = sh_linker_get_dlinfo_by_addr((void *)self->target_addr, &dlinfo, real_lib_name, sizeof(real_lib_name),
|
|
real_sym_name, sizeof(real_sym_name), self->ignore_symbol_check);
|
|
if (0 != r) goto end; // error
|
|
}
|
|
|
|
// In UNIQUE mode, if external users are hooking the linker dlopen() or do_dlopen(),
|
|
// we MUST hook this method with invisible for ourself first.
|
|
if (sh_linker_need_to_hook_dlopen(self->target_addr)) {
|
|
SH_LOG_INFO("task: hook dlopen/do_dlopen internal. target-address %" PRIxPTR, self->target_addr);
|
|
if (0 != (r = sh_task_start_monitor(false))) goto end;
|
|
}
|
|
|
|
// hook by target-address
|
|
r = sh_switch_hook(self->target_addr, self->new_addr, self->orig_addr, &backup_len, &dlinfo);
|
|
self->finished = true;
|
|
|
|
end:
|
|
if (0 == r || SHADOWHOOK_ERRNO_PENDING == r) // "PENDING" is NOT an error
|
|
{
|
|
pthread_rwlock_wrlock(&sh_tasks_lock);
|
|
TAILQ_INSERT_TAIL(&sh_tasks, self, link);
|
|
if (!self->finished) __atomic_add_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST);
|
|
pthread_rwlock_unlock(&sh_tasks_lock);
|
|
}
|
|
|
|
// record
|
|
sh_recorder_add_hook(r, is_hook_sym_addr, self->target_addr, real_lib_name, real_sym_name, self->new_addr,
|
|
backup_len, (uintptr_t)self, self->caller_addr);
|
|
|
|
return r;
|
|
}
|
|
|
|
int sh_task_unhook(sh_task_t *self, uintptr_t caller_addr) {
|
|
pthread_rwlock_wrlock(&sh_tasks_lock);
|
|
TAILQ_REMOVE(&sh_tasks, self, link);
|
|
if (!self->finished) __atomic_sub_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST);
|
|
pthread_rwlock_unlock(&sh_tasks_lock);
|
|
|
|
// check task status
|
|
int r;
|
|
if (self->error) {
|
|
r = SHADOWHOOK_ERRNO_UNHOOK_ON_ERROR;
|
|
goto end;
|
|
}
|
|
if (!self->finished) {
|
|
r = SHADOWHOOK_ERRNO_UNHOOK_ON_UNFINISHED;
|
|
goto end;
|
|
}
|
|
|
|
// do unhook
|
|
r = sh_switch_unhook(self->target_addr, self->new_addr);
|
|
|
|
end:
|
|
// record
|
|
sh_recorder_add_unhook(r, (uintptr_t)self, caller_addr);
|
|
return r;
|
|
}
|