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

学向勤中得,萤窗万卷书。这篇文章主要讲述Android so注入(inject)和Hook技术学习——Got表hook之导出表hook相关的知识,希望能为你提供帮助。
前文介绍了导入表hook,现在来说下导出表的hook。导出表的hook的流程如下。
1、获取动态库基值

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

2、计算program header table实际地址 
通过ELF文件头获取到程序表头的偏移地址及表头的个数
1Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr); 2if (memcmp(header-> e_ident, "\\177ELF", 4) != 0) { 3return 0; 4} 5int phOffset = header-> e_phoff; 6int phNumber = header-> e_phnum; 7int phphyAddr = phOffset + base_addr; 8 9int i = 0; 10 11Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + phOffset); 12if (phdr_table == 0) 13{ 14LOGD("[+] phdr_table address : 0"); 15return 0; 16}

3、遍历program header table,找到类型为PT_DYNAMIC的区段(动态链接段),ptype等于2即为dynamic,获取到p_offset 
这里需要参照程序表头结构体的相关信息,程序表头结构体结构如下:
struct Elf32_Phdr { Elf32_Word p_type; // Type of segment Elf32_Offp_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 { 3if (phdr_table[i].p_type == PT_DYNAMIC) 4{ 5dynamicAddr = phdr_table[i].p_vaddr + base_addr; 6dynamicSize = phdr_table[i].p_memsz; 7break; 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 { 3int val = dynamic_table[i].d_un.d_val; 4if (dynamic_table[i].d_tag == 6) 5{ 6symbolTableAddr = val + base_addr; 7break; 8} 9 }

5、遍历GOT表,查找GOT表中标记的目标函数地址,替换为新函数的地址。 
我们需要知道符号表的结构:
/* Symbol Table Entry */ typedef struct elf32_sym { Elf32_Wordst_name; /* name - index into string table */ Elf32_Addrst_value; /* symbol value */ Elf32_Wordst_size; /* symbol size */ unsigned charst_info; /* type and binding */ unsigned charst_other; /* 0 - no defined meaning */ Elf32_Halfst_shndx; /* section header index */ } Elf32_Sym;

然后替换成目标函数的st_value值,即偏移地址
1 while(1) 2 { 3//LOGD("[+] func Addr : %x", symTab[i].st_value); 4if(symTab[i].st_value =https://www.songbingjia.com/android/= oldFunc) 5{ 6//st_value 保存的是偏移地址 7symTab[i].st_value = newFunc; 8LOGD("[+] New Give func Addr : %x", symTab[i].st_value); 9break; 10} 11i++; 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(){ 18LOGD("[+] New call check_signatures.\\n"); 19if(old_check_signatures == -1){ 20LOGD("error.\\n"); 21} 22return old_check_signatures(); 23 } 24 25 void* get_module_base(pid_t pid, const char* module_name){ 26FILE* fp; 27long addr = 0; 28char* pch; 29char filename[32]; 30char line[1024]; 31 32// 格式化字符串得到 "/proc/pid/maps" 33if(pid < 0){ 34snprintf(filename, sizeof(filename), "/proc/self/maps"); 35}else{ 36snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); 37} 38 39// 打开文件/proc/pid/maps,获取指定pid进程加载的内存模块信息 40fp = fopen(filename, "r"); 41if(fp != NULL){ 42// 每次一行,读取文件 /proc/pid/maps中内容 43while(fgets(line, sizeof(line), fp)){ 44// 查找指定的so模块 45if(strstr(line, module_name)){ 46// 分割字符串 47pch = strtok(line, "-"); 48// 字符串转长整形 49addr = strtoul(pch, NULL, 16); 50 51// 特殊内存地址的处理 52if(addr == 0x8000){ 53addr = 0; 54} 55break; 56} 57} 58} 59fclose(fp); 60return (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"模块的加载地址 67void* base_addr = get_module_base(getpid(), LIB_PATH); 68LOGD("[+] libvivosgmain.so address = %p \\n", base_addr); 69 70//计算program header table实际地址 71Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr); 72if (memcmp(header-> e_ident, "\\177ELF", 4) != 0) { 73return 0; 74} 75 76void* handle = dlopen("/data/app-lib/com.bbk.appstore-2/libvivosgmain.so", RTLD_LAZY); 77//获取原函数地址 78void* funcaddr = dlsym(handle, "check_signatures"); 79LOGD("[+] libvivosgmain.so check_signatures address = %p \\n", (int)funcaddr); 80 81int phOffset = header-> e_phoff; 82int phNumber = header-> e_phnum; 83int phPhyAddr = phOffset + base_addr; 84LOGD("[+] phOffset: %x", phOffset); 85LOGD("[+] phNumber: %x", phNumber); 86LOGD("[+] phPhyAddr : %x", phPhyAddr); 87int i = 0; 88 89Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + phOffset); 90if (phdr_table == 0) 91{ 92LOGD("[+] phdr_table address : 0"); 93return 0; 94} 95 96/* 97// Program header for ELF32. 98struct Elf32_Phdr { 99Elf32_Word p_type; // Type of segment 100Elf32_Offp_offset; // File offset where segment is located, in bytes 101Elf32_Addr p_vaddr; // Virtual address of beginning of segment 102Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific) 103Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero) 104Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero) 105Elf32_Word p_flags; // Segment flags 106Elf32_Word p_align; // Segment alignment constraint 107}; 108*/ 109//遍历program header table,ptype等于2即为dynamic,获取到p_offset 110unsigned long dynamicAddr = 0; 111unsigned int dynamicSize = 0; 112 113for (i = 0; i < phNumber; i++) 114{ 115if (phdr_table[i].p_type == PT_DYNAMIC) 116{ 117dynamicAddr = phdr_table[i].p_vaddr + base_addr; 118dynamicSize = phdr_table[i].p_memsz; 119break; 120} 121} 122LOGD("[+] Dynamic Addr : %x", dynamicAddr); 123LOGD("[+] Dynamic Size : %x", dynamicSize); 124 125/* 126typedef struct dynamic { 127Elf32_Sword d_tag; 128union { 129Elf32_Sword d_val; 130Elf32_Addr d_ptr; 131} d_un; 132} Elf32_Dyn; 133*/ 134//开始遍历dynamic段结构,d_tag为6即为GOT表地址 135int symbolTableAddr = 0; 136Elf32_Dyn* dynamic_table = (Elf32_Dyn*)(dynamicAddr); 137 138for(i=0; i < dynamicSize / 8; i++) 139{ 140int val = dynamic_table[i].d_un.d_val; 141if (dynamic_table[i].d_tag == 6) 142{ 143symbolTableAddr = val + base_addr; 144break; 145} 146} 147LOGD("Symbol Table Addr : %x", symbolTableAddr); 148 149/* 150typedef struct elf32_sym { 151Elf32_Word st_name; 152Elf32_Addr st_value; 153Elf32_Word st_size; 154unsigned char st_info; 155unsigned char st_other; 156Elf32_Half st_shndx; 157} Elf32_Sym; 158*/ 159//遍历GOT表,查找GOT表中标记的check_signatures函数地址,替换为new_check_signatures的地址 160int giveValuePtr = 0; 161int fakeValuePtr = 0; 162int newFunc = (int)new_check_signatures - (int)base_addr; 163int oldFunc = (int)funcaddr - (int)base_addr; 164i = 0; 165LOGD("[+] newFunc Addr : %x", newFunc); 166LOGD("[+] oldFunc Addr : %x", oldFunc); 167 168// 获取当前内存分页的大小 169uint32_t page_size = getpagesize(); 170// 获取内存分页的起始地址(需要内存对齐) 171uint32_t mem_page_start = (uint32_t)(((Elf32_Addr)symbolTableAddr)) & (~(page_size - 1)); 172LOGD("[+] mem_page_start = %lx, page size = %lx\\n", mem_page_start, page_size); 173mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC); 174Elf32_Sym* symTab = (Elf32_Sym*)(symbolTableAddr); 175while(1) 176{ 177//LOGD("[+] func Addr : %x", symTab[i].st_value); 178if(symTab[i].st_value =https://www.songbingjia.com/android/= oldFunc) 179{ 180//st_value 保存的是偏移地址 181symTab[i].st_value = newFunc; 182LOGD("[+] New Give func Addr : %x", symTab[i].st_value); 183break; 184} 185i++; 186} 187mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_EXEC); 188 189return 0; 190 } 191 192 int hook_entry(char* a){ 193LOGD("[+] Start hooking.\\n"); 194hook_check_signatures(); 195return 0; 196 }

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

文章图片

参考资料:
【Android so注入(inject)和Hook技术学习——Got表hook之导出表hook】https://blog.csdn.net/u011247544/article/details/78564564

    推荐阅读