ELF文件结构 整体结构 ELF文件在链接视图(.o)文件和执行视图(可执行文件)有所差别,见下图
链接视图是以节(section)为单位,执行视图是以段(segment)为单位。
显示命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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: REL (Relocatable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 832 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 64 (bytes) Number of section headers: 1 Section header string table index: 1 <corrupt: out of range>
7f 、45、4c、46分别对应ascii码的Del(删除)、字母E、字母L、字母F。这四个字节被称为ELF文件的魔数,操作系统在加载可执行文件时会确认魔数是否正确,如果不正确则拒绝加载。 第五个字节标识ELF文件是32位(01)还是64位(02)的。 第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的。 第七个字节指示ELF文件的版本号,一般是01。 后九个字节ELF标准未做定义。一般为00.
ELF文件头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等一系列参数。
详细结构: ELF Header的详细结构定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct { unsigned char e_ident[EI_NIDENT]; Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; Elf64_Off e_phoff; Elf64_Off e_shoff; Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx; } Elf64_Ehdr;
e_entry
:程序入口地址
e_ehsize
:ELF Header结构大小
e_phoff
、e_phentsize
、e_phnum
:描述Program Header Table的偏移、大小、结构。
e_shoff
、e_shentsize
、e_shnum
:描述Section Header Table的偏移、大小、结构。
e_shstrndx
:这一项描述的是字符串表在Section Header Table中的索引,值25表示的是Section Header Table中第25项是字符串表(String Table)
其中各个类型所占字节的大小
Elf64_Word
:4字节
Elf64_Off
:8字节
Elf64_Addr
:8字节
Elf64_Half
:2字节
Elf64_Xword
:8字节
显示命令: 段表是ELF除了文件以外的最重要结构体,它描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。 如上面ELF Header所示,段表在ELF文件中的位置由ELF文件头的“e_shoff”
成员决定的。
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 Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .strtab STRTAB 0000000000000000 00000280 00000000000000bc 0000000000000000 0 0 1 [ 2] .text PROGBITS 0000000000000000 00000040 000000000000003a 0000000000000000 WAX 0 0 16 [ 3] .rela.text RELA 0000000000000000 00000220 0000000000000018 0000000000000018 I 12 2 8 [ 4] data PROGBITS 0000000000000000 0000007c 0000000000000008 0000000000000000 A 0 0 4 [ 5] .rodata.str1.1 PROGBITS 0000000000000000 00000084 000000000000000b 0000000000000001 AMS 0 0 1 [ 6] .bss NOBITS 0000000000000000 00000090 0000000000000004 0000000000000000 WA 0 0 4 [ 7] .comment PROGBITS 0000000000000000 00000090 0000000000000026 0000000000000001 MS 0 0 1 [ 8] .note.GNU-stack PROGBITS 0000000000000000 000000b6 0000000000000000 0000000000000000 0 0 1 [ 9] .eh_frame X86_64_UNWIND 0000000000000000 000000b8 0000000000000078 0000000000000000 A 0 0 8 [10] .rela.eh_frame RELA 0000000000000000 00000238 0000000000000048 0000000000000018 I 12 9 8 [11] .llvm_addrsig LOOS+0xfff4c03 0000000000000000 00000280 0000000000000000 0000000000000000 E 12 0 1 [12] .symtab SYMTAB 0000000000000000 00000130 00000000000000f0 0000000000000018
其中比较重要的段:
.text
段主要存储函数代码
.bss
段主要存储未初始化的全局变量以及局部静态变量
.rodata
:存储全局常量,字符串常量。
.data
:已初始化的全局变量和局部静态变量。
详细结构: Section Header table 本质上是Elf64_Shdr
结构体的数组,Elf64_Shdr
的结构定义如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct { Elf64_Word sh_name; Elf64_Word sh_type; Elf64_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf64_Xword sh_size; Elf64_Word sh_link; Elf64_Word sh_info; Elf64_Xword sh_addralign; Elf64_Xword sh_entsize; } Elf64_Shdr;
sh_name
:表示section的名称。由于每个section名称的长度不相同,并且为了节约空间,于是就将所有section的名称都存放在一个特定的名叫.shstrtab的section中,所以这里的sh_name的值指的就是在这个特定section中的偏移量,通过它可以获得一个字符串,也就是所需要的section名。0值表示无名称,一般用于类型为SHT_NULL的section中。
sh_type
:表示section的类型。
sh_offset
表示该section在文件中的偏移量。
sh_size
该section的大小。
sh_link
、sh_info
:如果该部分与链接相关,则“sh_link”和“sh_info”具有特殊的含义。
sh_addralign
:该部分在进程的虚拟地址空间中的对齐方式。
sh_entsize
:如果该部分包含表,如符号表、重新定位表,sh_entsize是表中每个条目的大小。
.strtab section .strtab是字符串表,保存了节中用到所有的字符串,如下图所示,保存了所有的字符串,其他节通过索引偏移指定字符串的具体值
可通过下面命令查询所有的字符串具体的偏移量。
1 $ readelf -p .strtab <ELF_FILE>
以.text段举例,如最开始命令所示,.text段在整体文件的偏移为0x40,.text section table在.strtab section table的后面,根据ELF table Header,每个section header为64字节,开始在832字节。所以.text section table的位置为 0x340(832字节)+0x40(64字节)*2 = 0x3C0开始
开始字段即为sh_name
为06,即从.strtab段开头偏移6,查表可知偏移6即为.text字符串。此即为.strtab段的作用。
.symtab section symbol介绍 在 ELF(可执行和可链接格式)文件中,symbol起着重要作用。 此上下文中的symbol本质上是一个命名实体。它可以是一个变量,一个函数, 或程序中任何其他可识别的代码段。
在 ELF 文件中符号的主要作用包括
1.链接:符号允许链接器解析引用。当您想要构建程序时 对于多个源文件,每个 .c 文件都编译为单独的可重定位对象文件。 如果一个目标文件引用另一个目标文件中定义的函数或变量,它通过一个符号来实现。在链接阶段,链接器将解析这些符号,将对符号的每个引用替换为所引用实体的实际地址。
2.分析:symbol对于 objdump/gdb/ghidra 等分析工具至关重要,没有symbol的程序比有symbol的更难分析。
3.动态链接:symbol也用于动态链接,它们允许程序确定运行时要从动态加载的库中调用哪些函数或要使用的变量。
常见symbol 每个对象文件都有自己的符号表 (.symtab),其中包含在 对象文件。符号表中的符号可以是以下类型之一:
全局symbol:这些符号对其他对象文件和库可见,例如函数(’main’、’foo’)和全局变量。
外部symbol:这些符号在其他对象文件或库中定义,例如 libc 中的“printf”。 3.节名:这些符号总是由编译器和汇编器生成,用于标记节的开头。
局部symbol:这些符号仅对目标文件本身可见,例如静态函数和静态变量。 5.调试符号:这些符号由编译器和汇编器生成,用于调试,通常采用“DWARF”格式。
详细结构 .symtab和Section Header Table一样,是一个存储Elf64_Sym
结构体的数组。
Elf64_Sym
的具体结构如下。
1 2 3 4 5 6 7 8 9 10 typedef struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym;
st_name
:symbol的名字在.strtab段中的偏移。
st_shndx
:symbol段的索引。
st_info
:高4位表示 Symbol Binding,低4位表示 Symbol Type
st_value
:根据不同的上下文,有不同的定义.
1.对于 Relocatable file,如果 st_shndx 的值为 SHN_COMMON,那么 st_value 表示对齐约束。(上面 st_shndx = SHN_COMMON 已经提及) 2.对于 Relocatable file,如果 st_shndx 的值为关联的 Section Header index,那么 st_value 表示从该 Section 起始位置开始的 offset。(上面 st_shndx = Section Header index 已经提及) 3.对于 Executable file 和 Shared object file,st_value 则是已经计算好的虚存地址,这是为了方便 dynamic linker(动态链接器)
st_size
:表示 Symbol 的大小,例如数据对象占据多少字节。如果 Symbol 没有大小或者大小未知,则值为0。
可通过命令来查看所有的symbol。
如上图所示即为各个symbol的详细信息。
查看进程虚拟地址范围文件
/proc/<pid>/maps
ELF的Program Header Table用于描述ELF可执行文件的内存布局。
操作系统并不关心ELF部分的详细数据,它主要关心每个部分的权限:是否可读,可写,可执行。因此,一些具有相同权限的ELF部分的可以合并到一个segment中。
segment由链接器创建,描述segment的结构就是Program Header Table。只有ELF可执行文件和共享库才有Program Header Table,这是一个数组Elf64_Phdr
结构,每个这样的结构定义一个segment。
详细结构 1 2 3 4 5 6 7 8 9 10 11 typedef struct { 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;
详细的介绍已经在注释中写明。
显示命令
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 Elf file type is EXEC (Executable file) Entry point 0x401690 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000520 0x0000000000000520 R 0x1000 LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000 0x0000000000074315 0x0000000000074315 R E 0x1000 LOAD 0x0000000000076000 0x0000000000476000 0x0000000000476000 0x0000000000029133 0x0000000000029133 R 0x1000 LOAD 0x000000000009f470 0x00000000004a0470 0x00000000004a0470 0x0000000000065620 0x000000000006ae98 RW 0x1000 NOTE 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8 0x0000000000000020 0x0000000000000020 R 0x8 NOTE 0x00000000000002c8 0x00000000004002c8 0x00000000004002c8 0x0000000000000044 0x0000000000000044 R 0x4 TLS 0x000000000009f470 0x00000000004a0470 0x00000000004a0470 0x0000000000000018 0x0000000000000058 R 0x8 GNU_PROPERTY 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8 0x0000000000000020 0x0000000000000020 R 0x8 GNU_EH_FRAME 0x0000000000091960 0x0000000000491960 0x0000000000491960 0x0000000000002174 0x0000000000002174 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x000000000009f470 0x00000000004a0470 0x00000000004a0470 0x0000000000063b90 0x0000000000063b90 R 0x1 Section to Segment mapping: Segment Sections... 00 .note.gnu.property .note.gnu.build-id .note.ABI-tag .rela.plt 01 .init .plt .text .fini 02 .rodata .stapsdt.base rodata.cst32 .eh_frame_hdr .eh_frame .gcc_except_table 03 .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data .bss 04 .note.gnu.property 05 .note.gnu.build-id .note.ABI-tag 06 .tdata .tbss 07 .note.gnu.property 08 .eh_frame_hdr 09 10 .tdata .init_array .fini_array .data.rel.ro .got
plt表和got表
静态链接和动态链接的区别,静态链接在编译为可执行文件时将用户文件和库文件打包到一起,动态链接打包库函数寻址的程序片段,加载到内存空间后在再进行寻找。
plt 表是一个数组,数组中的每个元素都一个可执行的代码片段 。数组中每个元素都与一个外部函数相对应(如strlen@plt。每当程序试图调用一个外部函数时,就会跳转到.plt表的对应代码片段,再由该代码片段跳转至对应函数。
got.plt 表是一个数组,数组中的每个元素都一个地址。数组中每个元素都与一个外部函数相对应(如strlen@got)。每当程序试图调用一个外部函数时,.plt表的跳转地址正是来自于.got.plt表。
每当程序试图调用一个外部函数时,就会跳转到.plt表的对应代码片段,再由该代码片段跳转至目标函数。
got表劫持 可以事先将got表中的函数地址改为后门函数地址当执行时plt表直接跳转到后门函数从而劫持获取权限。
通过命令即可查询到每个.plt表对应的got表。
1 $ objdump -d -j .plt /challenge/level41