%title缩略图

ELF文件头

ELF文件。在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。
1999年,被86open项目选为x86架构上的类Unix操作系统的二进制文件标准格式,用来取代COFF。因其可扩展性与灵活性,也可应用在其它处理器、计算机系统架构的操作系统上。

注:以上解释摘自百度百科

什么是ELF文件头呢?指的就是linux系统下的二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的头部,人有头,文件也有头,人的头部是由头发、眼睛、鼻子和耳朵等等组成的,那么ELF文件的头呢?是由哪些组成的呢?下面让我们来一起看一下。

我们从elf手册查看一下头部的构成,如下图:

%title插图%num

可以看出头部的构成由如下几个字段构成,一共14个。

e_ident
e_type
e_machine
e_version
e_entry
e_phoff
e_shoff
e_flags
e_ehsize
e_phentsize
e_phnum
e_shentsize
e_shnum
e_shstrndx

下面我们挨个看。

e_ident

我们可以从图中看出e_ident字段是个16位长度的数组,即ELF头文件的头部16个字节就是该字段对应的内容。下面使用C语言写了一个hello,world当作示例来看看头部16个字节是什么内容。

要用到xxd指令。该指令是以十六进制显示文件内容。接下来使用xxd读取elf文件的前16个字节。如下图:

%title插图%num

读取到了头部的16个字节的内容,但这16个字节的内容是什么呢?每一位分别是什么呢?下面来拆解。

我们就从ELF手册一个一个来看。
先看看e_ident数组的前4个字节:

此图像的alt属性为空;文件名为image-4.png

前4个字节是:7f 45 4c 46 分别对应的是图中的EI_MAG0、EI_MAG1、EI_MAG2和EI_MAG3。可以看到换成Ascii码就是ELF。有人说0x7f是什么意思呢?放在里面有什么意义或者含义吗?其实这个没有必要纠结,就是固定的,凡是ELF文件的头部第一个字节必然是0x7f,第二个字节必然是0x45,第三个字节必然是0x4c,第四个字节必然是0x46。其余字节会稍有不同。

继续往下看ELF手册,如下图是e_ident数组的第5个字节的解释:

此图像的alt属性为空;文件名为image-6.png

翻译过来就是代表这个二进制文件的体系结构,即通常说的32位还是64位。图中可以看出该字节可能出现的值分别是ELFCLASSNONE、ELFCLASS32和ELFCLASS64,3种值,如下图是4.4.166的内核,可以看到ELFCLASSNONE的值是0,ELFCLASS32的值是1,ELFCLASS64的值是2,一般见到的都是ELFCLASS32和ELFCLASS64这2种,ELFCLASS32表示32位的,ELFCLASS64表示64位的

此图像的alt属性为空;文件名为image-7.png

因此e_ident数组的第5个字节EI_CLASS是1则表示该文件是32位系统下的文件,如果该字段是2则表示该文件是64位系统下的文件。
从本文中的第一张截图可以看出第5个字节是2所以表示是64位系统下的文件。

继续往下看ELF手册,如下图是e_ident数组的第6个字节的解释:

此图像的alt属性为空;文件名为image-8.png

翻译过来就是处理器处理数据时的编码方式,即通常所说的大端序还是小端序。
该字段(EI_DATA)分别有3种值:ELFDATANONE、ELFDATA2LSB和ELFDATA2MSB,具体对应的数值分别是0、1和2。如下图:

此图像的alt属性为空;文件名为image-9.png

我们日常所见的基本都是intel处理器,所以通常该字段都是1,为0和为2的情况目前我没有见过,有见过的可以与我分享一下。从本文中的第一张截图可以看出第6个字节是1,所以是小端序。即高位字节存放在高地址单元,低位字节存放在低地址单元。

继续往下看ELF手册,如下图是e_ident数组的第7个字节的解释:

此图像的alt属性为空;文件名为image-10.png

翻译过来就是ELF的版本号。手册里显示分别有2种值,分别是EV_NONE、EV_CURRENT,各自具体数值分别是0和1,如下图:

此图像的alt属性为空;文件名为image-11.png

目前来看该字段的值始终都是1,从本文中的第一张截图可以看出第7个字节正好是1。

继续往下看ELF手册,如下图是e_ident数组的第8个字节的解释:

此图像的alt属性为空;文件名为image-12.png

翻译过来简述就是该字段代表的是什么操作系统的ABI(应用程序二进制接口。ABI用于约束二进制程序的,让二进制程序在遵循规范的前提下与操作系统交互),该字段在不同的操作系统下值会不一样。我这里的hello,word程序该字段是0,即ELFOSABI_NONE这个值,如图对这个值做了进一步解释:Same as ELFOSABI_SYSV,如图ELFOSABI_SYSV就在ELFOSABI_NONE这行的下面,意思是Unix System V ABI。

注:一些文章对e_ident数组的第8个字节开始的数据的解释都说是填充的,这样的说法有些牵强,手册里有解释,应该遵循手册。

继续往下看ELF手册,如下图是e_ident数组的第9个字节的解释:

%title插图%num

翻译过来就是该字段表示的是ABI(应用程序二进制接口。ABI用于约束二进制程序的,让二进制程序在遵循规范的前提下与操作系统交互)的版本,该字段用于区分ABI是否兼容,兼容的话都是填充0,该字段与EI_OSABI即第8个字段相关联。

换句话说该字段有可能是非0,目前来看是没有见过非0的情况,因为我们看到的都是遵循ABI规范的二进制文件,所以该字段都是0。从本文中的第一张截图可以看出第9个字节也是0。

继续往下看ELF手册,如下图是e_ident数组的第10个字节的解释:

%title插图%num

翻译过来就是开始填充,这些是保留字节暂时都填充0,将来视情况而定。也就是e_ident字段这个数组从第10个字节开始填充0,即后6个字节(第10-16字节)都是填充0。从本文中的第一张截图可以看出第10个字节开始都是0。

以上e_ident字段的解释已经结束。

接下来是e_type

e_type

e_type的类型是uint16_t,所以它的长度是2个字节,根据上面已知头部前16个字节被e_ident数组使用了,那么从第17个字节开始至第18个字节结束就是e_type的内容了,如下图我们继续使用xxd指令看一下第17和第18字节的内容:

%title插图%num

内容是03 00,由于是小端序,所以是0x0003,即0x3。我们来看看手册里对该字段的解释。

%title插图%num

elf手册里该字段有5种情况,分别对应的值是0、1、2、3和4,如下图:

%title插图%num

我这边的hello,world示例文件的该字段值是3,对应的就是ET_DYN这个宏定义,表示的是这个文件是一个依赖于系统库才能运行的共享对象文件。
ET_NOE 未知类型
ET_REL 重定位文件
ET_EXEC 可执行文件
ET_DYN 共享对象文件
ET_CORE 核心文件,即内存转储文件时或者进程转储、内核崩溃产生的文件都是该类型。
ET_LOPROC和ET_HIPROC是一个范围。这2个定义了一段空间,空间大小是256,即预留256个不同的值给特定处理器的。

e_machine

e_machine的类型是uint16_t,所以它的长度是2个字节,根据上面已知头部前18个字节被e_ident数组和e_type字段使用了,那么第19个字节和第20个字节就是e_machine的内容了,如下图我们继续使用xxd指令看一下第19和第20字节的内容:

%title插图%num

内容是3e 00,由于是小端序,所以是0x003e,即0x3e,我们来看看手册里对该字段的解释。

%title插图%num

翻译过来就是该字段指的是单个文件的体系架构,查看4.4.166的linux源码包里的elf-em.h头文件中的定义,发现有42种,我这里的hello,world示例该字段是0x3e,换成十进制是62,看看里面有定义吗?

%title插图%num

找到了,是AMD的cpu,体系架构是x86-64。

e_version

e_version的类型是uint32_t,所以它的长度是4个字节,根据上面已知头部前20个字节被e_ident数组、e_type字段和e_machine字段使用了,那么第21个字节开始z至第24个字节就是e_version的内容了,如下图我们继续使用xxd指令看一下第21个字节至第24个字节的内容:

%title插图%num

内容是 01 00 00 00,由于是小端序,所以是0x00000001,即0x1,我们来看看手册里对该字段的解释。

%title插图%num

翻译过来就是该字段代表文件的版本号。在linux源码中有定义,如下图:

%title插图%num

0表示EV_NONE、1表示EV_CURRENT,我们的示例文件是1,所以是EV_CURRENT,即当前版本是1,目前我所见到的都是1。

e_entry

发现elf手册有点落后,我这边结合linux源码来分析吧,linux源码显示e_entry的类型是Elf64_Addr,查看实际类型是unsigned long

%title插图%num
%title插图%num
%title插图%num

unsigend long的字节长度是多少呢?在所在的操作系统上试试便知

%title插图%num

结果显示它的长度是8个字节,根据上面已知头部前24个字节被e_ident数组、e_type字段、e_machine字段和e_version字段使用了,那么第25个字节开始至第32个字节就是e_entry的内容了,如下图我们继续使用xxd指令看一下第25个字节至第32个字节的内容:

%title插图%num

内容是 50 10 00 00 00 00 00 00,由于是小端序,所以是0x0000000000001050,即0x1050,我们来看看手册里对该字段的解释。

%title插图%num

翻译出来比较晦涩,意思就是该字段是cpu执行该程序时的程序入口地址,我这入口地址是0x1050,用radare2静态分析时可以看到也是停止在该地址。

%title插图%num

e_phoff

e_phoff在linux源码中的elf.h头文件中的类型是Elf64_Off,查看实际类型发现是

%title插图%num
%title插图%num

又是__u64,上一个字段我们已经发现e_entry也是__u64,那么说明e_phoff字段的字节长度与e_entry的字节长度相等,也是8个字节。根据上一个字段来看已知头部前32个字节被e_ident数组、e_type字段、e_machine字段、e_version字段和e_entry字段使用了,那么第33个字节开始至第40个字节就是e_phoff字段的内容了,如下图我们继续使用xxd指令看一下第33个字节至第40个字节的内容:

%title插图%num

内容是4000000000000000,即0x40,表示什么意思呢,来进入elf手册看一下

%title插图%num

翻译过来就是该字段表示整个程序头表的文件偏移量,如果文件没有程序头表则该字段内容是0,本文主讲文件头,致于什么是程序头表咱不作讨论,当前只要知道该字段保存的是程序头表的偏移地址即可,我这里是0x40,说明程序头表的偏移地址是0x40

e_shoff

e_shoff字段在Linux源码中的类型是Elf64_Off,说明也是8个字节

%title插图%num

根据上一个字段来看已知头部前40个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段和e_phoff字段使用了,那么第41个字节开始至第48个字节就是e_shoff字段的内容了,如下图我们继续使用xxd指令看一下第41个字节至第48个字节的内容:

%title插图%num

内容是0x3960,继续看看elf手册对该字段的解释变知道内容代表什么意思了

%title插图%num

翻译过来就是该字段表示整个节头表的文件偏移量,如果文件没有节头表则该字段内容是0,本文主讲文件头,致于什么是节头表也先不作讨论,当前只要知道该字段保存的是节头表的偏移地址即可,我这里是0x3960,说明程序头表的偏移地址是0x3960

e_flags

e_flags在linux源码中的类型是Elf64_Word,实际类型是unsigned int,字节长度是4个字节

%title插图%num

根据上一个字段来看已知头部前48个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段和e_shoff字段使用了,那么第49个字节开始至第52个字节就是e_flags字段的内容了,如下图我们继续使用xxd指令看一下第49个字节至第52个字节的内容:

%title插图%num

内容是0,看看ELF手册对该字段的解释

%title插图%num

翻译过来就是该字段是该文件与特定的处理器相关联的标志,目前还未定义。所以我这边看到的是0。

e_ehsize

e_ehsize在linux源码中的类型是Elf64_Half

%title插图%num

实际类型是unsigned short,长度是2个字节。

%title插图%num
%title插图%num

根据上一个字段来看已知头部前52个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段、e_shoff字段和e_flags字段使用了,那么第53个字节开始至第54个字节就是e_ehsize字段的内容了,如下图我们继续使用xxd指令看一下第53个字节至第54个字节的内容:

%title插图%num

内容是0x40,看看elf手册对该字段的解释

%title插图%num

翻译过来很浅显,即ELF文件头的大小,我这里文件头的大小是0x40。

e_phentsize

e_phentsize字段在linux源码中的类型也是Elf64_Half,字节长度等同于e_ehsize,也是2个字节

%title插图%num

根据上一个字段来看已知头部前54个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段、e_shoff字段、e_flags字段和e_ehsize字段使用了,那么第55个字节开始至第56个字节就是e_phentsize字段的内容了,如下图我们继续使用xxd指令看一下第55个字节至第56个字节的内容:

%title插图%num

内容是0x38,看看elf手册对该字段的解释

%title插图%num

翻译过来就是该字段保存文件程序头表中一个条目的大小,并且所有条目的大小均相同。

注意:我看到有些文章对该字段描述的不太对,估计是没有认真参考elf手册和深入分析。

我这里是0x38,说明程序头表中的单个条目的大小是0x38个字节。

e_phnum

e_phnum字段在linux源码中的类型是等同于e_phentsize的类型,长度都是2个字节。

根据上一个字段来看已知头部前56个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段、e_shoff字段、e_flags字段、e_ehsize字段和e_phentsize字段使用了,那么第57个字节开始至第58个字节就是e_phnum字段的内容了,如下图我们继续使用xxd指令看一下第57个字节至第58个字节的内容:

%title插图%num

内容是0xb,看看elf手册对该字段的解释

%title插图%num

解释一大堆,简要翻译就是该字段表示的是程序头表中的条目总数,如果文件没有程序头表,则该字段是0,另外如果程序头表的条目总数大于或等于0xffff,则该字段的值会是0xffff,而实际的条目总数会保存在节头表的初始条目的sh_info字段中。

我这里是0xb,说明程序头表中一共有11个条目。思维发散,结合上一个字段即可计算出程序头部的大小,e_phnum*e_phentsize就是整个程序头表的大小。

e_shentsize

e_shentsize字段在linux源码中的类型是等同于e_phnum的类型,长度也是2个字节。

根据上一个字段来看已知头部前58个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段、e_shoff字段、e_flags字段、e_ehsize字段、e_phentsize字段和e_phnum字段使用了,那么第59个字节开始至第60个字节就是e_shentsize字段的内容了,如下图我们继续使用xxd指令看一下第59个字节至第60个字节的内容:

%title插图%num

内容是0x40,看看elf手册的解释

%title插图%num

翻译一下就是该字段保存文件节头表中一个节的大小,一个节是节头表中的一项,并且所有节的大小均相同。

我这里是0x40,说明节头表中的节的大小是0x40

e_shnum

e_shnum字段在linux源码中的类型是等同于e_shentsize的类型,长度也是2个字节。

根据上一个字段来看已知头部前60个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段、e_shoff字段、e_flags字段、e_ehsize字段、e_phentsize字段、e_phnum字段和e_shentsize字段使用了,那么第61个字节开始至第62个字节就是e_shnum字段的内容了,如下图我们继续使用xxd指令看一下第61个字节至第62个字节的内容:

%title插图%num

内容是0x1e,看看elf手册的解释

%title插图%num

又是翻译一大堆,简要翻译就是该字段表示的是节头表中的所有节的条目总数,如果文件没有节头表,则该字段是0。另外,如果节头表中的所有节的条目总数大于等于0xff00,则该字段的值位0,而实际值保存至节头表中的初始条目的sh_size字段中,反之则sh_size字段的值始终是0。

我这里0x1e,说明节头表中一共有0x1e个节,由此可知,e_shnum*e_shentsize的大小就是整个节头表的大小。

e_shstrndx

e_shstrndx字段在linux源码中的类型是等同于e_shnum的类型,长度也是2个字节。

根据上一个字段来看已知头部前62个字节被e_ident数组、e_type字段、e_machine字段、e_version字段、e_entry字段、e_phoff字段、e_shoff字段、e_flags字段、e_ehsize字段、e_phentsize字段、e_phnum字段、e_shentsize字段和e_shnum字段使用了,那么第63个字节开始至第64个字节就是e_shstrndx字段的内容了,如下图我们继续使用xxd指令看一下第63个字节至第6个字节的内容:

%title插图%num

内容是0x1d,看看elf手册对该字段的解释

%title插图%num

解释也挺长的,用通俗易懂的文字简述一下就是节头表中有一个节是存放节名称字符串表的索引值,这个值就保存在该字段中,如果该字段的值大于或等于0xff00,则该字段的值会是0xffff,而实际索引则保存在节头表的初始条目的sh_link字段中,反之则节头表中的初始条目的sh_link字段的值始终是0。

注:什么是节名称字符串表,本文赞不做讨论,后续视情况而定再介绍。

以上就是ELF文件整个头部的内容。如有纰漏欢迎批评指正。

附加

下面要用到readelf指令。readelf从字面儿上就知道什么意思了,就不去介绍它了,关于它的详细用法请自行查阅资料。

用readelf指令查看elf文件头。格式:readelf -h 文件

%title插图%num

发表回复