全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数。
GOT表其实包含了导入表和导出表,导出表指将当前动态库的一些函数符号保留,供外部调用,导入表中的函数实际是在该动态库中调用外部的导出函数。
这里有几个关键点要说明一下:
(1) so文件的绝对路径和加载到内存中的基址是可以通过 /proc/[pid]/maps 获取到的。
(2) 修改导入表的函数地址的时候需要修改页的权限,增加写权限即可。
(3) 一般的导入表Hook是基于注入操作的,即把自己的代码注入到目标程序,本次实例重点讲述Hook的实现,注入代码采用上节所有代码inject.c。
导入表的hook有两种方法,以hook fopen函数为例。
方法一:
通过解析elf格式,分析Section header table找出静态的.got表的位置,并在内存中找到相应的.got表位置,这个时候内存中.got表保存着导入函数的地址,读取目标函数地址,与.got表每一项函数入口地址进行匹配,找到的话就直接替换新的函数地址,这样就完成了一次导入表的Hook操作了。
hook流程如下图所示:
图1 导入表Hook流程图
具体代码实现如下:
entry.c:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10
11 #define LOG_TAG "INJECT"
12 #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
13
14 //FILE *fopen(const char *filename, const char *modes)
15 FILE* (*old_fopen)(const char *filename, const char *modes);16 FILE* new_fopen(const char *filename, const char *modes){17 LOGD("[+] New call fopen.\n");18 if(old_fopen == -1){19 LOGD("error.\n");20 }21 returnold_fopen(filename, modes);22 }23
24 void* get_module_base(pid_t pid, const char*module_name){25 FILE*fp;26 long addr = 0;27 char*pch;28 char filename[32];29 char line[1024];30
31 //格式化字符串得到 "/proc/pid/maps"
32 if(pid < 0){33 snprintf(filename, sizeof(filename), "/proc/self/maps");34 }else{35 snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);36 }37
38 //打开文件/proc/pid/maps,获取指定pid进程加载的内存模块信息
39 fp = fopen(filename, "r");40 if(fp !=NULL){41 //每次一行,读取文件 /proc/pid/maps中内容
42 while(fgets(line, sizeof(line), fp)){43 //查找指定的so模块
44 if(strstr(line, module_name)){45 //分割字符串
46 pch = strtok(line, "-");47 //字符串转长整形
48 addr = strtoul(pch, NULL, 16);49
50 //特殊内存地址的处理
51 if(addr == 0x8000){52 addr = 0;53 }54 break;55 }56 }57 }58 fclose(fp);59 return (void*)addr;60 }61
62 #define LIB_PATH "/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
63 inthook_fopen(){