Android逆向之旅---SO(ELF)檔案格式詳解

來源:互聯網
上載者:User

Android逆向之旅---SO(ELF)檔案格式詳解
第一、前言

從今天開始我們正式開始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檔案的資料結構類:

 

package com.demo.parseso;import java.util.ArrayList;public class ElfType32 {public elf32_rel rel;public elf32_rela rela;public ArrayList symList = new ArrayList();public elf32_hdr hdr;//elf頭部資訊public ArrayList phdrList = new ArrayList();//可能會有多個程式頭public ArrayList shdrList = new ArrayList();//可能會有多個段頭public ArrayList strtbList = new ArrayList();//可能會有多個字串值public ElfType32() {rel = new elf32_rel();rela = new elf32_rela();hdr = new elf32_hdr();}/** *  typedef struct elf32_rel {  Elf32_Addrr_offset;  Elf32_Wordr_info;} Elf32_Rel; * */public class elf32_rel {public byte[] r_offset = new byte[4];public byte[] r_info = new byte[4];@Overridepublic String toString(){return r_offset:+Utils.bytes2HexString(r_offset)+;r_info:+Utils.bytes2HexString(r_info);}}/** *  typedef struct elf32_rela{  Elf32_Addrr_offset;  Elf32_Wordr_info;  Elf32_Swordr_addend;} Elf32_Rela; */public class elf32_rela{public byte[] r_offset = new byte[4];public byte[] r_info = new byte[4];public byte[] r_addend = new byte[4];@Overridepublic String toString(){return r_offset:+Utils.bytes2HexString(r_offset)+;r_info:+Utils.bytes2HexString(r_info)+;r_addend:+Utils.bytes2HexString(r_info);}}/** * typedef struct elf32_sym{  Elf32_Wordst_name;  Elf32_Addrst_value;  Elf32_Wordst_size;  unsigned charst_info;  unsigned charst_other;  Elf32_Halfst_shndx;} Elf32_Sym; */public static class Elf32_Sym{public byte[] st_name = new byte[4];public byte[] st_value = new byte[4];public byte[] st_size = new byte[4];public byte st_info;public byte st_other;public byte[] st_shndx = new byte[2];@Overridepublic String toString(){return st_name:+Utils.bytes2HexString(st_name)+st_value:+Utils.bytes2HexString(st_value)+st_size:+Utils.bytes2HexString(st_size)+st_info:+(st_info/16)+st_other:+(((short)st_other) & 0xF)+st_shndx:+Utils.bytes2HexString(st_shndx);}}public void printSymList(){for(int i=0;i> 4)#define ELF_ST_TYPE(x)(((unsigned int) x) & 0xf) *//** * typedef struct elf32_hdr{  unsigned chare_ident[EI_NIDENT];  Elf32_Halfe_type;  Elf32_Halfe_machine;  Elf32_Worde_version;  Elf32_Addre_entry;  // Entry point  Elf32_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; */public class elf32_hdr{public byte[] e_ident = new byte[16];public byte[] e_type = new byte[2];public byte[] e_machine = new byte[2];public byte[] e_version = new byte[4];public byte[] e_entry = new byte[4];public byte[] e_phoff = new byte[4];public byte[] e_shoff = new byte[4];public byte[] e_flags = new byte[4];public byte[] e_ehsize = new byte[2];public byte[] e_phentsize = new byte[2];public byte[] e_phnum = new byte[2];public byte[] e_shentsize = new byte[2];public byte[] e_shnum = new byte[2];public byte[] e_shstrndx = new byte[2];@Overridepublic String toString(){return  magic:+ Utils.bytes2HexString(e_ident) +e_type:+Utils.bytes2HexString(e_type)+e_machine:+Utils.bytes2HexString(e_machine)+e_version:+Utils.bytes2HexString(e_version)+e_entry:+Utils.bytes2HexString(e_entry)+e_phoff:+Utils.bytes2HexString(e_phoff)+e_shoff:+Utils.bytes2HexString(e_shoff)+e_flags:+Utils.bytes2HexString(e_flags)+e_ehsize:+Utils.bytes2HexString(e_ehsize)+e_phentsize:+Utils.bytes2HexString(e_phentsize)+e_phnum:+Utils.bytes2HexString(e_phnum)+e_shentsize:+Utils.bytes2HexString(e_shentsize)+e_shnum:+Utils.bytes2HexString(e_shnum)+e_shstrndx:+Utils.bytes2HexString(e_shstrndx);}}/** * typedef struct elf32_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; */public static class elf32_phdr{public byte[] p_type = new byte[4];public byte[] p_offset = new byte[4];public byte[] p_vaddr = new byte[4];public byte[] p_paddr = new byte[4];public byte[] p_filesz = new byte[4];public byte[] p_memsz = new byte[4];public byte[] p_flags = new byte[4];public byte[] p_align = new byte[4];@Overridepublic String toString(){return p_type:+ Utils.bytes2HexString(p_type)+p_offset:+Utils.bytes2HexString(p_offset)+p_vaddr:+Utils.bytes2HexString(p_vaddr)+p_paddr:+Utils.bytes2HexString(p_paddr)+p_filesz:+Utils.bytes2HexString(p_filesz)+p_memsz:+Utils.bytes2HexString(p_memsz)+p_flags:+Utils.bytes2HexString(p_flags)+p_align:+Utils.bytes2HexString(p_align);}}public void printPhdrList(){for(int i=0;i這個沒什麼問題,也沒難度,就是在看elf.h檔案中定義的資料結構的時候,要記得每個欄位的佔用位元組數就可以了。

 

 

有了結構定義,下面就來看看如何解析吧。

在解析之前我們需要將so檔案讀取到byte[]中,定義一個資料結構類型

 

public static ElfType32 type_32 = new ElfType32();byte[] fileByteArys = Utils.readFile(so/libhello-jni.so);if(fileByteArys == null){System.out.println(read file byte failed...);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的頭部資訊 * @param header */private static void  parseHeader(byte[] header, int offset){if(header == null){System.out.println(header is null);return;}/** *  public byte[] e_ident = new byte[16];public short e_type;public short e_machine;public int e_version;public int e_entry;public int e_phoff;public int e_shoff;public int e_flags;public short e_ehsize;public short e_phentsize;public short e_phnum;public short e_shentsize;public short e_shnum;public short e_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中的描述吧,這裡就不做解釋了。後面我們會手動的構造這樣的一個資料結構,到時候在詳細說明每個欄位含義。

按照這個結構。我們解析也簡單了:

 

/** * 解析段頭資訊內容 */public static void parseSectionHeaderList(byte[] header, int offset){int header_size = 40;//40個位元組int header_count = Utils.byte2Short(type_32.hdr.e_shnum);//頭部的個數byte[] des = new byte[header_size];for(int i=0;i這裡需要注意的是,我們看到的Section Header一般都是多個的,這裡用一個List來儲存

 

 

4、解析程式頭(Program Header)資訊

這裡的欄位,這裡也不做解釋了,看pdf文檔。

我們按照這個結構來進行解析:

 

/** * 解析程式頭資訊 * @param header */public static void parseProgramHeaderList(byte[] header, int offset){int header_size = 32;//32個位元組int header_count = Utils.byte2Short(type_32.hdr.e_phnum);//頭部的個數byte[] des = new byte[header_size];for(int i=0;i

 

 

當然還有其他結構的解析工作,這裡就不在一一介紹了,因為這些結構我們在後面的介紹中不會用到,但是也是需要瞭解的,詳細參見pdf文檔。

 

5、驗證解析結果

那麼上面我們的解析工作做完了,為了驗證我們的解析工作是否正確,我們需要給每個結構定義個列印函數,也就是從寫toString方法即可。

然後我們在使用readelf工具來查看so檔案的各個結構內容,對比就可以知道解析的是否成功了。

 

解析代碼:http://download.csdn.net/detail/jiangwei0910410003/9204119

 

上面我們用的是Java代碼來進行解析的,為了照顧廣大程式猿,所以給出一個C++版本的解析類:

 

#include#include#include#include elf.h/**非常重要的一個宏,功能很簡單:P:需要對其的段地址ALIGNBYTES:對其的位元組數功能:將P值補充到時ALIGNBYTES的整數倍這個函數也叫:頁面對其函數eg: 0x3e45/0x1000 == >0x4000*/#define ALIGN(P, ALIGNBYTES)  ( ((unsigned long)P + ALIGNBYTES -1)&~(ALIGNBYTES-1) )int addSectionFun(char*, char*, unsigned int);int main(){addSectionFun(D:libhello-jni.so, .jiangwei, 0x1000);return 0;}int addSectionFun(char *lpPath, char *szSecname, unsigned int nNewSecSize){char name[50];FILE *fdr, *fdw;char *base = NULL;Elf32_Ehdr *ehdr;Elf32_Phdr *t_phdr, *load1, *load2, *dynamic;Elf32_Shdr *s_hdr;int flag = 0;int i = 0;unsigned mapSZ = 0;unsigned nLoop = 0;unsigned int nAddInitFun = 0;unsigned int nNewSecAddr = 0;unsigned int nModuleBase = 0;memset(name, 0, sizeof(name));if(nNewSecSize == 0){return 0;}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(Open file failed);return 1;}fseek(fdr, 0, SEEK_END);mapSZ = ftell(fdr);//源檔案的長度大小printf(mapSZ:0x%x, mapSZ);base = (char*)malloc(mapSZ * 2 + nNewSecSize);//2*源檔案大小+新加的Section sizeprintf(base 0x%x , base);memset(base, 0, mapSZ * 2 + nNewSecSize);fseek(fdr, 0, SEEK_SET);fread(base, 1, mapSZ, fdr);//拷貝源檔案內容到baseif(base == (void*) -1){printf(fread fd failed);return 2;}//判斷Program Headerehdr = (Elf32_Ehdr*) base;t_phdr = (Elf32_Phdr*)(base + sizeof(Elf32_Ehdr));for(i=0;ie_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 , load1, load1->p_offset);}else{load2 = t_phdr;printf(load2 = %p, offset = 0x%x , load2, load2->p_offset);}}if(t_phdr->p_type == PT_DYNAMIC){dynamic = t_phdr;printf(dynamic = %p, offset = 0x%x , dynamic, dynamic->p_offset);}t_phdr ++;}//section headers_hdr = (Elf32_Shdr*)(base + ehdr->e_shoff);//擷取到新加section的位置,這個是重點,需要進行頁面對其操作printf(addr:0x%x,load2->p_paddr);nNewSecAddr = ALIGN(load2->p_paddr + load2->p_memsz - nModuleBase, load2->p_align);printf(new section add:%x , nNewSecAddr);if(load1->p_filesz < ALIGN(load2->p_paddr + load2->p_memsz, load2->p_align) ){printf(offset:%x,(ehdr->e_shoff + sizeof(Elf32_Shdr) * ehdr->e_shnum));//注意這裡的代碼的執行條件,這裡其實就是判斷section header是不是在檔案的末尾if( (ehdr->e_shoff + sizeof(Elf32_Shdr) * ehdr->e_shnum) != mapSZ){if(mapSZ + sizeof(Elf32_Shdr) * (ehdr->e_shnum + 1) > nNewSecAddr){printf(無法添加節);return 3;}else{memcpy(base + mapSZ, base + ehdr->e_shoff, sizeof(Elf32_Shdr) * ehdr->e_shnum);//將Section Header拷貝到原來檔案的末尾ehdr->e_shoff = mapSZ;mapSZ += sizeof(Elf32_Shdr) * ehdr->e_shnum;//加上Section Header的長度s_hdr = (Elf32_Shdr*)(base + ehdr->e_shoff);printf(ehdr_offset:%x,ehdr->e_shoff);}}}else{nNewSecAddr = load1->p_filesz;}printf(還可添加 %d 個節, (nNewSecAddr - ehdr->e_shoff) / sizeof(Elf32_Shdr) - ehdr->e_shnum - 1);int nWriteLen = nNewSecAddr + ALIGN(strlen(szSecname) + 1, 0x10) + nNewSecSize;//添加section之後的檔案總長度:原來的長度 + section name + section sizeprintf(write len %x,nWriteLen);char *lpWriteBuf = (char *)malloc(nWriteLen);//nWriteLen :最後檔案的總大小memset(lpWriteBuf, 0, nWriteLen);//ehdr->e_shstrndx是section name的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);//添加section name//以下代碼是構建一個Section HeaderElf32_Shdr newSecShdr = {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;//修改Program Header資訊load1->p_filesz = nWriteLen;load1->p_memsz = nNewSecAddr + nNewSecSize;load1->p_flags = 7;//可讀 可寫 可執行//修改Elf header中的section的count值ehdr->e_shnum++;memcpy(lpWriteBuf, base, mapSZ);//從base中拷貝mapSZ長度的位元組到lpWriteBufmemcpy(lpWriteBuf + mapSZ, &newSecShdr, sizeof(Elf32_Shdr));//將新加的Section Header追加到lpWriteBuf末尾//寫檔案fseek(fdw, 0, SEEK_SET);fwrite(lpWriteBuf, 1, nWriteLen, fdw);fclose(fdw);fclose(fdr);free(base);free(lpWriteBuf);return 0;}

 

 

看了C++代碼解析之後,這裡不得不多說兩句了,看看C++中的代碼多麼簡單,原因很簡單:在做檔案位元組操作的時候,C++中的指標真的很牛逼的,這個也是Java望成莫及的。。

 

第五、總結

關於Elf檔案的格式,就介紹到這裡,通過自己寫一個解析類的話,可以很深刻的瞭解elf檔案的格式,所以我們在以後遇到一個檔案格式的瞭解過程中,最好的方式就是手動的寫一個工具類就好了。那麼這篇文章是逆向之旅的第一篇,也是以後篇章的基礎,下面一篇文章我們會介紹如何來手動的在elf中添加一個段資料結構,盡情期待~~

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.