前言
由于Android中的so文件就是elf文件,所以想要了解so文件,必须先了解elf文件的格式,网上虽然ELF格式的分析不少,但是感觉不透彻,便耐着性子,慢慢开始了elf格式分析。ELF格式分析
- ELF: 什么是ELF
- ELF结构: 详细的ELF数据结构
引言
ELF(Executable and Linkable Format)即可执行连接文件格式,是一种比较复杂的文件格式,但其应用广泛。与linux下的其他可执行文件(a.out,cof)相比,它对节的定义和gnu工具链对它的支持使它十分灵活,它保存的足够了系统相关信息使它能支持不同平台上的交叉编译和交叉链接,可移植性很强.同时它在执行中支持动态链接共享库。
目标文件有三种类型:
- 可重定位文件(Relocatable File) .o)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
- 可执行文件(Executable File) .exe) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
- 共享目标文件(Shared Object File) .so) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理, 生成另外一个目标文件。其次动态链接器(Dynamic Linker)可能将它与某 个可执行文件以及其它共享目标一起组合,创建进程映像。
目标文件全部是程序的二进制表示,目的是直接在某种处理器上直接执行。
ELF组成
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定.
通过图,我们可以直观的了解整个结构.
- ELF 头部(ELF Header),用来描述整个文件的组织。节区部 分包含链接视图的大量信息:指令、数据、符号表、重定位信息等等。
- 程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。 用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
- 节区头部表(Section Heade Table)包含了描述文件节区的信息,每个节区在表中 都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包 含节区头部表,其他目标文件可以有,也可以没有这个表。
ELF数据类型
名称 | 大小 | 对齐 | 用途 |
---|---|---|---|
Elf32_Addr | 4 | 4 | 无符号程序地址 |
Elf32_Half | 2 | 2 | 无符号中等大小整数 |
Elf32_Off | 4 | 4 | 无符号文件偏移 |
Elf32_Sword | 4 | 4 | 有符号大整数 |
Elf32_Word | 4 | 4 | 无符号大整数 |
unsigned char | 1 | 1 | 无符号小整数 |
ELF Hearder
我们知道了基本的数据结构,就可以开始了解整个header部分.1
/* 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;
最开头是16个字节的e_ident, 其中包含用以表示ELF文件的字符,以及其他一些与机器无关的信息。开头的4个字节值固定不变,为0x7f和ELF三个字符。
- e_ident 数组给出了 ELF 的一些标识信息,这个数组中不同下标的含义如表所示:
- e_type 它标识的是该文件的类型。
- e_machine 表明运行该程序需要的体系结构。
- e_version 表示文件的版本。
- e_entry 程序的入口地址。
- e_phoff 表示Program header table 在文件中的偏移量(以字节计数)。
- e_shoff 表示Section header table 在文件中的偏移量(以字节计数)。
- e_flags 对IA32而言,此项为0。
- e_ehsize 表示ELF header大小(以字节计数)。
- e_phentsize 表示Program header table中每一个条目的大小。
- e_phnum 表示Program header table中有多少个条目。
- e_shentsize 表示Section header table中的每一个条目的大小。
- e_shnum 表示Section header table中有多少个条目。
- e_shstrndx 包含节名称的字符串是第几个节(从零开始计数)
在linux下,我们可以使用readelf指令观察1
$kali -h test.so
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
类别: ELF32
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: ARM
版本: 0x1
入口点地址: 0x0
程序头起点: 52 (bytes into file)
Start of section headers: 61816 (bytes into file)
标志: 0x5000000, Version5 EABI
本头的大小: 52 (字节)
程序头大小: 32 (字节)
Number of program headers: 9
节头大小: 40 (字节)
节头数量: 24
字符串表索引节头: 23
Program Header
可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段 或者系统准备程序执行所必需的其它信息。目标文件的“段”包含一个或者多个“节区”, 也就是“段内容(Segment Contents)”。程序头部仅对于可执行文件和共享目标文件有意义。
可执行目标文件在 ELF 头部的 e_phentsize 和 e_phnum 成员中给出其自身程序头部的大小.
其数据结构如下所示:1
/* Program Header */
typedef struct {
Elf32_Word p_type; /* segment type */
Elf32_Off p_offset; /* segment offset */
Elf32_Addr p_vaddr; /* virtual address of segment */
Elf32_Addr p_paddr; /* physical address - ignored? */
Elf32_Word p_filesz; /* number of bytes in file for seg. */
Elf32_Word p_memsz; /* number of bytes in mem. for seg. */
Elf32_Word p_flags; /* flags */
Elf32_Word p_align; /* memory alignment */
} Elf32_Phdr;
其具体的内容如下:
- p_type 此数组元素描述的段的类型,或者如何解释此数组元素的信息。
- p_offset 此成员给出从文件头到该段第一个字节的偏移。
- p_vaddr 此成员给出段的第一个字节将被放到内存中的虚拟地址。
- p_paddr 此成员仅用于与物理地址相关的系统中。因为 System V 忽略所有应用程序的物理地址信息,此字段对与可执行文件和共享目标文件而言具体内容是指定的。
- p_filesz 此成员给出段在文件映像中所占的字节数。可以为 0。
- p_memsz 此成员给出段在内存映像中占用的字节数。可以为 0。
- p_flags 此成员给出与段相关的标志。
- p_align 可加载的进程段的 p_vaddr 和 p_offset 取值必须合适,相对于对页面大小的取模而言。此成员给出段在文件中和内存中如何 对齐。数值 0 和 1 表示不需要对齐。否则 p_align 应该是个正整数,并且是 2 的幂次数,p_vaddr 和 p_offset 对 p_align 取模后应该相等。
Sections
节区中包含目标文件中的所有信息(除ELF头部、程序头部表格、节区头部表格)。节区满足以下条件:
- 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意 味着有节区。
- 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
- 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
- 目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容指定
详见的节区如下所示:
Sections head table
节区头部表格,每个节区头部数据结构描述如下:
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
总结
通过以上的分析,最后通过一个整体的图,使得对整体的结构有一个宏观的了解。
此处推荐一篇实践的ELF格式分析,便于更加直观的了解。http://www.jianshu.com/p/7a75324e98ab