zoukankan      html  css  js  c++  java
  • 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.
    ");
     18     if(old_fopen == -1){
     19         LOGD("error.
    ");
     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 
    ", base_addr);
     68     
     69     // 保存被Hook的目标函数的原始调用地址
     70     old_fopen = fopen;
     71     LOGD("[+] Orig fopen = %p
    ", 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.
    ");
     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
    ", 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.
    ");
    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
    ", 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.
    ");
    155                             return -1;
    156                         }
    157                         LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx
    ", "before hook function", got_item, new_fopen);
    158                         
    159                         // Hook函数为我们自己定义的函数
    160                         got_item = new_fopen;
    161                         LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx
    ", "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.
    ");
    165                             return -1;
    166                         }
    167                         break;
    168                     // 此时,目标函数的调用地址已经被Hook了
    169                     }else if(got_item == new_fopen){
    170                         LOGD("[+] Already hooked.
    ");
    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.
    ");
    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.
    ");
     19     if(old_fopen == -1){
     20         LOGD("error.
    ");
     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 
    ", base_addr);
     69     
     70     // 保存被Hook的目标函数的原始调用地址
     71     old_fopen = fopen;
     72     LOGD("[+] Orig fopen = %p
    ", 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
    ", 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.
    ");
    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

  • 相关阅读:
    tp框架实现ajax
    tp框架的增删改查
    tp框架获取常量信息、方法、命名空间
    tp框架,访问方式、空方法
    tp基础,文件存储路径
    缓存
    CMS系统存储路径
    Smarty模版引擎的原理
    php的empty(),trim(),strlen()方法
    PHP 流程管理
  • 原文地址:https://www.cnblogs.com/goodhacker/p/9306997.html
Copyright © 2011-2022 走看看