ELF文件结构

January 4, 2021 逆向 访问: 46 次

文件结构

ELF主要包括三种类型文件:

  • 可重定位文件(relocatable):编译器和汇编器产生的.o文件,被Linker所处理
  • 可执行文件(executable):Linker对.o文件进行处理输出的文件,进程映像
  • 共享对象文件(shared object):动态库文件.so

ELF布局:
-w529

用到的工具是readelf

╭─root@ubuntu ~/test 
╰─# readelf --help                                                                                                                                                     1 ↵
用法:readelf <选项> elf-文件
 显示关于 ELF 格式文件内容的信息
 Options are:
  -a --all               Equivalent to: -h -l -S -s -r -d -V -A -I
  -h --file-header       显示elf文件头
  -l --program-headers   Display the program headers
     --segments          An alias for --program-headers
  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
  -g --section-groups    Display the section groups
  -t --section-details   Display the section details
  -e --headers           Equivalent to: -h -l -S
  -s --syms              Display the symbol table
     --symbols           An alias for --syms
  --dyn-syms             Display the dynamic symbol table
  -n --notes             Display the core notes (if present)
  -r --relocs            Display the relocations (if present)
  -u --unwind            Display the unwind info (if present)
  -d --dynamic           Display the dynamic section (if present)
  -V --version-info      Display the version sections (if present)
  -A --arch-specific     Display architecture specific information (if any)
  -c --archive-index     Display the symbol/file index in an archive
  -D --use-dynamic       Use the dynamic section info when displaying symbols
  -x --hex-dump=<number|name>
                         Dump the contents of section <number|name> as bytes
  -p --string-dump=<number|name>
                         Dump the contents of section <number|name> as strings
  -R --relocated-dump=<number|name>
                         Dump the contents of section <number|name> as relocated bytes
  -z --decompress        Decompress section before dumping it
  -w[lLiaprmfFsoRt] or
  --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
               =frames-interp,=str,=loc,=Ranges,=pubtypes,
               =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
               =addr,=cu_index]
                         Display the contents of DWARF2 debug sections
  --dwarf-depth=N        Do not display DIEs at depth N or greater
  --dwarf-start=N        Display DIEs starting with N, at the same depth
                         or deeper
  -I --histogram         Display histogram of bucket list lengths
  -W --wide              Allow output width to exceed 80 characters
  @<file>                Read options from <file>
  -H --help              Display this information
  -v --version           Display the version number of readelf

文件头

结构

#define EI_NIDENT 16
typedef struct{
    /*ELF的一些标识信息,固定值*/
    unsigned char e_ident[EI_NIDENT];
    /*目标文件类型:1-可重定位文件,2-可执行文件,3-共享目标文件等*/
    Elf32_Half e_type;
    /*文件的目标体系结构类型:3-intel 80386*/
    Elf32_Half e_machine;
    /*目标文件版本:1-当前版本*/
    Elf32_Word e_version;
    /*程序入口的虚拟地址,如果没有入口,可为0*/
    Elf32_Addr e_entry;
    /*程序头表(segment header table)的偏移量,如果没有,可为0*/
    Elf32_Off e_phoff;
    /*节区头表(section header table)的偏移量,没有可为0*/
    Elf32_Off e_shoff;
    /*与文件相关的,特定于处理器的标志*/
    Elf32_Word e_flags;
    /*ELF头部的大小,单位字节*/
    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;

eg:

readelf -h test                                                                                                                                                    1 ↵
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x400430
  程序头起点:          64 (bytes into file)
  Start of section headers:          6616 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       56 (字节)
  Number of program headers:         9
  节头大小:         64 (字节)
  节头数量:         31
  字符串表索引节头: 28

16092960252570.jpg

从链接的角度来看,elf文件是按照Section(节)的形式存储的;从装载的角度,elf文件是按照Segment(节)来划分的;

program headers

结构

typedef struct
{
    /*segment的类型:PT_LOAD= 1 可加载的段*/
    Elf32_Word p_type;
    /*从文件头到该段第一个字节的偏移*/
    Elf32_Off p_offset;
    /*该段第一个字节被放到内存中的虚拟地址*/
    Elf32_Addr p_vaddr;
    /*在linux中这个成员没有任何意义,值与p_vaddr相同*/
    Elf32_Addr p_paddr;
    /*该段在文件映像中所占的字节数*/
    Elf32_Word p_filesz;
    /*该段在内存映像中占用的字节数*/
    Elf32_Word p_memsz;
    /*段标志*/
    Elf32_Word p_flags;
    /*p_vaddr是否对齐*/
    Elf32_Word p_align;
} Elf32_phdr;

显示出每个段的信息,以及每个段中包含哪些节的信息

❯ readelf -l test

Elf 文件类型为 EXEC (可执行文件)
入口点 0x400430
共有 9 个程序头,开始于偏移量 64

程序头:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c  R      1  [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000006fc 0x00000000000006fc  R E    200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x0000000000000228 0x0000000000000230  RW     200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x00000000000005d4 0x00000000004005d4 0x00000000004005d4 0x0000000000000034 0x0000000000000034  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x00000000000001f0 0x00000000000001f0  R      1

 Section to Segment mapping:
  段节...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .dynamic .got

程序头类型:

  • PT_LOAD: 表明本程序头指向一个可装载的段。段的内容会被从文件中拷贝到 内存中
  • PT_DYNAMIC: 表明本段指明了动态连接的信息
  • PT_INTERP: 指向了一个以”null”结尾的字符串,这个字符串是一个 ELF 解析器的 路径
  • 此类型的程序头如果存在的话,它表明的是其自身所在的程序头表在文件 或内存中的位置和大小。这样的段在文件中可以不存在,只有当所在程序头 表所覆盖的段只是整个程序的一部分时,才会出现一次这种表项,而且这种 表项一定出现在其它可装载段的表项之前

.bss节的类型SHT_NOBITS,即它在目标文件中不占空间,但它在段中,即在进程空间中却会占有一席之地。一般地,未初始化的全局变量会存放在.bss 节中,而整个.bss 节会出现在段的最末尾,也正是因为这样,段的内存

空间大小(p_memsz)可能会比它在文件中的大小(p_filesz)大一些

section

结构:

typedef struct{
/*节区名称*/
Elf32_Word sh_name;
/*节区类型:PROGBITS-程序定义的信息,NOBITS-不占用文件空间(bss),REL-重定位表项*/
Elf32_Word sh_type;
/*每一bit位代表一种信息,表示节区内的内容是否可以修改,是否可执行等信息*/
Elf32_Word sh_flags;
/*如果节区将出现在进程的内存影响中,此成员给出节区的第一个字节应处的位置*/
Elf32_Addr sh_addr;
/*节区的第一个字节与文件头之间的偏移*/
Elf32_Off sh_offset;
/*节区的长度,单位字节,NOBITS虽然这个值非0但不占文件中的空间*/
Elf32_Word sh_size;
/*节区头部表索引链接*/
Elf32_Word sh_link;
/*节区附加信息*/
Elf32_Word sh_info;
/*节区带有地址对齐的约束*/
Elf32_Word sh_addralign;
/*某些节区中包含固定大小的项目,如符号表,那么这个成员给出其固定大小*/
Elf32_Word sh_entsize;
}Elf32_Shdr;

eg:

❯ readelf -S  test
共有 31 个节头,从偏移量 0x19d8 开始:

节头:
  [号] 名称               类型              地址               偏移量      大小              全体大小           旗标     链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000   0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238   000000000000001c  0000000000000000   A       0     0     1
  ………………
  [30] .strtab           STRTAB           0000000000000000  000016b8   0000000000000210  0000000000000000           0     0     1
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)

root@ubuntu ~/test
节表名字含义
.bss本节中包含目标文件中未初始化的全局变量。一般情况下,可执行程序在开始运行的时候,系统会把这一段内容清零。但是,在运行期间的 bss 段是由系统初始化而成的,在目标文件中.bss 节并不包含任何内容,其长度为 0,所以它的节类型为 SHT_NOBITS
.comment本节包含版本控制信息
.data/.data1这两个节用于存放程序中被初始化过的全局变量。在目标文件中,它们是占用实际的存储空间的,与.bss 节不同
.debug本节中含有调试信息,内容格式没有统一规定。所有以”.debug”为前缀的节名字都是保留的
.dynamic本节包含动态连接信息,并且可能有 SHF_ALLOC 和 SHF_WRITE 等属性。是否具有 SHF_WRITE 属性取决于操作系统和处理器
.dynstr此节含有用于动态连接的字符串,一般是那些与符号表相关的名字
.dynsym此节含有动态连接符号表
.fini此节包含进程终止时要执行的程序指令。当程序正常退出时,系统会执行这一节中的代码
.got此节包含全局偏移量表
.hash本节包含一张符号哈希表
.init此节包含进程初始化时要执行的程序指令。当程序开始运行时,系统会在进入主函数之前执行这一节中的代码
.interp此节含有 ELF 程序解析器的路径名。如果此节被包含在某个可装载的段中,那么本节的属性中应置 SHF_ALLOC 标志位,否则不置此标志
.note
.plt此节包含函数连接表
.relname 和.relaname这两个节含有重定位信息。如果此节被包含在某个可装载的段中,那么本节的属性中应置 SHF_ALLOC 标志位,否则不置此标志。注意,这两个节的名字中”name”是可替换的部分,执照惯例,对哪一节做重定位就把”name”换成哪一节的名字。比如,.text 节的重定位节的名字将是.rel.text 或.rela.text。
rodata/.rodata1本节包含程序中的只读数据,在程序装载时,它们一般会被装入进程空间中那些只读的段中去
.shstrtab本节是“节名字表”,含有所有其它节的名字
.strtab本节用于存放字符串,主要是那些符号表项的名字。如果一个目标文件有一个可装载的段,并且其中含有符号表,那么本节的属性中应该有 SHF_ALLOC
.symtab本节用于存放符号表。如果一个目标文件有一个可载入的段,并且其中含有符号表,那么本节的属性中应该有 SHF_ALLOC
.text本节包含程序指令代码

符号表

typedef struct {
 Elf32_Word st_name;/*指向字符串表的索引值*/
 Elf32_Addr st_value;
 Elf32_Word st_size;/*符号的大小*/
 unsigned char st_info;/*符号的类型和属性*/
 unsigned char st_other;/**/
 Elf32_Half st_shndx;/*指明了相关联的节*/
} Elf32_Sym; 

eg:

❯ readelf -s test

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

Symbol table '.symtab' contains 67 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2
     3: 0000000000400274     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000400298     0 SECTION LOCAL  DEFAULT    4
     5: 00000000004002b8     0 SECTION LOCAL  DEFAULT    5
     ………………

    65: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    66: 00000000004003c8     0 FUNC    GLOBAL DEFAULT   11 _init

root@ubuntu ~/test
❯

实例分析

elf头部
16093879917141.jpg

程序头
16093880083053.jpg

.interp
16093881167598.jpg

添加新评论