// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Created by caikelun on 2018-04-11. #include #include #include #include #include #include #include #include #include #include #include #include "xh_errno.h" #include "xh_log.h" #include "xh_util.h" #include "xh_elf.h" #define XH_ELF_DEBUG 0 #ifndef EI_ABIVERSION #define EI_ABIVERSION 8 #endif #if defined(__arm__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_ARM_JUMP_SLOT //.rel.plt #define XH_ELF_R_GENERIC_GLOB_DAT R_ARM_GLOB_DAT //.rel.dyn #define XH_ELF_R_GENERIC_ABS R_ARM_ABS32 //.rel.dyn #elif defined(__aarch64__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT #define XH_ELF_R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT #define XH_ELF_R_GENERIC_ABS R_AARCH64_ABS64 #elif defined(__i386__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_386_JMP_SLOT #define XH_ELF_R_GENERIC_GLOB_DAT R_386_GLOB_DAT #define XH_ELF_R_GENERIC_ABS R_386_32 #elif defined(__x86_64__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_X86_64_JUMP_SLOT #define XH_ELF_R_GENERIC_GLOB_DAT R_X86_64_GLOB_DAT #define XH_ELF_R_GENERIC_ABS R_X86_64_64 #endif #if defined(__LP64__) #define XH_ELF_R_SYM(info) ELF64_R_SYM(info) #define XH_ELF_R_TYPE(info) ELF64_R_TYPE(info) #else #define XH_ELF_R_SYM(info) ELF32_R_SYM(info) #define XH_ELF_R_TYPE(info) ELF32_R_TYPE(info) #endif //iterator for plain PLT typedef struct { uint8_t *cur; uint8_t *end; int is_use_rela; } xh_elf_plain_reloc_iterator_t; static void xh_elf_plain_reloc_iterator_init(xh_elf_plain_reloc_iterator_t *self, ElfW(Addr) rel, ElfW(Word) rel_sz, int is_use_rela) { self->cur = (uint8_t *)rel; self->end = self->cur + rel_sz; self->is_use_rela = is_use_rela; } static void *xh_elf_plain_reloc_iterator_next(xh_elf_plain_reloc_iterator_t *self) { if(self->cur >= self->end) return NULL; void *ret = (void *)(self->cur); self->cur += (self->is_use_rela ? sizeof(ElfW(Rela)) : sizeof(ElfW(Rel))); return ret; } //sleb128 decoder typedef struct { uint8_t *cur; uint8_t *end; } xh_elf_sleb128_decoder_t; static void xh_elf_sleb128_decoder_init(xh_elf_sleb128_decoder_t *self, ElfW(Addr) rel, ElfW(Word) rel_sz) { self->cur = (uint8_t *)rel; self->end = self->cur + rel_sz; } static int xh_elf_sleb128_decoder_next(xh_elf_sleb128_decoder_t *self, size_t *ret) { size_t value = 0; static const size_t size = 8 * sizeof(value); size_t shift = 0; uint8_t byte; do { if(self->cur >= self->end) return XH_ERRNO_FORMAT; byte = *(self->cur)++; value |= ((size_t)(byte & 127) << shift); shift += 7; } while(byte & 128); if(shift < size && (byte & 64)) { value |= -((size_t)(1) << shift); } *ret = value; return 0; } //iterator for sleb128 decoded packed PLT typedef struct { xh_elf_sleb128_decoder_t decoder; size_t relocation_count; size_t group_size; size_t group_flags; size_t group_r_offset_delta; size_t relocation_index; size_t relocation_group_index; ElfW(Rela) rela; ElfW(Rel) rel; ElfW(Addr) r_offset; size_t r_info; ssize_t r_addend; int is_use_rela; } xh_elf_packed_reloc_iterator_t; const size_t RELOCATION_GROUPED_BY_INFO_FLAG = 1; const size_t RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG = 2; const size_t RELOCATION_GROUPED_BY_ADDEND_FLAG = 4; const size_t RELOCATION_GROUP_HAS_ADDEND_FLAG = 8; static int xh_elf_packed_reloc_iterator_init(xh_elf_packed_reloc_iterator_t *self, ElfW(Addr) rel, ElfW(Word) rel_sz, int is_use_rela) { int r; memset(self, 0, sizeof(xh_elf_packed_reloc_iterator_t)); xh_elf_sleb128_decoder_init(&(self->decoder), rel, rel_sz); self->is_use_rela = is_use_rela; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->relocation_count)))) return r; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), (size_t *)&(self->r_offset)))) return r; return 0; } static int xh_elf_packed_reloc_iterator_read_group_fields(xh_elf_packed_reloc_iterator_t *self) { int r; size_t val; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_size)))) return r; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_flags)))) return r; if(self->group_flags & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG) if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_r_offset_delta)))) return r; if(self->group_flags & RELOCATION_GROUPED_BY_INFO_FLAG) if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), (size_t *)&(self->r_info)))) return r; if((self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG) && (self->group_flags & RELOCATION_GROUPED_BY_ADDEND_FLAG)) { if(0 == self->is_use_rela) { XH_LOG_ERROR("unexpected r_addend in android.rel section"); return XH_ERRNO_FORMAT; } if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &val))) return r; self->r_addend += (ssize_t)val; } else if(0 == (self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG)) { self->r_addend = 0; } self->relocation_group_index = 0; return 0; } static void *xh_elf_packed_reloc_iterator_next(xh_elf_packed_reloc_iterator_t *self) { size_t val; if(self->relocation_index >= self->relocation_count) return NULL; if(self->relocation_group_index == self->group_size) { if(0 != xh_elf_packed_reloc_iterator_read_group_fields(self)) return NULL; } if(self->group_flags & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG) { self->r_offset += self->group_r_offset_delta; } else { if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &val)) return NULL; self->r_offset += val; } if(0 == (self->group_flags & RELOCATION_GROUPED_BY_INFO_FLAG)) if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &(self->r_info))) return NULL; if(self->is_use_rela && (self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG) && (0 == (self->group_flags & RELOCATION_GROUPED_BY_ADDEND_FLAG))) { if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &val)) return NULL; self->r_addend += (ssize_t)val; } self->relocation_index++; self->relocation_group_index++; if(self->is_use_rela) { self->rela.r_offset = self->r_offset; self->rela.r_info = self->r_info; self->rela.r_addend = self->r_addend; return (void *)(&(self->rela)); } else { self->rel.r_offset = self->r_offset; self->rel.r_info = self->r_info; return (void *)(&(self->rel)); } } //ELF header checker int xh_elf_check_elfheader(uintptr_t base_addr) { ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base_addr; //check magic if(0 != memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) return XH_ERRNO_FORMAT; //check class (64/32) #if defined(__LP64__) if(ELFCLASS64 != ehdr->e_ident[EI_CLASS]) return XH_ERRNO_FORMAT; #else if(ELFCLASS32 != ehdr->e_ident[EI_CLASS]) return XH_ERRNO_FORMAT; #endif //check endian (little/big) if(ELFDATA2LSB != ehdr->e_ident[EI_DATA]) return XH_ERRNO_FORMAT; //check version if(EV_CURRENT != ehdr->e_ident[EI_VERSION]) return XH_ERRNO_FORMAT; //check type if(ET_EXEC != ehdr->e_type && ET_DYN != ehdr->e_type) return XH_ERRNO_FORMAT; //check machine #if defined(__arm__) if(EM_ARM != ehdr->e_machine) return XH_ERRNO_FORMAT; #elif defined(__aarch64__) if(EM_AARCH64 != ehdr->e_machine) return XH_ERRNO_FORMAT; #elif defined(__i386__) if(EM_386 != ehdr->e_machine) return XH_ERRNO_FORMAT; #elif defined(__x86_64__) if(EM_X86_64 != ehdr->e_machine) return XH_ERRNO_FORMAT; #else return XH_ERRNO_FORMAT; #endif //check version if(EV_CURRENT != ehdr->e_version) return XH_ERRNO_FORMAT; return 0; } //ELF hash func static uint32_t xh_elf_hash(const uint8_t *name) { uint32_t h = 0, g; while (*name) { h = (h << 4) + *name++; g = h & 0xf0000000; h ^= g; h ^= g >> 24; } return h; } //GNU hash func static uint32_t xh_elf_gnu_hash(const uint8_t *name) { uint32_t h = 5381; while(*name != 0) { h += (h << 5) + *name++; } return h; } static ElfW(Phdr) *xh_elf_get_first_segment_by_type(xh_elf_t *self, ElfW(Word) type) { ElfW(Phdr) *phdr; for(phdr = self->phdr; phdr < self->phdr + self->ehdr->e_phnum; phdr++) { if(phdr->p_type == type) { return phdr; } } return NULL; } static ElfW(Phdr) *xh_elf_get_first_segment_by_type_offset(xh_elf_t *self, ElfW(Word) type, ElfW(Off) offset) { ElfW(Phdr) *phdr; for(phdr = self->phdr; phdr < self->phdr + self->ehdr->e_phnum; phdr++) { if(phdr->p_type == type && phdr->p_offset == offset) { return phdr; } } return NULL; } static int xh_elf_hash_lookup(xh_elf_t *self, const char *symbol, uint32_t *symidx) { uint32_t hash = xh_elf_hash((uint8_t *)symbol); const char *symbol_cur; uint32_t i; for(i = self->bucket[hash % self->bucket_cnt]; 0 != i; i = self->chain[i]) { symbol_cur = self->strtab + self->symtab[i].st_name; if(0 == strcmp(symbol, symbol_cur)) { *symidx = i; XH_LOG_INFO("found %s at symidx: %u (ELF_HASH)\n", symbol, *symidx); return 0; } } return XH_ERRNO_NOTFND; } static int xh_elf_gnu_hash_lookup_def(xh_elf_t *self, const char *symbol, uint32_t *symidx) { uint32_t hash = xh_elf_gnu_hash((uint8_t *)symbol); static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8; size_t word = self->bloom[(hash / elfclass_bits) % self->bloom_sz]; size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) | (size_t)1 << ((hash >> self->bloom_shift) % elfclass_bits); //if at least one bit is not set, this symbol is surely missing if((word & mask) != mask) return XH_ERRNO_NOTFND; //ignore STN_UNDEF uint32_t i = self->bucket[hash % self->bucket_cnt]; if(i < self->symoffset) return XH_ERRNO_NOTFND; //loop through the chain while(1) { const char *symname = self->strtab + self->symtab[i].st_name; const uint32_t symhash = self->chain[i - self->symoffset]; if((hash | (uint32_t)1) == (symhash | (uint32_t)1) && 0 == strcmp(symbol, symname)) { *symidx = i; XH_LOG_INFO("found %s at symidx: %u (GNU_HASH DEF)\n", symbol, *symidx); return 0; } //chain ends with an element with the lowest bit set to 1 if(symhash & (uint32_t)1) break; i++; } return XH_ERRNO_NOTFND; } static int xh_elf_gnu_hash_lookup_undef(xh_elf_t *self, const char *symbol, uint32_t *symidx) { uint32_t i; for(i = 0; i < self->symoffset; i++) { const char *symname = self->strtab + self->symtab[i].st_name; if(0 == strcmp(symname, symbol)) { *symidx = i; XH_LOG_INFO("found %s at symidx: %u (GNU_HASH UNDEF)\n", symbol, *symidx); return 0; } } return XH_ERRNO_NOTFND; } static int xh_elf_gnu_hash_lookup(xh_elf_t *self, const char *symbol, uint32_t *symidx) { if(0 == xh_elf_gnu_hash_lookup_def(self, symbol, symidx)) return 0; if(0 == xh_elf_gnu_hash_lookup_undef(self, symbol, symidx)) return 0; return XH_ERRNO_NOTFND; } static int xh_elf_find_symidx_by_name(xh_elf_t *self, const char *symbol, uint32_t *symidx) { if(self->is_use_gnu_hash) return xh_elf_gnu_hash_lookup(self, symbol, symidx); else return xh_elf_hash_lookup(self, symbol, symidx); } static int xh_elf_replace_function(xh_elf_t *self, const char *symbol, ElfW(Addr) addr, void *new_func, void **old_func) { void *old_addr; unsigned int old_prot = 0; unsigned int need_prot = PROT_READ | PROT_WRITE; int r; //already replaced? //here we assume that we always have read permission, is this a problem? if(*(void **)addr == new_func) return 0; //get old prot if(0 != (r = xh_util_get_addr_protect(addr, self->pathname, &old_prot))) { XH_LOG_ERROR("get addr prot failed. ret: %d", r); return r; } if(old_prot != need_prot) { //set new prot if(0 != (r = xh_util_set_addr_protect(addr, need_prot))) { XH_LOG_ERROR("set addr prot failed. ret: %d", r); return r; } } //save old func old_addr = *(void **)addr; if(NULL != old_func) *old_func = old_addr; //replace func *(void **)addr = new_func; //segmentation fault sometimes if(old_prot != need_prot) { //restore the old prot if(0 != (r = xh_util_set_addr_protect(addr, old_prot))) { XH_LOG_WARN("restore addr prot failed. ret: %d", r); } } //clear cache xh_util_flush_instruction_cache(addr); XH_LOG_INFO("XH_HK_OK %p: %p -> %p %s %s\n", (void *)addr, old_addr, new_func, symbol, self->pathname); return 0; } static int xh_elf_check(xh_elf_t *self) { if(0 == self->base_addr) { XH_LOG_ERROR("base_addr == 0\n"); return 1; } if(0 == self->bias_addr) { XH_LOG_ERROR("bias_addr == 0\n"); return 1; } if(NULL == self->ehdr) { XH_LOG_ERROR("ehdr == NULL\n"); return 1; } if(NULL == self->phdr) { XH_LOG_ERROR("phdr == NULL\n"); return 1; } if(NULL == self->strtab) { XH_LOG_ERROR("strtab == NULL\n"); return 1; } if(NULL == self->symtab) { XH_LOG_ERROR("symtab == NULL\n"); return 1; } if(NULL == self->bucket) { XH_LOG_ERROR("bucket == NULL\n"); return 1; } if(NULL == self->chain) { XH_LOG_ERROR("chain == NULL\n"); return 1; } if(1 == self->is_use_gnu_hash && NULL == self->bloom) { XH_LOG_ERROR("bloom == NULL\n"); return 1; } return 0; } #if XH_ELF_DEBUG static void xh_elf_dump_elfheader(xh_elf_t *self) { static char alpha_tab[17] = "0123456789ABCDEF"; int i; uint8_t ch; char buff[EI_NIDENT * 3 + 1]; for(i = 0; i < EI_NIDENT; i++) { ch = self->ehdr->e_ident[i]; buff[i * 3 + 0] = alpha_tab[(int)((ch >> 4) & 0x0F)]; buff[i * 3 + 1] = alpha_tab[(int)(ch & 0x0F)]; buff[i * 3 + 2] = ' '; } buff[EI_NIDENT * 3] = '\0'; XH_LOG_DEBUG("Elf Header:\n"); XH_LOG_DEBUG(" Magic: %s\n", buff); XH_LOG_DEBUG(" Class: %#x\n", self->ehdr->e_ident[EI_CLASS]); XH_LOG_DEBUG(" Data: %#x\n", self->ehdr->e_ident[EI_DATA]); XH_LOG_DEBUG(" Version: %#x\n", self->ehdr->e_ident[EI_VERSION]); XH_LOG_DEBUG(" OS/ABI: %#x\n", self->ehdr->e_ident[EI_OSABI]); XH_LOG_DEBUG(" ABI Version: %#x\n", self->ehdr->e_ident[EI_ABIVERSION]); XH_LOG_DEBUG(" Type: %#x\n", self->ehdr->e_type); XH_LOG_DEBUG(" Machine: %#x\n", self->ehdr->e_machine); XH_LOG_DEBUG(" Version: %#x\n", self->ehdr->e_version); XH_LOG_DEBUG(" Entry point address: %"XH_UTIL_FMT_X"\n", self->ehdr->e_entry); XH_LOG_DEBUG(" Start of program headers: %"XH_UTIL_FMT_X" (bytes into file)\n", self->ehdr->e_phoff); XH_LOG_DEBUG(" Start of section headers: %"XH_UTIL_FMT_X" (bytes into file)\n", self->ehdr->e_shoff); XH_LOG_DEBUG(" Flags: %#x\n", self->ehdr->e_flags); XH_LOG_DEBUG(" Size of this header: %u (bytes)\n", self->ehdr->e_ehsize); XH_LOG_DEBUG(" Size of program headers: %u (bytes)\n", self->ehdr->e_phentsize); XH_LOG_DEBUG(" Number of program headers: %u\n", self->ehdr->e_phnum); XH_LOG_DEBUG(" Size of section headers: %u (bytes)\n", self->ehdr->e_shentsize); XH_LOG_DEBUG(" Number of section headers: %u\n", self->ehdr->e_shnum); XH_LOG_DEBUG(" Section header string table index: %u\n", self->ehdr->e_shstrndx); } static void xh_elf_dump_programheader(xh_elf_t *self) { ElfW(Phdr) *phdr = self->phdr; size_t i; XH_LOG_DEBUG("Program Headers:\n"); XH_LOG_DEBUG(" %-8s " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-8s " \ "%-s\n", "Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flg", "Align"); for(i = 0; i < self->ehdr->e_phnum; i++, phdr++) { XH_LOG_DEBUG(" %-8x " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%-8x " \ "%"XH_UTIL_FMT_X"\n", phdr->p_type, phdr->p_offset, phdr->p_vaddr, phdr->p_paddr, phdr->p_filesz, phdr->p_memsz, phdr->p_flags, phdr->p_align); } } static void xh_elf_dump_dynamic(xh_elf_t *self) { ElfW(Dyn) *dyn = self->dyn; size_t dyn_cnt = (self->dyn_sz / sizeof(ElfW(Dyn))); size_t i; XH_LOG_DEBUG("Dynamic section contains %zu entries:\n", dyn_cnt); XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_S" " \ "%s\n", "Tag", "Val"); for(i = 0; i < dyn_cnt; i++, dyn++) { XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_X" " \ "%-"XH_UTIL_FMT_X"\n", dyn->d_tag, dyn->d_un.d_val); } } static void xh_elf_dump_rel(xh_elf_t *self, const char *type, ElfW(Addr) rel_addr, ElfW(Word) rel_sz) { ElfW(Rela) *rela; ElfW(Rel) *rel; ElfW(Word) cnt; ElfW(Word) i; ElfW(Sym) *sym; if(self->is_use_rela) { rela = (ElfW(Rela) *)(rel_addr); cnt = rel_sz / sizeof(ElfW(Rela)); } else { rel = (ElfW(Rel) *)(rel_addr); cnt = rel_sz / sizeof(ElfW(Rel)); } XH_LOG_DEBUG("Relocation section '.rel%s%s' contains %u entries:\n", (self->is_use_rela ? "a" : ""), type, cnt); XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-8s " \ "%-8s " \ "%-8s " \ "%s\n", "Offset", "Info", "Type", "Sym.Idx", "Sym.Val", "Sym.Name"); const char *fmt = " %."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%.8x " \ "%.8u " \ "%.8x " \ "%s\n"; for(i = 0; i < cnt; i++) { if(self->is_use_rela) { sym = &(self->symtab[XH_ELF_R_SYM(rela[i].r_info)]); XH_LOG_DEBUG(fmt, rela[i].r_offset, rela[i].r_info, XH_ELF_R_TYPE(rela[i].r_info), XH_ELF_R_SYM(rela[i].r_info), sym->st_value, self->strtab + sym->st_name); } else { sym = &(self->symtab[XH_ELF_R_SYM(rel[i].r_info)]); XH_LOG_DEBUG(fmt, rel[i].r_offset, rel[i].r_info, XH_ELF_R_TYPE(rel[i].r_info), XH_ELF_R_SYM(rel[i].r_info), sym->st_value, self->strtab + sym->st_name); } } } static void xh_elf_dump_symtab(xh_elf_t *self) { if(self->is_use_gnu_hash) return; ElfW(Word) symtab_cnt = self->chain_cnt; ElfW(Word) i; XH_LOG_DEBUG("Symbol table '.dynsym' contains %u entries:\n", symtab_cnt); XH_LOG_DEBUG(" %-8s " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%s\n", "Idx", "Value", "Name"); for(i = 0; i < symtab_cnt; i++) { XH_LOG_DEBUG(" %-8u " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%s\n", i, self->symtab[i].st_value, self->strtab + self->symtab[i].st_name); } } static void xh_elf_dump(xh_elf_t *self) { if(xh_log_priority < ANDROID_LOG_DEBUG) return; XH_LOG_DEBUG("Elf Pathname: %s\n", self->pathname); XH_LOG_DEBUG("Elf bias addr: %p\n", (void *)self->bias_addr); xh_elf_dump_elfheader(self); xh_elf_dump_programheader(self); xh_elf_dump_dynamic(self); xh_elf_dump_rel(self, ".plt", self->relplt, self->relplt_sz); xh_elf_dump_rel(self, ".dyn", self->reldyn, self->reldyn_sz); xh_elf_dump_symtab(self); } #endif int xh_elf_init(xh_elf_t *self, uintptr_t base_addr, const char *pathname) { if(0 == base_addr || NULL == pathname) return XH_ERRNO_INVAL; //always reset memset(self, 0, sizeof(xh_elf_t)); self->pathname = pathname; self->base_addr = (ElfW(Addr))base_addr; self->ehdr = (ElfW(Ehdr) *)base_addr; self->phdr = (ElfW(Phdr) *)(base_addr + self->ehdr->e_phoff); //segmentation fault sometimes //find the first load-segment with offset 0 ElfW(Phdr) *phdr0 = xh_elf_get_first_segment_by_type_offset(self, PT_LOAD, 0); if(NULL == phdr0) { XH_LOG_ERROR("Can NOT found the first load segment. %s", pathname); return XH_ERRNO_FORMAT; } #if XH_ELF_DEBUG if(0 != phdr0->p_vaddr) XH_LOG_DEBUG("first load-segment vaddr NOT 0 (vaddr: %p). %s", (void *)(phdr0->p_vaddr), pathname); #endif //save load bias addr if(self->base_addr < phdr0->p_vaddr) return XH_ERRNO_FORMAT; self->bias_addr = self->base_addr - phdr0->p_vaddr; //find dynamic-segment ElfW(Phdr) *dhdr = xh_elf_get_first_segment_by_type(self, PT_DYNAMIC); if(NULL == dhdr) { XH_LOG_ERROR("Can NOT found dynamic segment. %s", pathname); return XH_ERRNO_FORMAT; } //parse dynamic-segment self->dyn = (ElfW(Dyn) *)(self->bias_addr + dhdr->p_vaddr); self->dyn_sz = dhdr->p_memsz; ElfW(Dyn) *dyn = self->dyn; ElfW(Dyn) *dyn_end = self->dyn + (self->dyn_sz / sizeof(ElfW(Dyn))); uint32_t *raw; for(; dyn < dyn_end; dyn++) { switch(dyn->d_tag) //segmentation fault sometimes { case DT_NULL: //the end of the dynamic-section dyn = dyn_end; break; case DT_STRTAB: { self->strtab = (const char *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->strtab) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_SYMTAB: { self->symtab = (ElfW(Sym) *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->symtab) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_PLTREL: //use rel or rela? self->is_use_rela = (dyn->d_un.d_val == DT_RELA ? 1 : 0); break; case DT_JMPREL: { self->relplt = (ElfW(Addr))(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->relplt) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_PLTRELSZ: self->relplt_sz = dyn->d_un.d_val; break; case DT_REL: case DT_RELA: { self->reldyn = (ElfW(Addr))(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->reldyn) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_RELSZ: case DT_RELASZ: self->reldyn_sz = dyn->d_un.d_val; break; case DT_ANDROID_REL: case DT_ANDROID_RELA: { self->relandroid = (ElfW(Addr))(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->relandroid) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_ANDROID_RELSZ: case DT_ANDROID_RELASZ: self->relandroid_sz = dyn->d_un.d_val; break; case DT_HASH: { //ignore DT_HASH when ELF contains DT_GNU_HASH hash table if(1 == self->is_use_gnu_hash) continue; raw = (uint32_t *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))raw < self->base_addr) return XH_ERRNO_FORMAT; self->bucket_cnt = raw[0]; self->chain_cnt = raw[1]; self->bucket = &raw[2]; self->chain = &(self->bucket[self->bucket_cnt]); break; } case DT_GNU_HASH: { raw = (uint32_t *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))raw < self->base_addr) return XH_ERRNO_FORMAT; self->bucket_cnt = raw[0]; self->symoffset = raw[1]; self->bloom_sz = raw[2]; self->bloom_shift = raw[3]; self->bloom = (ElfW(Addr) *)(&raw[4]); self->bucket = (uint32_t *)(&(self->bloom[self->bloom_sz])); self->chain = (uint32_t *)(&(self->bucket[self->bucket_cnt])); self->is_use_gnu_hash = 1; break; } default: break; } } //check android rel/rela if(0 != self->relandroid) { const char *rel = (const char *)self->relandroid; if(self->relandroid_sz < 4 || rel[0] != 'A' || rel[1] != 'P' || rel[2] != 'S' || rel[3] != '2') { XH_LOG_ERROR("android rel/rela format error\n"); return XH_ERRNO_FORMAT; } self->relandroid += 4; self->relandroid_sz -= 4; } //check elf info if(0 != xh_elf_check(self)) { XH_LOG_ERROR("elf init check failed. %s", pathname); return XH_ERRNO_FORMAT; } #if XH_ELF_DEBUG xh_elf_dump(self); #endif XH_LOG_INFO("init OK: %s (%s %s PLT:%u DYN:%u ANDROID:%u)\n", self->pathname, self->is_use_rela ? "RELA" : "REL", self->is_use_gnu_hash ? "GNU_HASH" : "ELF_HASH", self->relplt_sz, self->reldyn_sz, self->relandroid_sz); return 0; } static int xh_elf_find_and_replace_func(xh_elf_t *self, const char *section, int is_plt, const char *symbol, void *new_func, void **old_func, uint32_t symidx, void *rel_common, int *found) { ElfW(Rela) *rela; ElfW(Rel) *rel; ElfW(Addr) r_offset; size_t r_info; size_t r_sym; size_t r_type; ElfW(Addr) addr; int r; if(NULL != found) *found = 0; if(self->is_use_rela) { rela = (ElfW(Rela) *)rel_common; r_info = rela->r_info; r_offset = rela->r_offset; } else { rel = (ElfW(Rel) *)rel_common; r_info = rel->r_info; r_offset = rel->r_offset; } //check sym r_sym = XH_ELF_R_SYM(r_info); if(r_sym != symidx) return 0; //check type r_type = XH_ELF_R_TYPE(r_info); if(is_plt && r_type != XH_ELF_R_GENERIC_JUMP_SLOT) return 0; if(!is_plt && (r_type != XH_ELF_R_GENERIC_GLOB_DAT && r_type != XH_ELF_R_GENERIC_ABS)) return 0; //we found it XH_LOG_INFO("found %s at %s offset: %p\n", symbol, section, (void *)r_offset); if(NULL != found) *found = 1; //do replace addr = self->bias_addr + r_offset; if(addr < self->base_addr) return XH_ERRNO_FORMAT; if(0 != (r = xh_elf_replace_function(self, symbol, addr, new_func, old_func))) { XH_LOG_ERROR("replace function failed: %s at %s\n", symbol, section); return r; } return 0; } int xh_elf_hook(xh_elf_t *self, const char *symbol, void *new_func, void **old_func) { uint32_t symidx; void *rel_common; xh_elf_plain_reloc_iterator_t plain_iter; xh_elf_packed_reloc_iterator_t packed_iter; int found; int r; if(NULL == self->pathname) { XH_LOG_ERROR("not inited\n"); return XH_ERRNO_ELFINIT; //not inited? } if(NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL; XH_LOG_INFO("hooking %s in %s\n", symbol, self->pathname); //find symbol index by symbol name if(0 != (r = xh_elf_find_symidx_by_name(self, symbol, &symidx))) return 0; //replace for .rel(a).plt if(0 != self->relplt) { xh_elf_plain_reloc_iterator_init(&plain_iter, self->relplt, self->relplt_sz, self->is_use_rela); while(NULL != (rel_common = xh_elf_plain_reloc_iterator_next(&plain_iter))) { if(0 != (r = xh_elf_find_and_replace_func(self, (self->is_use_rela ? ".rela.plt" : ".rel.plt"), 1, symbol, new_func, old_func, symidx, rel_common, &found))) return r; if(found) break; } } //replace for .rel(a).dyn if(0 != self->reldyn) { xh_elf_plain_reloc_iterator_init(&plain_iter, self->reldyn, self->reldyn_sz, self->is_use_rela); while(NULL != (rel_common = xh_elf_plain_reloc_iterator_next(&plain_iter))) { if(0 != (r = xh_elf_find_and_replace_func(self, (self->is_use_rela ? ".rela.dyn" : ".rel.dyn"), 0, symbol, new_func, old_func, symidx, rel_common, NULL))) return r; } } //replace for .rel(a).android if(0 != self->relandroid) { xh_elf_packed_reloc_iterator_init(&packed_iter, self->relandroid, self->relandroid_sz, self->is_use_rela); while(NULL != (rel_common = xh_elf_packed_reloc_iterator_next(&packed_iter))) { if(0 != (r = xh_elf_find_and_replace_func(self, (self->is_use_rela ? ".rela.android" : ".rel.android"), 0, symbol, new_func, old_func, symidx, rel_common, NULL))) return r; } } return 0; }