Android安全–ELF文件格式解析

一、简介

可执行链接格式(Executable and Linking Format)最初是由 UNIX 系统实验室(UNIX System Laboratories,USL)开发并发布的,作为应用程序二进制接口(Application Binary Interface,ABI)的一部分。工具接口标准(Tool Interface Standards,TIS)委员会将还在发展的 ELF 标准选作为一种可移植的目标文件格式,可以在 32 位 Intel 体系结构上的很多操作系统中使用。

目标文件有三种类型:

  • 可重定位文件(Relocatable File)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
  • 可执行文件(Executable File)包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
  • 共享目标文件(Shared Object File)包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
  • 目标文件全部是程序的二进制表示,目的是直接在某种处理器上直接执行。

二、目标文件格式

目标文件既要参与程序链接又要参与程序执行。出于方便性和效率考虑,目标文件格式提供了两种并行视图,分别反映了这些活动的不同需求。

image

文件开始处是一个ELF 头部(ELF Header),用来描述整个文件的组织。节区部分包含链接视图的大量信息:指令、数据、符号表、重定位信息等等。
程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
节区头部表(Section Heade Table)包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。

注意:尽管图中显示的各个组成部分是有顺序的,实际上除了 ELF 头部表以外,其他节区和段都没有规定的顺序

目标文件中的数据表示

image

目标文件中的所有数据结构都遵从“自然”大小和对齐规则。如果必要,数据结构可以包含显式的补齐,例如为了确保4字节对象按4字节边界对齐。数据对齐同样适用于文件内部。

这里使用android-ndk下面的例子hello-jni.so来分析。

三、ELF Header部分

elf header数据如下:

image

ELF Header部分可以用下图中的数据结构表示:

#define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT];    //目标文件标识信息
Elf32_Half e_type;                             //目标文件类型
Elf32_Half e_machine;                       //目标体系结构类型
Elf32_Word e_version;                      //目标文件版本
Elf32_Addr e_entry;                          //程序入口的虚拟地址,若没有,可为0
Elf32_Off e_phoff;                            //程序头部表格(Program Header Table)的偏移量(按字节计算),若没有,可为0
Elf32_Off e_shoff;                            //节区头部表格(Section Header Table)的偏移量(按字节计算),若没有,可为0
Elf32_Word e_flags;                        //保存与文件相关的,特定于处理器的标志。标志名称采用 EF_machine_flag的格式。
Elf32_Half e_ehsize;                        //ELF 头部的大小(以字节计算)。
Elf32_Half e_phentsize;                 //程序头部表格的表项大小(按字节计算)。
Elf32_Half e_phnum;                      //程序头部表格的表项数目。可以为 0。
Elf32_Half e_shentsize;                  //节区头部表格的表项大小(按字节计算)。
Elf32_Half e_shnum;                      //节区头部表格的表项数目。可以为 0。
Elf32_Half e_shstrndx;                  //节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为 SHN_UNDEF。
}Elf32_Ehdr;

其中,e_ident数组给出了ELF的一些标识信息,这个数组中不同下标的含义如表:

image

数据说明:

①e_ident[EI_MAG0]~e_ident[EI_MAG3]即e_ident[0]~e_ident[3]被称为魔数(Magic Number),其值一般为0x7f,’E’,’L’,’F’。

②e_ident[EI_CLASS](即e_ident[4])识别目标文件运行在目标机器的类别,取值可为三种值:ELFCLASSNONE(0)非法类别;ELFCLASS32(1)32位目标;ELFCLASS64(2)64位目标。

③e_ident[EI_DATA](即e_ident[5])给出处理器特定数据的数据编码方式。即大端还是小端方式。取值可为3种:ELFDATANONE(0)非法数据编码;ELFDATA2LSB(1)高位在前;ELFDATA2MSB(2)低位在前。

④e_ident[EI_VERSION]](即e_ident[6])ELF头部的版本号码,此值必须是EV_CURRENT。

⑤e_ident[EI_PAD](即e_ident[7])标记e_ident中未使用字节的开始,初始化为0。

其它各个字段的含义如下:

image

四、Program Header Table部分

数据结构如下所示:

/* Program segment header. */ typedef struct { Elf32_Word p_type; /* Segment type */ Elf32_Off p_offset; /* Segment file offset */ Elf32_Addr p_vaddr; /* Segment virtual address */ 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;

其中p_type的定义如下:

/* Legal values for p_type (segment type).  */

#define PT_NULL         0               /* Program header table entry unused */
#define PT_LOAD         1               /* Loadable program segment */
#define PT_DYNAMIC      2               /* Dynamic linking information */
#define PT_INTERP       3               /* Program interpreter */
#define PT_NOTE         4               /* Auxiliary information */
#define PT_SHLIB        5               /* Reserved */
#define PT_PHDR         6               /* Entry for header table itself */
#define PT_TLS          7               /* Thread-local storage segment */
#define PT_NUM          8               /* Number of defined types */
#define PT_LOOS         0x60000000      /* Start of OS-specific */
#define PT_GNU_EH_FRAME 0x6474e550      /* GCC .eh_frame_hdr segment */
#define PT_LOSUNW       0x6ffffffa
#define PT_SUNWBSS      0x6ffffffa      /* Sun Specific segment */
#define PT_SUNWSTACK    0x6ffffffb      /* Stack segment */
#define PT_HISUNW       0x6fffffff
#define PT_HIOS         0x6fffffff      /* End of OS-specific */
#define PT_LOPROC       0x70000000      /* Start of processor-specific */
#define PT_HIPROC       0x7fffffff      /* End of processor-specific */

p_flag表示该该段是否可读可写可执行

/* Legal values for p_flags (segment flags).  */

#define PF_X            (1 << 0)        /* Segment is executable */
#define PF_W            (1 << 1)        /* Segment is writable */
#define PF_R            (1 << 2)        /* Segment is readable */
#define PF_MASKOS       0x0ff00000      /* OS-specific */
#define PF_MASKPROC     0xf0000000      /* Processor-specific */

五、Section Header Table部分

ELF 头部中,e_shoff 成员给出从文件头到节区头部表格的偏移字节数;e_shnum给出表格中条目数目;e_shentsize 给出每个项目的字节数。从这些信息中可以确切地定位节区的具体位置、长度。

数据结构如下:

typedef struct{
Elf32_Word sh_name;   //节区名,是节区头部字符串表节区(Section Header String Table Section)的索引。名字是一个 NULL 结尾的字符串。
Elf32_Word sh_type;    //为节区类型
Elf32_Word sh_flags;    //节区标志
Elf32_Addr sh_addr;    //如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。否则,此字段为 0。
Elf32_Off sh_offset;    //此成员的取值给出节区的第一个字节与文件头之间的偏移。
Elf32_Word sh_size;   //此 成 员 给 出 节 区 的 长 度 ( 字 节 数 )。
Elf32_Word sh_link;   //此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
Elf32_Word sh_info;       //此成员给出附加信息,其解释依赖于节区类型。
Elf32_Word sh_addralign;    //某些节区带有地址对齐约束.
Elf32_Word sh_entsize;    //某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。
}Elf32_Shdr;

sh_type字段:

image

sh_flags字段:

sh_flags字段定义了一个节区中包含的内容是否可以修改、是否可以执行等信息。如果一个标志位被设置,则该位取值为1。未定义的各位都设置为0。

SHF_WRITE   0x1

SHF_ALLOC   0x2

SHF_EXECINSTR   0x4

SHF_MASKPROC   0xF0000000

其中已经定义了的各位含义如下:

SHF_WRITE: 节区包含进程执行过程中将可写的数据。

SHF_ALLOC: 此节区在进程执行过程中占用内存。某些控制节区并不出现于目标文件的内存映像中,对于那些节区,此位应设置为 0。

SHF_EXECINSTR: 节区包含可执行的机器指令。

SHF_MASKPROC: 所有包含于此掩码中的四位都用于处理器专用的语义。

sh_link和sh_info字段:

根据节区类型的不同,sh_link 和 sh_info 的具体含义也有所不同:

image

六、特殊节区

image在分析这些节区的时候,需要注意如下事项:

①以“.”开头的节区名称是系统保留的。应用程序可以使用没有前缀的节区名称,以避免与系统节区冲突。

②目标文件格式允许人们定义不在上述列表中的节区。

③目标文件中也可以包含多个名字相同的节区。

④保留给处理器体系结构的节区名称一般构成为:处理器体系结构名称简写 + 节区名称。

⑤处理器名称应该与 e_machine 中使用的名称相同。例如 .FOO.psect 街区是由FOO 体系结构定义的 psect 节区。

七、字符串表(String Table)

首先要知道,字符串表它本身就是一个节区,从第二章描述中可知,每一个节区都存在一个节区头部表项与之对应,所以字符串表这个节区也存在一个节区头部表项对应,而在elf文件头部结构中存在一个成员e_shstrndx给出这个节区头部表项的索引位置。因此可以通过

shstrab  = (rt_uint8_t *)module_ptr +shdr[elf_module->e_shstrndx].sh_offset;

字符串表节区包含以NULL(ASCII码0)结尾的字符序列,通常称为字符串。ELF目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符串表中的下标给出。
一般,第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一个字节也定义为 NULL,以确保所有的字符串都以NULL结尾。索引为0的字符串在不同的上下文中可以表示无名或者名字为 NULL的字符串。
允许存在空的字符串表节区,其节区头部的sh_size成员应该为0。对空的字符串表而言,非0的索引值是非法的。

在使用、分析字符串表时,要注意以下几点:

①字符串表索引可以引用节区中任意字节

②字符串可以出现多次

③可以存在对子字符串的引用

④同一个字符串可以被引用多次

⑤字符串表中也可以存在未引用的字符串

 

先介绍这么多,其它的信息大家自己参考:

非虫大大附图:http://bbs.pediy.com/attachment.php?attachmentid=74501&d=1355835585

ELF文件格式分析:http://staff.ustc.edu.cn/~sycheng/sst/exp_crack/ELF.pdf

ELF文件格式官方文档:http://download.csdn.net/detail/walkingman321/3016369

本文链接:http://www.alonemonkey.com/elf-format.html