全局符号表(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(){64
65 //获取目标pid进程中"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"模块的加载地址
66 void* base_addr =get_module_base(getpid(), LIB_PATH);67 LOGD("[+] libvivosgmain.so address = %p \n", base_addr);68
69 //保存被Hook的目标函数的原始调用地址
70 old_fopen =fopen;71 LOGD("[+] Orig fopen = %p\n", old_fopen);72
73 intfd;74 //打开内存模块文件"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
75 fd =open(LIB_PATH, O_RDONLY);76 if(-1 ==fd){77 LOGD("error.\n");78 return -1;79 }80
81 //elf32文件的文件头结构体Elf32_Ehdr
82 Elf32_Ehdr ehdr;83 //读取elf32格式的文件"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"的文件头信息
84 read(fd, &ehdr, sizeof(Elf32_Ehdr));85
86 //elf32文件中节区表信息结构的文件偏移
87 unsigned long shdr_addr =ehdr.e_shoff;88 //elf32文件中节区表信息结构的数量
89 int shnum =ehdr.e_shnum;90 //elf32文件中每个节区表信息结构中的单个信息结构的大小(描述每个节区的信息的结构体的大小)
91 int shent_size =ehdr.e_shentsize;92
93 //elf32文件节区表中每个节区的名称存放的节区名称字符串表,在节区表中的序号index
94 unsigned long stridx =ehdr.e_shstrndx;95
96 //elf32文件中节区表的每个单元信息结构体(描述每个节区的信息的结构体)
97 Elf32_Shdr shdr;98 //elf32文件中定位到存放每个节区名称的字符串表的信息结构体位置.shstrtab
99 lseek(fd, shdr_addr + stridx *shent_size, SEEK_SET);100 //读取elf32文件中的描述每个节区的信息的结构体(这里是保存elf32文件的每个节区的名称字符串的)
101 read(fd, &shdr, shent_size);102 LOGD("[+] String table offset is %lu, size is %lu", shdr.sh_offset, shdr.sh_size); //41159, size is 254103
104 //为保存elf32文件的所有的节区的名称字符串申请内存空间
105 char * string_table = (char *)malloc(shdr.sh_size);106 //定位到具体存放elf32文件的所有的节区的名称字符串的文件偏移处
107 lseek(fd, shdr.sh_offset, SEEK_SET);108 //从elf32内存文件中读取所有的节区的名称字符串到申请的内存空间中
109 read(fd, string_table, shdr.sh_size);110
111 //重新设置elf32文件的文件偏移为节区信息结构的起始文件偏移处
112 lseek(fd, shdr_addr, SEEK_SET);113
114 inti;115 uint32_t out_addr = 0;116 uint32_t out_size = 0;117 uint32_t got_item = 0;118 int32_t got_found = 0;119
120 //循环遍历elf32文件的节区表(描述每个节区的信息的结构体)
121 for(i = 0; i
123 read(fd, &shdr, shent_size);124 //判断当前节区描述结构体描述的节区是否是SHT_PROGBITS类型125 //类型为SHT_PROGBITS的.got节区包含全局偏移表
126 if(shdr.sh_type ==SHT_PROGBITS){127 //获取节区的名称字符串在保存所有节区的名称字符串段.shstrtab中的序号
128 int name_idx =shdr.sh_name;129
130 //判断节区的名称是否为".got.plt"或者".got"
131 if(strcmp(&(string_table[name_idx]), ".got.plt") == 0
132 || strcmp(&(string_table[name_idx]), ".got") == 0){133 //获取节区".got"或者".got.plt"在内存中实际数据存放地址
134 out_addr = base_addr +shdr.sh_addr;135 //获取节区".got"或者".got.plt"的大小
136 out_size =shdr.sh_size;137 LOGD("[+] out_addr = %lx, out_size = %lx\n", out_addr, out_size);138 int j = 0;139 //遍历节区".got"或者".got.plt"获取保存的全局的函数调用地址
140 for(j = 0; j
142 got_item = *(uint32_t*)(out_addr +j);143 //判断节区".got"或者".got.plt"中函数调用地址是否是将要被Hook的目标函数地址
144 if(got_item ==old_fopen){145 LOGD("[+] Found fopen in got.\n");146 got_found = 1;147 //获取当前内存分页的大小
148 uint32_t page_size =getpagesize();149 //获取内存分页的起始地址(需要内存对齐)
150 uint32_t entry_page_start = (out_addr + j) & (~(page_size - 1));151 LOGD("[+] entry_page_start = %lx, page size = %lx\n", entry_page_start, page_size);152 //修改内存属性为可读可写可执行
153 if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){154 LOGD("mprotect false.\n");155 return -1;156 }157 LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "before hook function", got_item, new_fopen);158
159 //Hook函数为我们自己定义的函数
160 got_item =new_fopen;161 LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "after hook function", got_item, new_fopen);162 //恢复内存属性为可读可执行
163 if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1){164 LOGD("mprotect false.\n");165 return -1;166 }167 break;168 //此时,目标函数的调用地址已经被Hook了
169 }else if(got_item ==new_fopen){170 LOGD("[+] Already hooked.\n");171 break;172 }173 }174 //Hook目标函数成功,跳出循环
175 if(got_found)176 break;177 }178 }179 }180 free(string_table);181 close(fd);182 }183
184 int hook_entry(char*a){185 LOGD("[+] Start hooking.\n");186 hook_fopen();187 return 0;188 }
运行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 returnold_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 < 0){34 snprintf(filename, sizeof(filename), "/proc/self/maps");35 }else{36 snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);37 }38
39 //打开文件/proc/pid/maps,获取指定pid进程加载的内存模块信息
40 fp = fopen(filename, "r");41 if(fp !=NULL){42 //每次一行,读取文件 /proc/pid/maps中内容
43 while(fgets(line, sizeof(line), fp)){44 //查找指定的so模块
45 if(strstr(line, module_name)){46 //分割字符串
47 pch = strtok(line, "-");48 //字符串转长整形
49 addr = strtoul(pch, NULL, 16);50
51 //特殊内存地址的处理
52 if(addr == 0x8000){53 addr = 0;54 }55 break;56 }57 }58 }59 fclose(fp);60 return (void*)addr;61 }62
63 #define LIB_PATH "/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
64 inthook_fopen(){65
66 //获取目标pid进程中"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"模块的加载地址
67 void* base_addr =get_module_base(getpid(), LIB_PATH);68 LOGD("[+] libvivosgmain.so address = %p \n", base_addr);69
70 //保存被Hook的目标函数的原始调用地址
71 old_fopen =fopen;72 LOGD("[+] Orig fopen = %p\n", old_fopen);73
74 //计算program header table实际地址
75 Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);76 if (memcmp(header->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 < phdr_count; j++)95 {96 if (phdr_table[j].p_type ==PT_DYNAMIC)97 {98 dynamicAddr = phdr_table[j].p_vaddr +base_addr;99 dynamicSize =phdr_table[j].p_memsz;100 break;101 }102 }103 LOGD("[+] Dynamic Addr : %x",dynamicAddr);104 LOGD("[+] Dynamic Size : %x",dynamicSize);105
106 /*
107 typedef struct dynamic {108 Elf32_Sword d_tag;109 union {110 Elf32_Sword d_val;111 Elf32_Addr d_ptr;112 } d_un;113 } Elf32_Dyn;114 */
115 Elf32_Dyn* dynamic_table = (Elf32_Dyn*)(dynamicAddr);116 unsigned long jmpRelOff = 0;117 unsigned long strTabOff = 0;118 unsigned long pltRelSz = 0;119 unsigned long symTabOff = 0;120 inti;121 for(i=0;i < dynamicSize / 8;i ++)122 {123 int val =dynamic_table[i].d_un.d_val;124 if (dynamic_table[i].d_tag ==DT_JMPREL)125 {126 jmpRelOff =val;127 }128 if (dynamic_table[i].d_tag ==DT_STRTAB)129 {130 strTabOff =val;131 }132 if (dynamic_table[i].d_tag ==DT_PLTRELSZ)133 {134 pltRelSz =val;135 }136 if (dynamic_table[i].d_tag ==DT_SYMTAB)137 {138 symTabOff =val;139 }140 }141
142 Elf32_Rel* rel_table = (Elf32_Rel*)(jmpRelOff +base_addr);143 LOGD("[+] jmpRelOff : %x",jmpRelOff);144 LOGD("[+] strTabOff : %x",strTabOff);145 LOGD("[+] symTabOff : %x",symTabOff);146 //遍历查找要hook的导入函数,这里以fopen做示例
147 for(i=0;i < pltRelSz / 8;i++)148 {149 int number = (rel_table[i].r_info >> 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
参考文章: