内核级木马与病毒攻防:Linux可执行文件的ELF格式描述

2020-07-22 16:33:49 浏览数 (1)

要想在Linux系统上实现逆向工程,分析,设计或查杀病毒和恶意代码,你不得不深入掌握其可执行文件的ELF格式,这样你才能了解进程在内存空间的布局和运行的基本规律,这样你才能有针对性的设计有效的病毒或恶意代码入侵系统。

ELF文件主要有以下几种类型,ET_NONE表示该文件的作用未知;ET_REL表示重定向文件或叫目标文件,它们将会被链接并加装到某个指定的虚拟内存位置,常见的以.o结尾的二进制文件就属于这种类型。ET_EXEC表示可执行文件,它是由多个.o文件链接起来,可以被加载到内存进行执行的进程数据文件;ET_DYN表示动态链接库,它里面包含一系列向外导出的函数代码,当进程需要调用其内部函数时会将其加载到内存;最后一种是ET_CORE,它是进程奔溃时产生的二进制信息文件,通过GDB加载该文件可以查找进程奔溃的原因。

ELF文件的头部结构能反映出该文件很多重要信息,使用readelf -h可以读取指定ELF文件的文件头,其使用示例如下:

其二进制数据结构如下:

代码语言:javascript复制
#define  EI_NIDENT  16
typedef  struct  {
  unsigned char e_ident[EI_NIDENT];
 uint16_t e_type;
 uint16_t e_machine;
 uint32_t e_version;
 ElfN_Addr e_entry;
 ElfN_Off e_phoff;
 ElfN_Off e_shoff;
 uint32_t e_flags;
 uint16_t e_ehsize;
 uint16_t e_phentsize;
 uint16_t e_phnum;
 uint16_t e_shentsize;
 uint16_t e_shnum;
 uint16_t e_shstrndx;
}ELFN_Ehdr

掌握其二进制结构很重要,后面我们要使用代码来实现ELF文件的解析。ELF文件中的程序表头是很重要的数据结构,它用来描述各个段的信息,例如哪里是代码段,哪里是数据段,这些段有多长,要加载到虚拟内存哪个位置等等。通过程序表头中的e_phoff可以获得该表头在ELF文件中的偏移,其二进制数据结构如下:

代码语言:javascript复制
typedef struct {
 uint32_t p_type; (segment type)
 Elf32_Off p_offset; (segment offset)
 Elf32_Addr p_vaddr; (segment virtual address)
 Elf32_Addr p_paddr; (segment physical address)
 uint32_t p_filesz; (size of segment in the file)
 uint32_t p_memsz; (size of segment in memory)
 uint32_t p_flags; (segment flags, I.E execute|read|read)
 uint32_t p_align; (segment alignment in memory)
 } Elf32_Phd

程序表头有若干种类型,其中的p_type字段就用来表示该表头类型。如果ELF文件类型是ET_EXEC,那么它必须包含类型为PT_LOAD类型的表头,这种表头会告诉系统在如何加载器数据段或代码段到虚拟内存空间的特定位置,表头内各个字段的在逆向工程时会有重要作用。如果编译成的文件是动态链接库,那么它的程序表头类型为PT_DYNAMIC,表头中包含很多信息用来告诉系统如何对它进行加载,它通常包含这几类信息:加载它时所需要的共享连接库;全局偏离表的起始地址,这个表在后面会描述;以及有关重定向入口的信息。动态链接库对开发病毒和恶意代码非常重要,在后续章节我们会深入研究。

ELF格式的可执行文件在概念上由各种”段“组成,例如用于存储代码的文本段,用于存储数据的数据段等,这些段的数量和相关信息就由程序表头来描述,在加载运行ELF可执行文件时,系统会读取程序表头,获得各个段的信息,将段对应的内容加载到内存,这样可执行文件才能变成可以运行的进程,我们可以使用命令readelf -l 来读取程序表头的内容,具体情况如下:

接下来我们看看ELF文件中的段,该结构包含着要加载到内存的代码和数据,其二进制数据结构如下:

代码语言:javascript复制
typedef struct {
uint32_t sh_name; // offset into shdr string table for shdr name
 uint32_t sh_type; // shdr type I.E SHT_PROGBITS
 uint32_t sh_flags; // shdr flags I.E SHT_WRITE|SHT_ALLOC
 Elf32_Addr sh_addr; // address of where section begins
 Elf32_Off sh_offset; // offset of shdr from beginning of file
 uint32_t sh_size; // size that section takes up on disk
 uint32_t sh_link; // points to another section
 uint32_t sh_info; // interpretation depends on section type
uint32_t sh_addralign; // alignment for address of section
uint32_t sh_entsize; // size of each certain entries that may be in
section
} Elf32_Shdr;

我们审视几个非常重要的段及其内容。首先是.text段,它包含代码经过编译后的可执行二进制指令,.rodata段包含只读数据,在代码中写死的字符串,例如printf(“hello world”)中的”hello world”就包含在该段。.plt段也叫函数链接表,它告诉系统如何将动态链接库里面的函数加载到内存以便被进程调用,后面会对其进行深入研究。.data段用于存储初始化后的全局变量等数据;.bss段包含未经初始化的全局变量;.got.plt段也叫全局偏移表,它结合.plt段用来告知系统如何加载动态链接库内部的函数,该段是黑客对进程进行感染或劫持的关键入口;、

.dynsym段包含了动态链接库有关的符号信息;.rel.*段包含的信息用于告诉系统将如何变换某些段的虚拟空间地址;.hash段包含一个哈希表,用来加速对符号的查找,所谓符号是编译器设置的用于记录代码中变量类型,取值等信息的数据结构,它属于编译原理的内容,有了符号,我们才能使用调试器对程序进行调试。.symtab对应符号表,它用于指导调试器或系统如何查找调试程序所需的各个符号;.cotrs和.dtors两个段各自包含一个指针,前者指针指向一段初始化代码,在进程的main函数执行前必须先执行初始化代码,后者指向结束代码,当进程执行结束后,必须执行结束代码后进程才能完全被杀掉。由此初始化代码就是黑客或病毒制造者的主要目标,他们会在这个地方注入一种叫PTRACE_TRACEME的代码用于阻止调试器挂载到进程上进行调试。

我们可以通过如下方式查看各种段的信息,首先先将代码编译成.o类型的中间文件:gcc -c hello_world.c,然后调用命令readelf -S hello_wolrd.o,所得结果如下图:

本节所描述的信息过于底层,它涉及到编译原理,系统运行机制等绝大多数程序员在其一生的职业生涯中都可能没有接触的知识。但所谓黑客,本质上是无限制扩充自己技术能力圈的程序员,因此我们需要掌握哪些工作上不常用,但能帮你探寻到技术核心真相的知识和信息,这就像黑客帝国中莫非是拉尼奥入伙时给出的两颗药丸,吞入红色药丸他就能理解世界的本质,普通程序员选择吞下蓝色药丸,而渴望探索和把握技术世界本质的黑客当然要吞下红色药丸。

本节的信息枯燥难懂,你看了后会云里雾里。没关系,本文描述只是给你留个概念,在后续实战中这些概念才容易被消化和理解。从这里你一定能体会要想拥有黑客级技术能力的困难之处,你需要有耐心,恒心,探索真相之路永远是艰辛,但黑客的目的就是主宰技术而不是被技术主宰,那些35岁被裁掉的程序员大多都是吞下蓝药丸并被技术所主宰,于是他们失去了对自己命运的把控力。

0 人点赞