前言 前一陣子在研究so加固,發現其中涉及自實現的Linker加載so的技術,而我對此知之什少,因此只好先來學習下Linker的加載流程。
本文參考AOSP源碼和r0ysue大佬的文章 ( 不知為何文中給出的那個demo我一直跑不起來 )來實現一個簡單的自實現Linker Demo。
環境:Pixel1XL
、AOSP - Oreo - 8.1.0_r81
Demo實現 Linker在加載so時大致可以分成五步:
讀取so文件:讀取ehdr( Elf header )、phdr( Program header )等信息。
載入so:預留一片內存空間,隨後將相關信息加載進去,最後修正so。
預鏈接:主要處理.dynamic
節的內容。
正式鏈接:處理重定位的信息。
調用.init
、.init_array
Read
利用open
+mmap
來將待加載的so文件映射到內存空間,存放在start_addr_
中。然後調用Read
函數來獲取ehdr、phdr等信息。
1 2 3 4 5 6 7 8 9 10 11 12 int fd;struct stat sb;fd = open (path, O_RDONLY); fstat (fd, &sb);start_addr_ = static_cast <void **>(mmap (NULL , sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0 )); if (!Read (path, fd, 0 , sb.st_size)){ LOGD ("Read so failed" ); munmap (start_addr_, sb.st_size); close (fd); }
Read
函數實現如下,調用ReadElfHeader
和ReadProgramHeaders
來讀取ehdr和phdr。
AOSP源碼的Read
中還會讀取Section Headers和Dynamic節,一開始我也有實現這部份的邏輯,但後來發現讀取後的信息根本沒有被用到,因此就把這部份給刪了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool MyLoader::Read (const char * name, int fd, off64_t file_offset, off64_t file_size) { bool res = false ; name_ = name; fd_ = fd; file_offset_ = file_offset; file_size_ = file_size; if (ReadElfHeader () && ReadProgramHeaders ()) { res = true ; } return res; }
ReadElfHeader
的實現如下,直接通過memcpy
來賦值。
1 2 3 bool MyLoader::ReadElfHeader () { return memcpy (&(header_),start_addr_,sizeof (header_)); }
ReadProgramHeaders
的實現直接copy源碼就可以,本質上還是內存映射的過程。
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 bool MyLoader::ReadProgramHeaders () { phdr_num_ = header_.e_phnum; size_t size = phdr_num_ * sizeof (ElfW (Phdr)); void * data = Utils::getMapData (fd_, file_offset_, header_.e_phoff, size); if (data == nullptr ) { LOGE ("ProgramHeader mmap failed" ); return false ; } phdr_table_ = static_cast <ElfW (Phdr)*>(data); return true ; } void * Utils::getMapData (int fd, off64_t base_offset, size_t elf_offset, size_t size) { off64_t offset; safe_add (&offset, base_offset, elf_offset); off64_t page_min = page_start (offset); off64_t end_offset; safe_add (&end_offset, offset, size); safe_add (&end_offset, end_offset, page_offset (offset)); size_t map_size = static_cast <size_t >(end_offset - page_min); uint8_t * map_start = static_cast <uint8_t *>( mmap64 (nullptr , map_size, PROT_READ, MAP_PRIVATE, fd, page_min)); if (map_start == MAP_FAILED) { return nullptr ; } return map_start + page_offset (offset); }
Load
載入so基本信息 調用Load
來載入so。
1 2 3 4 5 6 if (!Load ()) { LOGD ("Load so failed" ); munmap (start_addr_, sb.st_size); close (fd); }
Load
的實現如下:
ReserveAddressSpace
用於生成一片新的內存空間,之後的操作基本上都是在這片內存空間進行。LoadSegments
、FindPhdr
用於將待加載so的對應信息填充到此內存空間。
最後要修正so,將當前so修正為待加載的so,這部份放到後面來解析。
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 bool MyLoader::Load () { bool res = false ; if (ReserveAddressSpace () && LoadSegments () && FindPhdr ()) { LOGD ("Load Done........." ); res = true ; } si_ = Utils::get_soinfo ("libnglinker.so" ); if (!si_) { LOGE ("si_ return nullptr" ); return false ; } LOGD ("si_ -> base: %lx" , si_->base); mprotect ((void *) PAGE_START (reinterpret_cast <ElfW (Addr)>(si_)), 0x1000 , PROT_READ | PROT_WRITE); si_->base = load_start (); si_->size = load_size (); si_->load_bias = load_bias (); si_->phnum = phdr_count (); si_->phdr = loaded_phdr (); return res; }
ReserveAddressSpace
的具體實現如下,先計算出load_size_
後mmap
一片內存,在我這個demo中min_vaddr
是0
,因此load_start_ == load_bias_
,load_bias_
代表的就是這片內存,而這片內存是用來存放待加載的so。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 bool MyLoader::ReserveAddressSpace () { ElfW (Addr) min_vaddr; load_size_ = phdr_table_get_load_size (phdr_table_, phdr_num_, &min_vaddr); LOGD ("load_size_: %x" , load_size_); if (load_size_ == 0 ) { LOGE ("\"%s\" has no loadable segments" , name_.c_str ()); return false ; } uint8_t * addr = reinterpret_cast <uint8_t *>(min_vaddr); void * start; void * mmap_hint = nullptr ; start = mmap (mmap_hint, load_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); load_start_ = start; load_bias_ = reinterpret_cast <uint8_t *>(start) - addr; return true ; }
LoadSegments
的具體實現如下,遍歷Program Header Table將所有type為PT_LOAD
的段加載進內存,源碼中是采用mmap
來映射,但我嘗試後發現會有權限問題,因而采用memcpy
的方案。
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 bool MyLoader::LoadSegments () { for (size_t i = 0 ; i < phdr_num_; ++i) { const ElfW (Phdr) * phdr = &phdr_table_[i]; if (phdr->p_type != PT_LOAD) { continue ; } ElfW (Addr) seg_start = phdr->p_vaddr + load_bias_; ElfW (Addr) seg_end = seg_start + phdr->p_memsz; ElfW (Addr) seg_page_start = PAGE_START (seg_start); ElfW (Addr) seg_page_end = PAGE_END (seg_end); ElfW (Addr) seg_file_end = seg_start + phdr->p_filesz; ElfW (Addr) file_start = phdr->p_offset; ElfW (Addr) file_end = file_start + phdr->p_filesz; ElfW (Addr) file_page_start = PAGE_START (file_start); ElfW (Addr) file_length = file_end - file_page_start; if (file_size_ <= 0 ) { LOGE ("\"%s\" invalid file size: %" , name_.c_str (), file_size_); return false ; } if (file_end > static_cast <size_t >(file_size_)) { LOGE ("invalid ELF file" ); return false ; } if (file_length != 0 ) { mprotect (reinterpret_cast <void *>(seg_page_start), seg_page_end - seg_page_start, PROT_WRITE); void * c = (char *)start_addr_ + file_page_start; void * res = memcpy (reinterpret_cast <void *>(seg_page_start), c, file_length); LOGD ("[LoadSeg] %s seg_page_start: %lx c : %lx" , strerror (errno), seg_page_start, c); } if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET (seg_file_end) > 0 ) { memset (reinterpret_cast <void *>(seg_file_end), 0 , PAGE_SIZE - PAGE_OFFSET (seg_file_end)); } seg_file_end = PAGE_END (seg_file_end); if (seg_page_end > seg_file_end) { size_t zeromap_size = seg_page_end - seg_file_end; void * zeromap = mmap (reinterpret_cast <void *>(seg_file_end), zeromap_size, PFLAGS_TO_PROT (phdr->p_flags), MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1 , 0 ); if (zeromap == MAP_FAILED) { LOGE ("couldn't zero fill \"%s\" gap: %s" , name_.c_str (), strerror (errno)); return false ; } prctl (PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss" ); } } return true ; }
FindPhdr
的具體實現如下,簡單來說就是將Phdr信息填充進load_bias_
那片內存。
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 bool MyLoader::FindPhdr () { const ElfW (Phdr) * phdr_limit = phdr_table_ + phdr_num_; for (const ElfW (Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { if (phdr->p_type == PT_PHDR) { return CheckPhdr (load_bias_ + phdr->p_vaddr); } } for (const ElfW (Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { if (phdr->p_type == PT_LOAD) { if (phdr->p_offset == 0 ) { ElfW (Addr) elf_addr = load_bias_ + phdr->p_vaddr; const ElfW (Ehdr) * ehdr = reinterpret_cast <const ElfW (Ehdr) *>(elf_addr) ; ElfW (Addr) offset = ehdr->e_phoff; return CheckPhdr (reinterpret_cast <ElfW (Addr)>(ehdr) + offset); } break ; } } LOGE ("can't find loaded phdr for \"%s\"" , name_.c_str ()); return false ; } bool MyLoader::CheckPhdr (ElfW(Addr) loaded) { const ElfW (Phdr) * phdr_limit = phdr_table_ + phdr_num_; ElfW (Addr) loaded_end = loaded + (phdr_num_ * sizeof (ElfW (Phdr))); for (const ElfW (Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { if (phdr->p_type != PT_LOAD) { continue ; } ElfW (Addr) seg_start = phdr->p_vaddr + load_bias_; ElfW (Addr) seg_end = phdr->p_filesz + seg_start; if (seg_start <= loaded && loaded_end <= seg_end) { loaded_phdr_ = reinterpret_cast <const ElfW (Phdr)*>(loaded); return true ; } } LOGE ("\"%s\" loaded phdr %p not in loadable segment" , name_.c_str (), reinterpret_cast <void *>(loaded)); return false ; }
修正so Load
函數最後是在對soinfo的修正,將當前so( 加載器 )修正為待加載的so。AOSP源碼中的si_
是通過特定方法new出來的全新soinfo,而我看大多數文章都是獲取當前so作為si_
,然後修正其中的信息。
本來是想嘗試按AOSP源碼那樣new一個soinfo看看結果有什麼不同,但最終被soinfo結構的複雜性勸退了。
修正so的第一步是要獲取當前so的soinfo對象,從這篇文章 發現find_containing_library
這個函數,似乎可以一步到位直接獲取soinfo對象。該函數位於linker64
中,將它拉入IDA,能直接搜尋到該函數,這意味著能夠「借用」這個函數。
想要「借用」linker64
裡的find_containing_library
,需要知道linker64
在內存的基址和find_containing_library
的函數偏移( 相對基址的偏移 ),前者可以通過遍歷/proc/self/maps
來取得,而後者的獲取有以下兩種思路:
直接從IDA查看其偏移( 0x9AB0
)
解析linker64
的文件,自動獲取,具體實現在Utils::get_export_func
中。
成功獲取find_containing_library
地址後,強轉成FunctionPtr
函數指針後即可調用,參數為當前so的地址( 同樣是遍歷maps取得的 ),最終會返回當前so的soinfo對象。
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 soinfo* Utils::get_soinfo (const char * so_name) { typedef soinfo* (*FunctionPtr)(ElfW (Addr)); char line[1024 ]; ElfW (Addr) linker_base = 0 ; ElfW (Addr) so_addr = 0 ; FILE *fp=fopen ("/proc/self/maps" ,"r" ); while (fgets (line, sizeof (line), fp)) { if (strstr (line, "linker64" ) && !linker_base) { char * addr = strtok (line, "-" ); linker_base = strtoull (addr, NULL , 16 ); }else if (strstr (line, so_name) && !so_addr) { char * addr = strtok (line, "-" ); so_addr = strtoull (addr, NULL , 16 ); } if (linker_base && so_addr)break ; } ElfW (Addr) func_offset = Utils::get_export_func ("/system/bin/linker64" , "find_containing_library" ); if (!func_offset) { LOGE ("func_offset == 0? check it ---> get_soinfo" ); return nullptr ; } ElfW (Addr) find_containing_library_addr = static_cast <ElfW (Addr)>(linker_base + func_offset); FunctionPtr find_containing_library = reinterpret_cast <FunctionPtr>(find_containing_library_addr); return find_containing_library (so_addr); }
get_export_func
的實現如下,主要依賴於elf的文件結構,可以參考下我之前寫的文章 ,大致原理如下:
elf header的e_shstrndx
是一個索引,指向了.shstrtab
節區,而.shstrtab
節區存儲著所有節區的名字。
遍歷所有節區,找到名為.symtab
和.strtab
的節區( .symtab
節每項都有一個st_name
屬性,是.strtab
節區的一個索引值,指向某符號名 )
遍歷.symtab
節區,對比func_name
,匹配則返回對應的函數偏移。
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 ElfW (Addr) Utils::get_export_func (char * path, char * func_name) { struct stat sb; int fd = open (path, O_RDONLY); fstat (fd, &sb); void * base = mmap (NULL , sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0 ); ElfW (Ehdr) header; memcpy (&(header), base, sizeof (header)); size_t size = header.e_shnum * sizeof (ElfW (Shdr)); void * tmp = mmap (nullptr , size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); LOGD ("error: %s" , strerror (errno)); ElfW (Shdr)* shdr_table; memcpy (tmp, (void *)((ElfW (Off))base + header.e_shoff), size); shdr_table = static_cast <ElfW (Shdr)*>(tmp); char * shstrtab = reinterpret_cast <char *>(shdr_table[header.e_shstrndx].sh_offset + (ElfW (Off))base); void * symtab = nullptr ; char * strtab = nullptr ; uint32_t symtab_size = 0 ; for (size_t i = 0 ; i < header.e_shnum; ++i) { const ElfW (Shdr) *shdr = &shdr_table[i]; char * section_name = shstrtab + shdr->sh_name; if (!strcmp (section_name, ".symtab" )) { symtab = reinterpret_cast <void *>(shdr->sh_offset + (ElfW (Off))base); symtab_size = shdr->sh_size; } if (!strcmp (section_name, ".strtab" )) { strtab = reinterpret_cast <char *>(shdr->sh_offset + (ElfW (Off))base); } if (strtab && symtab)break ; } ElfW (Sym)* sym_table; tmp = mmap (nullptr , symtab_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); memcpy (tmp, symtab, symtab_size); sym_table = static_cast <ElfW (Sym)*>(tmp); int sym_num = symtab_size / sizeof (ElfW (Sym)); for (int i = 0 ; i < sym_num; i++) { const ElfW (Sym) *sym = &sym_table[i]; char * sym_name = strtab + sym->st_name; if (strstr (sym_name, func_name)) { return sym->st_value; } } return 0 ; }
成功獲取si_
後要修改其對應屬性。在這裡我遇到一個很玄學的問題,就是一開始不知為什麼死活修改不了si_
的屬性,一改就會報內存讀寫的錯,即使mprotect
賦予可讀可寫權限也無用,嘗試了各種方法都無用,在這卡了我好幾天,直到某次重啟手機後就突然好了???
1 2 3 4 5 6 7 8 9 10 mprotect ((void *) PAGE_START (reinterpret_cast <ElfW (Addr)>(si_)), 0x1000 , PROT_READ | PROT_WRITE);si_->base = load_start (); si_->size = load_size (); si_->load_bias = load_bias (); si_->phnum = phdr_count (); si_->phdr = loaded_phdr ();
補充:soinfo結構( 巨TM坑 ) soinfo結構體定義在bionic/linker/linker_soinfo.h
中。
將它copy到本地後會有很多報錯,一開始我是將那些沒有用到又報紅的直接刪掉,但後來發現這樣做會間接導致最後發生「android linker java.lang.unsatisfiedlinkerror: no implementation found for XXX
」的錯誤( 這個錯誤我排查了很久很久,最終才發現是soinfo結構的問題,果然細節決定成敗…… )。
正確的做法是必須要保留所有的成員變量( 即使該變量用不到也要留下來占位 ),函數由於不占空間可以隨便刪掉。
prelink_image
預鏈接,主要是在遍歷.dynamic
節獲取各種動態信息並保存在修正後的soinfo中。
prelink_image
的具體實現太長( 基本上是copy源碼的 )就不展示了,比較大的改動是在DT_NEEDED
時手動保存對應的依賴庫,之後重定向時會用到。
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 bool soinfo::prelink_image () { ElfW (Word) dynamic_flags = 0 ; Utils::phdr_table_get_dynamic_section (phdr, phnum, load_bias, &dynamic, &dynamic_flags); if (dynamic == nullptr ) { return false ; } else { } for (ElfW (Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) { LOGD ("d = %p, d[0](tag) = %p d[1](val) = %p" , d, reinterpret_cast <void *>(d->d_tag), reinterpret_cast <void *>(d->d_un.d_val)); switch (d->d_tag) { case DT_NEEDED: myneed[needed_count] = d->d_un.d_val; ++needed_count; break ; } } return true ; }
link_image
link_image
裡處理重定向信息。
link_image
的實現如下,android_relocs_
的重定向我沒有處理( 嘗試處理過,但有點問題就刪了 ),好像問題不大?
之後調用relocate
對rela_
和plt_rela_
的內容進行重定向。
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 bool soinfo::link_image () { local_group_root_ = this ; if (android_relocs_ != nullptr ) { LOGD ("android_relocs_ 不用處理?" ); } else { LOGE ("bad android relocation header." ); } #if defined(USE_RELA) if (rela_ != nullptr ) { LOGD ("[ relocating %s ]" , get_realpath ());if (!relocate (plain_reloc_iterator (rela_, rela_count_))) { return false ; } } if (plt_rela_ != nullptr ) {LOGD ("[ relocating %s plt ]" , get_realpath ());if (!relocate (plain_reloc_iterator (plt_rela_, plt_rela_count_))) { return false ; } } #else LOGE ("TODO: !defined(USE_RELA) " ); #endif LOGD ("[ finished linking %s ]" , get_realpath ()); if (!((flags_ & FLAG_LINKER) != 0 ) && !protect_relro ()) { return false ; } return true ; }
relocate
函數的實現如下,在重定位時最需要確定的就是目標函數的真實地址。
這裡采用一種偷懶的方式,直接遍歷所有依賴庫( 之前保存在myneed
中 ),調用dlopen
+dlsym
查找對應函數地址,找到的結果會保存在sym_addr
中,後續再根據type
來決定重定位的方式;而如果遍歷完所有依賴庫都沒有找到,則嘗試從symtab_[sym].st_value
裡獲取。
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 template <typename ElfRelIteratorT>bool soinfo::relocate (ElfRelIteratorT&& rel_iterator) { for (size_t idx = 0 ; rel_iterator.has_next (); ++idx) { const auto rel = rel_iterator.next (); if (rel == nullptr ) { return false ; } ElfW (Word) type = ELFW (R_TYPE)(rel->r_info); ElfW (Word) sym = ELFW (R_SYM)(rel->r_info); ElfW (Addr) reloc = static_cast <ElfW (Addr)>(rel->r_offset + load_bias); ElfW (Addr) sym_addr = 0 ; const char * sym_name = nullptr ; ElfW (Addr) addend = Utils::get_addend (rel, reloc); if (type == R_GENERIC_NONE) { continue ; } const ElfW (Sym) * s = nullptr ; soinfo* lsi = nullptr ; if (sym != 0 ) { sym_name = get_string (symtab_[sym].st_name); LOGD ("sym = %lx sym_name: %s st_value: %lx" , sym, sym_name, symtab_[sym].st_value); for (int s = 0 ; s < needed_count; s++) { void * handle = dlopen (get_string (myneed[s]),RTLD_NOW); sym_addr = reinterpret_cast <Elf64_Addr>(dlsym (handle, sym_name)); if (sym_addr) break ; } if (!sym_addr) { if (symtab_[sym].st_value != 0 ) { sym_addr = load_bias + symtab_[sym].st_value; }else { LOGE ("%s find addr fail" , sym_name); } }else { LOGD ("%s find addr success : %lx" , sym_name, sym_addr); } } LOGD ("reloc addr: %x" , (reloc - base)); LOGD ("type: %x" , type); switch (type) { case R_GENERIC_JUMP_SLOT: *reinterpret_cast <ElfW (Addr)*>(reloc) = (sym_addr + addend); break ; case R_GENERIC_GLOB_DAT: *reinterpret_cast <ElfW (Addr)*>(reloc) = (sym_addr + addend); break ; case R_GENERIC_RELATIVE: *reinterpret_cast <ElfW (Addr)*>(reloc) = (load_bias + addend); break ; case R_GENERIC_IRELATIVE: { ElfW (Addr) ifunc_addr = Utils::call_ifunc_resolver (load_bias + addend); *reinterpret_cast <ElfW (Addr)*>(reloc) = ifunc_addr; } break ; #if defined(__aarch64__) case R_AARCH64_ABS64: LOGD ("R_AARCH64_ABS64 %lx addend: %lx" , sym_addr + addend, addend); *reinterpret_cast <ElfW (Addr)*>(reloc) = sym_addr + addend; break ; case R_AARCH64_ABS32: { const ElfW (Addr) min_value = static_cast <ElfW (Addr)>(INT32_MIN); const ElfW (Addr) max_value = static_cast <ElfW (Addr)>(UINT32_MAX); if ((min_value <= (sym_addr + addend)) && ((sym_addr + addend) <= max_value)) { *reinterpret_cast <ElfW (Addr)*>(reloc) = sym_addr + addend; } else { LOGE ("0x%016llx out of range 0x%016llx to 0x%016llx" , sym_addr + addend, min_value, max_value); return false ; } } break ; case R_AARCH64_ABS16: { const ElfW (Addr) min_value = static_cast <ElfW (Addr)>(INT16_MIN); const ElfW (Addr) max_value = static_cast <ElfW (Addr)>(UINT16_MAX); if ((min_value <= (sym_addr + addend)) && ((sym_addr + addend) <= max_value)) { *reinterpret_cast <ElfW (Addr)*>(reloc) = (sym_addr + addend); } else { LOGE ("0x%016llx out of range 0x%016llx to 0x%016llx" , sym_addr + addend, min_value, max_value); return false ; } } break ; case R_AARCH64_PREL64: *reinterpret_cast <ElfW (Addr)*>(reloc) = sym_addr + addend - rel->r_offset; break ; case R_AARCH64_PREL32: { const ElfW (Addr) min_value = static_cast <ElfW (Addr)>(INT32_MIN); const ElfW (Addr) max_value = static_cast <ElfW (Addr)>(UINT32_MAX); if ((min_value <= (sym_addr + addend - rel->r_offset)) && ((sym_addr + addend - rel->r_offset) <= max_value)) { *reinterpret_cast <ElfW (Addr)*>(reloc) = sym_addr + addend - rel->r_offset; } else { LOGE ("0x%016llx out of range 0x%016llx to 0x%016llx" , sym_addr + addend - rel->r_offset, min_value, max_value); return false ; } } break ; case R_AARCH64_PREL16: { const ElfW (Addr) min_value = static_cast <ElfW (Addr)>(INT16_MIN); const ElfW (Addr) max_value = static_cast <ElfW (Addr)>(UINT16_MAX); if ((min_value <= (sym_addr + addend - rel->r_offset)) && ((sym_addr + addend - rel->r_offset) <= max_value)) { *reinterpret_cast <ElfW (Addr)*>(reloc) = sym_addr + addend - rel->r_offset; } else { LOGE ("0x%016llx out of range 0x%016llx to 0x%016llx" , sym_addr + addend - rel->r_offset, min_value, max_value); return false ; } } break ; case R_AARCH64_COPY: LOGE ("%s R_AARCH64_COPY relocations are not supported" , get_realpath ()); return false ; case R_AARCH64_TLS_TPREL64: LOGD ("RELO TLS_TPREL64 *** %16llx <- %16llx - %16llx\n" , reloc, (sym_addr + addend), rel->r_offset); break ; case R_AARCH64_TLS_DTPREL32: LOGD ("RELO TLS_DTPREL32 *** %16llx <- %16llx - %16llx\n" , reloc, (sym_addr + addend), rel->r_offset); break ; #endif default : LOGE ("unknown reloc type %d @ %p (%zu) sym_name: %s" , type, rel, idx, sym_name); return false ; } } return true ; }
call_constructors
調用soinfo的構建函數:.init
和.init_array
內所有函數
1 2 3 4 5 6 mprotect (reinterpret_cast <void *>(load_bias_), sb.st_size, PROT_READ | PROT_WRITE | PROT_EXEC);si_->call_constructors ();
原版Linker在調用.init
和.init_array
時傳入的是0, nullptr, nullptr
,我這裡與其保持一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void soinfo::call_constructors () { if (init_func_) { LOGD ("init func: %p" , init_func_); init_func_ (0 , nullptr , nullptr ); } if (init_array_) { for (int i = 0 ; i < init_array_count_; i++) { if (!init_array_[i])continue ; init_array_[i](0 , nullptr , nullptr ); } } }
完整代碼 項目地址:https://github.com/ngiokweng/ng1ok-linker
測試 隨便寫一個so作為待加載的so( 名為libdemo1.so
),內容如下,將它push到/data/local/tmp
。
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 #include <jni.h> #include <string> #include <android/log.h> #define TAG "nglog" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) extern "C" JNIEXPORT jstring JNICALL Java_ng1ok_demo1_NativeLib_stringFromJNI ( JNIEnv* env, jobject ) { std::string hello = "Hello from C++" ; return env->NewStringUTF (hello.c_str ()); } extern "C" JNIEXPORT jstring JNICALL Java_ng1ok_linker_MainActivity_demo1Func (JNIEnv *env, jobject thiz) { LOGD ("Java_ng1ok_linker_MainActivity_demo1Func calleeeeeeeddddddddd" ); std::string str = "Java_ng1ok_linker_MainActivity_demo1Func" ; return env->NewStringUTF (str.c_str ()); } __attribute__((constructor ())) void sayHello () { LOGD ("[from libdemo1.so .init_array] Hello~~~" ); } extern "C" { void _init(void ){ LOGD ("[from libdemo1.so .init] _init~~~~" ); } }
Demo的用例如下,實例化MyLoader
,調用run
函數加載指定路徑的so。
Java層的onCreate
如下,在test
之後調用待加載so裡的demo1Func
函數。
輸出如下,大功告成~
結語 前前後後弄了兩、三周的時間,最終總算是弄好了這一個小Demo。自知該Demo仍有很多不足之處( 如無法捕獲try…catch ),而且只經過簡單的測試,定然存在諸多的BUG,歡迎各位大佬的指正!!有任何也問題歡迎評論,或者私聊我/找我聊聊天都可以!!!