ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.
读取ELF头: 首先需要先来编译一个简单的ELF文件,然后将文件编译并连接.
[root@localhost ~]# cat lyshark.c #include <stdio.h>
int main() { printf("hello lyshark"); return 0; } [root@localhost ~]# gcc -c lyshark.c [root@localhost ~]# gcc -o lyshark lyshark.o
|
Linux系统中有一个默认命令readelf -h
可以解析指定文件的头结构.
[root@localhost ~] ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 64位程序 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V 调用约定 ABI Version: 0 Type: EXEC (Executable file) 可执行文件 Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400430 #程序的入口地址 Start of program headers: 64 (bytes into file) Start of section headers: 6464 (bytes into file) Flags: 0x0 #标志 Size of this header: 64 (bytes) #本头大小 Size of program headers: 56 (bytes) #程序头大小 Number of program headers: 9 Size of section headers: 64 (bytes) #节头大小 Number of section headers: 31 #节表数量 Section header string table index: 30 #字符串表索引节头
|
通过hexdump工具查看文件16进制文件头hexdump -s 0 -n 64 -C lyshark
[root@localhost ~]
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 30 04 40 00 00 00 00 00 |..>.....0.@.....| 00000020 40 00 00 00 00 00 00 00 40 19 00 00 00 00 00 00 |@.......@.......| 00000030 00 00 00 00 40 00 38 00 09 00 40 00 1f 00 1e 00 |....@.8...@.....|
|
linux系统中的节头文件保存在/usr/include/elf.h
我通过查找找到了ELF64所对应的结构数据
typedef uint16_t Elf64_Half; 16 typedef uint32_t Elf64_Word; 32 typedef uint64_t Elf64_Addr; 64 typedef uint64_t Elf64_Off; 64 #define EI_NIDENT (16)
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;
|
通过编程实现Magic的读取,或者说实现的是文件头e_ident[16]
文件的读取,通过定义可得知文件头大小是16字节
#include <stdio.h> #include <stdlib.h> #include <elf.h>
int main(int argc,char* argv[]) { if(argc < 2){ exit(0); } FILE *fp; Elf64_Ehdr elf_header;
fp = fopen(argv[1],"r"); if(fp == NULL) { exit(0); }
int readfile; readfile = fread(&elf_header,sizeof(Elf64_Ehdr),1,fp); if(readfile == 0){ exit(0); }
if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E') { printf("头标志: "); for(int x =0;x<16;x++) { printf("%x ",elf_header.e_ident[x]); } printf("\n"); } return 0; }
|
编译并运行即可读取出文件头部的前16个字节的字节数组,我们最需要关注的就是开头前4个字节,其标志着PE文件的开始
[root@localhost ~] [root@localhost ~] 头标志: 7f 45 4c 46 2 1 1 0 0 0 0 0 0 0 0 0
|
除此之外,读取其他头结构数据,代码与上方类似,只需要稍微改动一下就好.
if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E') { printf("文件类型: %hx\n",elf_header.e_type); printf("运行平台: %hx\n",elf_header.e_machine); printf("入口虚拟RVA: 0x%x\n",elf_header.e_entry); printf("程序头文件偏移: %d(bytes)\n",elf_header.e_phoff); printf("节头表文件偏移: %d(bytes)\n",elf_header.e_shoff); printf("ELF文件头大小: %d\n",elf_header.e_ehsize); printf("ELF程序头大小: %d\n",elf_header.e_phentsize); printf("ELF程序头表计数: %d\n",elf_header.e_phnum); printf("ELF节头表大小: %d\n",elf_header.e_shentsize); printf("ELF节头表计数: %d\n",elf_header.e_shnum); printf("字符串表索引节头: %d\n",elf_header.e_shstrndx); }
|
运行后,就可以读取到所有的节头数据.
[root@localhost ~] 文件类型: 2 运行平台: 3e 入口虚拟RVA: 0x400430 程序头文件偏移: 64(bytes) 节头表文件偏移: 6464(bytes) ELF文件头大小: 64 ELF程序头大小: 56 ELF程序头表计数: 9 ELF节头表大小: 64 ELF节头表计数: 31 字符串表索引节头: 30
|
读取ELF节表: 首先打开elf.h头文件,找到这个声明处Elf64_Shdr
.
typedef uint32_t Elf64_Word; 32 typedef uint64_t Elf64_Addr; 64 typedef uint64_t Elf64_Off; 64 typedef uint64_t Elf64_Xword; 64
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;
|
通过使用hexdump -s 144
从偏移为144的位置开始读取,向后读取100个字节,就是节表所在位置.
[root@localhost ~] 00000090 38 02 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 |8.@.............| 000000a0 1c 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................| 000000b0 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................| 000000c0 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....| 000000d0 0c 07 00 00 00 00 00 00 0c 07 00 00 00 00 00 00 |................| 000000e0 00 00 20 00 00 00 00 00 01 00 00 00 06 00 00 00 |.. .............| 000000f0 10 0e 00 00 |....|
|
编程实现简单的节表读取,只需要在上方代码基础上进行修改即可.
if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E') { int shnum, x; Elf64_Shdr *shdr = (Elf64_Shdr*)malloc(sizeof(Elf64_Shdr) * elf_header.e_shnum); temp = fseek(fp, elf_header.e_shoff, SEEK_SET); temp = fread(shdr, sizeof(Elf64_Shdr) * elf_header.e_shnum, 1, fp); rewind(fp); fseek(fp, shdr[elf_header.e_shstrndx].sh_offset, SEEK_SET); char shstrtab[shdr[elf_header.e_shstrndx].sh_size]; char *names = shstrtab; temp = fread(shstrtab, shdr[elf_header.e_shstrndx].sh_size, 1, fp); printf("节类型\t节地址\t节偏移\t节大小\t节名称\n"); for(shnum = 0; shnum < elf_header.e_shnum; shnum++) { names = shstrtab; names=names+shdr[shnum].sh_name; printf("%x\t%x\t%x\t%x\t%s \n",shdr[shnum].sh_type,shdr[shnum].sh_addr,shdr[shnum].sh_offset,shdr[shnum].sh_size,names); }
}
|
Linux系统中也可以使用objdump
命令读取程序的节表信息.
[root@localhost ~] lyshark: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000060 00000000004002b8 00000000004002b8 000002b8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 0000003f 0000000000400318 0000000000400318 00000318 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000008 0000000000400358 0000000000400358 00000358 2**1
|
当然objdump命令,还可以排查文件的SO加载情况.
[root@localhost ~] NEEDED libpcre.so.1 NEEDED libz.so.1 NEEDED libpthread.so.0 NEEDED libc.so.6
|