• 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...
    ");
        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...
    ",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...
    ");
        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:
    ");
        LOG("[+]e_ident[EI_NIDENT]:   %s
    ",header->e_ident);
        LOG("[+]e_type:%d(ET_DYN:%d,DYN (Shared object file))
    ",header->e_type,ET_DYN);
        LOG("[+]e_machine:%d(EM_ARM:%d,Advanced RISC Machines)
    ",header->e_machine,EM_ARM);
        LOG("[+]e_shoff:%d bytes
    ",header->e_shoff);
    
        LOG("[+]libc.so section header:
    ");
        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...
    ",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加密的知识。

  • 相关阅读:
    array_keys
    strval
    is_numeric
    php static延迟静态绑定
    page39 类的访问权限控制
    page34类的继承
    被遗忘在角落的类型检查函数
    2.2.5重写静态变量
    2.2.3使用parent作用域
    16个最棒的WordPress博客写作发布工具【博主桌面工具】
  • 原文地址:https://www.cnblogs.com/lanrenxinxin/p/4898513.html
Copyright © 2020-2023  润新知