Linux 二进制分析-ELF二进制格式(chapter 2.1&2.2)

目录

1. ELF文件类型

2. ELF程序头(program header)

2.1 PT_LOAD

2.2 PT_DYNAMIC

2.3 PT_NOTE

2.4 PT_INTERP

2.5 PT_PHDR


理解Linux二进制文件,才能反编译。ELF目前成为Unix和类Unix操作系统上二进制格式的标准。在Linux和BSD变种以及其它操作系统中,也都使用ELF二进制格式。可用于可执行文件、共享库、目标文件、coredump文件、内核引导镜像文件等。

学习ELF二进制格式的规范,将加深理解程序如何映射到磁盘并加载到内存,对于二进制黑客、逆向工程师或者普通程序员来说,对二进制的理解是非常重要的知识。

本章节首先介绍ELF二进制格式规范,然后进行实践实验,后续章节将继续了解ELF中的跟病毒、进程内存取证、二进制包含、rootkit相关联的知识。

1. ELF文件类型

ELF有以下文件类型:

  • ET_NONE

未知类型,标记表明文件类型不明确,或未定义。确,或未定义。

  • ET_REL

可重定位文件。relocatable文件,意味着该文件被标记了一段可重定位代码,有时也称作目标文件。

可重定位目标文件通常是还未被链接到可执行程序的一段位置独立的代码(PICposition independent code)。 编译完成后,通常是.o格式的文件。该文件包含了创建可执行文件所需要的代码和数据。

  • ET_EXEC

可执行文件。executable文件,该文件也称为程序,可以被加载到内存执行,包含进程创建、开始执行的入口。

  • ET_DYN

共享目标文件。dynamic文件,该文件是一个动态的、可链接的目标文件,也称为共享库。该文件在程序运行时被装载并链接到程序的进程内存镜像中。

  • ET_CORE

核心文件。core文件,在程序崩溃或进程传递一个SIGSEGV信号时,会在核心文件中记录整个进程的内存镜像。可以使用GDB读取该类文件来辅助调试并查找程序崩溃的原因。

使用readelf -h可以查看ELF文件,原始的ELF文件头,ELF文件头从文件的0偏移处开始,是文件剩余部分的描述元数据。标记了ELF类型、结构和程序开始执行的入口地址;提供了其他ELF头(节头和程序头)的偏移量。 示例如下,ls命令的文件头:

其文件头结构体如下:

// /usr/include/elf.h

#define EI_NIDENT (16)
// 32位
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;
// 64位
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;

 hexdump验证结果如下:

2. ELF程序头(program header)

ELF程序头对二进制文件中的段(segment)进行了描述,程序向内存load(装载)时会用到。

段(segment)是由内核装载并解析的,其描述了可执行文件的内存布局,以及如何映射到内存中。可以通过原始ELF头中名为e_phoff的偏移量来得到程序头表(ElfN_Ehdr结构体中的e_phoff字段)。

有5种场景的程序头类型:

2.1 PT_LOAD

可在内存中装载的段。 一个可执行文件至少包含一个PT_LOAD段,该类型的程序头描述了可被装载或映射到内存的段。例如:

一个需要动态链接的ELF可执行文件,至少包含两个可装载的段(程序头表中至少有两个PT_LOAD类型的表项):

  • 存放程序代码的text段(通常权限为PF_X|PF_R,可读可执行)

  • 存放全局变量和动态链接信息的data段(通常权限设置为PF_W|PF_R,可读可写)

著名的千人面病毒(polymorphic virus)感染的文件的text段或data段的权限可能会被改写,如通过在程序头的段标记(p_flags)处增加PF_W标记来修改text段的权限。

2.2 PT_DYNAMIC

动态链接可执行文件特有的段。 包含了动态链接所必需的一些信息,至少如下:

  • 运行时需要链接的共享库列表

  • 全局偏移表(GOT)的地址

  • 重定位条目的相关信息

动态段中包含一些标记和指针,完整的标记名和指针列表如下:

标记名

描述

DT_HASH

符号散列表的地址

DT_STRTAB

字符串表的地址

DT_SYMTAB

符号表地址

DT_RELA

相对地址重定位表的地址

DT_RELASZ

Rela表的字节大小

DT_RELAENT

Rela表条目的字节大小

DT_STRSZ

字符串表的字节大小

DT_SYMENT

符号表条目的字节大小

DT_INIT

初始化函数的地址

DT_FINI

终止函数的地址

DT_SONAME

共享目标文件名的字符串表偏移量

DT_RPATH

库搜索路径的字符串表偏移量

DT_SYMBOLIC

修改链接器,在可执行文件之前的共享目标文件中搜索符号

DT_REL

Rel relocs表的地址

DT_RELSZ

Rel表的字节大小

DT_RELENT

Rel表条目的字节大小

DT_PLTREL

PLT引用的reloc类型(Rela或Rel)

DT_DEBUG

还未进行定义,为调试保留

DT_TEXTREL

缺少此项表明重定位只能应用于可写段

DT_JMPREL

仅用于PLT的重定位条目地址

DT_BIND_NOW

指示动态链接器在将控制权交给可执行文件之前处理所有的重定位

DT_RUNPATH

库运行路径的字符串表偏移量?

动态段中包含一些结构体,存放着与动态链接相关的信息,如下:

// /usr/include/elf.h

typedef struct
{
  Elf32_Sword   d_tag;                  /* Dynamic entry type */
  union
    {
      Elf32_Word d_val;                 /* Integer value */
      Elf32_Addr d_ptr;                 /* Address value */
    } d_un;
} Elf32_Dyn;

typedef struct
{
  Elf64_Sxword  d_tag;                  /* Dynamic entry type */
  union
    {
      Elf64_Xword d_val;                /* Integer value */
      Elf64_Addr d_ptr;                 /* Address value */
    } d_un;
} Elf64_Dyn;

d_tag成员控制着d_un成员的含义。

2.3 PT_NOTE

PT_NOTE类型的段可能保存了与特定供应商或者系统相关的附件信息。ELF规范中的定义如下:

供应商或者系统构建者可以在目标文件中标记特定的信息,以便于对其它程序对一致性、兼容性等的检查。

PT_NOTE类型的程序头和SHT_NOTE类型的节元素可以用于实现此目的。程序头或节元素中的备注信息可以有任意数量的项数,每个条目都是一个4自己的目标处理器格式的数组。

而在实际上,目前这一段只保存了操作系统的规范信息,在可执行文件运行时是不需要该段的。 因为今天的系统是始终假设一个可执行文件是本地的,有非常多的病毒会感染NOTE段。

2.4 PT_INTERP

该段将位置和大小信息放在一个以null终止符的字符串中,是对程序解释器位置的描述。例如,/lib/linux-ld.so.2一般是指该文件使用的动态链接器的位置,也即程序解释器的位置。

2.5 PT_PHDR

该段保存了程序头表本身的位置和大小。而Phdr(程序头表)表则保存了所有的Phdr对文件(以及内存镜像)中段的描述信息。

上述是一些至关重要的程序头表(Phdr)类型,更详细的可以查阅ELF规范。

使用readelf -l <filename>命令可以查看文件的Phdr表,如下:

# readelf -l /bin/ls

Elf file type is DYN (Shared object file)
Entry point 0x5850
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001f8 0x00000000000001f8  R E    0x8
  INTERP         0x0000000000000238 0x0000000000000238 0x0000000000000238
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x000000000001e6e8 0x000000000001e6e8  R E    0x200000
  LOAD           0x000000000001eff0 0x000000000021eff0 0x000000000021eff0
                 0x0000000000001278 0x0000000000002570  RW     0x200000
  DYNAMIC        0x000000000001fa38 0x000000000021fa38 0x000000000021fa38
                 0x0000000000000200 0x0000000000000200  RW     0x8
  NOTE           0x0000000000000254 0x0000000000000254 0x0000000000000254
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_EH_FRAME   0x000000000001b1a0 0x000000000001b1a0 0x000000000001b1a0
                 0x0000000000000884 0x0000000000000884  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x000000000001eff0 0x000000000021eff0 0x000000000021eff0
                 0x0000000000001010 0x0000000000001010  R      0x1

 Section to Segment mapping:
  Segment Sections...
   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 .data.rel.ro .dynamic .got .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .data.rel.ro .dynamic .got

上述结果,可以看到:1)可执行程序的入口点(0x5850);2)共有9个程序头(00-08),起始与文件的64字节处;3)大多数节(Sections)包含在PT_LOAD(LOAD)段中;4)data段是可读可写的(第2个LOAD),text段是可读可执行的(第1个LOAD)

关于作者:

犇叔,浙江大学计算机科学与技术专业,研究生毕业,而立有余。先后在华为、阿里巴巴和字节跳动,从事技术研发工作,资深研发专家。主要研究领域包括虚拟化、分布式技术和存储系统(包括CPU与计算、GPU异构计算、分布式块存储、分布式数据库等领域)、高性能RDMA网络协议和数据中心应用、Linux内核等方向。

专业方向爱好:数学、科学技术应用

关注犇叔,期望为您带来更多科研领域的知识和产业应用。

内容坚持原创,坚持干货有料。坚持长期创作,关注犇叔不迷路

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敩科炼技堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值