從今天開始我們正式開始Android的逆向之旅,關於逆向的相關知識,想必大家都不陌生了,逆向領域是一個充滿挑戰和神秘的領域。
作為一名Android開發者,每個人都想去探索這個領域,因為一旦你破解了別人的內容,成就感肯定爆棚,不過相反的是,我們不僅要研究破解之道,也要研究加密之道,因為加密和破解是相生相剋的。但是我們在破解的過程中可能最頭疼的是native層,也就是so文件的破解。
所以我們先來詳細了解一下so文件的內容下面就來看看我們今天所要介紹的內容。
今天我們先來介紹一下elf文件的格式,因為我們知道Android中的so文件就是elf文件,所以需要了解so文件,必須先來了解一下elf文件的格式,對於如何詳細了解一個elf文件,就是手動的寫一個工具類來解析一個elf文件。
第二、準備資料我們需要了解elf文件的格式,關於elf文件格式詳解,網上已經有很多介紹資料了。這裡我也不做太多的解釋了。不過有兩個資料還是需要介紹一下的,因為網上的內容真的很多,很雜。這兩個資料是最全的,也是最好的。我就是看這兩個資料來操作的:
第一個資料是非蟲大哥的經典之作:

看吧,是不是超級詳細?後面我們用Java代碼來解析elf文件的時候,就是按照這張圖來的。但是這張圖有些數據結構解釋的還不是很清楚,所以第二個資料來了。
第二個資料:北京大學實驗室出的標準版
http://download.csdn.net/detail/jiangwei0910410003/9204051
這裡就不對這個文件做詳細解釋了,後面在做解析工作的時候,會截圖說明。
關於上面的這兩個資料,這裡還是多數兩句:一定要仔細認真的閱讀。這個是經典之作。也是後面工作的基礎。
第三、工具當然這裡還需要介紹一個工具,因為這個工具在我們下面解析elf文件的時候,也非常有用,而且是檢查我們解析elf文件的模板。
就是很出名的:readelf命令
不過Window下這個命令不能用,因為這個命令是Linux的,所以我們還得做個工作就是安裝Cygwin。關於這個工具的安裝,大家可以看看這篇文章:
http://blog.csdn.net/jiangwei0910410003/article/details/17710243
不過在下載的過程中,我擔心小朋友們會遇到挫折,所以很貼心的,放到的雲盤裡面:
http://pan.baidu.com/s/1C1Zci
下載下來之後,需要改一個東西才能用:

該一下這個文件:

這個路徑要改成你本地cygwin64中的bin目錄的路徑,不然運行錯誤的。改好之後,直接運行Cygwin.bat就可以了。
關於readelf工具我們這裡不做太詳細的介紹,只介紹我們要用到的命令:
1、readelf -h xxx.so
查看so文件的頭部信息

2、readelf -S xxx.so
查看so文件的段(Section)頭的信息

3、readelf -l xxx.so
查看so文件的程序段頭信息(Program)

4、readelf -a xxx.so
查看so文件的全部內容

還有很多命令用法,這裡就不在細說了,網上有很多介紹的~~
第四、實際操作解析Elf文件(Java代碼&C++代碼)上面我們介紹了elf文件格式資料,elf文件的工具,那麼下面我們就來實際操作一下,來用Java代碼手把手的解析一個libhello-jni.so文件。關於這個libhello-jni.so文件的下載地址:
http://download.csdn.net/detail/jiangwei0910410003/9204087
1、首先定義elf文件中各個結構體內容這個我們需要參考elf.h這個頭文件的格式了。這個文件網上也是有的,這裡還是給個下載鏈接吧:
http://download.csdn.net/detail/jiangwei0910410003/9204081
我們看看Java中定義的elf文件的數據結構類:
packagecom.demo.parseso;importjava.util.ArrayList;publicclassElfType32{publicelf32_relrel;publicelf32_relarela;publicArrayList<Elf32_Sym>symList=newArrayList<Elf32_Sym>();publicelf32_hdrhdr;//elf頭部信息publicArrayList<elf32_phdr>phdrList=newArrayList<elf32_phdr>();//可能會有多個程序頭publicArrayList<elf32_shdr>shdrList=newArrayList<elf32_shdr>();//可能會有多個段頭publicArrayList<elf32_strtb>strtbList=newArrayList<elf32_strtb>();//可能會有多個字符串值publicElfType32(){rel=newelf32_rel();rela=newelf32_rela();hdr=newelf32_hdr();}/***typedefstructelf32_rel{Elf32_Addrr_offset;Elf32_Wordr_info;}Elf32_Rel;**/publicclasself32_rel{publicbyte[]r_offset=newbyte[4];publicbyte[]r_info=newbyte[4];@OverridepublicStringtoString(){return"r_offset:"+Utils.bytes2HexString(r_offset)+";r_info:"+Utils.bytes2HexString(r_info);}}/***typedefstructelf32_rela{Elf32_Addrr_offset;Elf32_Wordr_info;Elf32_Swordr_addend;}Elf32_Rela;*/publicclasself32_rela{publicbyte[]r_offset=newbyte[4];publicbyte[]r_info=newbyte[4];publicbyte[]r_addend=newbyte[4];@OverridepublicStringtoString(){return"r_offset:"+Utils.bytes2HexString(r_offset)+";r_info:"+Utils.bytes2HexString(r_info)+";r_addend:"+Utils.bytes2HexString(r_info);}}/***typedefstructelf32_sym{Elf32_Wordst_name;Elf32_Addrst_value;Elf32_Wordst_size;unsignedcharst_info;unsignedcharst_other;Elf32_Halfst_shndx;}Elf32_Sym;*/publicstaticclassElf32_Sym{publicbyte[]st_name=newbyte[4];publicbyte[]st_value=newbyte[4];publicbyte[]st_size=newbyte[4];publicbytest_info;publicbytest_other;publicbyte[]st_shndx=newbyte[2];@OverridepublicStringtoString(){return"st_name:"+Utils.bytes2HexString(st_name)+"\nst_value:"+Utils.bytes2HexString(st_value)+"\nst_size:"+Utils.bytes2HexString(st_size)+"\nst_info:"+(st_info/16)+"\nst_other:"+(((short)st_other)&0xF)+"\nst_shndx:"+Utils.bytes2HexString(st_shndx);}}publicvoidprintSymList(){for(inti=0;i<symList.size();i++){System.out.println();System.out.println("The"+(i+1)+"SymbolTable:");System.out.println(symList.get(i).toString());}}//Bind字段==》st_infopublicstaticfinalintSTB_LOCAL=0;publicstaticfinalintSTB_GLOBAL=1;publicstaticfinalintSTB_WEAK=2;//Type字段==》st_otherpublicstaticfinalintSTT_NOTYPE=0;publicstaticfinalintSTT_OBJECT=1;publicstaticfinalintSTT_FUNC=2;publicstaticfinalintSTT_SECTION=3;publicstaticfinalintSTT_FILE=4;/***這裡需要注意的是還需要做一次轉化*#defineELF_ST_BIND(x)((x)>>4)#defineELF_ST_TYPE(x)(((unsignedint)x)&0xf)*//***typedefstructelf32_hdr{unsignedchare_ident[EI_NIDENT];Elf32_Halfe_type;Elf32_Halfe_machine;Elf32_Worde_version;Elf32_Addre_entry;//EntrypointElf32_Offe_phoff;Elf32_Offe_shoff;Elf32_Worde_flags;Elf32_Halfe_ehsize;Elf32_Halfe_phentsize;Elf32_Halfe_phnum;Elf32_Halfe_shentsize;Elf32_Halfe_shnum;Elf32_Halfe_shstrndx;}Elf32_Ehdr;*/publicclasself32_hdr{publicbyte[]e_ident=newbyte[16];publicbyte[]e_type=newbyte[2];publicbyte[]e_machine=newbyte[2];publicbyte[]e_version=newbyte[4];publicbyte[]e_entry=newbyte[4];publicbyte[]e_phoff=newbyte[4];publicbyte[]e_shoff=newbyte[4];publicbyte[]e_flags=newbyte[4];publicbyte[]e_ehsize=newbyte[2];publicbyte[]e_phentsize=newbyte[2];publicbyte[]e_phnum=newbyte[2];publicbyte[]e_shentsize=newbyte[2];publicbyte[]e_shnum=newbyte[2];publicbyte[]e_shstrndx=newbyte[2];@OverridepublicStringtoString(){return"magic:"+Utils.bytes2HexString(e_ident)+"\ne_type:"+Utils.bytes2HexString(e_type)+"\ne_machine:"+Utils.bytes2HexString(e_machine)+"\ne_version:"+Utils.bytes2HexString(e_version)+"\ne_entry:"+Utils.bytes2HexString(e_entry)+"\ne_phoff:"+Utils.bytes2HexString(e_phoff)+"\ne_shoff:"+Utils.bytes2HexString(e_shoff)+"\ne_flags:"+Utils.bytes2HexString(e_flags)+"\ne_ehsize:"+Utils.bytes2HexString(e_ehsize)+"\ne_phentsize:"+Utils.bytes2HexString(e_phentsize)+"\ne_phnum:"+Utils.bytes2HexString(e_phnum)+"\ne_shentsize:"+Utils.bytes2HexString(e_shentsize)+"\ne_shnum:"+Utils.bytes2HexString(e_shnum)+"\ne_shstrndx:"+Utils.bytes2HexString(e_shstrndx);}}/***typedefstructelf32_phdr{Elf32_Wordp_type;Elf32_Offp_offset;Elf32_Addrp_vaddr;Elf32_Addrp_paddr;Elf32_Wordp_filesz;Elf32_Wordp_memsz;Elf32_Wordp_flags;Elf32_Wordp_align;}Elf32_Phdr;*/publicstaticclasself32_phdr{publicbyte[]p_type=newbyte[4];publicbyte[]p_offset=newbyte[4];publicbyte[]p_vaddr=newbyte[4];publicbyte[]p_paddr=newbyte[4];publicbyte[]p_filesz=newbyte[4];publicbyte[]p_memsz=newbyte[4];publicbyte[]p_flags=newbyte[4];publicbyte[]p_align=newbyte[4];@OverridepublicStringtoString(){return"p_type:"+Utils.bytes2HexString(p_type)+"\np_offset:"+Utils.bytes2HexString(p_offset)+"\np_vaddr:"+Utils.bytes2HexString(p_vaddr)+"\np_paddr:"+Utils.bytes2HexString(p_paddr)+"\np_filesz:"+Utils.bytes2HexString(p_filesz)+"\np_memsz:"+Utils.bytes2HexString(p_memsz)+"\np_flags:"+Utils.bytes2HexString(p_flags)+"\np_align:"+Utils.bytes2HexString(p_align);}}publicvoidprintPhdrList(){for(inti=0;i<phdrList.size();i++){System.out.println();System.out.println("The"+(i+1)+"ProgramHeader:");System.out.println(phdrList.get(i).toString());}}/***typedefstructelf32_shdr{Elf32_Wordsh_name;Elf32_Wordsh_type;Elf32_Wordsh_flags;Elf32_Addrsh_addr;Elf32_Offsh_offset;Elf32_Wordsh_size;Elf32_Wordsh_link;Elf32_Wordsh_info;Elf32_Wordsh_addralign;Elf32_Wordsh_entsize;}Elf32_Shdr;*/publicstaticclasself32_shdr{publicbyte[]sh_name=newbyte[4];publicbyte[]sh_type=newbyte[4];publicbyte[]sh_flags=newbyte[4];publicbyte[]sh_addr=newbyte[4];publicbyte[]sh_offset=newbyte[4];publicbyte[]sh_size=newbyte[4];publicbyte[]sh_link=newbyte[4];publicbyte[]sh_info=newbyte[4];publicbyte[]sh_addralign=newbyte[4];publicbyte[]sh_entsize=newbyte[4];@OverridepublicStringtoString(){return"sh_name:"+Utils.bytes2HexString(sh_name)/*Utils.byte2Int(sh_name)*/+"\nsh_type:"+Utils.bytes2HexString(sh_type)+"\nsh_flags:"+Utils.bytes2HexString(sh_flags)+"\nsh_add:"+Utils.bytes2HexString(sh_addr)+"\nsh_offset:"+Utils.bytes2HexString(sh_offset)+"\nsh_size:"+Utils.bytes2HexString(sh_size)+"\nsh_link:"+Utils.bytes2HexString(sh_link)+"\nsh_info:"+Utils.bytes2HexString(sh_info)+"\nsh_addralign:"+Utils.bytes2HexString(sh_addralign)+"\nsh_entsize:"+Utils.bytes2HexString(sh_entsize);}}/****************sh_type********************/publicstaticfinalintSHT_NULL=0;publicstaticfinalintSHT_PROGBITS=1;publicstaticfinalintSHT_SYMTAB=2;publicstaticfinalintSHT_STRTAB=3;publicstaticfinalintSHT_RELA=4;publicstaticfinalintSHT_HASH=5;publicstaticfinalintSHT_DYNAMIC=6;publicstaticfinalintSHT_NOTE=7;publicstaticfinalintSHT_NOBITS=8;publicstaticfinalintSHT_REL=9;publicstaticfinalintSHT_SHLIB=10;publicstaticfinalintSHT_DYNSYM=11;publicstaticfinalintSHT_NUM=12;publicstaticfinalintSHT_LOPROC=0x70000000;publicstaticfinalintSHT_HIPROC=0x7fffffff;publicstaticfinalintSHT_LOUSER=0x80000000;publicstaticfinalintSHT_HIUSER=0xffffffff;publicstaticfinalintSHT_MIPS_LIST=0x70000000;publicstaticfinalintSHT_MIPS_CONFLICT=0x70000002;publicstaticfinalintSHT_MIPS_GPTAB=0x70000003;publicstaticfinalintSHT_MIPS_UCODE=0x70000004;/*****************sh_flag***********************/publicstaticfinalintSHF_WRITE=0x1;publicstaticfinalintSHF_ALLOC=0x2;publicstaticfinalintSHF_EXECINSTR=0x4;publicstaticfinalintSHF_MASKPROC=0xf0000000;publicstaticfinalintSHF_MIPS_GPREL=0x10000000;publicvoidprintShdrList(){for(inti=0;i<shdrList.size();i++){System.out.println();System.out.println("The"+(i+1)+"SectionHeader:");System.out.println(shdrList.get(i));}}publicstaticclasself32_strtb{publicbyte[]str_name;publicintlen;@OverridepublicStringtoString(){return"str_name:"+str_name+"len:"+len;}}}這個沒什麼問題,也沒難度,就是在看elf.h文件中定義的數據結構的時候,要記得每個字段的占用字節數就可以了。
有了結構定義,下面就來看看如何解析吧。
在解析之前我們需要將so文件讀取到byte[]中,定義一個數據結構類型
publicstaticElfType32type_32=newElfType32();byte[]fileByteArys=Utils.readFile("so/libhello-jni.so");if(fileByteArys==null){System.out.println("readfilebytefailed...");return;}2、解析elf文件的頭部信息
關於這些字段的解釋,要看上面提到的那個pdf文件中的描述
這裡我們介紹幾個重要的字段,也是我們後面修改so文件的時候也會用到:
1)、e_phoff
這個字段是程序頭(Program Header)內容在整個文件的偏移值,我們可以用這個偏移值來定位程序頭的開始位置,用於解析程序頭信息
2)、e_shoff
這個字段是段頭(Section Header)內容在這個文件的偏移值,我們可以用這個偏移值來定位段頭的開始位置,用於解析段頭信息
3)、e_phnum
這個字段是程序頭的個數,用於解析程序頭信息
4)、e_shnum
這個字段是段頭的個數,用於解析段頭信息
5)、e_shstrndx
這個字段是String段在整個段列表中的索引值,這個用於後面定位String段的位置
按照上面的圖我們就可以很容易的解析
/***解析Elf的頭部信息*@paramheader*/privatestaticvoidparseHeader(byte[]header,intoffset){if(header==null){System.out.println("headerisnull");return;}/***publicbyte[]e_ident=newbyte[16];publicshorte_type;publicshorte_machine;publicinte_version;publicinte_entry;publicinte_phoff;publicinte_shoff;publicinte_flags;publicshorte_ehsize;publicshorte_phentsize;publicshorte_phnum;publicshorte_shentsize;publicshorte_shnum;publicshorte_shstrndx;*/type_32.hdr.e_ident=Utils.copyBytes(header,0,16);//魔數type_32.hdr.e_type=Utils.copyBytes(header,16,2);type_32.hdr.e_machine=Utils.copyBytes(header,18,2);type_32.hdr.e_version=Utils.copyBytes(header,20,4);type_32.hdr.e_entry=Utils.copyBytes(header,24,4);type_32.hdr.e_phoff=Utils.copyBytes(header,28,4);type_32.hdr.e_shoff=Utils.copyBytes(header,32,4);type_32.hdr.e_flags=Utils.copyBytes(header,36,4);type_32.hdr.e_ehsize=Utils.copyBytes(header,40,2);type_32.hdr.e_phentsize=Utils.copyBytes(header,42,2);type_32.hdr.e_phnum=Utils.copyBytes(header,44,2);type_32.hdr.e_shentsize=Utils.copyBytes(header,46,2);type_32.hdr.e_shnum=Utils.copyBytes(header,48,2);type_32.hdr.e_shstrndx=Utils.copyBytes(header,50,2);}按照對應的每個字段的字節個數,讀取byte就可以了。
3、解析段頭(Section Header)信息
這個結構中字段見pdf中的描述吧,這裡就不做解釋了。後面我們會手動的構造這樣的一個數據結構,到時候在詳細說明每個字段含義。
按照這個結構。我們解析也簡單了:
這裡需要注意的是,我們看到的Section Header一般都是多個的,這裡用一個List來保存
/***解析段頭信息內容*/publicstaticvoidparseSectionHeaderList(byte[]header,intoffset){intheader_size=40;//40個字節intheader_count=Utils.byte2Short(type_32.hdr.e_shnum);//頭部的個數byte[]des=newbyte[header_size];for(inti=0;i<header_count;i++){System.arraycopy(header,i*header_size+offset,des,0,header_size);type_32.shdrList.add(parseSectionHeader(des));}}privatestaticelf32_shdrparseSectionHeader(byte[]header){ElfType32.elf32_shdrshdr=newElfType32.elf32_shdr();/***publicbyte[]sh_name=newbyte[4];publicbyte[]sh_type=newbyte[4];publicbyte[]sh_flags=newbyte[4];publicbyte[]sh_addr=newbyte[4];publicbyte[]sh_offset=newbyte[4];publicbyte[]sh_size=newbyte[4];publicbyte[]sh_link=newbyte[4];publicbyte[]sh_info=newbyte[4];publicbyte[]sh_addralign=newbyte[4];publicbyte[]sh_entsize=newbyte[4];*/shdr.sh_name=Utils.copyBytes(header,0,4);shdr.sh_type=Utils.copyBytes(header,4,4);shdr.sh_flags=Utils.copyBytes(header,8,4);shdr.sh_addr=Utils.copyBytes(header,12,4);shdr.sh_offset=Utils.copyBytes(header,16,4);shdr.sh_size=Utils.copyBytes(header,20,4);shdr.sh_link=Utils.copyBytes(header,24,4);shdr.sh_info=Utils.copyBytes(header,28,4);shdr.sh_addralign=Utils.copyBytes(header,32,4);shdr.sh_entsize=Utils.copyBytes(header,36,4);returnshdr;}4、解析程序頭(Program Header)信息
我們按照這個結構來進行解析:
/***解析程序頭信息*@paramheader*/publicstaticvoidparseProgramHeaderList(byte[]header,intoffset){intheader_size=32;//32個字節intheader_count=Utils.byte2Short(type_32.hdr.e_phnum);//頭部的個數byte[]des=newbyte[header_size];for(inti=0;i<header_count;i++){System.arraycopy(header,i*header_size+offset,des,0,header_size);type_32.phdrList.add(parseProgramHeader(des));}}privatestaticelf32_phdrparseProgramHeader(byte[]header){/***publicintp_type;publicintp_offset;publicintp_vaddr;publicintp_paddr;publicintp_filesz;publicintp_memsz;publicintp_flags;publicintp_align;*/ElfType32.elf32_phdrphdr=newElfType32.elf32_phdr();phdr.p_type=Utils.copyBytes(header,0,4);phdr.p_offset=Utils.copyBytes(header,4,4);phdr.p_vaddr=Utils.copyBytes(header,8,4);phdr.p_paddr=Utils.copyBytes(header,12,4);phdr.p_filesz=Utils.copyBytes(header,16,4);phdr.p_memsz=Utils.copyBytes(header,20,4);phdr.p_flags=Utils.copyBytes(header,24,4);phdr.p_align=Utils.copyBytes(header,28,4);returnphdr;}當然還有其他結構的解析工作,這裡就不在一一介紹了,因為這些結構我們在後面的介紹中不會用到,但是也是需要了解的,詳細參見pdf文檔。
5、驗證解析結果那麼上面我們的解析工作做完了,為了驗證我們的解析工作是否正確,我們需要給每個結構定義個打印函數,也就是從寫toString方法即可。

然後我們在使用readelf工具來查看so文件的各個結構內容,對比就可以知道解析的是否成功了。
解析代碼下載地址:https://github.com/fourbrother/parse_androidso
上面我們用的是Java代碼來進行解析的,為了照顧廣大程序猿,所以給出一個C++版本的解析類:
#include<iostream.h>#include<string.h>#include<stdio.h>#include"elf.h"/**非常重要的一個宏,功能很簡單:P:需要對其的段地址ALIGNBYTES:對其的字節數功能:將P值補充到時ALIGNBYTES的整數倍這個函數也叫:頁面對其函數eg:0x3e45/0x1000==>0x4000*/#defineALIGN(P,ALIGNBYTES)(((unsignedlong)P+ALIGNBYTES-1)&~(ALIGNBYTES-1))intaddSectionFun(char*,char*,unsignedint);intmain(){addSectionFun("D:\libhello-jni.so",".jiangwei",0x1000);return0;}intaddSectionFun(char*lpPath,char*szSecname,unsignedintnNewSecSize){charname[50];FILE*fdr,*fdw;char*base=NULL;Elf32_Ehdr*ehdr;Elf32_Phdr*t_phdr,*load1,*load2,*dynamic;Elf32_Shdr*s_hdr;intflag=0;inti=0;unsignedmapSZ=0;unsignednLoop=0;unsignedintnAddInitFun=0;unsignedintnNewSecAddr=0;unsignedintnModuleBase=0;memset(name,0,sizeof(name));if(nNewSecSize==0){return0;}fdr=fopen(lpPath,"rb");strcpy(name,lpPath);if(strchr(name,'.')){strcpy(strchr(name,'.'),"_new.so");}else{strcat(name,"_new");}fdw=fopen(name,"wb");if(fdr==NULL||fdw==NULL){printf("Openfilefailed");return1;}fseek(fdr,0,SEEK_END);mapSZ=ftell(fdr);//源文件的長度大小printf("mapSZ:0x%x\n",mapSZ);base=(char*)malloc(mapSZ*2+nNewSecSize);//2*源文件大小+新加的Sectionsizeprintf("base0x%x\n",base);memset(base,0,mapSZ*2+nNewSecSize);fseek(fdr,0,SEEK_SET);fread(base,1,mapSZ,fdr);//拷貝源文件內容到baseif(base==(void*)-1){printf("freadfdfailed");return2;}//判斷ProgramHeaderehdr=(Elf32_Ehdr*)base;t_phdr=(Elf32_Phdr*)(base+sizeof(Elf32_Ehdr));for(i=0;i<ehdr->e_phnum;i++){if(t_phdr->p_type==PT_LOAD){//這裡的flag只是一個標誌位,去除第一個LOAD的Segment的值if(flag==0){load1=t_phdr;flag=1;nModuleBase=load1->p_vaddr;printf("load1=%p,offset=0x%x\n",load1,load1->p_offset);}else{load2=t_phdr;printf("load2=%p,offset=0x%x\n",load2,load2->p_offset);}}if(t_phdr->p_type==PT_DYNAMIC){dynamic=t_phdr;printf("dynamic=%p,offset=0x%x\n",dynamic,dynamic->p_offset);}t_phdr++;}//sectionheaders_hdr=(Elf32_Shdr*)(base+ehdr->e_shoff);//獲取到新加section的位置,這個是重點,需要進行頁面對其操作printf("addr:0x%x\n",load2->p_paddr);nNewSecAddr=ALIGN(load2->p_paddr+load2->p_memsz-nModuleBase,load2->p_align);printf("newsectionadd:%x\n",nNewSecAddr);if(load1->p_filesz<ALIGN(load2->p_paddr+load2->p_memsz,load2->p_align)){printf("offset:%x\n",(ehdr->e_shoff+sizeof(Elf32_Shdr)*ehdr->e_shnum));//注意這裡的代碼的執行條件,這裡其實就是判斷sectionheader是不是在文件的末尾if((ehdr->e_shoff+sizeof(Elf32_Shdr)*ehdr->e_shnum)!=mapSZ){if(mapSZ+sizeof(Elf32_Shdr)*(ehdr->e_shnum+1)>nNewSecAddr){printf("無法添加節\n");return3;}else{memcpy(base+mapSZ,base+ehdr->e_shoff,sizeof(Elf32_Shdr)*ehdr->e_shnum);//將SectionHeader拷貝到原來文件的末尾ehdr->e_shoff=mapSZ;mapSZ+=sizeof(Elf32_Shdr)*ehdr->e_shnum;//加上SectionHeader的長度s_hdr=(Elf32_Shdr*)(base+ehdr->e_shoff);printf("ehdr_offset:%x",ehdr->e_shoff);}}}else{nNewSecAddr=load1->p_filesz;}printf("還可添加%d個節\n",(nNewSecAddr-ehdr->e_shoff)/sizeof(Elf32_Shdr)-ehdr->e_shnum-1);intnWriteLen=nNewSecAddr+ALIGN(strlen(szSecname)+1,0x10)+nNewSecSize;//添加section之後的文件總長度:原來的長度+ section name + section sizeprintf("writelen%x\n",nWriteLen);char*lpWriteBuf=(char*)malloc(nWriteLen);//nWriteLen:最後文件的總大小memset(lpWriteBuf,0,nWriteLen);//ehdr->e_shstrndx是sectionname的string表在section表頭中的偏移值,修改string段的大小s_hdr[ehdr->e_shstrndx].sh_size=nNewSecAddr-s_hdr[ehdr->e_shstrndx].sh_offset+strlen(szSecname)+1;strcpy(lpWriteBuf+nNewSecAddr,szSecname);//添加sectionname//以下代碼是構建一個SectionHeaderElf32_ShdrnewSecShdr={0};newSecShdr.sh_name=nNewSecAddr-s_hdr[ehdr->e_shstrndx].sh_offset;newSecShdr.sh_type=SHT_PROGBITS;newSecShdr.sh_flags=SHF_WRITE|SHF_ALLOC|SHF_EXECINSTR;nNewSecAddr+=ALIGN(strlen(szSecname)+1,0x10);newSecShdr.sh_size=nNewSecSize;newSecShdr.sh_offset=nNewSecAddr;newSecShdr.sh_addr=nNewSecAddr+nModuleBase;newSecShdr.sh_addralign=4;//修改ProgramHeader信息load1->p_filesz=nWriteLen;load1->p_memsz=nNewSecAddr+nNewSecSize;load1->p_flags=7;//可讀可寫可執行//修改Elfheader中的section的count值ehdr->e_shnum++;memcpy(lpWriteBuf,base,mapSZ);//從base中拷貝mapSZ長度的字節到lpWriteBufmemcpy(lpWriteBuf+mapSZ,&newSecShdr,sizeof(Elf32_Shdr));//將新加的SectionHeader追加到lpWriteBuf末尾//寫文件fseek(fdw,0,SEEK_SET);fwrite(lpWriteBuf,1,nWriteLen,fdw);fclose(fdw);fclose(fdr);free(base);free(lpWriteBuf);return0;}看了C++代碼解析之後,這裡不得不多說兩句了,看看C++中的代碼多麼簡單,原因很簡單:在做文件字節操作的時候,C++中的指針真的很牛逼的,這個也是Java望成莫及的。。
C++代碼下載:http://download.csdn.net/detail/jiangwei0910410003/9204139
第五、總結關於Elf文件的格式,就介紹到這裡,通過自己寫一個解析類的話,可以很深刻的了解elf文件的格式,所以我們在以後遇到一個文件格式的了解過程中,最好的方式就是手動的寫一個工具類就好了。那麼這篇文章是逆向之旅的第一篇,也是以後篇章的基礎,下面一篇文章我們會介紹如何來手動的在elf中添加一個段數據結構,盡情期待~~
轉:https://www.jianshu.com/p/a2d87d9467cc
So文件添加Section段詳細說明簡介在逆向Android底層時,一般都或多或少的接觸so文件,需要逆向so文件,一般的方法是往so文件植入我們的調試的代碼;而通常都是通過添加section段來植入代碼;查看本篇文章之前你需要先了解elf文件格式,so文件就是採用這種格式的
簡單介紹elf文件格式elf文件一般是由elf文件頭、program header頭(多個)、section段(多個)和section header頭(多個組成)。
program header程序運行時,定位文件中各個段的位置,提供創建程序映像的具體信息;一個program header指向一個segment(運行時的稱呼),而一個segement可能包含多個section 段

執行之前描述文件結構的,理論上來說,Linux運行期間不需要section header頭的,可以刪掉;一個section header頭對應一個section 段,所以添加一個section段時還要添加一個section header頭
section段section段是elf文件的主題,很多內容都保存在裡面,包括我們熟悉的.text、.rodata、.data等等,還有我們不熟悉的.got(全局偏移表)、.plt(過程鏈接表)等等,這兩個表主要用於函數和全局變量的調用,可參考這個鏈接理解這兩個表,下面是展示部分的section header頭:

當ELF文件被加載到內存中後,系統會將多個具有相同權限(flg值)section合併一個segment。操作系統往往以頁為基本單位來管理內存分配,一般頁的大小為4096B,即4KB的大小。同時,內存的權限管理的粒度也是以頁為單位,頁內的內存是具有同樣的權限等屬性,並且操作系統對內存的管理往往追求高效和高利用率這樣的目標。ELF文件在被映射時,是以系統的頁長度為單位的,那麼每個section在映射時的長度都是系統頁長度的整數倍,如果section的長度不是其整數倍,則導致多餘部分也將占用一個頁。所以section段的位置和長度不是隨便添加的,要根據program header頭的align對齊方式來添加
確定添加位置新加入的section一般是在文件末尾,但是這裡不是真正的文件末尾,而是將文件加載到內存映像中的末尾,一般等於虛擬地址vaddr+占用空間大小memsiz,但是這樣還不行,還要做字節對其,我們知道文件末尾不等於映像末尾,一般這個映像末尾比文件末尾大,因為操作系統對內存都是以頁的粒度來操作的,所以我們添加section段要找到真正的映像末尾,操作系統會把program header的type為LOAD的段加入內存映像中,而LOAD類型的program header一般都是做升序排列,我們只需要取最後一個LOAD就可以,讓vaddr+memsiz在和align做對齊操作就能夠得到映像末尾,算法如下:
/***對其算法---保證addr能被align整除,系統按align字節對齊的訪問方式*返回一個addr是align的整數倍的值*@param addr:vaddr + memsi*@param align:對其的字節數*@return*/publicstaticintalign(intaddr,intalign){if(align>addr){//這種情況不好弄,我也不知道怎麼辦returnaddr;}intoffset=addr%align;returnaddr+(align-offset);}
添加section header頭的目的是把5000H的表名和5010H的內容聯繫起來;先看section header的結構:
typedefstruct{Elf32_Wordsh_name;Elf32_Wordsh_type;Elf32_Wordsh_flags;Elf32_Addrsh_addr;Elf32_Offsh_offset;Elf32_Wordsh_size;Elf32_Wordsh_link;Elf32_Wordsh_info;Elf32_Wordsh_addralign;Elf32_Wordsh_entsize;}Elf32_Shdr;sh_name : 是一個字符串表的索引偏移(5000H - 第一個字符串表的位置,第一個字符串表位置等於)
sh_addr和 sh_offset填上一個步驟得到的地址即可
sh_size:就是我們填入的section長度
sh_type:SHT_PROGBITS 1 此節區包含程序定義的信息,其格式和含義都由程序來解釋。
sh_flags:
名稱取值意義SHF_WRITE0x1節區包含進程執行過程中將可寫的數據SHF_ALLOC0x2節區在進程執行過程中占用內存。某些控制節區並不出現於目標文件的內存映像中,對於那些節區,此位應設置為 0SHF_EXECINSTR0x4節區包含可執行的機器指令SHF_MASKPROC0xF0000000所有包含於此掩碼中的四位都用於處理器專用的語義其他的幾個可以不用處理,以下是示例代碼:
publicstaticbyte[]addSectionHeader(byte[]src){/***publicbyte[]sh_name=newbyte[4];publicbyte[]sh_type=newbyte[4];publicbyte[]sh_flags=newbyte[4];publicbyte[]sh_addr=newbyte[4];publicbyte[]sh_offset=newbyte[4];publicbyte[]sh_size=newbyte[4];publicbyte[]sh_link=newbyte[4];publicbyte[]sh_info=newbyte[4];publicbyte[]sh_addralign=newbyte[4];publicbyte[]sh_entsize=newbyte[4];*/byte[]newHeader=newbyte[sectionSize];//構建一個NewSectionHeadernewHeader=Utils.replaceByteAry(newHeader,0,Utils.int2Byte(addSectionStartAddr-stringSectionOffset));newHeader=Utils.replaceByteAry(newHeader,4,Utils.int2Byte(ElfType32.SHT_PROGBITS));//type=PROGBITSnewHeader=Utils.replaceByteAry(newHeader,8,Utils.int2Byte(ElfType32.SHF_ALLOC+ElfType32.SHF_WRITE));//改字節區可以被寫入newHeader=Utils.replaceByteAry(newHeader,12,Utils.int2Byte(addSectionStartAddr+0x10));//所代表的字節區其實地址newHeader=Utils.replaceByteAry(newHeader,16,Utils.int2Byte(addSectionStartAddr+0x10));newHeader=Utils.replaceByteAry(newHeader,20,Utils.int2Byte(newSectionSize));//字節區大小newHeader=Utils.replaceByteAry(newHeader,24,Utils.int2Byte(0));newHeader=Utils.replaceByteAry(newHeader,28,Utils.int2Byte(0));newHeader=Utils.replaceByteAry(newHeader,32,Utils.int2Byte(4));newHeader=Utils.replaceByteAry(newHeader,36,Utils.int2Byte(0));//在末尾增加Sectionbyte[]newSrc=newbyte[src.length+newHeader.length];newSrc=Utils.replaceByteAry(newSrc,0,src);newSrc=Utils.replaceByteAry(newSrc,src.length,newHeader);returnnewSrc;}完善步驟修改第一個LOAD的program header將其filesize和memsiz改為文件的總長度
修改section header為.shstrta將他的size在原有的基礎上加16即可,因為增加了一個section name
至此,完結,如果把so文件弄回去報了這個錯誤 load segment1: p_offset (0x0) + p_filesz (0x53f8) ( = 0x53f8) past end of file (0x53f8) 這是因為你剛剛添加的段的大小超出了文件大小,因為我們修改program header的filesize和memsize文件總長度,這個長度包括啦文件結束標誌0x0a,我們要修改這兩個值為文件長度減去1即可
作者:jackzhoud 鏈接:https://www.jianshu.com/p/a2d87d9467cc 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。