Android so文件進階 <一>


0x00  前言

  最近一段時間在弄android方面的東西,今天有人發了張截圖,問:在要dump多大的內存?

  

一時之間我竟然想不起來ELF文件的哪個字段表示的是文件大小,雖然最后給出了解決方法,IDA CTRL+S,直接看Segements信息,可以得出整個文件的大小。但說明了自己對於ELF文件格式遠不如PE文件那么熟悉,畢竟看PE文件格式是抱着《加密與解密》來來回回看了好幾遍,而對於ELF文件只是在網上隨便找了幾遍博客,現在還記得大致的結構,對於一些具體的細節卻並已經遺忘的差不多。於是想自動動手寫篇關於ELF文件格式的文章,加深自己對ELF文件的理解和記憶。關於整個系列的文章中的名詞都使用的是英文原名,避免產生歧義,就比如說section和segment,可能各個書中翻譯過來都存在差異,對於一些專有名詞,我個人還是比較喜歡閱讀英文,也不必翻譯。

 

0x01 ELF文件

 關於ELF文件的起源,官方的一些東西就不在進行綴述了。直奔主題,先從elf文件的三種類型講起,elf文件分為三種類型:

1) 可重定位的對象文件(Relocatable file),也就是平常說的目標文件,后綴為.o;

2) 可執行的對象文件(Executable file);

3) 可被共享的對象文件(Shared object file)。

而我們的主要精力就放在第3種,可被共享的對象文件上,也就是所謂的動態庫文件,即.so文件,類似於Windows下的dll文件。而動態庫文件在發揮作用的都要經過兩個過程

①編譯階段。鏈接編輯器(link editor)拿它和其他Relocatable object file以及其他shared object file作為輸入,經鏈接處理后,生存另外的 shared object file 或者 executable file。

②運行階段。動態鏈接器(dynamic linker)拿它和一個executable file以及另外一些 shared object file 來一起處理,在Linux系統里面創建一個進程映像。

下面是elf文件的兩種view,linking view 和execution view,Linking View主要是為了給Linker使用,而Execution View是為了給Loader使用。而我們關心的.so文件可以是這兩種View並存的,只不過是同一個文件的兩種划分方式,這樣load完還可以relocate,而.so文件僅僅只是用來執行時,是允許沒有section的,而關於section與segment的關系,相同屬性的section會被映射到同一個segment中。

ELF Header結構的定義如下,各個字段的含義都有注釋:

/* ELF Header */
typedef struct elfhdr {
    unsigned char    e_ident[EI_NIDENT]; /* ELF Identification */
    Elf32_Half    e_type;        /* object file type */
    Elf32_Half    e_machine;    /* machine */
    Elf32_Word    e_version;    /* object file version */
    Elf32_Addr    e_entry;    /* virtual entry point */
    Elf32_Off    e_phoff;    /* program header table offset */
    Elf32_Off    e_shoff;    /* section header table offset */
    Elf32_Word    e_flags;    /* processor-specific flags */
    Elf32_Half    e_ehsize;    /* ELF header size */
    Elf32_Half    e_phentsize;    /* program header entry size */
    Elf32_Half    e_phnum;    /* number of program header entries */
    Elf32_Half    e_shentsize;    /* section header entry size */
    Elf32_Half    e_shnum;    /* number of section header entries */
    Elf32_Half    e_shstrndx;    /* section header table's "section 
                       header string table" entry offset */
} Elf32_Ehdr;

 關於每個字段的含義后面的注釋都很清楚,唯一需要說明的一點就是e_shstrndx,這個表示的是.shstrtab section的section header在section header table中的索引,或者恰當的說是偏移。

接下來就是兩個header table ,Section header table 和 Program header table。下面分別介紹這兩個table的entry的結構,先是section header table entry的結構

Section Header Table 

/* Section Header */
typedef struct {
    Elf32_Word    sh_name;    /* name - index into section heade string table section */
    Elf32_Word    sh_type;    /* type */
    Elf32_Word    sh_flags;    /* flags */
    Elf32_Addr    sh_addr;    /* address */
    Elf32_Off     sh_offset;    /* file offset */
    Elf32_Word    sh_size;    /* section size */
    Elf32_Word    sh_link;    /* section header table index link */
    Elf32_Word    sh_info;    /* extra information */
    Elf32_Word    sh_addralign;    /* address alignment */
    Elf32_Word    sh_entsize;    /* section entry size */
} Elf32_Shdr;

 

這里簡要的介紹下各個字段的含義

sh_name:  該Section的名字,類型是Elf32_Word,指向在串表中的偏移值;

sh_type: 按內容和意義將section分類,下面給出不同的值代表的不同的含義;

關於sh_type的類型,值和表示的含義如下表:

SHT_NULL

       0

   section頭是無效的;它沒有相關的section

SHT_PROGBITS

       1

   該section保存被程序定義了的一些信息,它的格式和意義取決於程序本身

SHT_SYMTAB

       2

    保存着符號表

SHT_STRTAB

       3

    保存着一個字符串表

SHT_RELA  

       4

    保存着具有明確加數的重定位入口

SHT_HASH

       5

    保存着一個標號的哈希(hash)表

SHT_DYNAMIC

       6

    保存着動態連接的信息

SHT_NOTE

       7

    保存着其他的一些標志文件的信息

SHT_NOBITS

       8

    在文件中不占空間,sh_offset成員包含了概念上的文件偏移量

SHT_REL

       9

     保存着具有明確加數的重定位的入口

SHT_SHLIB

      10

     類型保留但語意沒有指明,包含這個類型的section的程序是不符合ABI的規定

SHT_DYNSYM

     11

     保存着符號表

SHT_LOPROC

    0x70000000

 

      在這范圍之間的值為特定處理器語意保留

SHT_HIPROC

     0x7fffffff

SHT_LOUSER

    0x80000000

      為應用程序保留的索引范圍的最小邊界

SHT_HIUSER

      0xffffffff

      為應用程序保留的索引范圍的最大邊界

 

sh_flags:  表示該section的類型,下表包含了各個類型的解釋;

關於sh_flags的值與含義:

SHF_WRITE         0x1      該section包含了在進程執行過程中可被寫的數據
SHF_ALLOC         0x2      該section在進程執行過程中占據着內存
SHF_EXECINSTR        0x4      該section包含了可執行的機器指令
SHF_MASKPROC   0xf0000000      為特定處理語意保留的

 sh_addr:  表示該section在內存中,相對於基址的偏移;

sh_offset:  表示該section到文件頭部的字節偏移;

sh_size:  該section大小;

sh_link:  表示與當前section有link關系的section索引;

sh_info:  一些附加信息;

sh_addralign:  section的地址對齊;

sh_entsize:  section項的大小(bytes)。

講了那么多也就只是原理的性的東西,為了加深理解,用一個實際的例子。

最后的一個字符串表索引節頭就是之前提過的Elf32_Hder中的e_shstrndx成員,來看shstrtab的內容:

我們分析一個特定的節,選擇.mytext節

開始的4個字節00 00 00 39 就是“.mytext”字符串在shstrtab中的偏移量(注意大小端的問題,這里是小端存儲)。

這里給大家“安利”一個GitHub的開源項目010Editor-stuff,關於各種文件解析模板,超級好用。

還有就是關於android下so文件的問題:

這個是位於/obj/local/armeabi/目錄下的so文件的section header :

下面是位於/libs/armeabi/下的so文件,也就是打包進apk的文件;

對比一下,發現很多的section都被刪除了,主要是有關debug和.symtab,.strtab section 都被刪除了,也就是說android下打包進apk的so文件只支持動態鏈接。

Program Header Table

 接下來就Program header table entry的結構:

typedef struct
{
    Elf32_Word     p_type;      /* Segment type */
    Elf32_Off      p_offset;      /* Segment offset in file */
    Elf32_Addr     p_vaddr;      /* Segment virtual address in    memory*/                       
Elf32_Addr p_paddr; /* Segment physical address */ Elf32_Word p_filesz; /* Segment size in file */ Elf32_Word p_memsz; /* Segment size in memory */ Elf32_Word p_flags; /* Segment flags */ Elf32_Word p_align; /* Segment alignment */ } Elf32_Phdr;

每個Segment包含若干個section,只有對executable file和shared object file才能存在Program header。

p_type:表示該Program header所指的Segment的類型

PT_NULL 0 該數組元素未使用;其他的成員值是未定義的
PT_LOAD 1 該數組元素指定一個可載入的段,由 p_filesz 和 p_memsz 描述
PT_DYNAMIC  2 該數組元素指定動態鏈接信息
PT_INTERP 3 該數組元素指定輔助信息的位置和大小
PT_NOTE 4 該段類型保留且具有未指定的語義,具有一個這種類型數組元素的程序並不遵守ABI 。
 PT_SHLIB  5 該段類型保留且具有未指定的語義
PT_PHDR 6

該數組元素(如果出現),指定了程序頭表本身的位置和大小(包括在文件中
和在該程序的內存映像中)

PT_LOPROC 0x70000000 保留用於特定處理器的語義
PT_HIPROC 0x7fffffff

 

p_offset:表示該Program header所指的Segment在文件中偏移量。

p_vaddr:表示該Program header所指的Segment在內存中的虛擬地址。

p_vaddr:表示該Program header所指的Segment在內存中的物理地址,一般可忽略。

p_filesz:表示該Program header所指的Segment在文件中的大小。

p_memsz:表示該Program header所指的Segment在內存中的大小,一般地,p_memsz>=p_filesz,對p_filesz不足的內存區域填0。

 

最后作為結尾寫段代碼加深對個格式的理解:

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#ifdef __x86_64
    #define Elf_Ehdr Elf64_Ehdr
    #define Elf_Shdr Elf64_Shdr
    #define Elf_Sym Elf64_Sym
    #define Elf_Rel Elf64_Rela
    #define ELF_R_SYM ELF64_R_SYM
    #define REL_DYN ".rela.dyn"
    #define REL_PLT ".rela.plt"
#else
    #define Elf_Ehdr Elf32_Ehdr
    #define Elf_Shdr Elf32_Shdr
    #define Elf_Sym Elf32_Sym
    #define Elf_Rel Elf32_Rel
    #define ELF_R_SYM ELF32_R_SYM
    #define REL_DYN ".rel.dyn"
    #define REL_PLT ".rel.plt"
#endif

#define LOG(...) printf(__VA_ARGS__);


static uint32_t get_module_base(pid_t pid, const char *module_path) {
    FILE *fp = NULL;
    char *pch = NULL;
    char filename[32];
    char line[512];
    uint32_t addr = 0;

    LOG("[+] get libc base...\n");
    if (pid < 0)
        snprintf(filename, sizeof(filename), "/proc/self/maps");
    else
        snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);

    if ((fp = fopen(filename, "r")) == NULL) {
        LOG("[-]open %s failed!", filename);
        return 0;
    }

    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, module_path)) {
            pch = strtok(line, "-");
            addr = strtoul(pch, NULL, 16);
            break;
        }
    }

    fclose(fp);
    LOG("[+] libc base:0x%x...\n",addr);

    return addr;
}


int read_header(int d, Elf_Ehdr **header)//read elf header structure
{
    *header = (Elf_Ehdr *)malloc(sizeof(Elf_Ehdr));

    if (lseek(d, 0, SEEK_SET) < 0)//seek to the begin of file
    {
        free(*header);

        return errno;
    }

    if (read(d, *header, sizeof(Elf_Ehdr)) <= 0)//read from begin,read sizof(Elf_Ehdr) bytes ==> header
    {
        free(*header);

        return errno = EINVAL;
    }

    return 0;
}


static int read_section_table(int d, Elf_Ehdr const *header, Elf_Shdr **table)//read elf header,find section header base address
{
    size_t size;

    if (NULL == header)
        return EINVAL;

    size = header->e_shnum * sizeof(Elf_Shdr);//section numbers and total size
    *table = (Elf_Shdr *)malloc(size);

    if (lseek(d, header->e_shoff, SEEK_SET) < 0)//point to section header,offset 0
    {
        free(*table);

        return errno;
    }

    if (read(d, *table, size) <= 0)//read section header structure to **table
    {
        free(*table);

        return errno = EINVAL;
    }

    return 0;
}


static int read_string_table(int d, Elf_Shdr const *section, char const **strings)
{
    if (NULL == section)//section == > .dynstr section
        return EINVAL;

    *strings = (char const *)malloc(section->sh_size);

    if (lseek(d, section->sh_offset, SEEK_SET) < 0)
    {
        free((void *)*strings);

        return errno;
    }

    if (read(d, (char *)*strings, section->sh_size) <= 0)//strings include all strings in .dynstr sections
    {
        free((void *)*strings);

        return errno = EINVAL;
    }

    return 0;
}

int main()
{
    LOG("[+]Arm ELF32 reader...\n");
    uint32_t lic_base = get_module_base(-1,"/system/lib/libc.so");
    int descriptor = open("/system/lib/libc.so", O_RDONLY);//open libc.so,and return the handle
    Elf_Ehdr *header = NULL;//elf header
    Elf_Shdr *section_header = NULL;//section header array ptr
    char const *strings = NULL;//string table ptr
    read_header(descriptor,&header);
    LOG("[+]libc.so elf header:\n");
    LOG("[+]e_ident[EI_NIDENT]:   %s\n",header->e_ident);
    LOG("[+]e_type:%d(ET_DYN:%d,DYN (Shared object file))\n",header->e_type,ET_DYN);
    LOG("[+]e_machine:%d(EM_ARM:%d,Advanced RISC Machines)\n",header->e_machine,EM_ARM);
    LOG("[+]e_shoff:%d bytes\n",header->e_shoff);

    LOG("[+]libc.so section header:\n");
    read_section_table(descriptor,header,§ion_header);
    read_string_table(descriptor,§ion_header[header->e_shstrndx], &strings);//header->e_shstrndx ==>the index of string section header in section headers
    int i = 0;
    for(i = 0;i<header->e_shnum;++i)
    {
        LOG("Section[%d] name:%s,type:%d,addr:0x%x,offset:0x%x,size:%dbytes,etc...\n",i,&strings[section_header[i].sh_name],section_header[i].sh_type,section_header[i].sh_addr,section_header[i].sh_offset,section_header[i].sh_size);
    }
    close(descriptor);
    return 0;
}

 

0x02 小結

      只是總結了ELF文件的大體的結構,對於每個section的結構並沒有深入,之后的學習會更加深入elf文件的學習,主要聚焦在android 下的.so文件,還會加上一些關於 so加密的知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM