读取ELF文件的程序头内容

问题描述 投票:0回答:4

如何从 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_offsetp_filesz决定吗?

c linux ubuntu linux-kernel elf
4个回答
3
投票
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);

1
投票

是由当前程序头的字段p_offset和p_filesz决定的吗?

是的,完全正确。


0
投票

通过读取

e_phoff
获得程序头表地址,通过读取
e_phnum
获得头计数(头数),通过从elf文件头读取
e_phentsize
获得每个头的大小。诀窍是每个标头的大小都相同
e_phentsize
。因此,在每个
e_phentsize
之后,新的标题开始,并且标题总数为
e_phnum


0
投票

您需要了解的有关使用 Elf 文件进行编程的所有信息都可以在 Linux 上使用以下命令找到,

man 5 elf
,

读取可执行文件或共享对象的程序头的内容非常简单,包括以下步骤:

  1. 打开目标文件并使用描述符将文件数据
    mmap
    转换为
    uint8_t*
    指针。
  2. 使用 mmaped 数据,创建并填充 32 位和 64 位大小的 Elf 头结构(
    Elf32_Ehdr *
    Elf64_Ehdr *
  3. 检查 32 位或 64 位标头的
    e_ident[EI_CLASS]
    变量以确定是否应使用 32 位或 64 位代码。检查班级是否
    ELFCLASS32
  4. 通过 ehdr 的
    e_phnum
    变量获取程序头总数以创建 for 循环。
  5. 通过 ehdr
    e_phoff
    变量获取程序头的基本偏移量
  6. 通过ehdr
    e_phentsize
    变量获取每个程序头的大小
  7. 通过类型转换获取索引 X 处的程序头,如下所示:
    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
*

开头
© www.soinside.com 2019 - 2024. All rights reserved.