精华内容
下载资源
问答
  • ELF文件结构

    2021-09-20 17:35:33
    文章目录ELF文件结构1.磁盘中的elf文件1.1 宏观结构展示1.2 ELF header解释1.2.1 字段定义1.2.2 不同字段详细解释1.2.3 readelf读取elf头部1.3 section header table解释1.4 program header table解释1.5 section ...

    ELF文件结构

    博客参考:https://segmentfault.com/a/1190000016766079

    1.磁盘中的elf文件

    1.1 宏观结构展示

    •ELF文件头表(ELF header):记录了ELF文件的组织结构

    •程序头表/段表(Program header table)

    1. 告诉系统如何创建进程

    2. 生成进程的可执行文件必须拥有此结构

    3. 重定位文件不一定需要

    •节头表(Section header table)

    1. 记录了ELF文件的节区信息

    2. 用于链接的目标文件必须拥有此结构

    3. 其它类型目标文件不一定需要

    1.2 ELF header解释

    elf header的定义在:/usr/include/elf.h

    1.2.1 字段定义

    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;
    

    1.2.2 不同字段详细解释

    1. e_ident占16个字节。前四个字节被称作ELF的Magic Number。后面的字节描述了ELF文件内容如何解码等信息。

    ​ 如图,前4个字节是ELF的Magic Number,固定为7f 45 4c 46
    ​ 第5个字节指明ELF文件是32位还是64位的。
    ​ 第6个字节指明了数据的编码方式,即我们通常说的little endian或是big endian。little endian我喜欢称作小头在前,低位字节在前,或者直接说低位字节在低 位地址,比如0x7f454c46,存储顺序就是46 4c 45 7f。big endian就是大头在前,高位字节在前,直接说就是高位字节在低位地址,比如0x7f454c46,在 文件中的存储顺序是7f 45 4c 46
    ​ 第7个字节指明了ELF header的版本号,目前值都是1。
    ​ 第8-16个字节,都填充为0。

    1. e_type,2字节,描述了ELF文件的类型。以下取值有意义:
     ET_NONE, 0, No file type
     ET_REL, 1, Relocatable file(可重定位文件,通常是文件名以.o结尾,目标文件)
     ET_EXEC, 2, Executable file (可执行文件)
     ET_DYN, 3, Shared object file (动态库文件,你用gcc编译出的二进制往往也属于这种类型,惊讶吗?)
     ET_CORE, 4, Core file (core文件,是core dump生成的吧?)
     ET_NUM, 5,表示已经定义了5种文件类型
     ET_LOPROC, 0xff00, Processor-specific
     ET_HIPROC, 0xffff, Processor-specific
    
    1. e_machine,2字节。描述了文件面向的架构,可取值如下(参见/usr/include/elf.h中的EM_开头的宏定义):
     EM_NONE, 0, No machine
     EM_M32, 1, AT&T WE 32100
     EM_SPARC, 2, SPARC
     EM_386, 3, Intel 80386
     EM_68K, 4, Motorola 68000
     EM_88K, 5, Motorola 88000
     EM_860, 7, Intel 80860
     EM_MIPS, 8, MIPS RS3000
     ... ...
    
    1. e_version,2字节,描述了ELF文件的版本号,合法取值如下:
    2. e_entry,(32位4字节,64位8字节),执行入口点,如果文件没有入口点,这个域保持0。
    3. e_phoff, (32位4字节,64位8字节),program header table的offset,如果文件没有PH,这个值是0。
    4. e_shoff, (32位4字节,64位8字节), section header table 的offset,如果文件没有SH,这个值是0。
    5. e_flags, 4字节,特定于处理器的标志,32位和64位Intel架构都没有定义标志,因此eflags的值是0。
    6. e_ehsize, 2字节,ELF header的大小,32位ELF是52字节,64位是64字节。
    7. e_phentsize,2字节。program header table中每个入口的大小。
    8. e_phnum, 2字节。如果文件没有program header table, e_phnum的值为0。e_phentsize乘以e_phnum就得到了整个program header table的大小。
    9. e_shentsize, 2字节,section header table中entry的大小,即每个section header占多少字节。
    10. e_shnum, 2字节,section header table中header的数目。如果文件没有section header table, e_shnum的值为0。e_shentsize乘以e_shnum,就得到了整个section header table的大小。
    11. e_shstrndx, 2字节。section header string table index. 包含了section header table中section name string table。如果没有section name string table, e_shstrndx的值是SHN_UNDEF.

    1.2.3 readelf读取elf头部

    源程序

    // gcc test.c -o test
    #include <stdio.h>
    
    int main() {
    	printf("hellod world!");
    	return 0;
    }
    

    readelf查看头部内容:

    $ readelf -h test
    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:                              DYN (Shared object file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x1060
      Start of program headers:          64 (bytes into file)
      Start of section headers:          14712 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         13
      Size of section headers:           64 (bytes)
      Number of section headers:         31
      Section header string table index: 30
    

    1.3 section header table解释

    写汇编程序时,用.text,.bss,.data这些指示,都指的是section,比如.text,告诉汇编器后面的代码放入.text section中。

    目标代码文件中的section和section header table中的条目是一一对应的。section的信息用于链接器对代码重定位。

    因此,section是一个逻辑层面的节,是用来方便连接器识别的,在链接时section header table是必须的

    可以用readelf -S 读出section header table:

    还是用1.2中的源代码:

    $ readelf -S test.o
    There are 14 section headers, starting at offset 0x320:
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .text             PROGBITS         0000000000000000  00000040
           0000000000000020  0000000000000000  AX       0     0     1
      [ 2] .rela.text        RELA             0000000000000000  00000260
           0000000000000030  0000000000000018   I      11     1     8
      [ 3] .data             PROGBITS         0000000000000000  00000060
           0000000000000000  0000000000000000  WA       0     0     1
      [ 4] .bss              NOBITS           0000000000000000  00000060
           0000000000000000  0000000000000000  WA       0     0     1
      [ 5] .rodata           PROGBITS         0000000000000000  00000060
           000000000000000e  0000000000000000   A       0     0     1
      [ 6] .comment          PROGBITS         0000000000000000  0000006e
           000000000000002b  0000000000000001  MS       0     0     1
      [ 7] .note.GNU-stack   PROGBITS         0000000000000000  00000099
           0000000000000000  0000000000000000           0     0     1
      [ 8] .note.gnu.propert NOTE             0000000000000000  000000a0
           0000000000000020  0000000000000000   A       0     0     8
      [ 9] .eh_frame         PROGBITS         0000000000000000  000000c0
           0000000000000038  0000000000000000   A       0     0     8
      [10] .rela.eh_frame    RELA             0000000000000000  00000290
           0000000000000018  0000000000000018   I      11     9     8
      [11] .symtab           SYMTAB           0000000000000000  000000f8
           0000000000000138  0000000000000018          12    10     8
      [12] .strtab           STRTAB           0000000000000000  00000230
           000000000000002a  0000000000000000           0     0     1
      [13] .shstrtab         STRTAB           0000000000000000  000002a8
           0000000000000074  0000000000000000           0     0     1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
      L (link order), O (extra OS processing required), G (group), T (TLS),
      C (compressed), x (unknown), o (OS specific), E (exclude),
      l (large), p (processor specific)
    

    可以看出test.o中有14个节,Flags表示了这个节的权限,比如:.data 和 .bss的Flags的Flags都是WA,表示可写,需分配内存,这是数据段的特征;.text section的Flags是AX,表示要分配内存,并且是可执行的,这是代码。

    而如果去读test.o的program header table,则会显示不存在:

    $ readelf -l test.o
    
    There are no program headers in this file.
    

    1.4 program header table解释

    可执行文件的内容组织成为segment,因此在可执行文件中,program header table是必须的,而section header不是必须的,但没有strip过的二进制文件中都含有此信息。

    对test使用readelf -l 查看:

    $ readelf -l test
    
    Elf file type is DYN (Shared object file)
    Entry point 0x1060
    There are 13 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                     0x00000000000002d8 0x00000000000002d8  R      0x8
      INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                     0x000000000000001c 0x000000000000001c  R      0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000600 0x0000000000000600  R      0x1000
      LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                     0x00000000000001f5 0x00000000000001f5  R E    0x1000
      LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                     0x0000000000000160 0x0000000000000160  R      0x1000
      LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000258 0x0000000000000260  RW     0x1000
      DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                     0x00000000000001f0 0x00000000000001f0  RW     0x8
      NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                     0x0000000000000020 0x0000000000000020  R      0x8
      GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                     0x0000000000000248 0x0000000000000248  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     
       01     .interp 
       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
       03     .init .plt .plt.got .plt.sec .text .fini 
       04     .rodata .eh_frame_hdr .eh_frame 
       05     .init_array .fini_array .dynamic .got .data .bss 
       06     .dynamic 
       07     .note.gnu.property 
       08     .note.gnu.build-id .note.ABI-tag 
       09     .note.gnu.property 
       10     .eh_frame_hdr 
       11     
       12     .init_array .fini_array .dynamic .got 
    

    如输出所示,文件中共有13个segment。只有类型为LOAD的段(4个)是运行时真正需要的,即02、03、04、05段。

    • 03 .init .plt .plt.got .plt.sec .text .fini ,权限未:RE,可读可执行,.text在其中,因此为代码段
    • 04 .rodata .eh_frame_hdr .eh_frame ,权限为:R,只可读,因此存储只读数据,也就是程序中用到的字符串常量
    • 05 .init_array .fini_array .dynamic .got .data .bss ,它的权限为:RW,可读可写,因此为数据段

    1.5 section header table和program header table的关系

    程序在汇编为机器码(.o)后,下一步为链接,链接时需要知道节(section)信息,因此section header table在这个时候是必须的,此时elf文件的结构为1.1中的节示图;

    当链接完成,变为了可执行文件,如果后续程序运行起来,需要知道段(segment)信息,因此program header table在这个时候是必须的,此时elf文件的结构为1.1中的段示图。

    2.内存中的elf文件

    当elf运行起来后,会被加载到内存中(以下展示的是32位的情况):

    程序在内存中的情况,可以使用pwndbg的vmmap指令查看:

    pwndbg> vmmap
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
        0x555555554000     0x555555555000 r--p     1000 0      /mnt/d/study/ctf/test
        0x555555555000     0x555555556000 r-xp     1000 1000   /mnt/d/study/ctf/test
        0x555555556000     0x555555557000 r--p     1000 2000   /mnt/d/study/ctf/test
        0x555555557000     0x555555558000 r--p     1000 2000   /mnt/d/study/ctf/test
        0x555555558000     0x555555559000 rw-p     1000 3000   /mnt/d/study/ctf/test
        0x7ffff7dc4000     0x7ffff7de9000 r--p    25000 0      /usr/lib/x86_64-linux-gnu/libc-2.31.so
        0x7ffff7de9000     0x7ffff7f61000 r-xp   178000 25000  /usr/lib/x86_64-linux-gnu/libc-2.31.so
        0x7ffff7f61000     0x7ffff7fab000 r--p    4a000 19d000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
        0x7ffff7fab000     0x7ffff7fac000 ---p     1000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
        0x7ffff7fac000     0x7ffff7faf000 r--p     3000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
        0x7ffff7faf000     0x7ffff7fb2000 rw-p     3000 1ea000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
        0x7ffff7fb2000     0x7ffff7fb8000 rw-p     6000 0      
        0x7ffff7fca000     0x7ffff7fcd000 r--p     3000 0      [vvar]
        0x7ffff7fcd000     0x7ffff7fcf000 r-xp     2000 0      [vdso]
        0x7ffff7fcf000     0x7ffff7fd0000 r--p     1000 0      /usr/lib/x86_64-linux-gnu/ld-2.31.so
        0x7ffff7fd0000     0x7ffff7ff3000 r-xp    23000 1000   /usr/lib/x86_64-linux-gnu/ld-2.31.so
        0x7ffff7ff3000     0x7ffff7ffb000 r--p     8000 24000  /usr/lib/x86_64-linux-gnu/ld-2.31.so
        0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000 2c000  /usr/lib/x86_64-linux-gnu/ld-2.31.so
        0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000 2d000  /usr/lib/x86_64-linux-gnu/ld-2.31.so
        0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000 0      
        0x7ffffffde000     0x7ffffffff000 rw-p    21000 0      [stack]
    
    展开全文
  • elf文件结构

    2020-10-11 15:45:28
    elf文件结构 有三种类型 可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为 .o ...

    elf文件结构
    有三种类型
    可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为 .o 。

    可执行文件(Executable File),就是我们通常在 Linux 中执行的程序。

    共享目标文件(Shared Object File),包含代码和数据,这种文件是我们所称的库文件,一般以 .so 结尾。一般情况下,它有以下两种使用情景
    链接器(Link eDitor, ld)可能会处理它和其它可重定位文件以及共享目标文件,生成另外一个目标文件。
    动态链接器(Dynamic Linker)将它与可执行文件以及其它共享目标组合在一起生成进程镜像
    目标文件既会参与程序链接又会参与程序执行。出于方便性和效率考虑,根据过程的不同,目标文件格式提供了其内容的两种并行视图,如下
    在这里插入图片描述链接视图

    在这里插入图片描述文件开始处是 ELF 头部,给出文件的组织情况
    program header table 告诉系统如何创建进程
    section header table 描述文件节区信息

    ELF Header
    描述elf文件的概要信息

    #define EI_NIDENT   16
    
    typedef struct {
        unsigned char   e_ident[EI_NIDENT];'\x7felf'(魔数)
        ELF32_Half      e_type;
        ELF32_Half      e_machine;
        ELF32_Word      e_version;
        ELF32_Addr      e_entry;
        ELF32_Off       e_phoff;
        ELF32_Off       e_shoff;
        ELF32_Word      e_flags;
        ELF32_Half      e_ehsize;
        ELF32_Half      e_phentsize;
        ELF32_Half      e_phnum;
        ELF32_Half      e_shentsize;
        ELF32_Half      e_shnum;
        ELF32_Half      e_shstrndx;
    } Elf32_Ehdr;
    

    e_ident放魔数,64还是32,小端还是大端,EV_CURRENT(版本必须是),填充
    e_type是什么类型的elf文件
    e_mechine是什么机器架构
    e_version 是EV_CURRENt
    e_entey 是 程序入口点的虚拟地址
    e_phoff是程序头部表的字节偏移
    e_shoff是节头表(段表)的字节偏移
    e_flags不知道有什么用
    e_ehsize头部的字节长度(head)
    e_phentsize头部每个表相的字节长度(Program Header ENTry SIZE)
    e_phnum 头部表的项数(Program Header entry NUMbe)
    e_shentsize节头的字节长度
    e_shnum节头的项数
    e_shstrndx(看不懂)

    Program Header Table

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

    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;
    

    内存中任何段的虚拟地址与文件中对应的虚拟地址之间的差值对于任何一个可执行文件或共享对象来说是一个单一常量值。这个差值就是基地址,基地址的一个用途就是在动态链接期间重新定位程序
    段内容
    一个段可能包括一到多个节区,但是这并不会影响程序的加载。尽管如此,我们也必须需要各种各样的数据来使得程序可以执行以及动态链接等等
    Section Header Table
    其实这个数据结构是在 ELF 文件的尾部

    该结构用于定位 ELF 文件中的每个节区的具体位置。

    首先,ELF头中的 e_shoff 项给出了从文件开头到节头表位置的字节偏移。e_shnum 告诉了我们节头表包含的项数;e_shentsize 给出了每一项的字节大小。

    其次,节头表是一个数组,每个数组的元素的类型是 ELF32_Shdr ,每一个元素都描述了一个节区的概要内容

    typedef struct {
        ELF32_Word      sh_name;
        ELF32_Word      sh_type;
        ELF32_Word      sh_flags;
        ELF32_Addr      sh_addr;
        ELF32_Off       sh_offset;
        ELF32_Word      sh_size;
        ELF32_Word      sh_link;
        ELF32_Word      sh_info;
        ELF32_Word      sh_addralign;
        ELF32_Word      sh_entsize;
    } Elf32_Shdr;
    

    sh_name 节名称
    sh_type 节类型
    sh_flags 描述节是否可写,可执行,需要分配内存等属性
    sh_addr 在内存镜像的位置
    sh_offset在文件的偏移
    sh_size 节区的字节大小
    sh_link 此成员给出节区头部表索引链接
    sh_info 此成员给出附加信息
    sh_addralign 某些节区的地址需要对齐
    sh_entsize(不晓得)

    elf中一些特殊的表(段)

    .rel.xxxx (重定位表 用来描述修改段的内容 调指令的) sh_type为SHT_REL ()

    typedef struct {
        Elf32_Addr r_offset;//重定位入口点的偏移
        Elf32_Word r_info;//类型和符号
    }Elf32_Re;
    

    .strtab 字符串表
    .shstrtab 段表
    .symtab 符号表(经常被删。。。)
    Elf32_Sym的数组

    typedef struct {
      Elf32_Word st_name;//符号名
      Elf32_Addr st_value;//值 common块之类的
      Elf322_Word st_size;//符号大小
      unsigned char st_info;//符号类型和绑定信息
      unsigned char st_other;//没用
      Elf32_Half st_shndx;//符号所在段
    }
    

    (动态链接 延迟绑定的)
    got
    plt (dl_runtime_resolve())
    .got保留全局变量引用的地址
    .got.plt(函数引用的地址)(数组)
    0 .dynamic段的地址
    1 id
    2 _d1_runtime_resolve(的地址)
    .dynsym(动态符号表)

    展开全文
  • elf文件结构解析

    2019-08-02 21:52:28
    elf文件解析1、ELF文件结构格式:2、ELF头文件解析:3、ELF段表: 1、ELF文件结构格式: ELF Header .text .data .bss … section header table string tables symbol tables … elf文件头...

    1、ELF文件结构格式:

    ELF Header
    .text
    .data
    .bss
    section header table
    string tables
    symbol tables

    elf文件头包含了整个文件的基本属性,如文件版本,入口地址等,接着就是ELF文件的各个段

    2、ELF头文件解析:

    linux定义了自己的一套变量体系,如下

    自定义类型描述长度(字节)
    Elf32_Addr32位版本程序地址4
    Elf32_Half32位版本的无符号短整形2
    Elf32_Off32位版本的偏移地址4
    Elf32_Sword32位版本有符号整形4
    Elf32_Word32位版本无符号整形4
    Elf64_Addr64位版本程序地址8
    Elf64_Half64位版本的无符号短整形2
    Elf64_Off64位版本的偏移地址8
    Elf64_Sword64位版本有符号整形4
    Elf64_Word64位版本无符号整形4

    以32位版本为例,其头定义如下
    typedef struct{
    unsigned char e_ident[16];//前四个字节为ELF文件魔术,第五个字节为ELF文件类,0x01为32位,0x02为64位;第6位为字节序,规定为大端绪还是小端;第7位为elf文件的版本号,后9字节未定义,可作为扩展标志
    Elf32_Half e_type;//ELF文件类型,1表示可重定位文件,2表示可执行文件,3表示共享文件
    Elf32_Half e_machine;//ELF文件运行平台,1为AT&T WE 32100,2为SPARC,3为intel x86,4为Motorola 68000,5为Motorola 88000,6为intel 80860,
    Elf32_Word e_version;//elf版本号
    Elf32_Addr e_entry;//程序入口地址
    Elf32_Off e_phoff;//程序表头文件偏移
    Elf32_Off e_shoff;//段表在FOA中的便宜
    Elf32_Word e_flags;//标志位,用来标志文件平台等
    Elf32_Half e_ehsize;//ELF文件头大小
    Elf32_Half e_phentisize;//程序头表大小
    Elf32_Half e_phnum;//程序头表数目
    Elf32_Half e_shentsize;//段表描述符大小
    Elf32_Half e_shnum;//段表数量
    Elf32_Half e_shstrndx;//段表字符串表所在段在段表中的下标
    }Elf32_Ehdr;

    我们以一个ELF文件为例,
    在这里插入图片描述
    可以看到ELF头文件信息,我们打开以16进制打开一个这个文件
    在这里插入图片描述
    第一行前4个字节为 7F 45 4C 46,这个为ELF文件的魔数,其为DEL控制符,第5个字节为01,表示此文件为32位,第6个字节01表示小端序。与解析出的头文件内容相一致。
    第二行的前2个字节为0x0002(小端序),表示为此文件类型为可执行文件,第3,4字节为0x0003,表示在intel x86平台运行,第5-8字节为0x00000001,表示版本号为0x1,第9-12字节为0x080484B0,表示程序的入口地址。
    第三行的前四个字节为0x00002224,表示段表在文件中的偏移。第5-8个字节为0x00000000,为平台信息,等等后面可依次解析

    3、ELF段表:

    在这里插入图片描述
    成员信息如下:
    在这里插入图片描述
    由上知,elf段表的文件偏移为0x2224,找到文件偏移位置,如下
    在这里插入图片描述
    由上知第一个段表全为空,段表解析就不一一阐述,本文件的段表信息如下图
    在这里插入图片描述

    展开全文
  • ELF文件结构描述

    2019-10-24 16:20:31
    ELF文件结构描述 ELF目标文件格式最前部ELF文件头(ELF Header),它包含了描述了整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。其中ELF文件与段有关的重要结构就是段表(Section Header Tab...

    郑重声明: 该文章转载于以下博客园–博主yooooooo

    博客园-博主yooooooo
    此文对于小菜学习帮助很大,小菜实在是忍不住重新写了一遍加深理解。

    ELF文件结构描述

    ELF目标文件格式最前部ELF文件头(ELF Header),它包含了描述了整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。其中ELF文件与段有关的重要结构就是段表(Section Header Table)

    ELF文件格式

    1. 可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)
    2. 可执行文件:文件保存着一个用来执行的程序。(例如bash,gcc等)
    3. 共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件。)

    另外的windows下为pe格式的文件;

    ELF视图

    首先,ELF文件格式提供了两种视图,分别是链接视图和执行视图。
    在这里插入图片描述
    链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。总个文件可以分为四个部分:

    ELF header: 描述整个文件的组织。
    Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。
    sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出, segments与sections是包含的关系,一个segment包含若干个section。
    Section Header Table: 包含了文件各个segction的属性信息,我们都将结合例子来解释。

    在这里插入图片描述

    程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。
    节区头部表(Section Header Table)包含了描述文件节区的信息,比如大小、偏移等。

    如下图,可以通过执行命令”readelf -S android_server”来查看该可执行文件中有哪些section。
    在这里插入图片描述
    通过执行命令readelf –segments android_server,可以查看该文件的执行视图。
    在这里插入图片描述
    这验证了第一张图中所述,segment是section的一个集合,sections按照一定规则映射到segment。那么为什么需要区分两种不同视图?

    当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment。操作系统往往以页为基本单位来管理内存分配,一般页的大小为4096B,即4KB的大小。同时,内存的权限管理的粒度也是以页为单位,页内的内存是具有同样的权限等属性,并且操作系统对内存的管理往往追求高效和高利用率这样的目标。ELF文件在被映射时,是以系统的页长度为单位的,那么每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页。而我们从上面的例子中知道,一个ELF文件具有很多的section,那么会导致内存浪费严重。这样可以减少页面内部的碎片,节省了空间,显著提高内存利用率。

    文件头(ELF header)

    我们可以使用readelf命令来详细查看elf文件,代码如清单3-2所示:
    在这里插入图片描述
    从上面输出的结构可以看到:ELF文件头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等。

    ELF文件头结构及相关常数被定义在“/usr/include/elf.h”,因为ELF文件在各种平台下都通用,ELF文件有32位版本和64位版本的ELF文件的文件头内容是一样的,只不过有些成员的大小不一样。它的文件图也有两种版本:分别叫“Elf32_Ehdr”和“Elf64_Ehdr”。

    typedef struct {
        unsigned char e_ident[16];  
        Elf32_Half e_type;
        Elf32_Half e_machine;
        Elf32_Word e_version;
        Elf32_Addr e_entry;
        Elf32_Off e_phoff;
        Elf32_Off e_shoff;
        Elf32_Word e_flags;
        Elf32_Half e_ehsize;
        Elf32_Half e_phentsize;
        Elf32_Half e_phnum;
        Elf32_Half e_shentsize;
        Elf32_Half e_shnum;
        Elf32_Half e_shstrndx;
    }Elf32_Ehdr;
    

    在这里插入图片描述
    在ELF文件头中,我们需要重点关注以下几个字段:

    1. e_entry:程序入口地址 e_ehsize:ELF Header结构大小
    2. e_phoff、e_phentsize、e_phnum:描述Program Header Table的偏移、大小、结构。
    3. e_shoff、e_shentsize、e_shnum:描述Section Header Table的偏移、大小、结构。
    4. e_shstrndx:这一项描述的是字符串表在Section Header Table中的索引,值25表示的是Section
    5. Header Table中第25项是字符串表(String Table)。

    段表(Section Header Table)

    段表就是保存ELF文件中各种各样段的基本属性的结构。段表是ELF除了文件以外的最重要结构体,它描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。段表在ELF文件中的位置由ELF文件头的“e_shoff”成员决定的,比如SimpleSection.o中,段表位于偏移0x118。
    在这里插入图片描述

    typedef struct {
        Elf32_Word sh_name;        //section的名字
        Elf32_Word sh_type;        //section类别
        Elf32_Word sh_flags;        //section在进程中执行的特性(读、写)
        Elf32_Addr sh_addr;        //在内存中开始的虚地址
        Elf32_Off sh_offset;        //此section在文件中的偏移
        Elf32_Word sh_size;
        Elf32_Word sh_link;
        Elf32_Word sh_info;
        Elf32_Word sh_addralign;
        Elf32_Word sh_entsize; 
    }
    

    表(Section)

    符号表(.dynsym)

    符号表包含用来定位、重定位程序中符号定义和引用的信息,简单的理解就是符号表记录了该文件中的所有符号,所谓的符号就是经过修饰了的函数名或者变量名,不同的编译器有不同的修饰规则。例如符号_ZL15global_static_a,就是由global_static_a变量名经过修饰而来。

    符号表项的格式如下:

    typedef struct {  
         Elf32_Word st_name;      //符号表项名称。如果该值非0,则表示符号名的字
                                                 //符串表索引(offset),否则符号表项没有名称。
         Elf32_Addr st_value;       //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。
         Elf32_Word st_size;         //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。
         unsigned char st_info;    //符号的类型和绑定属性。
         unsigned char st_other;  //未定义。
         Elf32_Half st_shndx;        //每个符号表项都以和其他节区的关系的方式给出定义。
                 //此成员给出相关的节区头部表索引。
    } Elf32_sym;
    

    重定位表

    重定位表在ELF文件中扮演很重要的角色,首先我们得理解重定位的概念,程序从代码到可执行文件这个过程中,要经历编译器,汇编器和链接器对代码的处理。然而编译器和汇编器通常为每个文件创建程序地址从0开始的目标代码,但是几乎没有计算机会允许从地址0加载你的程序。如果一个程序是由多个子程序组成的,那么所有的子程序必需要加载到互不重叠的地址上。重定位就是为程序不同部分分配加载地址,调整程序中的数据和代码以反映所分配地址的过程。简单的言之,则是将程序中的各个部分映射到合理的地址上来。
    换句话来说,重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。
    具体来说,就是把符号的value进行重新定位。

    字符串表(.dynstr)

    ELF文件中用到了许多的字符串,比如段名,变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。
    通常用这种方式,在ELF文件中引用字符串只需给一个数字下标即可,不用考虑字符串的长度问题。一般字符串标在ELF文件中国也以段的方式保存,常见的段名为“.strtab”或“.shstrtab”。这两个字符串分别表示为字符串表和段表字符串表。
    只有分析ELF文件头,就可以得到段表和段表字符串表的位置,从而解析整个ELF文件。

    装载elf文件步骤

    首先在用户层面,shell进行会调用fork()系统调用创建一个新进程 - 新进程调用execve()系统调用执行制定的ELF文件 - 原来的shell进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入。

    execve()系统调用的原型如下:

    int execve(const char *filename, char *const argv[], char *const envp[]);
    

    它所对应的三个参数分别是程序文件名, 执行参数, 环境变量,通过对内核代码的分析,我们知道execve()系统调用的相应入口是sys_execve(),在sys_execve之后,内核会分别调用do_execve(), search_binary_handle(), load_elf_binary 等等,其中do_execve()是最主要的函数,所以接下来我们主要对它来进行具体分析。

    do_execve

    具体看一下下面链接:Linux进程启动过程分析do_execve(可执行程序的加载和运行)—Linux进程的管理与调度(十一)

    int do_execve(struct filename *filename,
        const char __user *const __user *__argv,
        const char __user *const __user *__envp)
    {
        return do_execve_common(filename, argv, envp);
    }
     
    //do_execve_common
    static int do_execve_common(struct filename *filename,
                    struct user_arg_ptr argv,
                    struct user_arg_ptr envp)
    {
        // 检查进程的数量限制
     
        // 选择最小负载的CPU,以执行新程序
        sched_exec();
     
        // 填充 linux_binprm结构体
        retval = prepare_binprm(bprm);
     
        // 拷贝文件名、命令行参数、环境变量
        retval = copy_strings_kernel(1, &bprm->filename, bprm);
        retval = copy_strings(bprm->envc, envp, bprm);
        retval = copy_strings(bprm->argc, argv, bprm);
     
        // 调用里面的 search_binary_handler 
        retval = exec_binprm(bprm);
     
        // exec执行成功
     
    }
     
    // exec_binprm
    static int exec_binprm(struct linux_binprm *bprm)
    {
        // 扫描formats链表,根据不同的文本格式,选择不同的load函数
        ret = search_binary_handler(bprm);
        // ...
        return ret;
    }
    
    • 如果想要了解elf文件格式,可以在命令行下面man elf,Linux手册中有参考.
    • 在do_exec()中会调用do_execve_common(),这个函数的参数与do_exec()一模一样
    • 在do_execve_common()中的sched_exec(),会选择一个负载最小的CPU来执行新进程,这里我们可以得知Linux内核中是做了负载均衡的.
    • 在这段代码中间出现了变量bprm,这个是一个重要的结构体struct linux_binfmt,下面我贴出此结构体的具体定义:
    /*
     * This structure is used to hold the arguments that are used when loading binaries.
    */
    // 内核中注释表明了这个结构体是用于保存载入二进制文件的参数.
    struct linux_binprm {
        char buf[BINPRM_BUF_SIZE];
    #ifdef CONFIG_MMU
        struct vm_area_struct *vma;
        unsigned long vma_pages;
    #else
        //...
        unsigned interp_flags;
        unsigned interp_data;
        unsigned long loader, exec;
    };
    
    • 在do_execve_common()中的searchbinaryhandler(),这个函数回去搜索和匹配合适的可执行文件装载处理过程,下面这个函数的精简代码:
    int search_binary_handler(struct linux_binprm *bprm)
    {
        // 遍历formats链表
        list_for_each_entry(fmt, &formats, lh) {
            if (!try_module_get(fmt->module))
                continue;
            read_unlock(&binfmt_lock);
            bprm->recursion_depth++;
     
            // 应用每种格式的load_binary方法
            retval = fmt->load_binary(bprm);
            read_lock(&binfmt_lock);
            put_binfmt(fmt);
            bprm->recursion_depth--;
            // ...
        }
        return retval;
    }
    

    这里需要说明的是,这里的fmt变量的类型是struct linux_binfmt *, 但是这一个类型与之前在do_execve_common()中的bprm是不一样的,具体定义如下:

    这里的linux_binfmt对象包含了一个单链表,这个单链表中的第一个元素的地址存储在formats这个变量中list_for_each_entry依次应用load_binary的方法,同时我们可以看到这里会有递归调用,bprm会记录递归调用的深度装载ELF可执行程序的load_binary的方法叫做load_elf_binary方法.

    下面会进行具体分析:

    /*
    * This structure defines the functions that are used to load the binary formats that
    * linux accepts.
    */
    struct linux_binfmt {
        struct list_head lh; //单链表表头
        struct module *module;
        int (*load_binary)(struct linux_binprm *);
        int (*load_shlib)(struct file *);
        int (*core_dump)(struct coredump_params *cprm);
        unsigned long min_coredump; /* minimal dump size */
    };
    

    == load_elf_binary()==

    static int load_elf_binary(struct linux_binprm *bprm)
    {
        // ....
        struct pt_regs *regs = current_pt_regs();  // 获取当前进程的寄存器存储位置
     
        // 获取elf前128个字节,作为魔数
        loc->elf_ex = *((struct elfhdr *)bprm->buf);
     
        // 检查魔数是否匹配
        if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
            goto out;
     
        // 如果既不是可执行文件也不是动态链接程序,就错误退出
        if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
            // 
        // 读取所有的头部信息
        // 读入程序的头部分
        retval = kernel_read(bprm->file, loc->elf_ex.e_phoff,
                     (char *)elf_phdata, size);
     
        // 遍历elf的程序头
        for (i = 0; i < loc->elf_ex.e_phnum; i++) {
     
            // 如果存在解释器头部
            if (elf_ppnt->p_type == PT_INTERP) {
                // 
                // 读入解释器名
                retval = kernel_read(bprm->file, elf_ppnt->p_offset,
                             elf_interpreter,
                             elf_ppnt->p_filesz);
     
                // 打开解释器文件
                interpreter = open_exec(elf_interpreter);
     
                // 读入解释器文件的头部
                retval = kernel_read(interpreter, 0, bprm->buf,
                             BINPRM_BUF_SIZE);
     
                // 获取解释器的头部
                loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);
                break;
            }
            elf_ppnt++;
        }
     
        // 释放空间、删除信号、关闭带有CLOSE_ON_EXEC标志的文件
        retval = flush_old_exec(bprm);
     
     
        setup_new_exec(bprm);
     
        // 为进程分配用户态堆栈,并塞入参数和环境变量
        retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
                     executable_stack);
        current->mm->start_stack = bprm->p;
     
        // 将elf文件映射进内存
        for(i = 0, elf_ppnt = elf_phdata;
            i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
     
            if (unlikely (elf_brk > elf_bss)) {
                unsigned long nbyte;
     
                // 生成BSS
                retval = set_brk(elf_bss + load_bias,
                         elf_brk + load_bias);
                // ...
            }
     
            // 可执行程序
            if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
                elf_flags |= MAP_FIXED;
            } else if (loc->elf_ex.e_type == ET_DYN) { // 动态链接库
                // ...
            }
     
            // 创建一个新线性区对可执行文件的数据段进行映射
            error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
                    elf_prot, elf_flags, 0);
     
            }
     
        }
     
        // 加上偏移量
        loc->elf_ex.e_entry += load_bias;
        // ....
     
     
        // 创建一个新的匿名线性区,来映射程序的bss段
        retval = set_brk(elf_bss, elf_brk);
     
        // 如果是动态链接
        if (elf_interpreter) {
            unsigned long interp_map_addr = 0;
     
            // 调用一个装入动态链接程序的函数 此时elf_entry指向一个动态链接程序的入口
            elf_entry = load_elf_interp(&loc->interp_elf_ex,
                            interpreter,
                            &interp_map_addr,
                            load_bias);
            // ...
        } else {
            // elf_entry是可执行程序的入口
            elf_entry = loc->elf_ex.e_entry;
            // ....
        }
     
        // 修改保存在内核堆栈,但属于用户态的eip和esp
        start_thread(regs, elf_entry, bprm->p);
        retval = 0;
        // 
    }
    

    这段代码相当之长,我们做了相当大的精简,虽然对主要部分做了注释,但是为了方便我还是把主要过程阐述一遍:

    1. 检查ELF的可执行文件的有效性,比如魔数,程序头表中段(segment)的数量
    2. 寻找动态链接的.interp段,设置动态链接路径
    3. 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码,数据,只读数据
    4. 初始化ELF进程环境
    5. 将系统调用的返回地址修改为ELF可执行程序的入口点,这个入口点取决于程序的连接方式,对于静态链接的程序其入口就是e_entry,而动态链接的程序其入口是动态链接器
    6. 最后调用start_thread,修改保存在内核堆栈,但属于用户态的eip和esp,该函数代码如下:

    start_thread

    void start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
    {
        set_user_gs(regs, 0); // 将用户态的寄存器清空
        regs->fs        = 0;
        regs->ds        = __USER_DS;
        regs->es        = __USER_DS;
        regs->ss        = __USER_DS;
        regs->cs        = __USER_CS;
        regs->ip        = new_ip; // 新进程的运行位置- 动态链接程序的入口处
        regs->sp        = new_sp; // 用户态的栈顶
        regs->flags     = X86_EFLAGS_IF;
     
        set_thread_flag(TIF_NOTIFY_RESUME);
    }
    

    总结

    如你所见,执行程序的过程是一个十分复杂的过程,exec本质在于替换fork()后,根据制定的可执行文件对进程中的相应部分进行替换,最后根据连接方式的不同来设置好执行起始位置,然后开始执行进程。

    展开全文
  • 对PE文件结构和ELF文件结构的具体描述,PE是windows操作系统的,ELF是linux操作系统的
  • Linux[ELF]: ELF文件结构简单梳理

    千次阅读 2019-05-15 23:20:52
    Linux[ELF]: ELF文件结构简单梳理 一.编译过程-ELF文件生成 二.ELF的文件概述 2.1 4种ELF文件类型 ELF文件类型 说明 实例 Relocatable File 包含例代码和数据,可以被链接成可执行文件或共享目标文件 Linux...
  • ELF文件结构 基本结构 ELF Header .text .data .bss other sections Section header table String Tables Symbol Tables … 文件头 ELF文件头(Elf header)位于最前部...
  • ELF文件结构解析

    2019-08-16 14:43:01
    ELF文件结构 File Header .text section .data section .bss section . . . 目标文件中的内容:编译后的机器指令代码、数据、链接时所须耍的一些信息(比如...
  • elf文件结构分析

    千次阅读 2018-03-23 14:48:53
    elf简介 ELF(Excutable and Linking Format)是可执行与链接格式的...网上有很多elf结构分析相关的文章,这里推荐一个pdf《ELF文件格式分析.pdf》,细节讲的比较系统,大家可以参考,可以到https://download.csdn....
  • 对学习到ELF文件结构的相关知识做一个简单的梳理,这些知识主要来自自己文件和代码的翻阅和其他博客,做提醒作用,想真的理解还请翻阅相关源码和结合手上的so慢慢翻看理解 一、ELF文件概览: 我们将图片以左右两边...
  • 文章目录前言ELF目标文件类型以下面例子深入分析ELF详解file命令结果的各个部分ELF文件结构ELF知识扩展Linux系统装载ELF的过程用户层面系统层面 前言 一般程序符号和数据,包括:全局变量,静态全局变量,全局函数...
  • ELF文件结构详解

    千次阅读 2017-03-07 14:44:48
    ELF文件介绍在linux下,由汇编程序和链接编辑器生成的是目标文件为ELF(Excutable and Linking Format)格式,它们共分为四种类型: 可重定位文件(Relocatable File) 可执行文件(Executable File) 共享目标文件(Shared...
  • DEX | ELF 文件结构介绍

    千次阅读 2021-09-07 18:29:24
    Dex文件结构: 1.dex文件中的数据结构 u1/uint8_t=>表示1字节的无符号数 u2/uint16_t=>表示2字节的无符号数 u4/uint32_t=>表示4字节的无符号数 u8/unit64_t=>表示8字节的无符号数 sleb128=>有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,469
精华内容 10,987
关键字:

elf文件结构