全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数。
GOT表其实包含了导入表和导出表,导出表指将当前动态库的一些函数符号保留,供外部调用,导入表中的函数实际是在该动态库中调用外部的导出函数。
这里有几个关键点要说明一下:
(1) so文件的绝对路径和加载到内存中的基址是可以通过 /proc/[pid]/maps 获取到的。
(2) 修改导入表的函数地址的时候需要修改页的权限,增加写权限即可。
(3) 一般的导入表Hook是基于注入操作的,即把自己的代码注入到目标程序,本次实例重点讲述Hook的实现,注入代码采用上节所有代码inject.c。
导入表的hook有两种方法,以hook fopen函数为例。
方法一:
通过解析elf格式,找出静态的.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 return old_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
运行ndk-build编译,得到libentry.so,push到/data/local/tmp目录下,运行上节所得到的inject程序,得到如下结果,表明hook成功:
图2.
方法二
通过分析program header table查找got表。导入表对应在动态链接段.got.plt(DT_PLTGOT)指向处,但是每项的信息是和GOT表中的表项对应的,因此,在解析动态链接段时,需要解析DT_JMPREL、DT_SYMTAB,前者指向了每一个导入表表项的偏移地址和相关信息,包括在GOT表中偏移,后者为GOT表。
具体代码如下:
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 15 //FILE *fopen(const char *filename, const char *modes) 16 FILE* (*old_fopen)(const char *filename, const char *modes); 17 FILE* new_fopen(const char *filename, const char *modes){ 18 LOGD("[+] New call fopen.\n"); 19 if(old_fopen == -1){ 20 LOGD("error.\n"); 21 } 22 return old_fopen(filename, modes); 23 } 24 25 void* get_module_base(pid_t pid, const char* module_name){ 26 FILE* fp; 27 long addr = 0; 28 char* pch; 29 char filename[32]; 30 char line[1024]; 31 32 // 格式化字符串得到 "/proc/pid/maps" 33 if(pid e_ident, "\177ELF", 4) != 0) { 77 return 0; 78 } 79 80 Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + header->e_phoff); 81 if (phdr_table == 0) 82 { 83 LOGD("[+] phdr_table address : 0"); 84 return 0; 85 } 86 size_t phdr_count = header->e_phnum; 87 LOGD("[+] phdr_count : %d", phdr_count); 88 89 90 //遍历program header table,ptype等于PT_DYNAMIC即为dynameic,获取到p_offset 91 unsigned long dynamicAddr = 0; 92 unsigned int dynamicSize = 0; 93 int j = 0; 94 for (j = 0; j > 8) & 0xffffff;150 Elf32_Sym* symTableIndex = (Elf32_Sym*)(number*16 + symTabOff + base_addr);151 char* funcName = (char*)(symTableIndex->st_name + strTabOff + base_addr);152 //LOGD("[+] Func Name : %s",funcName);153 if(memcmp(funcName, "fopen", 5) == 0)154 {155 // 获取当前内存分页的大小156 uint32_t page_size = getpagesize();157 // 获取内存分页的起始地址(需要内存对齐)158 uint32_t mem_page_start = (uint32_t)(((Elf32_Addr)rel_table[i].r_offset + base_addr)) & (~(page_size - 1));159 LOGD("[+] mem_page_start = %lx, page size = %lx\n", mem_page_start, page_size);160 //void* pstart = (void*)MEM_PAGE_START(((Elf32_Addr)rel_table[i].r_offset + base_addr));161 mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);162 LOGD("[+] r_off : %x",rel_table[i].r_offset + base_addr);163 LOGD("[+] new_fopen : %x",new_fopen);164 *(unsigned int*)(rel_table[i].r_offset + base_addr) = new_fopen;165 }166 }167 168 return 0;169 }170 171 int hook_entry(char* a){172 LOGD("[+] Start hooking.\n");173 hook_fopen();174 return 0;175 }
运行后的结果为:
图3
参考文章:
打开App,阅读手记