自实现目的
对so hook有刚需的人应该都知道,hook的第一步就是要拿到被hook方法的地址,而想要获取方法地址就避不开dlopen()和dlsym()这两个好基友函数,但Android为了避免伤及友军便在Android 7.0以上版本对这两个函数的使用做了限制,以至于在获取很多未公开库方法时会获取失败,目的也是为了提高安全性和稳定性。出发点是好的,但我们有刚需,我们就得造它。
浅谈思路
dlopen
目的就是将目标so文件模拟加载到内存中,大致分为以下几个步骤:
- 打开文件,读取头表段表
- 申请内存,映射LOAD段
- 定位、解析动态段,获取有用信息
- 重定位、初始化
dlSym
dlsym则模拟源码实现方式
- 通过hash判断目标字符是否存在
- 遍历hash表获取目标位置
具体实现
dlopen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | //初始化函数typedef void*(*PFN_INIT)();Elf64_Hash g_hash = {0};char* pStrTable = NULL;Elf64_Sym* pSymTable = NULL;char* bufNedded[256] = {0};size_t nNumOfNeede = 0;Elf64_Rela* pRelaDyn = NULL;size_t nNumOfRela=0;Elf64_Rela* pRelaPlt = NULL;size_t nNumOfRelaPlt = 0;PFN_INIT* bufInis = NULL;size_t nNumOfInis = 0;//重定位函数__attribute__((noinline))void Relocate(uint8_t* pBase, Elf64_Rela* pRel, size_t nNumOfRels,Elf64_Sym* pSym,void*hSos[],size_t nNumOfSos,const char* pStr){ //解析重定位表 for (size_t i = 0; i < nNumOfRels; i++) { uint32_t nSym = ELF64_R_SYM(pRel[i].r_info); //索引 uint32_t nType = ELF64_R_TYPE(pRel[i].r_info); //类型 //根据符号获取地址 void* nAddr = NULL; if(pSym[nSym].st_value != 0) { //导出符号,自己模块内部的符号 nAddr = pBase+pSym[nSym].st_value; } else{ //导入符号,其他模块符号 for(size_t i=0; i<nNumOfSos;i++) { nAddr = dlsym(hSos[i],pStr+pSym[nSym].st_name); if(nAddr!=NULL) { break; //找到了 } } } switch (nType) { case R_AARCH64_RELATIVE: //相对 *(uint64_t*)(pBase+pRel[i].r_offset) = (uint64_t)(pBase+pRel[i].r_addend); break; case R_AARCH64_GLOB_DAT: //全局偏移量 *(uint64_t*)(pBase+pRel[i].r_offset) = (uint64_t)nAddr; break; case R_AARCH64_JUMP_SLOT: //跳转槽 *(uint64_t*)(pBase+pRel[i].r_offset) = (uint64_t)nAddr; break; default: break; } }}__attribute__((noinline))void* MyDlSym(uint8_t* pBase, const char* szName){ //判断要查找的字符是否存在于此so内 uint32_t hash = gnu_hash(szName); //计算hash uint32_t h2 = hash>>g_hash.shift2; uint32_t bloom_mask_bits = 64; uint32_t word_num = (hash / bloom_mask_bits)& g_hash.maskswords; uint64_t bloom_word = g_hash.gnu_bloom_filter_[word_num]; if( (1&(bloom_word>>(hash%bloom_mask_bits)) & (bloom_word>>(h2%bloom_mask_bits))) == 0 ) { //不在模块内 return NULL; } uint32_t n = g_hash.gnu_bucket_[hash % g_hash.nbucket]; do { Elf64_Sym* s = pSymTable+n; if( ((g_hash.gnu_chain_[n] ^ hash) >> 1) == 0 && strcmp(pStrTable+s->st_name, szName)==0 ) { //找到了,返回地址 return pBase+s->st_value; } } while ((g_hash.gnu_chain_[n++]&1) == 0);}void* MyDlopen(const char* szName){ //读取文件、文件头和段表 FILE* file = fopen(szName,"rb"); if(file == NULL) { printf("open file error!\n"); return 0; } Elf64_Ehdr hdr = {0}; //elf hander if(fread(&hdr, 1, sizeof(hdr), file) != sizeof(hdr)) { printf("read Ehdr error!\n"); return 0; } size_t nSizeOfPhdrs = hdr.e_phentsize*hdr.e_phnum; //段表大小 Elf64_Phdr* phdrs = (Elf64_Phdr*)malloc(nSizeOfPhdrs); if( fread(phdrs, 1, nSizeOfPhdrs,file) != nSizeOfPhdrs) { printf("read phdr error!\n"); return 0; } //fclose(file); //申请内存、映射 size_t nLoadSize = 0; for(int i=hdr.e_phnum-1;i>=0;i--) { if(phdrs[i].p_type == PT_LOAD) { nLoadSize = ((phdrs[i].p_vaddr+phdrs[i].p_memsz+PAGE_SIZE-1)/PAGE_SIZE)*PAGE_SIZE; //需要申请的内存大小 break; } } uint8_t* pBase = mmap64(NULL, nLoadSize, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); //申请空间 if(pBase == MAP_FAILED) { printf("mmap64 error: %s\n",strerror(errno)); return 0; } for(size_t i=0;i<hdr.e_phnum;i++) { if(phdrs[i].p_type == PT_LOAD) { fseek(file, phdrs[i].p_offset,SEEK_SET); fread(pBase+phdrs[i].p_vaddr, 1 , phdrs[i].p_filesz,file); //读取文件到内存 } } free(phdrs); // 定位动态段 phdrs = (Elf64_Phdr*)(pBase+hdr.e_phoff); int nDyncIdx = 0; //动态段索引 Elf64_Dyn* pDyns = NULL; size_t nNumofDyns = 0; //动态段元素个数 for(size_t i=0;i<hdr.e_phnum;i++) { if(phdrs[i].p_type == PT_DYNAMIC) { nDyncIdx = i; pDyns = (Elf64_Dyn*)(pBase+phdrs[i].p_vaddr); nNumofDyns = phdrs[i].p_filesz/sizeof(Elf64_Dyn); break; } } //解析动态段 while(pDyns->d_tag!= DT_NULL) { switch(pDyns->d_tag) { case DT_STRTAB: //字符串段 pStrTable = (char*)(pBase+pDyns->d_un.d_ptr); //模块基址+字符串表偏移 break; case DT_SYMTAB: //符号表 pSymTable = (Elf64_Sym*)(pBase+pDyns->d_un.d_ptr); break; case DT_NEEDED: //elf文件依赖项 bufNedded[nNumOfNeede++] = pDyns->d_un.d_ptr; //str表还没解析,先拿偏移 break; case DT_RELA: //重定位表 pRelaDyn = (Elf64_Rela*)(pBase+pDyns->d_un.d_ptr); break; case DT_RELASZ: nNumOfRela = pDyns->d_un.d_val/sizeof(Elf64_Rela); break; case DT_JMPREL: //plt表 pRelaPlt = (Elf64_Rela*)(pBase+pDyns->d_un.d_ptr); break; case DT_PLTRELSZ: nNumOfRelaPlt = pDyns->d_un.d_val/sizeof(Elf64_Rela); break; case DT_GNU_HASH: //hash表 { uint8_t* pHashTable = pBase+pDyns->d_un.d_ptr; g_hash.nbucket = ((uint32_t*)pHashTable)[0]; g_hash.symindex = ((uint32_t*)pHashTable)[1]; g_hash.maskswords = ((uint32_t*)pHashTable)[2]; g_hash.shift2 = ((uint32_t*)pHashTable)[3]; g_hash.gnu_bloom_filter_ = (uint64_t*)(pHashTable+16); g_hash.gnu_bucket_ = (uint32_t*)(g_hash.gnu_bloom_filter_+g_hash.maskswords); g_hash.gnu_chain_ = g_hash.gnu_bucket_+g_hash.nbucket-g_hash .symindex; --g_hash.maskswords; break; } case DT_INIT_ARRAY: //初始化 bufInis = (PFN_INIT*)(pBase+pDyns->d_un.d_ptr); break; case DT_INIT_ARRAYSZ: nNumOfInis = pDyns->d_un.d_val/sizeof(void*); break; default: break; } pDyns++; } //加载模块,此so需要依赖的elf,方便查找导入符号 void** hSos = malloc(sizeof(void*)*nNumOfNeede); for(size_t i=0;i<nNumOfNeede;i++) { bufNedded[i] = (uint64_t)bufNedded[i]+pStrTable; //字符串表已加载,偏移加字符串表基址为目标字符串地址 hSos[i] = dlopen(bufNedded[i], RTLD_NOW); } //重定位 Relocate(pBase, pRelaDyn, nNumOfRela,pSymTable,hSos,nNumOfNeede,pStrTable); Relocate(pBase, pRelaPlt,nNumOfRelaPlt,pSymTable,hSos,nNumOfNeede,pStrTable); //调用初始化函数 for(size_t i = 0;i<nNumOfInis;i++) { bufInis[i](); } return pBase;} |
dlsym
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | typedef struct elf64_hash{ uint32_t nbucket; uint32_t symindex; uint32_t maskswords; uint32_t shift2; uint64_t* gnu_bloom_filter_; uint32_t* gnu_bucket_; uint32_t* gnu_chain_;}Elf64_Hash;//hash值计算__attribute__((noinline))uint32_t gnu_hash(const char* name_){ uint32_t h=5381; const uint8_t* name = (const uint8_t*)(name_); while(*name != 0){ h+=(h<<5)+*name++; } return h;}__attribute__((noinline))void* MyDlSym(uint8_t* pBase, const char* szName){ //判断要查找的字符是否存在于此so内 uint32_t hash = gnu_hash(szName); //计算hash uint32_t h2 = hash>>g_hash.shift2; uint32_t bloom_mask_bits = 64; uint32_t word_num = (hash / bloom_mask_bits)& g_hash.maskswords; uint64_t bloom_word = g_hash.gnu_bloom_filter_[word_num]; if( (1&(bloom_word>>(hash%bloom_mask_bits)) & (bloom_word>>(h2%bloom_mask_bits))) == 0 ) { //不在模块内 return NULL; } uint32_t n = g_hash.gnu_bucket_[hash % g_hash.nbucket]; //n为该桶对应的链数组的下标 do { Elf64_Sym* s = pSymTable+n; if( ((g_hash.gnu_chain_[n] ^ hash) >> 1) == 0 && strcmp(pStrTable+s->st_name, szName)==0 ) { //找到了,返回地址 return pBase+s->st_value; } } while ((g_hash.gnu_chain_[n++]&1) == 0);} |
麻烦 标题能不能修正下 dlopen 不是 dlopne,看到时我一直在想open这个单词该怎么拼