android的got表HOOK實現代碼
概述
對於android的so文件的hook根據ELF文件特性分為:Got表hook、Sym表hook和inline hook等。
全局符號表(GOT表)hook,它是通過解析SO文件,將待hook函數在got表的地址替換為自己函數的入口地址,這樣目標進程每次調用待hook函數時,實際上是執行瞭我們自己的函數。
Androd so註入和函數Hook(基於got表)的步驟:
1.ptrace附加目標pid進程;
2.在目標pid進程中,查找內存空間(用於存放被註入的so文件的路徑和so中被調用的函數的名稱或者shellcode);
3.調用目標pid進程中的dlopen、dlsym等函數,用於加載so文件實現Android so的註入和函數的Hook;
4.釋放附加的目標pid進程和卸載註入的so文件。
具體代碼實現
以下以fopen函數進行got hook為例。
//獲取模塊地址功能實現 void* getModuleBase(pid_t pid, const char* module_name){ FILE* fp; long address = 0; char* pch; char filename[32]; char line[1024]; // 格式化字符串得到 "/proc/pid/maps" if(pid < 0){ snprintf(filename, sizeof(filename), "/proc/self/maps"); }else{ snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); } // 打開文件/proc/pid/maps,獲取指定pid進程加載的內存模塊信息 fp = fopen(filename, "r"); if(fp != NULL){ // 每次一行,讀取文件 /proc/pid/maps中內容 while(fgets(line, sizeof(line), fp)){ // 查找指定的so模塊 if(strstr(line, module_name)){ // 分割字符串 pch = strtok(line, "-"); // 字符串轉長整形 address = strtoul(pch, NULL, 16); } break; } } } fclose(fp); return (void*)address; }
//hook fopen進行實現 //(libxxxx.so文件是ELF32文件) #define LIBPATH "/data/app-lib/com.xxxx/libxxxx.so" int hookFopen(){ // 獲取目標pid中"/data/app-lib/com.xxxx/libxxxx.so"模塊的加載地址 void* base_addr = getModuleBase(getpid(), LIBPATH ); // 保存Hook目標函數的原始調用地址 old_fopen = fopen; int fd; // 用open打開內存模塊文件"/data/app-lib/com.xxxx/libxxxx.so" fd = open(LIB_PATH, O_RDONLY); if(-1 == fd){ return -1; } // elf32文件的文件頭結構體Elf32_Ehdr Elf32_Ehdr ehdr; // 讀取elf32格式的文件"/data/app-lib/com.xxxx/libxxxx.so"的文件頭信息 read(fd, &ehdr, sizeof(Elf32_Ehdr)); // elf32文件中節區表信息結構的文件偏移 unsigned long shdr_addr = ehdr.e_shoff; // elf32文件中節區表信息結構的數量 int shnum = ehdr.e_shnum; // elf32文件中每個節區表信息結構中的單個信息結構的大小(描述每個節區的信息的結構體的大小) int shent_size = ehdr.e_shentsize; // elf32文件節區表中每個節區的名稱存放的節區名稱字符串表,在節區表中的序號index unsigned long stridx = ehdr.e_shstrndx; Elf32_Shdr shdr; lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET); // 讀取elf32文件中的描述每個節區的信息的結構體(這裡是保存elf32文件的每個節區的名稱字符串的) read(fd, &shdr, shent_size); // 為保存elf32文件的所有的節區的名稱字符串申請內存空間 char * string_table = (char *)malloc(shdr.sh_size); // 定位到具體存放elf32文件的所有的節區的名稱字符串的文件偏移處 lseek(fd, shdr.sh_offset, SEEK_SET); read(fd, string_table, shdr.sh_size); lseek(fd, shdr_addr, SEEK_SET); int i; uint32_t out_addr = 0; uint32_t out_size = 0; uint32_t got_item = 0; int32_t got_found = 0; // 循環遍歷elf32文件的節區表(描述每個節區的信息的結構體) for(i = 0; i<shnum; i++){ // 依次讀取節區表中每個描述節區的信息的結構體 read(fd, &shdr, shent_size); // 判斷當前節區描述結構體描述的節區是否是SHT_PROGBITS類型 //類型為SHT_PROGBITS的.got節區包含全局偏移表 if(shdr.sh_type == SHT_PROGBITS){ // 獲取節區的名稱字符串在保存所有節區的名稱字符串段.shstrtab中的序號 int name_idx = shdr.sh_name; // 判斷節區的名稱是否為".got.plt"或者".got" if(strcmp(&(string_table[name_idx]), ".got.plt") == 0 || strcmp(&(string_table[name_idx]), ".got") == 0){ // 獲取節區".got"或者".got.plt"在內存中實際數據存放地址 out_addr = base_addr + shdr.sh_addr; // 獲取節區".got"或者".got.plt"的大小 out_size = shdr.sh_size; int j = 0; // 遍歷節區".got"或者".got.plt"獲取保存的全局的函數調用地址 for(j = 0; j<out_size; j += 4){ // 獲取節區".got"或者".got.plt"中的單個函數的調用地址 got_item = *(uint32_t*)(out_addr + j); // 判斷節區".got"或者".got.plt"中函數調用地址是否是將要被Hook的目標函數地址 if(got_item == old_fopen){ got_found = 1; // 獲取當前內存分頁的大小 uint32_t page_size = getpagesize(); // 獲取內存分頁的起始地址(需要內存對齊) uint32_t entry_page_start = (out_addr + j) & (~(page_size - 1)); // 修改內存屬性為可讀可寫可執行 if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){ return -1; } // Hook的函數,是我們自己定義的函數 got_item = new_fopen; // 進行恢復內存屬性為可讀可執行 if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1){ return -1; } break; // 目標函數的調用地址已經被Hook瞭 }else if(got_item == new_fopen){ break; } } // 對目標函數HOOk成功,跳出循環 if(got_found) break; } } } free(string_table); close(fd); }
到此這篇關於android的got表HOOK實現代碼的文章就介紹到這瞭,更多相關android HOOK實現got表內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- C++內存泄漏的檢測與實現詳細流程
- Linux之進程間通信(共享內存【mmap實現+系統V】)
- Android NDK開發(C語言-文件讀寫)
- C++中獲取字符串長度的函數sizeof()、strlen()、length()、size()詳解和區別(推薦)
- C++讀取wav文件中的PCM數據