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

Posted 人怜直节生来瘦,自许高材老更刚。

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.\\n");
 19     if(old_check_signatures == -1){
 20         LOGD("error.\\n");
 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 \\n", 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 \\n", (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\\n", 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.\\n");
194     hook_check_signatures();
195     return 0;
196 }

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

参考资料:

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

以上是关于Android so注入(inject)和Hook技术学习——Got表hook之导出表hook的主要内容,如果未能解决你的问题,请参考以下文章

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

Android Ptrace Inject

Android Native Hook技术

android注入so怎么使用

android的got表HOOK实现

C++实现inline hook,注入后程序异常退出