Magisk/native/src/external/libxhook/xh_core.c

664 lines
19 KiB
C

// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
//
// 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 caikelun on 2018-04-11.
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <regex.h>
#include <setjmp.h>
#include <errno.h>
#include "queue.h"
#include "tree.h"
#include "xh_errno.h"
#include "xh_log.h"
#include "xh_elf.h"
#include "xh_version.h"
#include "xh_core.h"
#define XH_CORE_DEBUG 0
//registered hook info collection
typedef struct xh_core_hook_info
{
#if XH_CORE_DEBUG
char *pathname_regex_str;
#endif
regex_t pathname_regex;
char *symbol;
void *new_func;
void **old_func;
TAILQ_ENTRY(xh_core_hook_info,) link;
} xh_core_hook_info_t;
typedef TAILQ_HEAD(xh_core_hook_info_queue, xh_core_hook_info,) xh_core_hook_info_queue_t;
//ignored hook info collection
typedef struct xh_core_ignore_info
{
#if XH_CORE_DEBUG
char *pathname_regex_str;
#endif
regex_t pathname_regex;
char *symbol; //NULL meaning for all symbols
TAILQ_ENTRY(xh_core_ignore_info,) link;
} xh_core_ignore_info_t;
typedef TAILQ_HEAD(xh_core_ignore_info_queue, xh_core_ignore_info,) xh_core_ignore_info_queue_t;
//required info from /proc/self/maps
typedef struct xh_core_map_info
{
char *pathname;
unsigned long inode;
uintptr_t base_addr;
xh_elf_t elf;
RB_ENTRY(xh_core_map_info) link;
} xh_core_map_info_t;
static __inline__ int xh_core_map_info_cmp(xh_core_map_info_t *a, xh_core_map_info_t *b)
{
int path_cmp = strcmp(a->pathname, b->pathname);
if (path_cmp == 0) {
return a->inode - b->inode;
} else {
return path_cmp;
}
}
typedef RB_HEAD(xh_core_map_info_tree, xh_core_map_info) xh_core_map_info_tree_t;
RB_GENERATE_STATIC(xh_core_map_info_tree, xh_core_map_info, link, xh_core_map_info_cmp)
//signal handler for SIGSEGV
//for xh_elf_init(), xh_elf_hook(), xh_elf_check_elfheader()
static int xh_core_sigsegv_enable = 1; //enable by default
static struct sigaction xh_core_sigsegv_act_old;
static volatile int xh_core_sigsegv_flag = 0;
static sigjmp_buf xh_core_sigsegv_env;
static void xh_core_sigsegv_handler(int sig)
{
(void)sig;
if(xh_core_sigsegv_flag)
siglongjmp(xh_core_sigsegv_env, 1);
else
sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL);
}
static int xh_core_add_sigsegv_handler()
{
struct sigaction act;
if(!xh_core_sigsegv_enable) return 0;
if(0 != sigemptyset(&act.sa_mask)) return (0 == errno ? XH_ERRNO_UNKNOWN : errno);
act.sa_handler = xh_core_sigsegv_handler;
if(0 != sigaction(SIGSEGV, &act, &xh_core_sigsegv_act_old))
return (0 == errno ? XH_ERRNO_UNKNOWN : errno);
return 0;
}
static void xh_core_del_sigsegv_handler()
{
if(!xh_core_sigsegv_enable) return;
sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL);
}
static xh_core_hook_info_queue_t xh_core_hook_info = TAILQ_HEAD_INITIALIZER(xh_core_hook_info);
static xh_core_ignore_info_queue_t xh_core_ignore_info = TAILQ_HEAD_INITIALIZER(xh_core_ignore_info);
static xh_core_map_info_tree_t xh_core_map_info = RB_INITIALIZER(&xh_core_map_info);
static pthread_mutex_t xh_core_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t xh_core_cond = PTHREAD_COND_INITIALIZER;
static volatile int xh_core_inited = 0;
static volatile int xh_core_init_ok = 0;
static volatile int xh_core_async_inited = 0;
static volatile int xh_core_async_init_ok = 0;
static pthread_mutex_t xh_core_refresh_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t xh_core_refresh_thread_tid;
static volatile int xh_core_refresh_thread_running = 0;
static volatile int xh_core_refresh_thread_do = 0;
int xh_core_register(const char *pathname_regex_str, const char *symbol,
void *new_func, void **old_func)
{
xh_core_hook_info_t *hi;
regex_t regex;
if(NULL == pathname_regex_str || NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL;
if(xh_core_inited)
{
XH_LOG_ERROR("do not register hook after refresh(): %s, %s", pathname_regex_str, symbol);
return XH_ERRNO_INVAL;
}
if(0 != regcomp(&regex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL;
if(NULL == (hi = malloc(sizeof(xh_core_hook_info_t)))) return XH_ERRNO_NOMEM;
if(NULL == (hi->symbol = strdup(symbol)))
{
free(hi);
return XH_ERRNO_NOMEM;
}
#if XH_CORE_DEBUG
if(NULL == (hi->pathname_regex_str = strdup(pathname_regex_str)))
{
free(hi->symbol);
free(hi);
return XH_ERRNO_NOMEM;
}
#endif
hi->pathname_regex = regex;
hi->new_func = new_func;
hi->old_func = old_func;
pthread_mutex_lock(&xh_core_mutex);
TAILQ_INSERT_TAIL(&xh_core_hook_info, hi, link);
pthread_mutex_unlock(&xh_core_mutex);
return 0;
}
int xh_core_ignore(const char *pathname_regex_str, const char *symbol)
{
xh_core_ignore_info_t *ii;
regex_t regex;
if(NULL == pathname_regex_str) return XH_ERRNO_INVAL;
if(xh_core_inited)
{
XH_LOG_ERROR("do not ignore hook after refresh(): %s, %s", pathname_regex_str, symbol ? symbol : "ALL");
return XH_ERRNO_INVAL;
}
if(0 != regcomp(&regex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL;
if(NULL == (ii = malloc(sizeof(xh_core_ignore_info_t)))) return XH_ERRNO_NOMEM;
if(NULL != symbol)
{
if(NULL == (ii->symbol = strdup(symbol)))
{
free(ii);
return XH_ERRNO_NOMEM;
}
}
else
{
ii->symbol = NULL; //ignore all symbols
}
#if XH_CORE_DEBUG
if(NULL == (ii->pathname_regex_str = strdup(pathname_regex_str)))
{
free(ii->symbol);
free(ii);
return XH_ERRNO_NOMEM;
}
#endif
ii->pathname_regex = regex;
pthread_mutex_lock(&xh_core_mutex);
TAILQ_INSERT_TAIL(&xh_core_ignore_info, ii, link);
pthread_mutex_unlock(&xh_core_mutex);
return 0;
}
static int xh_core_check_elf_header(uintptr_t base_addr, const char *pathname)
{
if(!xh_core_sigsegv_enable)
{
return xh_elf_check_elfheader(base_addr);
}
else
{
int ret = XH_ERRNO_UNKNOWN;
xh_core_sigsegv_flag = 1;
if(0 == sigsetjmp(xh_core_sigsegv_env, 1))
{
ret = xh_elf_check_elfheader(base_addr);
}
else
{
ret = XH_ERRNO_SEGVERR;
XH_LOG_WARN("catch SIGSEGV when check_elfheader: %s", pathname);
}
xh_core_sigsegv_flag = 0;
return ret;
}
}
static void xh_core_hook_impl(xh_core_map_info_t *mi)
{
//init
if(0 != xh_elf_init(&(mi->elf), mi->base_addr, mi->pathname)) return;
//hook
xh_core_hook_info_t *hi;
xh_core_ignore_info_t *ii;
int ignore;
TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info
{
if(0 == regexec(&(hi->pathname_regex), mi->pathname, 0, NULL, 0))
{
ignore = 0;
TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info
{
if(0 == regexec(&(ii->pathname_regex), mi->pathname, 0, NULL, 0))
{
if(NULL == ii->symbol) //ignore all symbols
return;
if(0 == strcmp(ii->symbol, hi->symbol)) //ignore the current symbol
{
ignore = 1;
break;
}
}
}
if(0 == ignore)
xh_elf_hook(&(mi->elf), hi->symbol, hi->new_func, hi->old_func);
}
}
}
static void xh_core_hook(xh_core_map_info_t *mi)
{
if(!xh_core_sigsegv_enable)
{
xh_core_hook_impl(mi);
}
else
{
xh_core_sigsegv_flag = 1;
if(0 == sigsetjmp(xh_core_sigsegv_env, 1))
{
xh_core_hook_impl(mi);
}
else
{
XH_LOG_WARN("catch SIGSEGV when init or hook: %s %lu", mi->pathname, mi->inode);
}
xh_core_sigsegv_flag = 0;
}
}
static void xh_core_refresh_impl()
{
char line[512];
FILE *fp;
uintptr_t base_addr;
char perm[5];
unsigned long offset;
unsigned long inode;
int pathname_pos;
char *pathname;
size_t pathname_len;
xh_core_map_info_t *mi, *mi_tmp;
xh_core_map_info_t mi_key;
xh_core_hook_info_t *hi;
xh_core_ignore_info_t *ii;
int match;
xh_core_map_info_tree_t map_info_refreshed = RB_INITIALIZER(&map_info_refreshed);
if(NULL == (fp = fopen("/proc/self/maps", "r")))
{
XH_LOG_ERROR("fopen /proc/self/maps failed");
return;
}
while(fgets(line, sizeof(line), fp))
{
if(sscanf(line, "%"PRIxPTR"-%*lx %4s %lx %*x:%*x %lu %n", &base_addr, perm, &offset, &inode, &pathname_pos) != 4) continue;
//check permission
if(perm[0] != 'r') continue;
if(perm[3] != 'p') continue; //do not touch the shared memory
//check offset
//
//We are trying to find ELF header in memory.
//It can only be found at the beginning of a mapped memory regions
//whose offset is 0.
if(0 != offset) continue;
//get pathname
while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1))
pathname_pos += 1;
if(pathname_pos >= (int)(sizeof(line) - 1)) continue;
pathname = line + pathname_pos;
pathname_len = strlen(pathname);
if(0 == pathname_len) continue;
if(pathname[pathname_len - 1] == '\n')
{
pathname[pathname_len - 1] = '\0';
pathname_len -= 1;
}
if(0 == pathname_len) continue;
if('[' == pathname[0]) continue;
//check pathname
//if we need to hook this elf?
match = 0;
TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info
{
if(0 == regexec(&(hi->pathname_regex), pathname, 0, NULL, 0))
{
TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info
{
if(0 == regexec(&(ii->pathname_regex), pathname, 0, NULL, 0))
{
if(NULL == ii->symbol)
goto check_finished;
if(0 == strcmp(ii->symbol, hi->symbol))
goto check_continue;
}
}
match = 1;
check_continue:
break;
}
}
check_finished:
if(0 == match) continue;
//check elf header format
//We are trying to do ELF header checking as late as possible.
if(0 != xh_core_check_elf_header(base_addr, pathname)) continue;
//check existed map item
mi_key.pathname = pathname;
mi_key.inode = inode;
if(NULL != (mi = RB_FIND(xh_core_map_info_tree, &xh_core_map_info, &mi_key)))
{
//exist
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
//repeated?
//We only keep the first one, that is the real base address
if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi))
{
#if XH_CORE_DEBUG
XH_LOG_DEBUG("repeated map info when update: %s", line);
#endif
free(mi->pathname);
free(mi);
continue;
}
//re-hook if base_addr changed
if(mi->base_addr != base_addr)
{
mi->base_addr = base_addr;
xh_core_hook(mi);
}
}
else
{
//not exist, create a new map info
if(NULL == (mi = (xh_core_map_info_t *)malloc(sizeof(xh_core_map_info_t)))) continue;
if(NULL == (mi->pathname = strdup(pathname)))
{
free(mi);
continue;
}
mi->inode = inode;
mi->base_addr = base_addr;
//repeated?
//We only keep the first one, that is the real base address
if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi))
{
XH_LOG_WARN("repeated map info when create: %s", line);
free(mi->pathname);
free(mi);
continue;
}
//hook
xh_core_hook(mi); //hook
}
}
fclose(fp);
//free all missing map item, maybe dlclosed?
RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp)
{
#if XH_CORE_DEBUG
XH_LOG_DEBUG("remove missing map info: %s", mi->pathname);
#endif
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
if(mi->pathname) free(mi->pathname);
free(mi);
}
//save the new refreshed map info tree
xh_core_map_info = map_info_refreshed;
XH_LOG_INFO("map refreshed");
#if XH_CORE_DEBUG
RB_FOREACH(mi, xh_core_map_info_tree, &xh_core_map_info)
XH_LOG_DEBUG(" %"PRIxPTR" %s\n", mi->base_addr, mi->pathname);
#endif
}
static void *xh_core_refresh_thread_func(void *arg)
{
(void)arg;
pthread_setname_np(pthread_self(), "xh_refresh_loop");
while(xh_core_refresh_thread_running)
{
//waiting for a refresh task or exit
pthread_mutex_lock(&xh_core_mutex);
while(!xh_core_refresh_thread_do && xh_core_refresh_thread_running)
{
pthread_cond_wait(&xh_core_cond, &xh_core_mutex);
}
if(!xh_core_refresh_thread_running)
{
pthread_mutex_unlock(&xh_core_mutex);
break;
}
xh_core_refresh_thread_do = 0;
pthread_mutex_unlock(&xh_core_mutex);
//refresh
pthread_mutex_lock(&xh_core_refresh_mutex);
xh_core_refresh_impl();
pthread_mutex_unlock(&xh_core_refresh_mutex);
}
return NULL;
}
static void xh_core_init_once()
{
if(xh_core_inited) return;
pthread_mutex_lock(&xh_core_mutex);
if(xh_core_inited) goto end;
xh_core_inited = 1;
//dump debug info
XH_LOG_INFO("%s\n", xh_version_str_full());
#if XH_CORE_DEBUG
xh_core_hook_info_t *hi;
TAILQ_FOREACH(hi, &xh_core_hook_info, link)
XH_LOG_INFO(" hook: %s @ %s, (%p, %p)\n", hi->symbol, hi->pathname_regex_str,
hi->new_func, hi->old_func);
xh_core_ignore_info_t *ii;
TAILQ_FOREACH(ii, &xh_core_ignore_info, link)
XH_LOG_INFO(" ignore: %s @ %s\n", ii->symbol ? ii->symbol : "ALL ",
ii->pathname_regex_str);
#endif
//register signal handler
if(0 != xh_core_add_sigsegv_handler()) goto end;
//OK
xh_core_init_ok = 1;
end:
pthread_mutex_unlock(&xh_core_mutex);
}
static void xh_core_init_async_once()
{
if(xh_core_async_inited) return;
pthread_mutex_lock(&xh_core_mutex);
if(xh_core_async_inited) goto end;
xh_core_async_inited = 1;
//create async refresh thread
xh_core_refresh_thread_running = 1;
if(0 != pthread_create(&xh_core_refresh_thread_tid, NULL, &xh_core_refresh_thread_func, NULL))
{
xh_core_refresh_thread_running = 0;
goto end;
}
//OK
xh_core_async_init_ok = 1;
end:
pthread_mutex_unlock(&xh_core_mutex);
}
int xh_core_refresh(int async)
{
//init
xh_core_init_once();
if(!xh_core_init_ok) return XH_ERRNO_UNKNOWN;
if(async)
{
//init for async
xh_core_init_async_once();
if(!xh_core_async_init_ok) return XH_ERRNO_UNKNOWN;
//refresh async
pthread_mutex_lock(&xh_core_mutex);
xh_core_refresh_thread_do = 1;
pthread_cond_signal(&xh_core_cond);
pthread_mutex_unlock(&xh_core_mutex);
}
else
{
//refresh sync
pthread_mutex_lock(&xh_core_refresh_mutex);
xh_core_refresh_impl();
pthread_mutex_unlock(&xh_core_refresh_mutex);
}
return 0;
}
void xh_core_clear()
{
//stop the async refresh thread
if(xh_core_async_init_ok)
{
pthread_mutex_lock(&xh_core_mutex);
xh_core_refresh_thread_running = 0;
pthread_cond_signal(&xh_core_cond);
pthread_mutex_unlock(&xh_core_mutex);
pthread_join(xh_core_refresh_thread_tid, NULL);
xh_core_async_init_ok = 0;
}
xh_core_async_inited = 0;
//unregister the sig handler
if(xh_core_init_ok)
{
xh_core_del_sigsegv_handler();
xh_core_init_ok = 0;
}
xh_core_inited = 0;
pthread_mutex_lock(&xh_core_mutex);
pthread_mutex_lock(&xh_core_refresh_mutex);
//free all map info
xh_core_map_info_t *mi, *mi_tmp;
RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp)
{
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
if(mi->pathname) free(mi->pathname);
free(mi);
}
//free all hook info
xh_core_hook_info_t *hi, *hi_tmp;
TAILQ_FOREACH_SAFE(hi, &xh_core_hook_info, link, hi_tmp)
{
TAILQ_REMOVE(&xh_core_hook_info, hi, link);
#if XH_CORE_DEBUG
free(hi->pathname_regex_str);
#endif
regfree(&(hi->pathname_regex));
free(hi->symbol);
free(hi);
}
//free all ignore info
xh_core_ignore_info_t *ii, *ii_tmp;
TAILQ_FOREACH_SAFE(ii, &xh_core_ignore_info, link, ii_tmp)
{
TAILQ_REMOVE(&xh_core_ignore_info, ii, link);
#if XH_CORE_DEBUG
free(ii->pathname_regex_str);
#endif
regfree(&(ii->pathname_regex));
free(ii->symbol);
free(ii);
}
pthread_mutex_unlock(&xh_core_refresh_mutex);
pthread_mutex_unlock(&xh_core_mutex);
}
void xh_core_enable_debug(int flag)
{
xh_log_priority = (flag ? ANDROID_LOG_DEBUG : ANDROID_LOG_WARN);
}
void xh_core_enable_sigsegv_protection(int flag)
{
xh_core_sigsegv_enable = (flag ? 1 : 0);
}