Android so注入(inject)和Hook技術學習(二)——Got表hook之導入表hook

  全局符號表(GOT表)hook實際是經過解析SO文件,將待hook函數在got表的地址替換爲本身函數的入口地址,這樣目標進程每次調用待hook函數時,其實是執行了咱們本身的函數。
  GOT表其實包含了導入表和導出表,導出表指將當前動態庫的一些函數符號保留,供外部調用,導入表中的函數實際是在該動態庫中調用外部的導出函數。
  這裏有幾個關鍵點要說明一下:
  (1) so文件的絕對路徑和加載到內存中的基址是能夠經過 /proc/[pid]/maps 獲取到的。
  (2) 修改導入表的函數地址的時候須要修改頁的權限,增長寫權限便可。
  (3) 通常的導入表Hook是基於注入操做的,即把本身的代碼注入到目標程序,本次實例重點講述Hook的實現,注入代碼採用上節全部代碼inject.c。
  導入表的hook有兩種方法,以hook fopen函數爲例。php

  方法一:android

  經過解析elf格式,分析Section header table找出靜態的.got表的位置,並在內存中找到相應的.got表位置,這個時候內存中.got表保存着導入函數的地址,讀取目標函數地址,與.got表每一項函數入口地址進行匹配,找到的話就直接替換新的函數地址,這樣就完成了一次導入表的Hook操做了。
  hook流程以下圖所示:app

  

 圖1 導入表Hook流程圖 ide

 

  具體代碼實現以下:函數

  entry.c:ui

  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 <sys/mman.h>
 10 
 11 #define LOG_TAG "INJECT"
 12 #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
 13 
 14 //FILE *fopen(const char *filename, const char *modes)
 15 FILE* (*old_fopen)(const char *filename, const char *modes);
 16 FILE* new_fopen(const char *filename, const char *modes){
 17     LOGD("[+] New call fopen.\n");
 18     if(old_fopen == -1){
 19         LOGD("error.\n");
 20     }
 21     return old_fopen(filename, modes);
 22 }
 23 
 24 void* get_module_base(pid_t pid, const char* module_name){
 25     FILE* fp;
 26     long addr = 0;
 27     char* pch;
 28     char filename[32];
 29     char line[1024];
 30     
 31     // 格式化字符串獲得 "/proc/pid/maps"
 32     if(pid < 0){
 33         snprintf(filename, sizeof(filename), "/proc/self/maps");
 34     }else{
 35         snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
 36     }
 37 
 38     // 打開文件/proc/pid/maps,獲取指定pid進程加載的內存模塊信息
 39     fp = fopen(filename, "r");
 40     if(fp != NULL){
 41         // 每次一行,讀取文件 /proc/pid/maps中內容
 42         while(fgets(line, sizeof(line), fp)){
 43             // 查找指定的so模塊
 44             if(strstr(line, module_name)){
 45                 // 分割字符串
 46                 pch = strtok(line, "-");
 47                 // 字符串轉長整形
 48                 addr = strtoul(pch, NULL, 16);
 49 
 50                 // 特殊內存地址的處理
 51                 if(addr == 0x8000){
 52                     addr = 0;
 53                 }
 54                 break;
 55             }
 56         }
 57     }
 58     fclose(fp);
 59     return (void*)addr;
 60 }
 61 
 62 #define LIB_PATH "/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
 63 int hook_fopen(){
 64 
 65     // 獲取目標pid進程中"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"模塊的加載地址
 66     void* base_addr = get_module_base(getpid(), LIB_PATH);
 67     LOGD("[+] libvivosgmain.so address = %p \n", base_addr);
 68     
 69     // 保存被Hook的目標函數的原始調用地址
 70     old_fopen = fopen;
 71     LOGD("[+] Orig fopen = %p\n", old_fopen);
 72 
 73     int fd;
 74     // 打開內存模塊文件"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
 75     fd = open(LIB_PATH, O_RDONLY);
 76     if(-1 == fd){
 77         LOGD("error.\n");
 78         return -1;
 79     }
 80     
 81         // elf32文件的文件頭結構體Elf32_Ehdr
 82     Elf32_Ehdr ehdr;
 83     // 讀取elf32格式的文件"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"的文件頭信息
 84     read(fd, &ehdr, sizeof(Elf32_Ehdr));
 85 
 86     // elf32文件中節區表信息結構的文件偏移
 87     unsigned long shdr_addr = ehdr.e_shoff;
 88     // elf32文件中節區表信息結構的數量
 89     int shnum = ehdr.e_shnum;
 90     // elf32文件中每一個節區表信息結構中的單個信息結構的大小(描述每一個節區的信息的結構體的大小)
 91     int shent_size = ehdr.e_shentsize;
 92 
 93     // elf32文件節區表中每一個節區的名稱存放的節區名稱字符串表,在節區表中的序號index
 94     unsigned long stridx = ehdr.e_shstrndx;
 95 
 96     // elf32文件中節區表的每一個單元信息結構體(描述每一個節區的信息的結構體)
 97     Elf32_Shdr shdr;
 98     // elf32文件中定位到存放每一個節區名稱的字符串表的信息結構體位置.shstrtab
 99     lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);
100     // 讀取elf32文件中的描述每一個節區的信息的結構體(這裏是保存elf32文件的每一個節區的名稱字符串的)
101     read(fd, &shdr, shent_size);
102     LOGD("[+] String table offset is %lu, size is %lu", shdr.sh_offset, shdr.sh_size); //41159, size is 254
103 
104     // 爲保存elf32文件的全部的節區的名稱字符串申請內存空間
105     char * string_table = (char *)malloc(shdr.sh_size);
106     // 定位到具體存放elf32文件的全部的節區的名稱字符串的文件偏移處
107     lseek(fd, shdr.sh_offset, SEEK_SET);
108     // 從elf32內存文件中讀取全部的節區的名稱字符串到申請的內存空間中
109     read(fd, string_table, shdr.sh_size);
110 
111     // 從新設置elf32文件的文件偏移爲節區信息結構的起始文件偏移處
112     lseek(fd, shdr_addr, SEEK_SET);
113 
114     int i;
115     uint32_t out_addr = 0;
116     uint32_t out_size = 0;
117     uint32_t got_item = 0;
118     int32_t got_found = 0;
119     
120     // 循環遍歷elf32文件的節區表(描述每一個節區的信息的結構體)
121     for(i = 0; i<shnum; i++){
122         // 依次讀取節區表中每一個描述節區的信息的結構體
123         read(fd, &shdr, shent_size);
124         // 判斷當前節區描述結構體描述的節區是不是SHT_PROGBITS類型
125         //類型爲SHT_PROGBITS的.got節區包含全局偏移表
126         if(shdr.sh_type == SHT_PROGBITS){
127             // 獲取節區的名稱字符串在保存全部節區的名稱字符串段.shstrtab中的序號
128             int name_idx = shdr.sh_name;
129 
130             // 判斷節區的名稱是否爲".got.plt"或者".got"
131             if(strcmp(&(string_table[name_idx]), ".got.plt") == 0
132                 || strcmp(&(string_table[name_idx]), ".got") == 0){
133                 // 獲取節區".got"或者".got.plt"在內存中實際數據存放地址
134                 out_addr = base_addr + shdr.sh_addr;
135                 // 獲取節區".got"或者".got.plt"的大小
136                 out_size = shdr.sh_size;
137                 LOGD("[+] out_addr = %lx, out_size = %lx\n", out_addr, out_size);
138                 int j = 0;
139                 // 遍歷節區".got"或者".got.plt"獲取保存的全局的函數調用地址
140                 for(j = 0; j<out_size; j += 4){
141                     // 獲取節區".got"或者".got.plt"中的單個函數的調用地址
142                     got_item = *(uint32_t*)(out_addr + j);
143                     // 判斷節區".got"或者".got.plt"中函數調用地址是不是將要被Hook的目標函數地址
144                     if(got_item == old_fopen){
145                         LOGD("[+] Found fopen in got.\n");
146                         got_found = 1;
147                         // 獲取當前內存分頁的大小
148                         uint32_t page_size = getpagesize();
149                         // 獲取內存分頁的起始地址(須要內存對齊)
150                         uint32_t entry_page_start = (out_addr + j) & (~(page_size - 1));
151                         LOGD("[+] entry_page_start = %lx, page size = %lx\n", entry_page_start, page_size);
152                         // 修改內存屬性爲可讀可寫可執行
153                         if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){
154                             LOGD("mprotect false.\n");
155                             return -1;
156                         }
157                         LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "before hook function", got_item, new_fopen);
158                         
159                         // Hook函數爲咱們本身定義的函數
160                         got_item = new_fopen;
161                         LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "after hook function", got_item, new_fopen);
162                         // 恢復內存屬性爲可讀可執行
163                         if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1){
164                             LOGD("mprotect false.\n");
165                             return -1;
166                         }
167                         break;
168                     // 此時,目標函數的調用地址已經被Hook了
169                     }else if(got_item == new_fopen){
170                         LOGD("[+] Already hooked.\n");
171                         break;
172                     }
173                 }
174                 // Hook目標函數成功,跳出循環
175                 if(got_found)
176                     break;
177             }
178         }
179     }
180     free(string_table);
181     close(fd);
182 }
183 
184 int hook_entry(char* a){
185     LOGD("[+] Start hooking.\n");
186     hook_fopen();
187     return 0;
188 }

運行ndk-build編譯,獲得libentry.so,push到/data/local/tmp目錄下,運行上節所獲得的inject程序,獲得以下結果,代表hook成功:spa

圖2..net

 

方法二code

  經過分析program header table查找got表。導入表對應在動態連接段.got.plt(DT_PLTGOT)指向處,可是每項的信息是和GOT表中的表項對應的,所以,在解析動態連接段時,須要解析DT_JMPREL、DT_SYMTAB,前者指向了每個導入表表項的偏移地址和相關信息,包括在GOT表中偏移,後者爲GOT表。blog

  具體代碼以下:

  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 <sys/mman.h>
 10 
 11 #define LOG_TAG "INJECT"
 12 #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
 13 
 14 
 15 //FILE *fopen(const char *filename, const char *modes)
 16 FILE* (*old_fopen)(const char *filename, const char *modes);
 17 FILE* new_fopen(const char *filename, const char *modes){
 18     LOGD("[+] New call fopen.\n");
 19     if(old_fopen == -1){
 20         LOGD("error.\n");
 21     }
 22     return old_fopen(filename, modes);
 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_fopen(){
 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     // 保存被Hook的目標函數的原始調用地址
 71     old_fopen = fopen;
 72     LOGD("[+] Orig fopen = %p\n", old_fopen);
 73 
 74     //計算program header table實際地址
 75     Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);
 76     if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
 77         return 0;
 78     }
 79 
 80     Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + header->e_phoff);
 81     if (phdr_table == 0)
 82     {
 83         LOGD("[+] phdr_table address : 0");
 84         return 0;
 85     }
 86     size_t phdr_count = header->e_phnum;
 87     LOGD("[+] phdr_count : %d", phdr_count);
 88 
 89 
 90     //遍歷program header table,ptype等於PT_DYNAMIC即爲dynameic,獲取到p_offset
 91     unsigned long dynamicAddr = 0;
 92     unsigned int dynamicSize = 0;
 93     int j = 0;
 94     for (j = 0; j < phdr_count; j++)
 95     {
 96         if (phdr_table[j].p_type == PT_DYNAMIC)
 97         {
 98             dynamicAddr = phdr_table[j].p_vaddr + base_addr;
 99             dynamicSize = phdr_table[j].p_memsz;
100             break;
101         }
102     }
103     LOGD("[+] Dynamic Addr : %x",dynamicAddr);
104     LOGD("[+] Dynamic Size : %x",dynamicSize);
105 
106 /*
107 typedef struct dynamic {
108     Elf32_Sword d_tag;
109     union {
110     Elf32_Sword d_val;
111     Elf32_Addr d_ptr;
112     } d_un;
113 } Elf32_Dyn;
114 */
115     Elf32_Dyn* dynamic_table = (Elf32_Dyn*)(dynamicAddr);
116     unsigned long jmpRelOff = 0;
117     unsigned long strTabOff = 0;
118     unsigned long pltRelSz = 0;
119     unsigned long symTabOff = 0;
120     int i;
121     for(i=0;i < dynamicSize / 8;i ++)
122     {
123         int val = dynamic_table[i].d_un.d_val;
124         if (dynamic_table[i].d_tag == DT_JMPREL)
125         {
126             jmpRelOff = val;
127         }
128         if (dynamic_table[i].d_tag == DT_STRTAB)
129         {
130             strTabOff = val;
131         }
132         if (dynamic_table[i].d_tag == DT_PLTRELSZ)
133         {
134             pltRelSz = val;
135         }
136         if (dynamic_table[i].d_tag == DT_SYMTAB)
137         {
138             symTabOff = val;
139         }
140     }
141 
142     Elf32_Rel* rel_table = (Elf32_Rel*)(jmpRelOff + base_addr);
143     LOGD("[+] jmpRelOff : %x",jmpRelOff);
144     LOGD("[+] strTabOff : %x",strTabOff);
145     LOGD("[+] symTabOff : %x",symTabOff);
146     //遍歷查找要hook的導入函數,這裏以fopen作示例
147     for(i=0;i < pltRelSz / 8;i++)
148     {
149         int number = (rel_table[i].r_info >> 8) & 0xffffff;
150         Elf32_Sym* symTableIndex = (Elf32_Sym*)(number*16 + symTabOff + base_addr);
151         char* funcName = (char*)(symTableIndex->st_name + strTabOff + base_addr);
152         //LOGD("[+] Func Name : %s",funcName);
153         if(memcmp(funcName, "fopen", 5) == 0)
154         {
155             // 獲取當前內存分頁的大小
156             uint32_t page_size = getpagesize();
157             // 獲取內存分頁的起始地址(須要內存對齊)
158             uint32_t mem_page_start = (uint32_t)(((Elf32_Addr)rel_table[i].r_offset + base_addr)) & (~(page_size - 1));
159             LOGD("[+] mem_page_start = %lx, page size = %lx\n", mem_page_start, page_size);
160             //void* pstart = (void*)MEM_PAGE_START(((Elf32_Addr)rel_table[i].r_offset + base_addr));
161             mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
162             LOGD("[+] r_off : %x",rel_table[i].r_offset + base_addr);
163             LOGD("[+] new_fopen : %x",new_fopen);
164             *(unsigned int*)(rel_table[i].r_offset + base_addr) = new_fopen;
165         }
166     }
167 
168     return 0;
169 }
170 
171 int hook_entry(char* a){
172     LOGD("[+] Start hooking.\n");
173     hook_fopen();
174     return 0;
175 }

運行後的結果爲:

 圖3

參考文章:

http://gslab.qq.com/portal.php?mod=view&aid=169

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

https://blog.csdn.net/qq1084283172/article/details/53942648

相關文章
相關標籤/搜索