如何从 ELF 文件中单独提取可加载程序头? 通过使用 readelf 检查二进制文件,可以获得类似于以下内容的输出:
$ readelf -l helloworld
Elf file type is EXEC (Executable file)
Entry point 0x400440
There are 9 program headers, starting at offset 64
Program Headers:
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
0x000000000000070c 0x000000000000070c R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000230 0x0000000000000238 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000005e4 0x00000000004005e4 0x00000000004005e4
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:
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 .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
这个问题回答了如何将可加载标头映射到内存(以及在哪里),但没有指定从哪里(从哪个偏移量和大小)读取给定二进制文件中的部分。
是由当前程序头的字段p_offset和p_filesz决定吗?
struct Proghdr {
uint32_t p_type;
uint32_t p_offset;
uint32_t p_va;
uint32_t p_pa;
uint32_t p_filesz;
uint32_t p_memsz;
uint32_t p_flags;
uint32_t p_align;
};
struct Elf *elf_header = ...
struct Proghdr *ph;
if (elf_header->e_magic != ELF_MAGIC)
goto bad;
ph = (struct Proghdr *) ((uint8_t *) elf_header + elf_header->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
if(ph->p_type == PT_LOAD)
/*read_pload (dst address in memory, how many bytes to read, offset in the file) */
read_pload(ph->p_pa, ph->p_memsz, ph->p_offset);
是由当前程序头的字段p_offset和p_filesz决定的吗?
是的,完全正确。
通过读取
e_phoff
获得程序头表地址,通过读取e_phnum
获得头计数(头数),通过从elf文件头读取e_phentsize
获得每个头的大小。诀窍是每个标头的大小都相同 e_phentsize
。因此,在每个 e_phentsize
之后,新的标题开始,并且标题总数为 e_phnum
您需要了解的有关使用 Elf 文件进行编程的所有信息都可以在 Linux 上使用以下命令找到,
man 5 elf
,
读取可执行文件或共享对象的程序头的内容非常简单,包括以下步骤:
mmap
转换为uint8_t*
指针。Elf32_Ehdr *
和 Elf64_Ehdr *
)e_ident[EI_CLASS]
变量以确定是否应使用 32 位或 64 位代码。检查班级是否ELFCLASS32
e_phnum
变量获取程序头总数以创建 for 循环。e_phoff
变量获取程序头的基本偏移量e_phentsize
变量获取每个程序头的大小Elf64_Phdr * phdr = (Elf64_Phdr *)mmappedData[ehdr->e_phoff+(ehdr->e_phentsize*X)];
如果按照以下步骤操作,您将能够获取 elf 文件中的所有程序头。
以下代码示例是从我正在开发的项目中提取的,因此除非放入类中,否则它不会编译。我把它放在这里,以便您可以看到上述步骤的代码示例。
/*
* Includes used by my program
* */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <elf.h>
#include <sys/mman.h>
/*
* Variable data types
* */
Elf64_Ehdr *header = NULL;
Elf32_Ehdr *header32 = NULL;
std::string _fileName = "";
struct stat _fileStat = {0};
uint8_t *_fileData = NULL;
int _fileDescriptor = -1;
bool class32 = false;
bool elfIs32(void){
return class32;
}
/*
* Example of opening file, getting stats, then memmapping it
*/
int _openFile(std::string f){
this->_fileName = f;
int ret = -1;
if((ret = open(f.c_str(), O_RDWR)) < 0){
perror("open");
return ret;
}
if(fstat(ret, &this->_fileStat) < 0){
perror("fstat");
close(ret);
return -1;
}
this->_fileData = (uint8_t *)mmap(NULL, this->_fileStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, ret, 0);
if(this->_fileData == MAP_FAILED){
perror("mmap");
close(ret);
return -1;
}
return ret;
}
/*
* Code to init both 32 and 64 bit headers
*/
void manualInit(std::string fileName){
this->_fileDescriptor = this->_openFile(fileName);
if(this->_fileDescriptor == -1){
printf("Failed to open binary file.\n");
}
header = (Elf64_Ehdr*)this->_fileData;
header32 = (Elf32_Ehdr*)this->_fileData;
if(header->e_ident[EI_CLASS] == ELFCLASS32){
this->class32 = true;
}else{
this->class32 = false;
}
}
/*
* Example of how to obtain a single program header from an
* initialized elf header struct; both 32 and 64 bit.
*/
void *getProgramHeader(int idx){
void *ret = NULL;
long int initalOffset = elfIs32() ? (long int)this->header32->e_phoff : this->header->e_phoff;
long int cellSize = elfIs32() ? (long int)this->header32->e_phentsize : (long int)this->header->e_phentsize;
ret = (void *)&this->_fileData[initalOffset+(cellSize * idx)];
return ret;
}
void printProgramHeader(int idx){
printf("\n");
if(elfIs32()){
printf("Segment Type: | Segment Offset | V Address | P Address | File Size | Memory Size | Flags | Alignment\n");
Elf32_Phdr *pheader = (Elf32_Phdr *)getProgramHeader(idx);
printf("%s|\t%lx|\t0x%lx|\t0x%lx|\t0x%lx|\t0x%lx|\t0x%lx|\t0x%lx\n",
getSegmentType((void *)pheader).c_str(),
(long)pheader->p_offset,
(long)pheader->p_vaddr,
(long)pheader->p_paddr,
(long)pheader->p_filesz,
(long)pheader->p_memsz,
(long)pheader->p_flags,
(long)pheader->p_align
);
}else{
printf("Segment Type: | Segment Offset | Flags | V Address | P Address | File Size | Memory Size | Alignment\n");
Elf64_Phdr *pheader = (Elf64_Phdr *)getProgramHeader(idx);
printf("%s|\t%lx|\t0x%lx|\t0x%lx|\t0x%lx|\t0x%lx|\t0x%lx|\t0x%lx|\n",
getSegmentType((void *)pheader).c_str(),
(long)pheader->p_offset,
(long)pheader->p_flags,
(long)pheader->p_vaddr,
(long)pheader->p_paddr,
(long)pheader->p_filesz,
(long)pheader->p_memsz,
(long)pheader->p_align
);
}
}
/*
* Quality of life functions
*/
std::string getSegmentType(void *phdr){
int ctx = elfIs32() ? ((Elf32_Phdr *)phdr)->p_type : ((Elf64_Phdr *)phdr)->p_type;
switch(ctx){
case PT_NULL:
return "PT_NULL";
case PT_LOAD:
return "PT_LOAD";
case PT_DYNAMIC:
return "PT_DYNAMIC";
case PT_INTERP:
return "PT_INTERP";
case PT_NOTE:
return "PT_NOTE";
case PT_SHLIB:
return "PT_SHLIB";
case PT_PHDR:
return "PT_PHDR";
case PT_LOPROC:
return "PT_LOPROC";
case PT_HIPROC:
return "PT_HIPROC";
case PT_GNU_STACK:
return "PT_GNU_STACK";
}
return "UNKNOWN TYPE";
}
long int getProgramHeaderCount(void){
return this->elfIs32() ? (long int)this->header32->e_phnum : this->header->e_phnum;
}
/*
* Example Usage of above code to print all program headers
*/
ElfSnake es(fileName);
printf("\nGetting %ld program headers...\n", es.getProgramHeaderCount());
for(int i=0; i<es.getProgramHeaderCount(); i++){
es.printProgramHeader(i);
}
旁注:要获取节标题,该过程或多或少与获取程序标题相同。变量名称相同;但不是以
e_ph
* 开头,而是以 e_sh
* 开头