ELF文件格式分析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELF文件格式分析相关的知识,希望对你有一定的参考价值。
要求:
1.分析文件头。
2.通过文件头找到section header table,理解其内容。
3.通过section header table找到各section。
4.理解常见的.text .strtab .symtab .rodata 等section。
5.报告独立完成,格式规范。
一、基础知识
ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件用于存储Linux程序。 学习ELF格式,ELF是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。编译器生成的目标文件时,会在文件的开始处会有一个elf头,描绘了整个文件的组织结构。它还包括很多节(section)。这些节有的是系统定义好的,有些是用户在文件在通过.section命令自定义的,链接器会将多个输入目标文件中的相同的节合并。
二、分析学习
以自编简单程序一个“ElF_1.c”为例
#include <stdio.h> int f(int x){ return g(x); } int main(void){ static int a=7; char str[]="变量为:"; printf("%s%d",str,f(a)+1); return f(a)+1; }
使用下面命令来编译 gcc -c ELF_1.c –o ELF_1.o
再使用下面命令来显示生成的目标文件ELF_1.o的类型 file ELF_1.o
ELF_1.o是一个可重定位文件
使用命令ls -l ELF_1.o查看ELF_1.o的大小
根据输出结果可以知道,ELF_1.o大小为1944字节
使用下面命令hexdump -x ELF_1.o 来用16进制的数字来显示ELF_1.o的内容
使用下面命令objdump –x ELP_1.o来显示ELF_1.o中各个段以及符号表的相关信息
也可以用命令readelf –a ELF_1.o来查看各个段信息
对应要求通过文件头,找到section header table,又通过section header table找到各section
段表Section header table
符号表 Symbol table
三、文件内容分析
ELF头与section table的数据结构分析
1.ElF文件头(定义在/usr/include/elf.h)//64位的系统ELF文件头包括以下两个部分
#define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr; typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf64_Half e_type; /* Object file type */ Elf64_Half e_machine; /* Architecture */ Elf64_Word e_version; /* Object file version */ Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */ Elf64_Off e_shoff; /* Section header table file offset */ Elf64_Word e_flags; /* Processor-specific flags */ Elf64_Half e_ehsize; /* ELF header size in bytes */ Elf64_Half e_phentsize; /* Program header table entry size */ Elf64_Half e_phnum; /* Program header table entry count */ Elf64_Half e_shentsize; /* Section header table entry size */ Elf64_Half e_shnum; /* Section header table entry count */ Elf64_Half e_shstrndx; /* Section header string table index */ } Elf64_Ehdr;
从上表可以看出32位系统和64位系统下ELF头的结构基本相同,不同的是两种结构中的某个成员字段占用字节个数有所变化。比如e_entry由32位下占4个字节的Elf32_Addr变为64位下占8个字节的Elf64_Addr,这是因为两种系统下CPU寻址能力不同造成的。同理,文件偏移也从4字节的Elf32_Off变为8字节的Elf64_Off。有些成员字段虽然类型声名从Elf32_XXXX变成了Elf64_XXXX,该域所占的字节个数并未改变。如Elf32_Half和Elf64_Half都占两个字节,Elf32_Word、Elf32_Sword、Elf64_Word、Elf64_Sword全都是4个字节。
因此,我们可以发现32位系统的ELF头文件大小是52字节,但是64位系统的ELF头文件变成了64字节!!!(十进制的64用16进制表示是40,因此下图白色部分是ELF文件头的信息)
注意:由于intel及其兼容处理器使用小端法,此处的-x命令选项是一次性输出两个字节,457f实际表示为7f45,464c实际上是4c46,依次类推。
第一行:
(对应e_ident[EI_NIDENT]):实际表示内容为7f45 4c46 0201 0100 0000 0000 0000 0000。
前四个字节7f45 4c46(0x45,0x4c,0x46是’e‘,‘l‘,‘f‘对应的ASCⅡ)是一个魔数(magic number),表示这是一个ELF对象,接下来的一个字节02表示是一个64位对象(32位的对象是01),再接下来的一个字节01表示采用小端法表示,再接下来的一个字节01表示文件头版本,剩下的默认都设置为0。
第二行:
e_type(两个字节)值为0x0001,表示是一个重定位文件。
e_machine(两个字节)值为0x003e,表示是X86-64的处理器体系结构。
e_version(四个字节)值为0x00000001,表示是当前版本。
e_entry(八个字节)值为0x0000000000000000,表示没有入口点。
第三行:
e_phoff(八个字节)值为0x0000000000000000,表示没有程序头表。
e_shoff(八个字节)值为0x0000000000000458,表示段表的偏移地址。
第四行:
e_flags(四个字节)值为0x00000000,表示未知处理器特定标志(#define EF_SH_UNKNOWN 0x0);e_ehsize(两个字节)值为0x0040,表示elf文件头大小;
e_phentsize(两个字节)值均为0x0000,因为重定位文件没有程序头表。
e_phnum(两个字节)的值为0x0000,因为重定位文件没有程序头表。
e_ehentsize(两个字节)值为0x0040表示段头大小为64个字节(由这里知道section header table里面每个header的大小)。
e_shnum(两个字节)值为0x000d,表示段表入口有13个(由这里知道段表有13个段)。
e_shstrndx(两个字节)值为0x000a,表示段名串表的在段表中的索引号(由这里知道.shstrtab段(符号表)的信息在段表的索引号是10)。
2.段表 (section table)的分析 //64位的系统段表包括以下两个部分
段入口的类型定义如下(/usr/include/elf.h)
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; typedef struct { Elf64_Word sh_name; /* Section name (string tbl index) */ Elf64_Word sh_type; /* Section type */ Elf64_Xword sh_flags; /* Section flags */ Elf64_Addr sh_addr; /* Section virtual addr at execution */ Elf64_Off sh_offset; /* Section file offset */ Elf64_Xword sh_size; /* Section size in bytes */ Elf64_Word sh_link; /* Link to another section */ Elf64_Word sh_info; /* Additional section information */ Elf64_Xword sh_addralign; /* Section alignment */ Elf64_Xword sh_entsize; /* Entry size if section holds table */ } Elf64_Shdr;
与文件头大小变化方式同理,我们可以发现32位系统的段表定义了每个段头大小是28H字节,但是64位系统变成了40H字节即64个字节。
以上两个表涉及两个数据结构,ELF文件头的数据结构存储在位偏移为0x 0000000000000000-0000000000000039之间,一共40H(十进制为64)个字节,而段表存储在0x00000000000001c8-0000000000000340之间,一共40H*13=340H字节。
3.符号表的数据结构分析
符号表入口的类型定义如下(/usr/include/elf.h)
/* Symbol table entry. */ typedef struct { Elf32_Word st_name; /* Symbol name (string tbl index) */ Elf32_Addr st_value; /* Symbol value */ Elf32_Word st_size; /* Symbol size */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf32_Section st_shndx; /* Section index */ } Elf32_Sym; typedef struct { Elf64_Word st_name; /* Symbol name (string tbl index) */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf64_Section st_shndx; /* Section index */ Elf64_Addr st_value; /* Symbol value */ Elf64_Xword st_size; /* Symbol size */ } Elf64_Sym;
该结构大小为16个字节,,所以总共可以分成16个符号(有些符号未使用)。
32位系统的符号表结构体大小是16个字节,但是64位系统的符号表结构体大小变成了22个字节,而整个符号表段的大小为168H(360D)个字节,360=22*16,所以总共可以分成16个符号(有些符号未使用)这16个符号刚好和前面readelf –s ELF_1.o命令输出的符号表(15个符号,第一个符号为空,所以被忽略)结果相对应:
符号表在ELF文件中的具体内容见下文。
找到并分析常见的section
.text section
.text section是可执行指令的集合, .data 和.text 都是属于PROGBITS类型的section,这是将来要正常运行的程序和代码。
查询段表可知.text section的位偏移为0x0000040,size=98H,使用hexdump -x ELF_1.o命令可以查看.text section的内容,如下图。
使用命令objdump –d ELF_1.o对ELF_1.o进行反汇编,得到的汇编代码对应ELF_1.o的ELF文件
.data section
.data是初始化后数据的集合,.data section 的位偏移为00000d8,size=4H,红框内的值是初始化的数据,即ELF_1.c中a=7的反映。
.symtab section
symtab section存放所有section中定义的的符号名字,如 "data_items","start_loop" 。.symtab是属于SYMTAB类型的section,它描述了.strtab中的符号在"内存"中对应的"内存地址",当然这里的还不是真正的内存地址,只是一个偏移量,等到链接之后就是真正的了。
.strtab section的位偏移=0x0000168,size=168H
.strtab section
.shstrtab和.strtab属于STRTAB类型的section,可以在文件中看到,它们都存着字符串,shstrtab存的是section的名字,而.strtab存的是符号的名字(符号表示一个固定的内存地址)。下图显示的是.strtab section.
.strtab section的位偏移=00002d0,size=2fH
以上是关于ELF文件格式分析的主要内容,如果未能解决你的问题,请参考以下文章