【移動應(yīng)用開發(fā)技術(shù)】AndroidLinker與SO加殼技術(shù)之上篇_第1頁
【移動應(yīng)用開發(fā)技術(shù)】AndroidLinker與SO加殼技術(shù)之上篇_第2頁
【移動應(yīng)用開發(fā)技術(shù)】AndroidLinker與SO加殼技術(shù)之上篇_第3頁
【移動應(yīng)用開發(fā)技術(shù)】AndroidLinker與SO加殼技術(shù)之上篇_第4頁
【移動應(yīng)用開發(fā)技術(shù)】AndroidLinker與SO加殼技術(shù)之上篇_第5頁
已閱讀5頁,還剩3頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

【移動應(yīng)用開發(fā)技術(shù)】AndroidLinker與SO加殼技術(shù)之上篇

1.前言Android系統(tǒng)安全愈發(fā)重要,像傳統(tǒng)pc安全的可執(zhí)行文件加固一樣,應(yīng)用加固是Android系統(tǒng)安全中非常重要的一環(huán)。目前Android應(yīng)用加固可以分為dex加固和Native加固,Native加固的保護(hù)對象為Native層的SO文件,使用加殼、反調(diào)試、混淆、VM等手段增加SO文件的反編譯難度。目前最主流的SO文件保護(hù)方案還是加殼技術(shù),在SO文件加殼和脫殼的***技術(shù)領(lǐng)域,最重要的基礎(chǔ)的便是對于Linker即裝載鏈接機(jī)制的理解。對于非安全方向開發(fā)者,深刻理解系統(tǒng)的裝載與鏈接機(jī)制也是進(jìn)階的必要條件。本文詳細(xì)分析了Linker對SO文件的裝載和鏈接過程,最后對SO加殼的關(guān)鍵技術(shù)進(jìn)行了簡要的介紹。對于Linker的學(xué)習(xí),還應(yīng)該包括Linker自舉、可執(zhí)行文件的加載等技術(shù),但是限于本人的技術(shù)水平,本文的討論范圍限定在SO文件的加載,也就是在調(diào)用dlopen("libxx.SO")之后,Linker的處理過程。本文基于Android5.0AOSP源碼,僅針對ARM平臺,為了增強(qiáng)可讀性,文中列舉的源碼均經(jīng)過刪減,去除了其他CPU架構(gòu)的相關(guān)源碼以及錯(cuò)誤處理。另:閱讀本文的讀者需要對ELF文件結(jié)構(gòu)有一定的了解。2.SO的裝載與鏈接2.1整體流程說明1.do_dlopen調(diào)用dl_open后,中間經(jīng)過dlopen_ext,到達(dá)第一個(gè)主要函數(shù)do_dlopen:soinfo*do_dlopen(constchar*name,intflags,constAndroid_dlextinfo*extinfo){

protect_data(PROT_READ|PROT_WRITE);

soinfo*si=find_library(name,flags,extinfo);//查找SO

if(si!=NULL){

si->CallConstructors();//調(diào)用SO的init函數(shù)

}

protect_data(PROT_READ);

returnsi;}do_dlopen調(diào)用了兩個(gè)重要的函數(shù),第一個(gè)是find_library,第二個(gè)是soinfo的成員函數(shù)CallConstructors,find_library函數(shù)是SO裝載鏈接的后續(xù)函數(shù),完成SO的裝載鏈接后,通過CallConstructors調(diào)用SO的初始化函數(shù)。2.find_library_internalfind_library直接調(diào)用了find_library_internal,下面直接看find_library_internal函數(shù):staticsoinfo*find_library_internal(constchar*name,intdlflags,constAndroid_dlextinfo*extinfo){

if(name==NULL){

returnsomain;

}

soinfo*si=find_loaded_library_by_name(name);

//判斷SO是否已經(jīng)加載

if(si==NULL){

TRACE("['%s'hasnotbeenfoundbyname.

Tryingharder...]",name);

si=load_library(name,dlflags,extinfo);

//繼續(xù)SO的加載流程

}

if(si!=NULL&&(si->flags&FLAG_LINKED)==0){

DL_ERR("recursivelinkto\"%s\"",si->name);

returnNULL;

}

returnsi;}find_library_internal首先通過find_loaded_library_by_name函數(shù)判斷目標(biāo)SO是否已經(jīng)加載,如果已經(jīng)加載則直接返回對應(yīng)的soinfo指針,沒有加載的話則調(diào)用load_library繼續(xù)加載流程,下面看load_library函數(shù)。3.load_librarystaticsoinfo*load_library(constchar*name,intdlflags,constAndroid_dlextinfo*extinfo){

intfd=-1;

...

//Openthefile.

fd=open_library(name);

//打開SO文件,獲得文件描述符fd

ElfReaderelf_reader(name,fd);

//創(chuàng)建ElfReader對象

...

//ReadtheELFheaderandloadthesegments.

if(!elf_reader.Load(extinfo)){

//使用ElfReader的Load方法,完成SO裝載

returnNULL;

}

soinfo*si=soinfo_alloc(SEARCH_NAME(name),&file_stat);

//為SO分配新的soinfo結(jié)構(gòu)

if(si==NULL){

returnNULL;

}

si->base=elf_reader.load_start();

//根據(jù)裝載結(jié)果,更新soinfo的成員變量

si->size=elf_reader.load_size();

si->load_bias=elf_reader.load_bias();

si->phnum=elf_reader.phdr_count();

si->phdr=elf_reader.loaded_phdr();

...

if(!soinfo_link_p_w_picpath(si,extinfo)){

//調(diào)用soinfo_link_p_w_picpath完成SO的鏈接過程

soinfo_free(si);

returnNULL;

}

returnsi;}load_library函數(shù)呈現(xiàn)了SO裝載鏈接的整個(gè)流程,主要有3步:裝載:創(chuàng)建ElfReader對象,通過ElfReader對象的Load方法將SO文件裝載到內(nèi)存分配soinfo:調(diào)用soinfo_alloc函數(shù)為SO分配新的soinfo結(jié)構(gòu),并按照裝載結(jié)果更新相應(yīng)的成員變量鏈接:調(diào)用soinfo_link_p_w_picpath完成SO的鏈接通過前面的分析,可以看到,load_library函數(shù)中包含了SO裝載鏈接的主要過程,后文主要通過分析ElfReader類和soinfo_link_p_w_picpath函數(shù),來分別介紹SO的裝載和鏈接過程。2.2裝載在load_library中,首先初始化elf_reader對象,第一個(gè)參數(shù)為SO的名字,第二個(gè)參數(shù)為文件描述符fd:ElfReaderelf_reader(name,fd)之后調(diào)用ElfReader的load方法裝載SO。

...

//ReadtheELFheaderandloadthesegments.

if(!elf_reader.Load(extinfo)){

returnNULL;

}

...ElfReader::Load方法如下:boolElfReader::Load(constAndroid_dlextinfo*extinfo){

returnReadElfHeader()&&

//讀取elfheader

VerifyElfHeader()&&

//驗(yàn)證elfheader

ReadProgramHeader()&&

//讀取programheader

ReserveAddressSpace(extinfo)&&//分配空間

LoadSegments()&&

//按照programheader指示裝載segments

FindPhdr();

//找到裝載后的phdr地址}ElfReader::Load方法首先讀取SO的elfheader,再對elfheader進(jìn)行驗(yàn)證,之后讀取programheader,根據(jù)programheader計(jì)算SO需要的內(nèi)存大小并分配相應(yīng)的空間,緊接著將SO按照以segment為單位裝載到內(nèi)存,最后在裝載到內(nèi)存的SO中找到programheader,方便之后的鏈接過程使用。下面深入ElfReader的這幾個(gè)成員函數(shù)進(jìn)行詳細(xì)介紹。2.2.1read&verifyelfheaderboolElfReader::ReadElfHeader(){

ssize_trc=read(fd_,&header_,sizeof(header_));

if(rc!=sizeof(header_)){

returnfalse;

}

returntrue;}ReadElfHeader使用read直接從SO文件中將elfheader讀取header

中,header_為ElfReader的成員變量,類型為Elf32_Ehdr,通過header可以方便的訪問elfheader中各個(gè)字段,elfheader中包含有programheadertable、sectionheadertable等重要信息。對elfheader的驗(yàn)證包括:magic字節(jié)32/64bit與當(dāng)前平臺是否一致大小端類型:可執(zhí)行文件、SO…版本:一般為1,表示當(dāng)前版本平臺:ARM、x86、amd64…有任何錯(cuò)誤都會導(dǎo)致加載失敗。2.2.2ReadProgramHeaderboolElfReader::ReadProgramHeader(){

phdr_num_=header_.e_phnum;

//programheader數(shù)量

//mmap要求頁對齊

ElfW(Addr)page_min=PAGE_START(header_.e_phoff);

ElfW(Addr)page_max=PAGE_END(header_.e_phoff+(phdr_num_*sizeof(ElfW(Phdr))));

ElfW(Addr)page_offset=PAGE_OFFSET(header_.e_phoff);

phdr_size_=page_max-page_min;

//使用mmap將programheader映射到內(nèi)存

void*mmap_result=mmap(NULL,phdr_size_,PROT_READ,MAP_PRIVATE,fd_,page_min);

phdr_mmap_=mmap_result;

//ElfReader的成員變量phdr_table_指向programheadertable

phdr_table_=reinterpret_cast<ElfW(Phdr)*>(reinterpret_cast<char*>(mmap_result)+page_offset);

returntrue;}將programheader在內(nèi)存中單獨(dú)映射一份,用于解析programheader時(shí)臨時(shí)使用,在SO裝載到內(nèi)存后,便會釋放這塊內(nèi)存,轉(zhuǎn)而使用裝載后的SO中的programheader。2.2.3reservespace&計(jì)算loadsizeboolElfReader::ReserveAddressSpace(constAndroid_dlextinfo*extinfo){

ElfW(Addr)min_vaddr;

//計(jì)算加載SO需要的空間大小

load_size_=phdr_table_get_load_size(phdr_table_,phdr_num_,&min_vaddr);

//min_vaddr一般情況為零,如果不是則表明SO指定了加載基址

uint8_t*addr=reinterpret_cast<uint8_t*>(min_vaddr);

void*start;

intmmap_flags=MAP_PRIVATE|MAP_ANONYMOUS;

start=mmap(addr,load_size_,PROT_NONE,mmap_flags,-1,0);

load_start_=start;

load_bias_=reinterpret_cast<uint8_t*>(start)-addr;

returntrue;}首先調(diào)用phdr_table_get_load_size函數(shù)獲取SO在內(nèi)存中需要的空間load_size,然后使用mmap匿名映射,預(yù)留出相應(yīng)的空間。

關(guān)于loadbias:SO可以指定加載基址,但是SO指定的加載基址可能不是頁對齊的,這種情況會導(dǎo)致實(shí)際映射地址和指定的加載地址有一個(gè)偏差,這個(gè)偏差便是

load_bias_,之后在針對虛擬地址進(jìn)行計(jì)算時(shí)需要使用

load_bias_

修正。普通的SO都不會指定加載基址,這時(shí)min_vaddr=0,則

load_bias_=load_start_,即load_bias_

等于加載基址,下文會將load_bias_

直接稱為基址。

下面深入phdr_table_get_load_size分析一下load_size的計(jì)算:使用成員變量phdr_table遍歷所有的programheader,找到所有類型為PT_LOAD的segment的p_vaddr的最小值,p_vaddr+p_memsz的最大值,分別作為min_vaddr和max_vaddr,在將兩個(gè)值分別對齊到頁首和頁尾,最終使用對齊后的max_vaddr-min_vaddr得到load_size。size_tphdr_table_get_load_size(constElfW(Phdr)*phdr_table,size_tphdr_count,

ElfW(Addr)*out_min_vaddr,

ElfW(Addr)*out_max_vaddr){

ElfW(Addr)min_vaddr=UINTPTR_MAX;

ElfW(Addr)max_vaddr=0;

boolfound_pt_load=false;

for(size_ti=0;i<phdr_count;++i){

constElfW(Phdr)*phdr=&phdr_table[i];

if(phdr->p_type!=PT_LOAD){

continue;

}

found_pt_load=true;

if(phdr->p_vaddr<min_vaddr){

min_vaddr=phdr->p_vaddr;

//記錄最小的虛擬地址

}

if(phdr->p_vaddr+phdr->p_memsz>max_vaddr){

max_vaddr=phdr->p_vaddr+phdr->p_memsz;

//記錄最大的虛擬地址

}

}

if(!found_pt_load){

min_vaddr=0;

}

min_vaddr=PAGE_START(min_vaddr);

//頁對齊

max_vaddr=PAGE_END(max_vaddr);

//頁對齊

if(out_min_vaddr!=NULL){

*out_min_vaddr=min_vaddr;

}

if(out_max_vaddr!=NULL){

*out_max_vaddr=max_vaddr;

}

returnmax_vaddr-min_vaddr;

//load_size=max_vaddr-min_vaddr}2.2.4LoadSegments遍歷programheadertable,找到類型為PT_LOAD的segment:計(jì)算segment在內(nèi)存空間中的起始地址segstart和結(jié)束地址seg_end,seg_start等于虛擬偏移加上基址load_bias,同時(shí)由于mmap的要求,都要對齊到頁邊界得到seg_page_start和seg_page_end。計(jì)算segment在文件中的頁對齊后的起始地址file_page_start和長度file_length。使用mmap將segment映射到內(nèi)存,指定映射地址為seg_page_start,長度為file_length,文件偏移為file_page_start。boolElfReader::LoadSegments(){

for(size_ti=0;i<phdr_num_;++i){

constElfW(Phdr)*phdr=&phdr_table_[i];

if(phdr->p_type!=PT_LOAD){

continue;

}

//Segment在內(nèi)存中的地址.

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_length!=0){

//將文件中的segment映射到內(nèi)存

void*seg_addr=mmap(reinterpret_cast<void*>(seg_page_start),

file_length,

PFLAGS_TO_PROT(phdr->p_flags),

MAP_FIXED|MAP_PRIVATE,

fd_,

file_page_start);

}

//如果segment可寫,并且沒有在頁邊界結(jié)束,那么就將segemntend到頁邊界的內(nèi)存清零。

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);

//將(內(nèi)存長度-文件長度)對應(yīng)的內(nèi)存進(jìn)行匿名映射

if(seg_page_end>seg_file_end){

void*zeromap=mmap(reinterpret_cast<void*>(seg_file_end),

seg_page_end-seg_file_end,

PFLAGS_TO_PROT(phdr->p_flags),

MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,

-1,

0);

}

}

returntrue;}2.3分配soinfoload_library在調(diào)用load_segments完成裝載后,接著調(diào)用soinfo_alloc函數(shù)為目標(biāo)SO分配soinfo,soinfo_alloc函數(shù)實(shí)現(xiàn)如下:staticsoinfo*soinfo_alloc(constchar*name,structstat*file_stat){

soinfo*si=g_soinfo_allocator.alloc();

//分配空間,可以簡單理解為malloc

//Initializethenewelement.

memset(si,0,sizeof(soinfo));

strlcpy(si->name,name,sizeof(si->name));

si->flags=FLAG_NEW_SOINFO;

sonex

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論