Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook

  全局符号表(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 <unistd.h>
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <android/log.h>
  5 #include <EGL/egl.h>
  6 #include <GLES/gl.h>
  7 #include <elf.h>
  8 #include <fcntl.h>
  9 #include <sys/mman.h>
 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 < 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 int hook_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     int fd;
 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 254
103 
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     int i;
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<shnum; i++){
122         // 依次读取节区表中每个描述节区的信息的结构体
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<out_size; j += 4){
141                     // 获取节区".got"或者".got.plt"中的单个函数的调用地址
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 <unistd.h>
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <android/log.h>
  5 #include <EGL/egl.h>
  6 #include <GLES/gl.h>
  7 #include <elf.h>
  8 #include <fcntl.h>
  9 #include <sys/mman.h>
 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 < 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 int hook_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     int i;
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

参考文章:

http://gslab.qq.com/portal.php?mod=view&aid=169

https://blog.csdn.net/u011247544/article/details/78668791

https://blog.csdn.net/qq1084283172/article/details/53942648

posted @ 2018-07-13 19:54  bamb00  阅读(6717)  评论(2编辑  收藏  举报