如何使用hex编辑器在Linux中制作一个可执行的ELF文件?

只是好奇。 这对于实际编程来说显然不是一个很好的解决scheme,但是我想在Bless(一个hex编辑器)中创build一个可执行文件。

我的架构是x86。 什么是我可以做的非常简单的程序? 你好世界? 无限循环? 类似于这个问题,但在Linux中。

Solutions Collecting From Web of "如何使用hex编辑器在Linux中制作一个可执行的ELF文件?"

正如我的评论中所提到的,你将基本上为可执行文件编写自己的精灵标头,从而消除不需要的部分。 还有几个需要的部分。 Muppetlabs-TinyPrograms的文档对解释这个过程做了一个公平的工作。 为了好玩,这里有几个例子:

相当于/ bin / true(45字节):

 00000000 7F 45 4C 46 01 00 00 00 00 00 00 00 00 00 49 25 |.ELF..........I%| 00000010 02 00 03 00 1A 00 49 25 1A 00 49 25 04 00 00 00 |......I%..I%....| 00000020 5B 5F F2 AE 40 22 5F FB CD 80 20 00 01 |[_..@"_... ..| 0000002d 

你的经典“Hello World!” (160字节):

 00000000 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 03 00 01 00 00 00 74 80 04 08 34 00 00 00 |........t...4...| 00000020 00 00 00 00 00 00 00 00 34 00 20 00 02 00 28 00 |........4. ...(.| 00000030 00 00 00 00 01 00 00 00 74 00 00 00 74 80 04 08 |........t...t...| 00000040 74 80 04 08 1f 00 00 00 1f 00 00 00 05 00 00 00 |t...............| 00000050 00 10 00 00 01 00 00 00 93 00 00 00 93 90 04 08 |................| 00000060 93 90 04 08 0d 00 00 00 0d 00 00 00 06 00 00 00 |................| 00000070 00 10 00 00 b8 04 00 00 00 bb 01 00 00 00 b9 93 |................| 00000080 90 04 08 ba 0d 00 00 00 cd 80 b8 01 00 00 00 31 |...............1| 00000090 db cd 80 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a |...Hello world!.| 000000a0 

不要忘记让他们可执行文件…

反编译一个NASM hello world并理解其中的每个字节

这个答案的版本有一个很好的TOC和更多的内容: http : //www.cirosantilli.com/elf-hello-world (在这里达到30k字符限制)

标准

ELF由LSB指定:

  • 核心泛型: http : //refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
  • 核心AMD64: http : //refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html

LSB基本上与其他标准联系在一起,只是次要的扩展,特别是:

一个方便的总结可以在:

 man elf 

它的结构可以通过像readelfobjdump这样的工具以可读的方式进行检查。

生成示例

我们来分析一个最小的可运行的Linux x86-64例子:

 section .data hello_world db "Hello world!", 10 hello_world_len equ $ - hello_world section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, hello_world_len syscall mov rax, 60 mov rdi, 0 syscall 

编译:

 nasm -w+all -f elf64 -o 'hello_world.o' 'hello_world.asm' ld -o 'hello_world.out' 'hello_world.o' 

版本:

  • NASM 2.10.09
  • Binutils版本2.24(包含ld
  • Ubuntu 14.04

我们不使用C程序,因为这会使分析复杂化,这将是2级:-)

Hexdumps

 hd hello_world.o hd hello_world.out 

输出: https : //gist.github.com/cirosantilli/7b03f6df2d404c0862c6

全局文件结构

ELF文件包含以下部分:

  • ELF标题。 指向节头表和节目表头的位置。

  • 段头表(可执行文件可选)。 每个都有e_shnum节标题,每个标题指向节的位置。

  • N部分, N <= e_shnum (可执行文件可选)

  • 程序头表(只在可执行文件中)。 每个都有e_phnum程序标题,每个标题指向一个段的位置。

  • N段, N <= e_phnum (可执行文件可选)

这些部分的顺序是固定的:唯一固定的东西是ELF头文件必须是文件中的第一件事:通用文档说:

ELF标题

观察标题最简单的方法是:

 readelf -h hello_world.o readelf -h hello_world.out 

输出: https : //gist.github.com/cirosantilli/7b03f6df2d404c0862c6

目标文件中的字节数:

 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............| 00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......| 00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |....@.....@.....| 

可执行文件:

 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>.......@.....| 00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............| 00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 |....@.8...@.....| 

代表的结构:

 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; 

手册细目:

  • 0 0: EI_MAG = 7f 45 4c 46 = EI_MAG 0x7f 'E', 'L', 'F' :ELF幻数

  • 0 4: EI_CLASS = 02 = ELFCLASS64 :64位精灵

  • 0 5: EI_DATA = 01 = ELFDATA2LSB :大端数据

  • 0 6: EI_VERSION = 01 :格式版本

  • 0 7: EI_OSABI (仅在2003更新)= 00 = ELFOSABI_NONE :没有扩展名。

  • 0 8: EI_PAD = 8x 00 :保留字节。 必须设置为0。

  • 1 0: e_type = 01 00 = 1(大端)= ET_REl :可重定位格式

    在可执行文件上,对于ET_EXEC02 00

  • 1 2: e_machine = 3e 00 = 62 = EM_X86_64 :AMD64架构

  • 1 4: e_version = 01 00 00 00 :必须是1

  • 1 8: e_entry = 8x 00 :执行地址入口点,如果不适用,则为0,因为没有入口点。

    在可执行文件上,它是b0 00 40 00 00 00 00 00 。 TODO:还有什么我们可以设置? 内核似乎把IP直接放在那个值上,而不是硬编码。

  • 2 0: e_phoff = 8x 00 :程序头表偏移量,如果不存在,则为0。

    40 00 00 00在可执行文件上,即在ELF头之后立即启动。

  • 2 8: e_shoff = 40 7x 00 = 0x40 :段头表文件偏移量,如果不存在,则为0。

  • 3 0: e_flags = 00 00 00 00 TODO。 拱特定。

  • 3 4: e_ehsize = 40 00 :这个精灵标题的大小。 TODO为什么这个领域? 它如何变化?

  • 3 6: e_phentsize = 00 00 :每个程序头的大小,如果不存在则为0。

    38 00可执行文件:长度为56个字节

  • 3 8: e_phnum = 00 00 :程序头条目的数量,如果不存在则为0。

    02 00可执行文件:有2个条目。

  • 3 A: e_shentsizee_shnum = 40 00 07 00 :节标题大小和条目数

  • 3 E: e_shstrndxSection Header STRing iNDeX )= 03 00.shstrtab部分的索引。

节头表

Elf64_Shdr结构数组。

每个条目都包含关于给定部分的元数据。

ELF头部的e_shoff给出起始位置,这里是0x40。

ELF头部的e_shentsizee_shnum表示我们有7个条目,每个条目长度为0x40个字节。

所以表格需要从0x40到0x40 + 7 + 0x40 - 1 = 0x1FF的字节。

某些部分名称是为某些部分类型保留的: http : //www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections例如.text需要SHT_PROGBITS类型和SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o

 There are 7 section headers, starting at offset 0x40: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .data PROGBITS 0000000000000000 00000200 000000000000000d 0000000000000000 WA 0 0 4 [ 2] .text PROGBITS 0000000000000000 00000210 0000000000000027 0000000000000000 AX 0 0 16 [ 3] .shstrtab STRTAB 0000000000000000 00000240 0000000000000032 0000000000000000 0 0 1 [ 4] .symtab SYMTAB 0000000000000000 00000280 00000000000000a8 0000000000000018 5 6 4 [ 5] .strtab STRTAB 0000000000000000 00000330 0000000000000034 0000000000000000 0 0 1 [ 6] .rela.text RELA 0000000000000000 00000370 0000000000000018 0000000000000018 4 2 4 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) 

每个条目表示的struct

 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; 

索引0部分

包含在字节0x40至0x7F中。

第一部分总是神奇的: http : //www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html说:

如果段数大于或等于SHN_LORESERVE(0xff00),则e_shnum的值为SHN_UNDEF(0),段标题表项的实际编号包含在段标题的索引为0的sh_size字段中(否则,初始条目的sh_size成员包含0)。

Figure 4-7: Special Section Indexes中还有其他一些魔术部分Figure 4-7: Special Section Indexes

SHT_NULL

在索引0中, SHT_NULL是强制的。 是否还有其他用途: ELF中SHT_NULL部分的用法是什么? ?

.data部分

.data是第1部分:

 00000080 01 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |................| 00000090 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |................| 000000a0 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000b0 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 
  • 80 0: sh_name = 01 00 00 00.shstrtab字符串表中的索引1

    在这里, 1表示这个部分的名字从该部分的第一个字符开始,结束于第一个NUL字符,组成字符串.data

    .data是具有预定意义的部分名称之一http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    这些部分保存对程序的内存镜像有贡献的初始化数据。

  • 80 4: sh_type = 01 00 00 00SHT_PROGBITS :段内容不是由ELF指定的,只是由程序如何解释它。 正常自.data节。

  • 80 8: sh_flags = 03 7x 00SHF_ALLOCSHF_EXECINSTR : http : //www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags ,根据.data部分的要求

  • 90 0: sh_addr = 8x 00 :执行期间将放置该部分的虚拟地址,如果未放置,则为0

  • 90 8: sh_offset = 00 02 00 00 00 00 00 00 = 0x200 :从程序开始到本节第一个字节的字节数

  • a0 0: sh_size = 0d 00 00 00 00 00 00 00

    如果我们从sh_offset 200开始取0xD字节,我们看到:

     00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 |Hello world!.. | 

    AHA! 所以我们的"Hello world!" 字符串在数据部分就像我们告诉它在NASM上。

    一旦我们从高中毕业,我们会看起来像:

     readelf -x .data hello_world.o 

    其输出:

     Hex dump of section '.data': 0x00000000 48656c6c 6f20776f 726c6421 0a Hello world!. 

    NASM为该部分设置了体面的属性,因为它神奇地对待.data : http : //www.nasm.us/doc/nasmdoc7.html#section-7.9.2

    另外请注意,这是一个糟糕的部分选择:一个好的C编译器会将该字符串放在.rodata ,因为它是只读的,它可以进一步优化操作系统。

  • a0 8: sh_linksh_link = 8x 0:不适用于此节类型。 http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections

  • b0 0: sh_addralign = 04 = TODO:为什么这个对齐是必要的? 它只适用于sh_addr ,还适用于sh_addr符号?

  • b0 8: sh_entsize = 00 =该部分不包含表格。 如果!= 0,则表示该部分包含一个固定大小条目的表格。 在这个文件中,我们从readelf输出中看到, .symtab.rela.text部分就是这种情况。

.text部分

现在我们手动完成了一个部分,让我们毕业并使用其他部分的readelf -S

  [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 2] .text PROGBITS 0000000000000000 00000210 0000000000000027 0000000000000000 AX 0 0 16 

.text是可执行文件,但不可写:如果我们尝试写入Linux segfaults。 让我们看看我们是否真的有一些代码:

 objdump -d hello_world.o 

得到:

 hello_world.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <_start>: 0: b8 01 00 00 00 mov $0x1,%eax 5: bf 01 00 00 00 mov $0x1,%edi a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11: 00 00 00 14: ba 0d 00 00 00 mov $0xd,%edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 mov $0x3c,%eax 20: bf 00 00 00 00 mov $0x0,%edi 25: 0f 05 syscall 

如果我们在hd上输入b8 01 00 00 ,我们就会发现这只发生在00000210 ,这就是该节所说的。 尺寸是27,这也匹配。 所以我们必须谈论正确的部分。

这看起来像正确的代码: write然后exit

最有趣的部分是行a

 movabs $0x0,%rsi 

将字符串的地址传递给系统调用。 目前, 0x0只是一个占位符。 链接发生后,它将被修改为包含:

 4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 

由于.rela.text部分的数据,此修改是可能的。

SHT_STRTAB

使用sh_type == SHT_STRTAB被称为字符串表

他们持有一个空字符串的数组。

当字符串名称被使用时,这些部分被其他部分使用。 使用部分说:

  • 他们正在使用哪个字符串表
  • 什么是字符串开始的目标字符串表上的索引

所以,例如,我们可以有一个字符串表,其中包含:TODO:它是否必须以\0开头?

 Data: \0 abc \0 def \0 Index: 0 1 2 3 4 5 6 7 8 

如果另一个部分想要使用字符串def ,他们必须指向本节的索引5 (字母d )。

值得注意的字符串表部分:

  • .shstrtab
  • .strtab

.shstrtab

节类型: sh_type == SHT_STRTAB

通用名称: 节标题字符串表

部分名称.shstrtab保留。 标准说:

本部分包含部分名称。

本节由ELF头本身的e_shstrnd字段指向。

本节的字符串索引是由节标题的sh_name字段指向的,它表示字符串。

本节没有标记SHF_ALLOC ,所以它不会出现在正在执行的程序中。

 readelf -x .shstrtab hello_world.o 

得到:

 Hex dump of section '.shstrtab': 0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh 0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab.. 0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex 0x00000030 7400 t. 

本节中的数据具有固定的格式: http : //www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

如果我们查看其他部分的名称,我们会看到它们都包含数字,例如.text部分是数字7

然后每个字符串在找到第一个NUL字符时结束,例如字符12.text\0之后是.text\0

的.symtab

节类型: sh_type == SHT_SYMTAB

通用名称: 符号表

首先我们注意到:

  • sh_link = 5
  • sh_info = 6

对于SHT_SYMTAB部分,这些数字表示:

  • 给出符号名称的字符串在第5节.strtab
  • 重定位数据在第6节.rela.text

一个很好的高级工具来拆解该部分是:

 nm hello_world.o 

这使:

 0000000000000000 T _start 0000000000000000 d hello_world 000000000000000d a hello_world_len 

然而,这是一个高层次的视图,省略了某些类型的符号和符号类型。 更详细的反汇编可以通过以下方式获得:

 readelf -s hello_world.o 

这使:

 Symbol table '.symtab' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start 

该表的二进制格式记录在http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

数据是:

 readelf -x .symtab hello_world.o 

这使:

 Hex dump of section '.symtab': 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000010 00000000 00000000 01000000 0400f1ff ................ 0x00000020 00000000 00000000 00000000 00000000 ................ 0x00000030 00000000 03000100 00000000 00000000 ................ 0x00000040 00000000 00000000 00000000 03000200 ................ 0x00000050 00000000 00000000 00000000 00000000 ................ 0x00000060 11000000 00000100 00000000 00000000 ................ 0x00000070 00000000 00000000 1d000000 0000f1ff ................ 0x00000080 0d000000 00000000 00000000 00000000 ................ 0x00000090 2d000000 10000200 00000000 00000000 -............... 0x000000a0 00000000 00000000 ........ 

这些条目是:

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

像在表格中一样,第一个条目是神奇的,并设置为一个固定的无意义值。

STT_FILE

条目1具有ELF64_R_TYPE == STT_FILEELF64_R_TYPEELF64_R_TYPE中继续。

字节分析:

  • 10 8: st_name = 01000000 = st_name字符1,直到下面的\0使得hello_world.asm

    链接器可以使用这条信息文件来决定哪个段的分段。

  • 10 12: st_info = 04

    位0-3 = ELF64_R_TYPE =类型= 4 = STT_FILE :这个入口的主要目的是用st_name来表示生成这个目标文件的文件的名字。

    位4-7 = ELF64_ST_BIND =绑定= 0 = STB_LOCALSTT_FILE必需值。

  • 10 13: st_shndx =符号表段头索引= f1ff = SHN_ABSSTT_FILE必需。

  • 20 0: st_value = 8x 00STT_FILE值是必需的

  • 20 8: st_size = 8x 00 :没有分配的大小

现在从readelf ,我们快速解释其他人。

STT_SECTION

有两个这样的条目,一个指向.data ,另一个指向.text (部分索引12 )。

 Num: Value Size Type Bind Vis Ndx Name 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 

TODO他们的目的是什么?

STT_NOTYPE

然后来最重要的符号:

 Num: Value Size Type Bind Vis Ndx Name 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start 

hello_world字符串在.data节(索引1)中。 它的值是0:它指向该部分的第一个字节。

_start被标记为GLOBAL可见性,因为我们写道:

 global _start 

在NASM。 这是必要的,因为它必须被视为切入点。 与C不同,默认情况下,NASM标签是本地的。

SHN_ABS

hello_world_len指向特殊的st_shndx == SHN_ABS == 0xF1FF

选择0xF1FF以免与其他部分发生冲突。

st_value == 0xD == 13这是我们存储在程序集上的值:字符串Hello World!的长度Hello World!

这意味着搬迁不会影响这个价值:这是一个常数。

这是我们的汇编程序为我们做的,并且有ELF支持的小优化。

如果我们在任何地方使用了hello_world_len的地址,那么汇编器就不能将它标记为SHN_ABS ,并且链接器稍后将会有额外的重定位工作。

SHT_SYMTAB在可执行文件上

默认情况下,NASM .symtab在可执行文件中放置一个.symtab文件。

这只用于调试。 如果没有这些符号,我们完全是盲目的,必须对一切进行逆向工程。

你可以用objcopy剥离它,然后可执行文件仍然可以运行。 这样的可执行文件被称为剥离的可执行文件

.strtab

包含符号表的字符串。

这部分有sh_type == SHT_STRTAB

它由.symtab节的sh_link == 5 .symtab

 readelf -x .strtab hello_world.o 

得到:

 Hex dump of section '.strtab': 0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm 0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel 0x00000020 6c6f5f77 6f726c64 5f6c656e 005f7374 lo_world_len._st 0x00000030 61727400 art. 

这意味着全局变量不能包含NUL字符是ELF级别的限制。

.rela.text

节类型: sh_type == SHT_RELA

通用名称: 重定位部分

.rela.text保存重定位数据,该数据说明当最终可执行文件被链接时如何修改地址。 这指向文本区域的字节,当链接指向正确的内存位置时,必须对其进行修改。

基本上,它翻译包含占位符0x0地址的对象文本:

  a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11: 00 00 00 

到包含最终0x6000d8的实际可执行代码:

 4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00 

它是由.symtab部分的.symtab = 6 .symtab

readelf -r hello_world.o给出:

 Relocation section '.rela.text' at offset 0x3b0 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0 

该部分不存在于可执行文件中。

实际的字节是:

 00000370 0c 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 |................| 00000380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 

代表的struct是:

 typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rela; 

所以:

  • 370 0: r_offset = 0xC:这个重定位将修改地址的.text的地址

  • 370 8: r_info = 0x200000001。 包含2个字段:

    • ELF64_R_TYPE = 0x1:含义取决于确切的体系结构。
    • ELF64_R_SYM = 0x2:地址指向的部分的索引,所以.data在索引2处。

    AMD64 ABI表示类型1被称为R_X86_64_64 ,它表示操作S + A ,其中:

    • S :目标文件上的符号值,这里是0因为我们指向00 00 00 00 00 00 00 00 movabs $0x0,%rsi
    • A :加数,出现在r_added字段中

    该地址被添加到重定位操作的部分。

    这个重定位操作共占用8个字节。

  • 380 0: r_addend = 0

所以在我们的例子中,我们得出结论:新地址将是: S + A = .data + 0 ,因此是数据部分的第一件事。

程序头表

只出现在可执行文件中。

包含如何将可执行文件放入进程虚拟内存的信息。

可执行文件由链接器从目标文件生成。 链接器所做的主要工作是:

  • 确定目标文件的哪些部分将进入可执行文件的哪些段。

    在Binutils中,这归结为解析链接器脚本,并处理一堆默认值。

    您可以获得与ld --verbose一起使用的链接器脚本,并使用ld -T设置一个自定义的脚本。

  • 对文本部分进行重定位。 这取决于多个部分如何被放入内存。

readelf -l hello_world.out给出:

 Elf file type is EXEC (Executable file) Entry point 0x4000b0 There are 2 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000d7 0x00000000000000d7 RE 200000 LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8 0x000000000000000d 0x000000000000000d RW 200000 Section to Segment mapping: Segment Sections... 00 .text 01 .data 

在ELF头上, e_phoffe_phnume_phentsize告诉我们,有两个程序头文件,它们从0x40开始,每个长度为0x38字节,所以它们是:

 00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....| 00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................| 00000070 00 00 20 00 00 00 00 00 |.. ..... | 

和:

 00000070 01 00 00 00 06 00 00 00 | ........| 00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....| 00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............| 000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....| 

结构代表http://www.sco.com/developers/gabi/2003-12-17/ch5.pheader.html

 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; 

第一个细分:

  • 40 0: p_type = 01 00 00 00 = PT_LOAD :TODO。 我认为这意味着它将被实际加载到内存中。 其他类型可能不一定是。
  • 40 4: p_flags = 05 00 00 00 =执行并读取权限,不写入TODO
  • 40 8: p_offset = 8x 00 TODO:这是什么? 看起来像段起点的偏移。 但是这意味着有些细分是交织在一起的? 有可能玩一下: gcc -Wl,-Ttext-segment=0x400030 hello_world.c
  • 50 0: p_vaddr = 00 00 40 00 00 00 00 00 :将该段加载到的初始虚拟内存地址
  • 50 8: p_paddr = 00 00 40 00 00 00 00 00 :要加载到内存中的初始物理地址。 只对程序可以设置物理地址的系统很重要。 否则,像系统V一样,可以是任何东西。 NASM似乎只是复制p_vaddrr
  • 60 0: p_filesz = d7 00 00 00 00 00 00 00 :TODO vs p_memsz
  • 60 8: p_memsz = d7 00 00 00 00 00 00 00 :TODO
  • 70 0: p_align = 00 00 20 00 00 00 00 00 :0或1表示不需要对齐TODO是什么意思? 否则与其他领域的冗余

第二个是类似的。

那么:

  Section to Segment mapping: 

readelf部分告诉我们:

  • 0是.text段。 啊哈,所以这就是为什么它是可执行的,而不是可写的
  • 1是.data段。