前文介绍了导入表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,运行结果如下:
参考资料: