首页
论坛
课程
招聘
[原创]利用xhook安卓系统底层抹机原理
2021-1-16 11:06 6210

[原创]利用xhook安卓系统底层抹机原理

2021-1-16 11:06
6210

先上github地址https://github.com/iqiyi/xhook

大概原理是:

  • 先读取/proc/self/maps文件内容

  • 正则匹配找到so文件路径和加载基址,

  • 解析elf格式找到要hook的函数的地址替换成自己指定的函数地址

//xh_core.c
static void xh_core_refresh_impl()    
{    
char                     line[512];    
FILE                    *fp;    
uintptr_t                base_addr;    
char                     perm[5];    
unsigned long            offset;    
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 %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) 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;    
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->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))    
{    
#if XH_CORE_DEBUG    
XH_LOG_DEBUG("repeated map info when create: %s", line);    
#endif    
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    
}

地址替换通过PLT表,详细原理https://zhuanlan.zhihu.com/p/36426206

//xh_elf.c
int xh_elf_hook(xh_elf_t *self, const char *symbol, void *new_func, void **old_func)
{
    uint32_t                        symidx;
    void                           *rel_common;
    xh_elf_plain_reloc_iterator_t   plain_iter;
    xh_elf_packed_reloc_iterator_t  packed_iter;
    int                             found;
    int                             r;

    if(NULL == self->pathname)
    {
        XH_LOG_ERROR("not inited\n");
        return XH_ERRNO_ELFINIT; //not inited?
    }

    if(NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL;

    XH_LOG_INFO("hooking %s in %s\n", symbol, self->pathname);
    
    //find symbol index by symbol name
    if(0 != (r = xh_elf_find_symidx_by_name(self, symbol, &symidx))) return 0;
    
    //replace for .rel(a).plt
    if(0 != self->relplt)
    {
        xh_elf_plain_reloc_iterator_init(&plain_iter, self->relplt, self->relplt_sz, self->is_use_rela);
        while(NULL != (rel_common = xh_elf_plain_reloc_iterator_next(&plain_iter)))
        {
            if(0 != (r = xh_elf_find_and_replace_func(self,
                                                      (self->is_use_rela ? ".rela.plt" : ".rel.plt"), 1,
                                                      symbol, new_func, old_func,
                                                      symidx, rel_common, &found))) return r;
            if(found) break;
        }
    }

    //replace for .rel(a).dyn
    if(0 != self->reldyn)
    {
        xh_elf_plain_reloc_iterator_init(&plain_iter, self->reldyn, self->reldyn_sz, self->is_use_rela);
        while(NULL != (rel_common = xh_elf_plain_reloc_iterator_next(&plain_iter)))
        {
            if(0 != (r = xh_elf_find_and_replace_func(self,
                                                      (self->is_use_rela ? ".rela.dyn" : ".rel.dyn"), 0,
                                                      symbol, new_func, old_func,
                                                      symidx, rel_common, NULL))) return r;
        }
    }

    //replace for .rel(a).android
    if(0 != self->relandroid)
    {
        xh_elf_packed_reloc_iterator_init(&packed_iter, self->relandroid, self->relandroid_sz, self->is_use_rela);
        while(NULL != (rel_common = xh_elf_packed_reloc_iterator_next(&packed_iter)))
        {
            if(0 != (r = xh_elf_find_and_replace_func(self,
                                                      (self->is_use_rela ? ".rela.android" : ".rel.android"), 0,
                                                      symbol, new_func, old_func,
                                                      symidx, rel_common, NULL))) return r;
        }
    }
    
    return 0;
}

抹机软件通过hook一些底层函数达到抹机的目的,其中经常用到的函数如下:

int access(const char *pathname, int mode);//判断文件能否访问,一般判断是否存在root文件
int open(const char *pathname, int flags);//打开一些设备配置文件
FILE* fopen(const char *filename, const char *mode);//打开一些设备配置文件
prop_info* __system_property_find(const char* name);//判断build.prop等prop文件的key值存在否
int __system_property_read(const prop_info *pi, char *name, char *value);//同等android.os.SystemProperties.get("xxxx")
int __system_property_get(const char *name, char *value);//同等android.os.SystemProperties.get("xxxx")
FILE* popen( const char *command , const char *type );//执行shell命令并读取结果返回一个文件指针

实例代码如下:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <jni.h>
#include <sys/system_properties.h>
#include <android/log.h>
#include <fcntl.h>
#include "xhook/src/xhook.h"

int (*oldaccess)(const char *pathname, int mode);
int (*oldopen)(const char *pathname, int flags);
FILE* (*oldfopen)(const char *filename, const char *mode);
FILE* (*oldpopen)( const char *command , const char *type );
prop_info* (*old_system_property_find)(const char* name);
int (*old_system_property_get)(const char *name, char *value);
int (*old_system_property_read)(const prop_info *pi, char *name, char *value);


static int new_access(const char * pathname, int mode){
    __android_log_print(3, "Nativehook", "Nativehook_access unhook %s %d", pathname, mode);
    return access(pathname,mode);
}


static int new_open(const char * pathname, int flags,...){
    __android_log_print(3, "Nativehook", "Nativehook_open unhook %s %d", pathname, flags);
    return oldopen(pathname,flags);
}

static FILE *new_fopen(const char *filename, const char *mode){
    __android_log_print(3, "Nativehook", "Nativehook_fopen unhook %s %s", filename, mode);
    return oldfopen(filename,mode);
}

static int new_system_property_get(const char *name, char *value){
    __android_log_print(3, "Nativehook", "Nativehook_system_property_get unhook %s %s",name,value);
    return old_system_property_get(name,value);
}

static int new_system_property_read(const prop_info *pi, char *name, char *value) {
    __android_log_print(3, "Nativehook", "Nativehook_system_property_read unhook %s %s",name,value);
    return old_system_property_read(pi,name,value);
}

const prop_info* new_system_property_find(const char* name){
    __android_log_print(3, "Nativehook", "Nativehook_system_property_find unhook %s",name);
    return old_system_property_find(name);
}

static FILE * newpopen(const char *command ,const char *type ){
    __android_log_print(3, "Nativehook", "Nativehook_popen unhook %s %s",command,type);
    return oldpopen(command,type);
}

void Java_com_ayona333_fix_party_NativeHook_start(JNIEnv* env, jobject obj)
{
    (void)env;
    (void)obj;

    xhook_ignore(".*/libnativehook.so$", NULL);//屏蔽hook自己

    xhook_register("/data/.*\\.so$", "access",new_access ,(void**)(&oldaccess));

    xhook_register("/data/.*\\.so$", "open", new_open,(void**)(&oldopen));

    xhook_register("/data/data.*\\.so$", "fopen",new_fopen,(void**)(&oldfopen));

    xhook_register("/data/.*\\.so$", "__system_property_find",new_system_property_find,(void**)(&old_system_property_find));

    xhook_register("/data/.*\\.so$", "__system_property_get",new_system_property_get,(void**)(&old_system_property_get));

    xhook_register("/data/.*\\.so$", "__system_property_read",new_system_property_read,(void**)(&old_system_property_read));

    xhook_register("/data/.*\\.so", "popen",newpopen,(void**)(&oldpopen));

    xhook_refresh(1);
}

jint Java_com_ayona333_fix_party_NativeHook_refresh(JNIEnv* env, jobject obj,jboolean flag)
{
    return xhook_refresh(flag);
}

void Java_com_ayona333_fix_party_NativeHook_clear(JNIEnv* env, jobject obj)
{
    xhook_clear();
}

void Java_com_ayona333_fix_party_NativeHook_enableDebug(JNIEnv* env, jobject obj,jboolean flag)
{
    xhook_enable_debug(flag);
}

void Java_com_ayona333_fix_party_NativeHook_enableSigSegvProtection(JNIEnv* env, jobject obj,jboolean flag)
{
    xhook_enable_sigsegv_protection(flag);
}

大概原理明白了以后运用起来比较简单,实际场景一般跟xposed等hook框架配合,拦截需要抹机的APP进程后读取该APP的/proc/self/maps文件、然后进行native层的hook。

或抽出so文件,然后嵌入到自己写APP里,配合xhook进行要抹机的so文件,绕过native层的设备指纹检测或者其他操作。

如果配合其他inlineHook框架的话能达到全hook的目的。


[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

最后于 2021-1-16 11:19 被AyonA333编辑 ,原因:
收藏
点赞6
打赏
分享
最新回复 (15)
雪    币: 13
活跃值: 活跃值 (171)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lukarl 活跃值 2021-1-18 16:38
2
0
好文,点赞
雪    币: 788
活跃值: 活跃值 (1047)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
又见飞刀z 活跃值 2021-1-19 09:42
3
0
zan
雪    币: 1078
活跃值: 活跃值 (754)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
TUGOhost 活跃值 2021-1-19 22:37
4
0
感谢分享,大赞
雪    币: 498
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wuwwyu 活跃值 2021-1-20 09:31
5
0
雪    币: 792
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_cjuobwlb 活跃值 2021-1-22 09:54
6
0
厉害了
雪    币: 183
活跃值: 活跃值 (112)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Rec0x 活跃值 2021-1-26 17:41
7
0
有没有源码呀 xhook如何导入项目中使用
雪    币: 256
活跃值: 活跃值 (1373)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
AyonA333 活跃值 2021-1-27 09:47
8
0
Rec0x 有没有源码呀[em_2] xhook如何导入项目中使用
xhook的源码在github,把文章中的实例代码复制到你项目里,然后写一个make编译就可以了
雪    币: 7
活跃值: 活跃值 (77)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Mr吕先生 活跃值 2021-1-27 16:47
9
0
好文,顶一个
雪    币: 1108
活跃值: 活跃值 (2889)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
supperlitt 活跃值 2021-1-29 11:22
10
0
点赞
雪    币: 8
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
万里星河 活跃值 2021-1-29 13:58
11
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_cxmjsgrw 活跃值 2021-4-22 16:49
14
0
牛逼了
雪    币: 27
活跃值: 活跃值 (127)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
duoduo231 活跃值 2021-4-23 09:32
15
0
open fopen 和 open  最后调用的都是__openat吧??
雪    币: 1045
活跃值: 活跃值 (313)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
疯子Tear 活跃值 2021-4-23 09:39
16
0
tql
游客
登录 | 注册 方可回帖
返回