zoukankan      html  css  js  c++  java
  • Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook

    前文介绍了导入表hook,现在来说下导出表的hook。导出表的hook的流程如下。
    1、获取动态库基值   

     1 void* get_module_base(pid_t pid, const char* module_name){
     2     FILE* fp;
     3     long addr = 0;
     4     char* pch;
     5     char filename[32];
     6     char line[1024];
     7     
     8     // 格式化字符串得到 "/proc/pid/maps"
     9     if(pid < 0){
    10         snprintf(filename, sizeof(filename), "/proc/self/maps");
    11     }else{
    12         snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
    13     }
    14 
    15     // 打开文件/proc/pid/maps,获取指定pid进程加载的内存模块信息
    16     fp = fopen(filename, "r");
    17     if(fp != NULL){
    18         // 每次一行,读取文件 /proc/pid/maps中内容
    19         while(fgets(line, sizeof(line), fp)){
    20             // 查找指定的so模块
    21             if(strstr(line, module_name)){
    22                 // 分割字符串
    23                 pch = strtok(line, "-");
    24                 // 字符串转长整形
    25                 addr = strtoul(pch, NULL, 16);
    26 
    27                 // 特殊内存地址的处理
    28                 if(addr == 0x8000){
    29                     addr = 0;
    30                 }
    31                 break;
    32             }
    33         }
    34     }
    35     fclose(fp);
    36     return (void*)addr;
    37 }

    2、计算program header table实际地址 

    通过ELF文件头获取到程序表头的偏移地址及表头的个数

     1     Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);
     2     if (memcmp(header->e_ident, "177ELF", 4) != 0) {
     3         return 0;
     4     }
     5     int phOffset = header->e_phoff;
     6     int phNumber = header->e_phnum;
     7     int phPhyAddr = phOffset + base_addr;
     8 
     9     int i = 0;
    10 
    11     Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + phOffset);
    12     if (phdr_table == 0)
    13     {
    14         LOGD("[+] phdr_table address : 0");
    15         return 0;
    16     }

    3、遍历program header table,找到类型为PT_DYNAMIC的区段(动态链接段),ptype等于2即为dynamic,获取到p_offset 

    这里需要参照程序表头结构体的相关信息,程序表头结构体结构如下:

    struct Elf32_Phdr {
      Elf32_Word p_type;   // Type of segment
      Elf32_Off  p_offset; // File offset where segment is located, in bytes
      Elf32_Addr p_vaddr;  // Virtual address of beginning of segment
      Elf32_Addr p_paddr;  // Physical address of beginning of segment (OS-specific)
      Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
      Elf32_Word p_memsz;  // Num. of bytes in mem image of segment (may be zero)
      Elf32_Word p_flags;  // Segment flags
      Elf32_Word p_align;  // Segment alignment constraint
    };

    因此得到dynamic段对应的地址:

    1 for (i = 0; i < phNumber; i++)
    2 {
    3         if (phdr_table[i].p_type == PT_DYNAMIC)
    4         {
    5             dynamicAddr = phdr_table[i].p_vaddr + base_addr;
    6             dynamicSize = phdr_table[i].p_memsz;
    7             break;
    8         }
    9 }

    4、开始遍历dynamic段结构,d_tag为6即为GOT表地址 

    同样需要参考动态链接段每项的结构体:

    typedef struct dynamic {
        Elf32_Sword d_tag;
        union {
        Elf32_Sword d_val;
        Elf32_Addr d_ptr;
        } d_un;
    } Elf32_Dyn;

    遍历方法为:

    1 for(i=0; i < dynamicSize / 8; i++)
    2 {
    3     int val = dynamic_table[i].d_un.d_val;
    4     if (dynamic_table[i].d_tag == 6)
    5     {
    6         symbolTableAddr = val + base_addr;
    7         break;
    8     }
    9 }

    5、遍历GOT表,查找GOT表中标记的目标函数地址,替换为新函数的地址。 

    我们需要知道符号表的结构:

    /* Symbol Table Entry */
    typedef struct elf32_sym {
        Elf32_Word    st_name;        /* name - index into string table */
        Elf32_Addr    st_value;        /* symbol value */
        Elf32_Word    st_size;         /* symbol size */
        unsigned char    st_info;     /* type and binding */
        unsigned char    st_other;    /* 0 - no defined meaning */
        Elf32_Half    st_shndx;        /* section header index */
    } Elf32_Sym;

    然后替换成目标函数的st_value值,即偏移地址

     1 while(1)
     2 {
     3     //LOGD("[+] func Addr : %x", symTab[i].st_value);
     4     if(symTab[i].st_value == oldFunc)
     5     {
     6         //st_value 保存的是偏移地址
     7         symTab[i].st_value = newFunc;
     8         LOGD("[+] New Give func Addr : %x", symTab[i].st_value);
     9         break;
    10     }
    11     i++;
    12 }

    注意点: 

    1、我们知道代码段一般都只会设置为可读可执行的,因此需要使用mprotect改变内存页为可读可写可执行;
    2、如果执行完这些操作后hook并没有生效,可能是由于缓存的原因,需要使用cacheflush函数对该内存进行操作。
    3、获取目标函数的偏移地址,可以通过dlsym得到绝对地址,再减去基址。

    我们以hook libvivosgmain.so中的check_signatures函数为例,完整代码如下:

      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 <dlfcn.h>
     10 #include <sys/mman.h>
     11 
     12 #define LOG_TAG "INJECT"
     13 #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
     14 
     15 
     16 int (*old_check_signatures)();
     17 int new_check_signatures(){
     18     LOGD("[+] New call check_signatures.
    ");
     19     if(old_check_signatures == -1){
     20         LOGD("error.
    ");
     21     }
     22     return old_check_signatures();
     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_check_signatures(){
     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     //计算program header table实际地址
     71     Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);
     72     if (memcmp(header->e_ident, "177ELF", 4) != 0) {
     73         return 0;
     74     }
     75     
     76     void* handle = dlopen("/data/app-lib/com.bbk.appstore-2/libvivosgmain.so", RTLD_LAZY);
     77     //获取原函数地址
     78     void* funcaddr = dlsym(handle, "check_signatures");
     79     LOGD("[+] libvivosgmain.so check_signatures address = %p 
    ", (int)funcaddr);
     80 
     81     int phOffset = header->e_phoff;
     82     int phNumber = header->e_phnum;
     83     int phPhyAddr = phOffset + base_addr;
     84     LOGD("[+] phOffset  : %x", phOffset);
     85     LOGD("[+] phNumber  : %x", phNumber);
     86     LOGD("[+] phPhyAddr : %x", phPhyAddr);
     87     int i = 0;
     88 
     89     Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + phOffset);
     90     if (phdr_table == 0)
     91     {
     92         LOGD("[+] phdr_table address : 0");
     93         return 0;
     94     }
     95 
     96     /*
     97     // Program header for ELF32.
     98     struct Elf32_Phdr {
     99       Elf32_Word p_type;   // Type of segment
    100       Elf32_Off  p_offset; // File offset where segment is located, in bytes
    101       Elf32_Addr p_vaddr;  // Virtual address of beginning of segment
    102       Elf32_Addr p_paddr;  // Physical address of beginning of segment (OS-specific)
    103       Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
    104       Elf32_Word p_memsz;  // Num. of bytes in mem image of segment (may be zero)
    105       Elf32_Word p_flags;  // Segment flags
    106       Elf32_Word p_align;  // Segment alignment constraint
    107     };
    108     */
    109     //遍历program header table,ptype等于2即为dynamic,获取到p_offset
    110     unsigned long dynamicAddr = 0;
    111     unsigned int dynamicSize = 0;
    112 
    113     for (i = 0; i < phNumber; i++)
    114     {
    115         if (phdr_table[i].p_type == PT_DYNAMIC)
    116         {
    117             dynamicAddr = phdr_table[i].p_vaddr + base_addr;
    118             dynamicSize = phdr_table[i].p_memsz;
    119             break;
    120         }
    121     }
    122     LOGD("[+] Dynamic Addr : %x", dynamicAddr);
    123     LOGD("[+] Dynamic Size : %x", dynamicSize);
    124 
    125     /*
    126     typedef struct dynamic {
    127         Elf32_Sword d_tag;
    128         union {
    129         Elf32_Sword d_val;
    130         Elf32_Addr d_ptr;
    131         } d_un;
    132     } Elf32_Dyn;
    133     */
    134     //开始遍历dynamic段结构,d_tag为6即为GOT表地址
    135     int symbolTableAddr = 0;
    136     Elf32_Dyn* dynamic_table = (Elf32_Dyn*)(dynamicAddr);
    137 
    138     for(i=0; i < dynamicSize / 8; i++)
    139     {
    140         int val = dynamic_table[i].d_un.d_val;
    141         if (dynamic_table[i].d_tag == 6)
    142         {
    143             symbolTableAddr = val + base_addr;
    144             break;
    145         }
    146     }
    147     LOGD("Symbol Table Addr : %x", symbolTableAddr);
    148 
    149     /*
    150     typedef struct elf32_sym {
    151         Elf32_Word st_name;
    152         Elf32_Addr st_value;
    153         Elf32_Word st_size;
    154         unsigned char st_info;
    155         unsigned char st_other;
    156         Elf32_Half st_shndx;
    157     } Elf32_Sym;
    158     */
    159     //遍历GOT表,查找GOT表中标记的check_signatures函数地址,替换为new_check_signatures的地址
    160     int giveValuePtr = 0;
    161     int fakeValuePtr = 0;
    162     int newFunc = (int)new_check_signatures - (int)base_addr;
    163     int oldFunc = (int)funcaddr - (int)base_addr;
    164     i = 0;
    165     LOGD("[+] newFunc Addr : %x", newFunc);
    166     LOGD("[+] oldFunc Addr : %x", oldFunc);
    167 
    168     // 获取当前内存分页的大小
    169     uint32_t page_size = getpagesize();
    170     // 获取内存分页的起始地址(需要内存对齐)
    171     uint32_t mem_page_start = (uint32_t)(((Elf32_Addr)symbolTableAddr)) & (~(page_size - 1));
    172     LOGD("[+] mem_page_start = %lx, page size = %lx
    ", mem_page_start, page_size);
    173     mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
    174     Elf32_Sym* symTab = (Elf32_Sym*)(symbolTableAddr);
    175     while(1)
    176     {
    177         //LOGD("[+] func Addr : %x", symTab[i].st_value);
    178         if(symTab[i].st_value == oldFunc)
    179         {
    180             //st_value 保存的是偏移地址
    181             symTab[i].st_value = newFunc;
    182             LOGD("[+] New Give func Addr : %x", symTab[i].st_value);
    183             break;
    184         }
    185         i++;
    186     }
    187     mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_EXEC);
    188 
    189     return 0;
    190 }
    191 
    192 int hook_entry(char* a){
    193     LOGD("[+] Start hooking.
    ");
    194     hook_check_signatures();
    195     return 0;
    196 }

    我们还是通过执行“Android so注入( inject)和Hook技术学习(一)”文中的inject程序将本程序注入到目标进程进行hook,运行结果如下:

    参考资料:

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

  • 相关阅读:
    Path expected for join!错误处理
    JPA 不生成外键
    SpringDataJPA
    MyCat-schema.xml详解
    aliyun阿里云Maven仓库地址——加速你的maven构建
    MyCAT简易入门 (Linux)
    CentOS 7 常用命令大全
    mycat 9066管理端口
    MyCat入门
    Virtualbox的centos7 nat和桥接网络配置
  • 原文地址:https://www.cnblogs.com/goodhacker/p/9313297.html
Copyright © 2011-2022 走看看