摘要:之前虽然了解过ELF文件的具体格式,但是对改文件的理解还是存在一些不足,因此本文尝试写了一个简单的ELF解析器对ELF文件进行解析并输出Header,Program Header Table和Section Header Table。
关键字:C++,ELF
读者注意:阅读本文是你需要对ELF有最基本的了解。
源码链接
在进行文件解析之前当然需要将Linux的ELF头文件引入过来,但是需要注意的该文件依赖Linux内部的一些头文件,所以我们需要将上面include的头文件删除,并重新定义对应的类型。没必要把依赖的头文件全部引入到我们的工程。
typedef uint32_t __u32;
typedef uint16_t __u16;
typedef uint64_t __u64;
typedef int32_t __s32;
typedef int16_t __s16;
typedef int64_t __s64;
解析的过程比较简单就是读文件然后将对应的内存转换为目标结构体即可一步一步解析,过程比较简单只是比较累人。
在进行解析前需要注意因为我们可能是在Windows上解析另一台Linux或者其他机器编译的可执行文件或者二进制文件,我们无法确认改文件的具体位数和大小端。因此我们需要先将ELF中的EIDENT魔数读取出来,根据该数值来判断具体的文件位数和大小端。
Error ElfParser::identity() {
unsigned char ident[EI_NIDENT]{};
int readlen{};
if (Error::None != readbuf(ident, sizeof(unsigned char) * EI_NIDENT)) {
return Error::IO;
}
if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 || ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) {
printf("the first fource byte is not 0x7f E L f but %x %c %c %c\n", ident[EI_MAG0], ident[EI_MAG1], ident[EI_MAG2], ident[EI_MAG3]);
return Error::INVALID_DATA;
}
_bit = static_cast<FileBit>(ident[4]);
_order = static_cast<BitOrder>(ident[5]);
resetio();
return Error::None;
}
如果该文件的存储顺序和本地机器不同就需要将数据进行转换,比如大端转小端,小端转大端。比如下面将ELFHeader的每个成员的顺序都进行转换。
template<class T>
T swapEndian(const T v) {
static_assert(CHAR_BIT == 8, "char_bit is not 8");
union{
T u;
unsigned char u8[sizeof(T)];
}src, dst;
src.u = v;
for (int i = 0; i < sizeof(T); i++) {
dst.u8[i] = src.u8[sizeof(T) - i - 1];
}
return dst.u;
}
void Elf64::parseHeader(unsigned char *ptr) {
_header = *(elf64_hdr*)ptr;
if (_order == nativeMachineOrder()) {
return;
}
swapEndian(_header.e_type);
swapEndian(_header.e_machine);
swapEndian(_header.e_version);
swapEndian(_header.e_entry);
swapEndian(_header.e_phoff);
swapEndian(_header.e_shoff);
swapEndian(_header.e_flags);
swapEndian(_header.e_ehsize);
swapEndian(_header.e_phentsize);
swapEndian(_header.e_phnum);
swapEndian(_header.e_shentsize);
swapEndian(_header.e_shnum);
swapEndian(_header.e_shstrndx);
}
从内存中拿到ELF文件的结构体后,如果需要将ELF的数据转换为可读的形式,需要一一对着标准文档来进行解析,按照自己觉得舒适的方式转换为字符串输出即可。过程比较简单,但是真的很费时间,因为东西比较多。
下面是readelf的输出:
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: 0x10c0
Start of program headers: 64 (bytes into file)
Start of section headers: 15336 (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
下面是本文实现的解析器的输出(有些字段可能是Unknown之类,因为字段太多了,我偷了个懒,比如Machine是x86_64,该字段有100个选项,太多了我只写了10个,后面的都是Unknown):
Elf Header:
Magic Number: 0x7f 0x45 0x4c 0x46 0x2 0x1 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
File Identity: ELF
Arch Bit: 64bit
Arch Bit Order: LSB
Version: 1
OSAbi: None
Type: Shared object file
Machine: Unknown
Version: Current Version
Entry Point: 0x10c0
Program Hedaer Table Offset: 64
Section Hedaer Table Offset: 15336
Flags: 0x0
Header Size: 64(in bytes)
Program Hedaer Size: 56(in bytes)
Program Header Number: 13
Section Hedaer Size: 64(in bytes)
Section Header Number: 31
String Table Seciton Index: 30
Segment Header
Type Offset VirtAddr PhyAddr FileSz MemSz Flags Align
PT_PHDR 64 0x40 0x40 728 728 4 8
PT_INTERP 792 0x318 0x318 28 28 4 1
PT_LOAD 0 0x0 0x0 2112 2112 4 4096
PT_LOAD 4096 0x1000 0x1000 725 725 5 4096
PT_LOAD 8192 0x2000 0x2000 432 432 4 4096
PT_LOAD 11640 0x3d78 0x3d78 664 992 6 4096
PT_DYNAMIC 11664 0x3d90 0x3d90 512 512 6 8
PT_NOTE 824 0x338 0x338 32 32 4 8
PT_NOTE 856 0x358 0x358 68 68 4 4
PT_INTERP 824 0x338 0x338 32 32 4 8
PT_NULL 8212 0x2014 0x2014 84 84 4 4
PT_LOAD 0 0x0 0x0 0 0 6 16
PT_DYNAMIC 11640 0x3d78 0x3d78 648 648 4 1
Section Header
Name Type Address Offset Size Entsize Flags Link Info Align
SHT_NULL 0x0 0 0 0 0 0 0 0
.interp SHT_PROGBITS 0x318 792 28 0 2 0 0 1
.note.gnu.property SHT_NOTE 0x338 824 32 0 2 0 0 8
.note.gnu.build-id SHT_NOTE 0x358 856 36 0 2 0 0 4
.note.ABI-tag SHT_NOTE 0x37c 892 32 0 2 0 0 4
.gnu.hash SHT_SYMTAB 0x3a0 928 40 0 2 6 0 8
.dynsym SHT_DYNSYM 0x3c8 968 312 24 2 7 1 8
.dynstr SHT_STRTAB 0x500 1280 355 0 2 0 0 1
.gnu.version SHT_DYNSYM 0x664 1636 26 2 2 6 0 2
.gnu.version_r SHT_SHLIB 0x680 1664 64 0 2 7 2 8
.rela.dyn SHT_RELA 0x6c0 1728 288 24 2 6 0 8
.rela.plt SHT_RELA 0x7e0 2016 96 24 66 6 24 8
.init SHT_PROGBITS 0x1000 4096 27 0 6 0 0 4
.plt SHT_PROGBITS 0x1020 4128 80 16 6 0 0 16
.plt.got SHT_PROGBITS 0x1070 4208 16 16 6 0 0 16
.plt.sec SHT_PROGBITS 0x1080 4224 64 16 6 0 0 16
.text SHT_PROGBITS 0x10c0 4288 517 0 6 0 0 16
.fini SHT_PROGBITS 0x12c8 4808 13 0 6 0 0 4
.rodata SHT_PROGBITS 0x2000 8192 17 0 2 0 0 4
.eh_frame_hdr SHT_PROGBITS 0x2014 8212 84 0 2 0 0 4
.eh_frame SHT_PROGBITS 0x2068 8296 328 0 2 0 0 8
.init_array SHT_INIT_ARRAY 0x3d78 11640 16 8 3 0 0 8
.fini_array SHT_FINI_ARRAY 0x3d88 11656 8 8 3 0 0 8
.dynamic SHT_DYNAMIC 0x3d90 11664 512 16 3 7 0 8
.got SHT_PROGBITS 0x3f90 12176 112 8 3 0 0 8
.data SHT_PROGBITS 0x4000 12288 16 0 3 0 0 8
.bss SHT_NOBITS 0x4040 12304 280 0 3 0 0 64
.comment SHT_PROGBITS 0x0 12304 43 1 48 0 0 1
.symtab SHT_SYMTAB 0x0 12352 1800 24 0 29 50 8
.strtab SHT_STRTAB 0x0 14152 897 0 0 0 0 1
.shstrtab SHT_STRTAB 0x0 15049 282 0 0 0 0 1