Android是基于Linux开发的,所以了解ELF格式对学习Android逆向和Linux逆向是必不可少的。
可执行与可链接格式 (Executable and Linkable Format,ELF),常被称为 ELF格式,是一种用于可执行文件、目标代码、共享库和核心转储(core dump)的标准文件格式,一般用于类Unix系统,比如Linux,Macox等。ELF 格式灵活性高、可扩展,并且跨平台。比如它支持不同的字节序和地址范围,所以它不会不兼容某一特别的 CPU 或指令架构。这也使得 ELF 格式能够被运行于众多不同平台的各种操作系统广泛采纳。
1. ELF文件类型
可重定位文件(.o
文件):文件保存着代码和适当的数据,用来和其它文件一起创建一个可执行文件或者是一个共享目标文件。
可执行文件(.out
文件):包含二进制代码和数据,可直接被加载器加载执行。
共享目标文件(.so
文件):共享库,用于和其它共享目标文件或者可重定位文件一起生成共享目标文件或者和可执行文件一起创建进程映像。
2. ELF文件作用 ELF文件参与程序的链接(建立一个程序)和程序的执行(运行一个程序),所以可以从不同的角度来看待ELF格式的文件:
如果用于编译和链接 (可重定位文件),则编译器和链接器 将把ELF文件看作是节头表描述的节的集合,程序头表可选。
如果用于加载执行 (可执行文件),则加载器 则将把ELF文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选。
3. ELF文件格式 ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如图所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
3.1 ELF文件头 ELF文件头描述了ELF文件的基本类型、地址偏移等信息,分为32bit和64bit两个版本,定义于Linux源码的/usr/include/elf.h
文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define EI_NIDENT 16 typedef struct elf32_hdr { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; } Elf32_Ehdr;
数据类型说明:
名称
大小
用途
unsigned char
1
无符号小整数
Elf32_Half
2
无符号中等大小整数
Elf32_Word
4
无符号大整数
Elf32_Addr
4
无符号程序地址
Elf32_Off
4
无符号文件偏移
Elf32_Sword
4
有符号大整数
64bit和32bit仅仅只是字长的区别,字段上没有实际上的差别。
某些成员的相关参数:
e_ident[EI_NIDENT]
:ELF文件的描述,是一个16字节的标识,表明当前文件的数据格式、位数等。
前4字节为魔数,取值为固定的0x7F454C46,标记当前文件为一个ELF文件。
第5个字节表明当前文件类别:0表示非法,1表示32bit,2表示64bit。
第6个字节表明当前文件的数据排列方式:0表示非法,1表示小端,2表示大端。
第7个字节表明当前文件的版本:0表示未知,1表示当前版本。
第某个字节是EI_PAD
的起点,即e_ident[EI_PAD]
表明e_ident
中未使用的字节的起点,未使用的字节会被初始化为0,解析ELF文件时需要忽略对应的字段。
e_type
:文件的标识字段标识文件的类型。
参数
说明
ET_NONE(0)
未知的文件格式
ET_REL(1)
可重定位文件
ET_EXEC(2)
可执行文件
ET_DYN(3)
共享目标文件
ET_CORE(4)
Core转储文件,比如程序crash后的转储文件
ET_LOPROC(0xff00)
特定处理器的文件标识
ET_HIPROC(0xffff)
特定处理器的文件标识
[ET_LOPROC, ET_HIPROC]
区间的值用来表示特定处理器的文件格式
e_machine
:目标文件的体系结构。
参数
说明
EM_NONE(0)
未知的处理器架构
EM_M32(1)
AT&T WE 32100
EM_SPARC(2)
SPARC
EM_386(3)
Intel 80386
EM_68K(4)
Motorola 68000
EM_88K(5)
Motorola 88000
EM_860(6)
Intel 80860
EM_MIPS(7)
MIPS RS3000大端
EM_MIPS_RS4_BE(10)
MIPS RS4000大端
其他
预留
3.2 程序头表 可执行文件或共享目标文件的程序头表是一个结构数组,每个元素描述了一个段或者系统准备程序执行所必需的其它信息。程序头表描述了ELF文件中Segment在文件中的布局,描述了OS该如何装载可执行文件到内存。程序头表的表项的描述如下,类似于ELF Header也有32和64位两个版本。但程序头表的相关字段偏移有些差别。
1 2 3 4 5 6 7 8 9 10 typedef struct elf32_phdr { Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr;
1 2 3 4 5 6 7 8 9 10 typedef struct elf64_phdr { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64_Xword p_align; } Elf64_Phdr;
某些成员的相关参数:
p_type
:当前Segment的类型。
参数
说明
PT_NULL(0)
当前项未使用,项中的成员是未定义的,需要忽略当前项
PT_LOAD(1)
当前Segment是一个可装载的Segment,即可以被装载映射到内存中,其大小由p_filesz
和p_memsz
描述。如果p_memsz > p_filesz
则剩余的字节被置零,但是p_filesz > p_memsz
是非法的。动态库一般包含两个该类型的段:代码段和数据段
PT_DYNAMIC(2)
动态段、动态库特有的段,包含了动态链接必须的一些信息,比如需要链接的共享库列表、GOT等等
PT_INTERP(3)
当前段用于存储一段以NULL为结尾的字符串,该字符串表明了程序解释器的位置。且当前段仅仅对于可执行文件有实际意义,一个可执行文件中不能出现两个当前段
PT_NOTE(4)
用于保存与特定供应商或者系统相关的附加信息以便于兼容性、一致性检查,但是实际上只保存了操作系统的规范信息
PT_SHLIB(5)
保留段
PT_PHDR(6)
保存程序头表本身的位置和大小,当前段不能在文件中出现一次以上,且仅仅当程序头表为内存映像的一部分时起作用,它必须在所有加载项目之前
[PT_LPROC(0x70000000), PT_HIPROC(0x7fffffff)]
该范围内的值用作预留
3.3 节头表 节头表描述了ELF文件中的节的基本信息。可执行文件不一定有节头表但是一定有节,节头表可利用特殊的方式去除。节头表是也是一个结构数组,每个元素描述了不同的节。
段和节的区别是:
段包含了程序装载可执行的基本信息,段告诉OS如何装载当前段到虚拟内存以及当前段的权限和执行相关的信息等,一个段可以包含0个或多个节;
节包含了程序的代码和数据等内容,链接器会将多个节合并为一个段。
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct elf32_shdr { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; 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
:描述节的类型和语义。
参数
说明
SHT_NULL(0)
当前节是非活跃的,没有一个对应的具体的节内存
SHT_PROGBITS(1)
包含了程序的指令信息、数据等程序运行相关的信息
SHT_SYMTAB(2)
保存了符号信息,用于重定位。此种类型节的sh_link
存储相关字符串表的节索引,sh_info
存储最后一个局部符号的符号表索引+1
SHT_DYNSYM(11)
保存共享库导入动态符号信息。此种类型节的sh_link
存储相关字符串表的节索引,sh_info
存储最后一个局部符号的符号表索引+1
SHT_STRTAB(3)
一个字符串表,保存了每个节的节名称
SHT_RELA(4)
存储可重定位表项,可能会有附加内容,目标文件可能有多个可重定位表项。此种类型节的sh_link
存储相关符号表的节索引,sh_info
存储重定位所使用节的索引
SHT_HASH(5)
存储符号哈希表,所有参与动态链接的目标只能包含一个哈希表,一个目标文件只能包含一个哈希表。此种类型节的sh_link
存储哈希表所使用的符号表的节索引,sh_info
为0
SHT_DYAMIC(6)
存储包含动态链接的信息,一个目标文件只能包含一个。此种类型的节的sh_link
存储当前节中使用到的字符串表格的节的索引,sh_info
为0
SHT_NOTE(7)
存储以某种形式标记文件的信息
SHT_NOBITS(8)
这种类型的节不占据文件空间,但是成员sh_offset
依然会包含对应的偏移
SHT_REL(9)
包含可重定位表项,无附加内容,目标文件可能有多个可重定位表项。此种类型节的sh_link
存储相关符号表的节索引,sh_info
存储重定位所使用节的索引
SHT_SHLIB(10)
保留区,包含此节的程序与ABI不兼容
[SHT_LOPROC(0x70000000), SHT_HIPROC(0x7fffffff)]
留给处理器专用语义
[SHT_LOUSER(0x80000000), SHT_HIUSER(0xffffffff)]
预留
sh_flags
:1bit位的标志位。
参数
说明
SHF_WRITE(0x1)
当前节包含进程执行过程中可写的数据
SHF_ALLOC(0x2)
当前节在运行阶段占据内存
SHF_EXECINSTR(0x4)
当前节包含可执行的机器指令
SHF_MASKPROC(0xf0000000)
所有包含当前掩码都表示预留给特定处理器的
3.4 节 每个节区都有对应的节头来描述它。但是反过来,节区头部并不一定会对应着一个节区。
ELF文件中用一些预定义的节来保存程序、数据和一些控制信息,这些节被用来链接或者装载程序。每个操作系统都支持一组链接模式,主要分为两类(也就是常说的动态库和静态库):
Static:静态绑定的一组目标文件、系统库和库档案(比如静态库),解析包含的符号引用并创建一个完全自包含的可执行文件;
Dynamic:一组目标文件、库、系统共享资源和其他共享库链接在一起创建可执行文件。当加载此可执行文件时必须使系统中其他共享资源和动态库可用,程序才能正常运行。
库文件无论是动态库还是静态库在其文件中都包含对应的节,一些特殊的节其功能如下:
.bss
,类型SHT_NOBITS
,属性SHF_ALLOC|SHF_WRITE
:存储未经初始化的数据。根据定义程序开始执行时,系统会将这些数据初始化为0,且此节不占用文件空间;
.comment
,类型SHT_PROGBITS
,属性none
:存储版本控制信息;
.data
,类型SHT_PROGBITS
,属性SHF_ALLOC|SHF_WRITE
:存放初始化的数据;
.data1
,类型SHT_PROGBITS
,属性SHF_ALLOC|SHF_WRITE
:存放初始化的数据;
.debug
,类型SHT_PROGBITS
,属性none
:存放用于符号调试的信息;
.dynamic
,类型SHT_DYNAMIC
,属性SHF_ALLOC
,是否有属性SHF_WRITE
屈居于处理器:包含动态链接的信息,
.hash
,类型SHT_HASH
,属性SHF_ALLOC
:哈希符号表;
.line
,类型SHT_PROGBITS
,属性none
:存储调试的行号信息,描述源代码和机器码之间的对应关系;
.note
,类型SHT_NOTE
,属性none
:额外的编译器信息,比如程序的公司名、发布版本号等;
.rodata
,类型SHT_PROGBITS
,属性SHF_ALLOC
:存储只读数据;
.rodata1
,类型SHT_PROGBITS
,属性SHF_ALLOC
:存储只读数据;
.shstrtab
,类型SHT_STRTAB
,属性none
:存储节的名称;
.strtab
,类型SHT_STRTAB
,属性none
:存储常见的与符号表关联的字符串。如果文件有一个包含符号字符串表的可加载段,则该段的属性将包括SHF_ALLOC
位; 否则,该位将关闭;
.symtab
,类型SHT_SYMTAB
,属性none
:存储一个符号表。如果文件具有包含符号表的可加载段,则该节的属性将包括SHF_ALLOC
位;否则,该位将关闭;
.text
,类型SHT_PROGBITS
,属性SHF_ALLOC|SHF_EXECINSTR
:存储程序的代码指令;
.dynstr
,类型SHT_STRTAB
,属性SHF_ALLOC
:存储动态链接所需的字符串,最常见的是表示与符号表条目关联的名称的字符串;
.dynsym
,类型SHT_DYNSYM
,属性SHF_ALLOC
:存储动态链接符号表;
.fini
,类型SHT_PROGBITS
,属性SHF_ALLOC|SHF_EXECINSTR
:存储有助于进程终止代码的可执行指令。 当程序正常退出时,系统执行本节代码;
.init
,类型SHT_PROGBITS
,属性SHF_ALLOC|SHF_EXECINSTR
:存储有助于进程初始化代码的可执行指令。 当程序开始运行时,系统会在调用主程序入口点(C 程序称为 main)之前执行本节中的代码;
.interp
,类型SHT_PROGBITS
,SHF_ALLOC
:保存程序解释器的路径名。 如果文件有一个包含该节的可加载段,则该节的属性将包括 SHF_ALLOC
位; 否则,该位将关闭;
.relname
,类型SHT_REL
:包含重定位信息。如果文件具有包含重定位的可加载段,则这些部分的属性将包括 SHF_ALLOC
位;否则,该位将关闭。通常,名称由 重定位适用的部分。因此.text
的重定位部分通常具有名称.rel.text
或.rela.text
;
.relaname
,类型SHT_RELA
:同relname。
其它:对于C++程序有些版本会有.ctors
(有时也会是.init_array
)和.dtors
两个节存储构造和析构相关的代码。
3.3.1 字符串表 字符串表是一个存储字符串的表格,而每个字符串是以NULL也就是\0
为结尾的。字符串表格中索引为0处的字符串被定义为空字符串。符号表中保存的字符串是节名和目标文件中使用到的符号。而需要使用对应字符串时,只需要在需要使用的地方指明对应字符在字符串表中的索引即可,使用的字符串就是索引处到第一个\0
之间的字符串。
3.3.2 符号表 目标文件的符号表包含定位和重定位程序的符号定义和引用所需的信息。符号表索引是该数组的下标。索引0既指定表中的第一个条目,又用作未定义的符号索引。
1 2 3 4 5 6 7 8 typedef struct elf32_sym { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym;
1 2 3 4 5 6 7 8 typedef struct elf64_sym { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Half st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym;
某些成员的相关参数:
st_value
:存储对应符号的取值,具体值依赖于上下文,可能是一个指针地址,立即数等。另外,不同对象文件类型的符号表条目对st_value
成员的解释略有不同:
在可重定位文件中,st_value
保存节索引为SHN_COMMON
的符号的对齐约束,st_value
保存已定义符号的节偏移量。 也就是说,st_value
是从st_shndx
标识的部分的开头的偏移量。
在可执行文件和共享对象文件中,st_value
保存一个虚拟地址。 为了使这些文件的符号对动态链接器更有用,节偏移(文件解释)让位于与节号无关的虚拟地址(内存解释)。
st_info
:指定符号的类型和绑定属性。可以用下面的代码分别解析出bind、type、info
三个属性:
1 2 3 #define ELF32_ST_BIND(i) ((i)>>4) #define ELF32_ST_TYPE(i) ((i)&0xf) #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
参数
说明
STB_LOCAL(0)
局部符号在包含其定义的目标文件之外是不可见的。 同名的本地符号可以存在于多个文件中,互不干扰
STB_GLOBAL(1)
全局符号对所有正在组合的目标文件都是可见的。 一个文件对全局符号的定义将满足另一个文件对同一全局符号的未定义引用
STB_WEAK(2)
弱符号类似于全局符号,但它们的定义具有较低的优先级
[STB_LOPROC(13), STB_HIPROC(15)]
预留位,用于特殊处理器的特定含义
参数
说明
STT_NOTYPE(0)
符号的类型未指定
STT_OBJECT(1)
符号与数据对象相关联,例如变量、数组等
STT_FUNC(2)
符号与函数或其他可执行代码相关联
STT_SECTION(3)
该符号与一个节相关联。 这种类型的符号表条目主要用于重定位,通常具有STB_LOCALBIND
属性
STT_FILE(4)
一个有STB_LOCAL
的BIND
属性的文件符号的节索引为SHN_ABS
。并且如果存在其他STB_LOCAL
属性的符号,则当前符号应该在其之前
[STT_LOPROC(13), STT_HIPROC(15)]
预留位,用于特殊处理器的特定含义
参数
说明
SHN_ABS
符号有一个绝对值,不会因为重定位而改变
SHN_COMMON
该符号标记尚未分配的公共块。 符号的值给出了对齐约束,类似于节的sh_addralign
成员。 也就是说,链接编辑器将为符号分配存储空间,该地址是 st_value
的倍数。 符号的大小表明需要多少字节
SHN_UNDEF
此节表索引表示该符号未定义。 当链接编辑器将此对象文件与另一个定义指定符号的文件组合时,此文件对符号的引用将链接到实际定义
4. ELF文件示例 1 2 3 4 5 6 7 8 #include <stdio.h> int main (int argc, char * argv[]) { printf ("hello world!\n" ); return 0 ; }
将上面的hello.c
分别编译为三种不同文件:
gcc -g -c hello.c
:生成可重定位文件hello.o
。
gcc -g -o hello hello.c
:生成可执行文件。
gcc -g -fPIC -o libhello.so -shared hello.c
:生成共享目标文件。
4.1 共享目标文件 4.1.1 ELF文件头 解析ELF文件头:readelf -h [文件名]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌──(kali㉿kali)-[~/Desktop] └─$ readelf -h libhello.so ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x0 Start of program headers: 64 (bytes into file) Start of section headers: 14296 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 34 Section header string table index: 33
从上面的ELF文件头信息中能够看出:当前文件类型为64bit的共享库,小端存储,版本为1,机器架构为x86-64,程序头表项有9项,节头表项有34项。
4.1.2 程序头表 解析ELF程序头表:readelf -l [文件名]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ┌──(kali㉿kali)-[~/Desktop] └─$ readelf -l libhello.so Elf file type is DYN (Shared object file) Entry point 0x0 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000498 0x0000000000000498 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000139 0x0000000000000139 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x00000000000000b4 0x00000000000000b4 R 0x1000 LOAD 0x0000000000002e10 0x0000000000003e10 0x0000000000003e10 0x0000000000000218 0x0000000000000220 RW 0x1000 DYNAMIC 0x0000000000002e20 0x0000000000003e20 0x0000000000003e20 0x00000000000001c0 0x00000000000001c0 RW 0x8 NOTE 0x0000000000000238 0x0000000000000238 0x0000000000000238 0x0000000000000024 0x0000000000000024 R 0x4 GNU_EH_FRAME 0x0000000000002010 0x0000000000002010 0x0000000000002010 0x0000000000000024 0x0000000000000024 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000002e10 0x0000000000003e10 0x0000000000003e10 0x00000000000001f0 0x00000000000001f0 R 0x1 Section to Segment mapping: Segment Sections... 00 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 01 .init .plt .plt.got .text .fini 02 .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .dynamic .got
从展示的程序头表可以看出上半部分的内容基本和程序头表项的每个字段基本对应。从下面的Segment Sections可以看出一个Segment是多个Section的集合。
4.1.3 节头表 解析节头表:readelf -S [文件名]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 ┌──(kali㉿kali)-[~/Desktop] └─$ readelf -S libhello.so There are 34 section headers, starting at offset 0x37d8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .note.gnu.bu[...] NOTE 0000000000000238 00000238 0000000000000024 0000000000000000 A 0 0 4 [ 2] .gnu.hash GNU_HASH 0000000000000260 00000260 0000000000000024 0000000000000000 A 3 0 8 [ 3] .dynsym DYNSYM 0000000000000288 00000288 00000000000000a8 0000000000000018 A 4 1 8 [ 4] .dynstr STRTAB 0000000000000330 00000330 0000000000000075 0000000000000000 A 0 0 1 [ 5] .gnu.version VERSYM 00000000000003a6 000003a6 000000000000000e 0000000000000002 A 3 0 2 [ 6] .gnu.version_r VERNEED 00000000000003b8 000003b8 0000000000000020 0000000000000000 A 4 1 8 [ 7] .rela.dyn RELA 00000000000003d8 000003d8 00000000000000a8 0000000000000018 A 3 0 8 [ 8] .rela.plt RELA 0000000000000480 00000480 0000000000000018 0000000000000018 AI 3 21 8 [ 9] .init PROGBITS 0000000000001000 00001000 0000000000000017 0000000000000000 AX 0 0 4 [10] .plt PROGBITS 0000000000001020 00001020 0000000000000020 0000000000000010 AX 0 0 16 [11] .plt.got PROGBITS 0000000000001040 00001040 0000000000000008 0000000000000008 AX 0 0 8 [12] .text PROGBITS 0000000000001050 00001050 00000000000000de 0000000000000000 AX 0 0 16 [13] .fini PROGBITS 0000000000001130 00001130 0000000000000009 0000000000000000 AX 0 0 4 [14] .rodata PROGBITS 0000000000002000 00002000 000000000000000d 0000000000000000 A 0 0 1 [15] .eh_frame_hdr PROGBITS 0000000000002010 00002010 0000000000000024 0000000000000000 A 0 0 4 [16] .eh_frame PROGBITS 0000000000002038 00002038 000000000000007c 0000000000000000 A 0 0 8 [17] .init_array INIT_ARRAY 0000000000003e10 00002e10 0000000000000008 0000000000000008 WA 0 0 8 [18] .fini_array FINI_ARRAY 0000000000003e18 00002e18 0000000000000008 0000000000000008 WA 0 0 8 [19] .dynamic DYNAMIC 0000000000003e20 00002e20 00000000000001c0 0000000000000010 WA 4 0 8 [20] .got PROGBITS 0000000000003fe0 00002fe0 0000000000000020 0000000000000008 WA 0 0 8 [21] .got.plt PROGBITS 0000000000004000 00003000 0000000000000020 0000000000000008 WA 0 0 8 [22] .data PROGBITS 0000000000004020 00003020 0000000000000008 0000000000000000 WA 0 0 8 [23] .bss NOBITS 0000000000004028 00003028 0000000000000008 0000000000000000 WA 0 0 1 [24] .comment PROGBITS 0000000000000000 00003028 000000000000001e 0000000000000001 MS 0 0 1 [25] .debug_aranges PROGBITS 0000000000000000 00003046 0000000000000030 0000000000000000 0 0 1 [26] .debug_info PROGBITS 0000000000000000 00003076 00000000000000b5 0000000000000000 0 0 1 [27] .debug_abbrev PROGBITS 0000000000000000 0000312b 0000000000000064 0000000000000000 0 0 1 [28] .debug_line PROGBITS 0000000000000000 0000318f 0000000000000052 0000000000000000 0 0 1 [29] .debug_str PROGBITS 0000000000000000 000031e1 00000000000000a7 0000000000000001 MS 0 0 1 [30] .debug_line_str PROGBITS 0000000000000000 00003288 000000000000001b 0000000000000001 MS 0 0 1 [31] .symtab SYMTAB 0000000000000000 000032a8 0000000000000270 0000000000000018 32 20 8 [32] .strtab STRTAB 0000000000000000 00003518 0000000000000178 0000000000000000 0 0 1 [33] .shstrtab STRTAB 0000000000000000 00003690 0000000000000141 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), l (large), p (processor specific)
从上面看出内容基本和程序头表项的每个字段基本对应。除了上面提到的特殊的节也有一些额外的节,比如.got.plt
。
4.1.4 符号表 解析符号表:readelf -s [文件名]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ┌──(kali㉿kali)-[~/Desktop] └─$ readelf -s libhello.so Symbol table '.dynsym' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...] 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...] 5: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (2) 6: 0000000000001109 37 FUNC GLOBAL DEFAULT 12 main Symbol table '.symtab' contains 26 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 2: 0000000000001050 0 FUNC LOCAL DEFAULT 12 deregister_tm_clones 3: 0000000000001080 0 FUNC LOCAL DEFAULT 12 register_tm_clones 4: 00000000000010c0 0 FUNC LOCAL DEFAULT 12 __do_global_dtors_aux 5: 0000000000004028 1 OBJECT LOCAL DEFAULT 23 completed.0 6: 0000000000003e18 0 OBJECT LOCAL DEFAULT 18 __do_global_dtor[...] 7: 0000000000001100 0 FUNC LOCAL DEFAULT 12 frame_dummy 8: 0000000000003e10 0 OBJECT LOCAL DEFAULT 17 __frame_dummy_in[...] 9: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c 10: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 11: 00000000000020b0 0 OBJECT LOCAL DEFAULT 16 __FRAME_END__ 12: 0000000000000000 0 FILE LOCAL DEFAULT ABS 13: 0000000000001130 0 FUNC LOCAL DEFAULT 13 _fini 14: 0000000000004020 0 OBJECT LOCAL DEFAULT 22 __dso_handle 15: 0000000000003e20 0 OBJECT LOCAL DEFAULT 19 _DYNAMIC 16: 0000000000002010 0 NOTYPE LOCAL DEFAULT 15 __GNU_EH_FRAME_HDR 17: 0000000000004028 0 OBJECT LOCAL DEFAULT 22 __TMC_END__ 18: 0000000000004000 0 OBJECT LOCAL DEFAULT 21 _GLOBAL_OFFSET_TABLE_ 19: 0000000000001000 0 FUNC LOCAL DEFAULT 9 _init 20: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...] 21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 22: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 23: 0000000000001109 37 FUNC GLOBAL DEFAULT 12 main 24: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...] 25: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@G[...]
可以看出符号表有两个,分别为dynsym
和symtab
,symtab
中包含所有在程序中出现的符号以及一些库函数的符号,而dynsym
中的符号是symtab
中符号的子集,仅仅出现了外部可以看到的符号(比如静态函数mult
的符号在dynsym
就看不到)。这是因为dynsym
中的符号只有在动态链接时也就是运行时才能被解析。
5. 解析ELF文件相关工具 5.1 objdump objdump 有点像快速查看之类的工具,就是以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息。对于想进一步了解系统的程序员,应该掌握这种工具,至少你可以自己写写shellcode了,或者看看人家给的 exploit 中的 shellcode 是什么东西。
Windows下使用objdump需要下载Android NDK ,将objdump所在路径添加到环境变量,就可以使用objdump了。Windows下的objdump工具名为llvm-objdump。
1 2 llvm-objdump -S pwn1 > Disassembly.txt //-S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数 llvm-objdump -s pwn1 > Section.txt //-s 显示指定section的完整内容
5.2 readelf 用法同4.1
Windows下的readelf也在Android NDK中,与objdump同一个目录。Windows下的objdump工具名为llvm-readelf。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 C:\Users\v5le0n9\Desktop>llvm-readelf -h pwn1 ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x401060 Start of program headers: 64 (bytes into file) Start of section headers: 14680 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 29 Section header string table index: 28
5.3 010 Editor 在分析ELF文件格式时就用到了010 Editor,载入ELF模板分析ELF文件事半功倍。