ELF文件格式
本文最后更新于11 天前,其中的信息可能已经过时,如有错误可以直接在文章下留言

1.ELF文件结构

ELF文件全称 Executable and Linking Format(可执行链接格式),是Linux的主要可执行文件格式

ELF文件有三种类型,不同类型文件的格式不同,可以通过file命令来进行查看。

  • 1. 可重定位文件(Relocatable File):后缀名一般为.o,包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他可重定位目标文件合并起来创建一个可执行目标文件。
  • 2. 可执行文件 (Executable File):后缀名一般为.out,是静态链接的可执行文件,包含二进制代码和数据,可直接复制到内存中指向。
  • 3.共享目标文件(Shared Object File):后缀名一般为.so,包含可在两种上下文中链接的代码和数据,链接编辑器可以将它和其他可重定位文件和共享目标文件一起处理,生成另一个目标文件;此外,在加载或者运行时动态连接器(Dynamic Linker)可以将它动态载入内存并链接,与某个可执行文件以及其他共享目标一起组合创建进程映像。

在Linux中并不以后缀名作为区分文件格式的绝对标准,所以后缀名可有可无。

参考ELF 文件 – CTF Wiki

ELF文件有两种视图:执行试图(Execution View)和链接试图(Linking View)。对于执行视图,主要使用程序头部表来将它载入内存;对于链接视图,则需要根据截取头部表中的信息来完成链接操作。

ELF文件开始处事ELF头部(ELF Header),用于描述整个文件的组织。

程序头部表(Program Header Table)用于告诉系统如何创建进程映像。用于构造目标映像的目标文件必须具有程序头部表,可重定位文件并不需要该表。

节区头部表(Section Header Table)包含了描述文件节区的信息,用于链接的目标文件必须包含节区头部表,其他目标文件则不做要求。

对于共享目标文件,两种试图都必须存在。因为链接编辑器在链接的时候需要节区头部表来查看信息从而对各个目标文件进行链接,而加载器需要根据程序头部表把相应的段加载到进程的虚拟内存当中。

2.ELF头部结构

查看Linux系统中的/usr/include/elf.h文件,可以找到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;

可以看到结构体上,ELF32和ELF64没啥区别,其中每个成员都是 e 开头的,它们应该都是 ELF 的缩写。

在Linux上面可以通过命令

readelf -h sample.out

来查看ELF的头部

下面我们来介绍结构体中每个成员的用处。

e_ident

e_ident给出了ELF的一些标识信息,通常共有16个字节如图

数组的前四个字节,称为魔数,标识该文件是一个 ELF 目标文件。通常是7F 45 4C 46,第一个是0x7F,后面三个字符是ELF三个字符的Ascii码值。

第五个字节e_ident[EI_CLASS]则是标识文件的类型或容量,其可能的值和对应的类型如下

#define EI_CLASS        4               /* File class byte index */
#define ELFCLASSNONE    0               /* Invalid class */
#define ELFCLASS32      1               /* 32-bit objects */
#define ELFCLASS64      2               /* 64-bit objects */
#define ELFCLASSNUM     3

下一个字节e_ident[EI_DATA]字节给出了目标文件中的特定处理器数据的编码方式。编码类型如下

#define EI_DATA         5               /* Data encoding byte index */
#define ELFDATANONE     0               /* Invalid data encoding */
#define ELFDATA2LSB     1               /* 2's complement, little endian */
#define ELFDATA2MSB     2               /* 2's complement, big endian */
#define ELFDATANUM      3

这里小端序和大端序就不需要过多解释了,我对这个熟悉还是因为TEA系列的解密。

e_ident[EI_VERSION] 给出了 ELF 头的版本号码,一般为0x1,表示EV_CURRENT当前版本。

e_ident[EI_PAD] 给出了 e_ident 中未使用字节的开始地址。这些字节被保留并置为 0;处理目标文件的程序应该忽略它们。如果之后这些字节被使用,EI_PAD 的值就会改变。

e_type

e_type 标识目标文件类型。

/* Legal values for e_type (object file type).  */

#define ET_NONE         0               /* No file type */
#define ET_REL          1               /* Relocatable file */
#define ET_EXEC         2               /* Executable file */
#define ET_DYN          3               /* Shared object file */
#define ET_CORE         4               /* Core file */
#define ET_NUM          5               /* Number of defined types */
#define ET_LOOS         0xfe00          /* OS-specific range start */
#define ET_HIOS         0xfeff          /* OS-specific range end */
#define ET_LOPROC       0xff00          /* Processor-specific range start */
#define ET_HIPROC       0xffff          /* Processor-specific range end */

这里我用gcc编译C源文件产生的程序的e_type值竟然是3,有点意外,问了一下GPT,是正确的。

好像还有相关文章

关于Linux下gcc 编译 C 源文件时,生成的是Shared object file而不是Executable file_可执行文件类型确实dyn-CSDN博客

e_machine

标识文件的目标体系结构类型,表明可以在哪种平台上运行。

值和类型有很多,可以看/usr/include/elf.h文件,都写出来了,这里就不列出来了。

e_version

标识目标文件版本。

/* Legal values for e_version (version).  */

#define EV_NONE         0               /* Invalid ELF version */
#define EV_CURRENT      1               /* Current version */
#define EV_NUM          2

1 表示初始文件格式;未来扩展新的版本的时候 (extensions) 将使用更大的数字。虽然在上面值EV_CURRENT为 1,但是为了反映当前版本号,它可能会改变,比如 ELF 到现在也就是 1.2 版本。而且可以注意到上面的e_ident[EI_CLASS]也留了个3的值,也许未来会出现32位和64位系统以外的版本,就会更新其值。

e_entry

标识程序入口的虚拟地址,类似C语言中的main函数入口地址,如果没有相关的入口项,则这一项为 0。

对于so文件是共享目标文件,没有程序入口点,这个值可以忽略。

e_phoff

程序头部表格在文件中的字节偏移量,可根据该偏移量找到相应的程序头部表,如果文件中没有程序头部表,则为 0。

e_shoff

节区头部表在文件中的字节偏移量,可根据该偏移量找到相应的节区头部表,如果文件中没有节区头部表,则为 0。

e_flags

这一项给出文件中与特定处理器相关的标志,这些标志命名格式为EF_machine_flag

e_ehsize

ELF头部的大小,上面的图中为64,而程序头部表在文件中的字节偏移量也为64,说明ELF头部后紧挨着程序头部表

e_phentsize

程序头部表中每个表项的字节长度(Program Header Entry Size),每个表项的大小相同。上图中为56个字节

e_phnum

这一项给出程序头部表的项数( Program Header Entry Number )。上图中为13。因此,e_phnum 与 e_phentsize 的乘积即为程序头部表的字节长度。如果文件中没有程序头部表,则该项值为 0

e_shentsize

节区头部表中每个表项的字节长度(Section Header Entry Size),一个节头是节头表中的一项;节头表中所有项占据的空间大小相同。上图中为64个字节

e_shnum

这一项给出节头表中的项数(Section Header Number)。上图中为31.因此, e_shnum 与 e_shentsize 的乘积即为节头表的字节长度。如果文件中没有节头表,则该项值为 0。

e_shstrndx

这一项给出节区头部表中与节区名称字符串表相关的表项的索引值(Section Header Table IndeX Related With Section Name String Table)。如果文件中没有节名字符串表,则该项值为SHN_UNDEF

2.程序头部表(Program Header Table)

可执行文件或者共享目标文件的程序头部是一个结构体数组,每一个元素的类型是 Elf32_Phdr,描述了一个段或者其它系统在准备程序执行时所需要的信息。其中,ELF 头中的 e_phentsize 和 e_phnum 指定了该数组每个元素的大小以及元素个数。一个目标文件的段包含一个或者多个节。程序的头部只有对于可执行文件和共享目标文件有意义。

Program Header Table 就是专门为 ELF 文件运行时中的段所准备的。

Elf32_Phdr 的数据结构如下

typedef struct {
    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;

各字段说明如下

//p_type:该元素负责描述段的类型,或者表明了该结构的相关信息。
//p_offset:该字段给出了从文件开始到该段开头的第一个字节的偏移。
//p_vaddr:该字段给出了该段第一个字节在内存中的虚拟地址。即相对偏移地址。
//p_paddr:标识该段将被放在内存中的物理地址,但现代常见的体系架构中很少直接使用物理地址,所以这里p_paddr的值与p_vaddr相同
//p_filesz:该字段给出了文件映像中该段所占的字节数,可能为 0。
//p_memsz:该字段给出了内存映像中该段所占的字节数,可能为 0。
//p_flags:标识段的标志
//p_align:约束段的字节对齐

可执行文件中的段类型如下

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇