从0开始的elf解析器

elf文件结构不废话,大致分为4部分,elf头(ELFheader),程序头表(program header table),乱七八糟的各种段(section),段表(section header table,现在好像都叫节头表)

目标:达到类似readelf的效果

-a :--all 显示全部信息,等价于 -h -l -S -s -r -d -V -A -I
-h :--file-header 显示elf文件开始的文件头信息. 
-l :--program-headers  ;--segments 显示程序头(段头)信息(如果有的话)。 
-S :--section-headers  ;--sections 显示节头信息(如果有的话)。 
-g :--section-groups 显示节组信息(如果有的话)。
-t :--section-details 显示节的详细信息(-S的)。 
-s :--syms  ;--symbols 显示符号表段中的项(如果有的话)。 
-e :--headers 显示全部头信息,等价于: -h -l -S 
-n :--notes 显示note段(内核注释)的信息。 
-r :--relocs 显示可重定位段的信息。 
-u :--unwind 显示unwind段信息。当前只支持IA64 ELF的unwind段信息。 
-d :--dynamic 显示动态段的信息。 
-V :--version-info 显示版本段的信息。 
-A :--arch-specific 显示CPU构架信息。 
-D :--use-dynamic 使用动态段中的符号表显示符号,而不是使用符号段。 
-x <number or name> :--hex-dump=<number or name> 以16进制方式显示指定段内内容。number指定段表中段的索引,或字符串指定文件中的段名。 
-w[liaprmfFsoR]或者
-debugdump[=line,=info,=abbrev,=pubnames,=aranges,
=macro,=frames,=frames-interp,=str,=loc,=Ranges] 显示调试段中指定的内容。 
-I :--histogram 显示符号的时候,显示bucket list长度的柱状图。 
-v :--version 显示readelf的版本信息。 
-H :--help 显示readelf所支持的命令行选项。 
-W :--wide 宽行输出。 

第一步:-h,展示elf头

打开一个程序,自然首先要看的就是elf头

linux系统中的节头文件保存在/usr/include/elf.h,如果懒得下一个libc,这个在线查看网站很棒:elf.h - elf/elf.h - Glibc source code (glibc-2.31) - Bootlin通过查找找到了ELF64所对应的结构数据,所以后续如果需要用到该结构,懒得再复制粘贴出一个文件的话就include<elf.h>

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];     /* 一个字节数组用来确认文件是否是一个ELF文件 */
  Elf64_Half    e_type;                 /* 描述文件是,可执行文件elf=2,重定位so=3 */
  Elf64_Half    e_machine;              /* 目标主机架构 */
  Elf64_Word    e_version;              /* ELF文件格式的版本 */
  Elf64_Addr    e_entry;                /* 入口点虚拟地址 */
  Elf64_Off     e_phoff;                /* 程序头文件偏移 */
  Elf64_Off     e_shoff;                /* 节头表文件偏移 */
  Elf64_Word    e_flags;                /* ELF文件标志 */
  Elf64_Half    e_ehsize;               /* ELF头大小 */
  Elf64_Half    e_phentsize;            /* 程序头大小 */
  Elf64_Half    e_phnum;                /* 程序头表计数 */
  Elf64_Half    e_shentsize;            /* 节头表大小 */
  Elf64_Half    e_shnum;                /* 节头表计数 */
  Elf64_Half    e_shstrndx;             /* 字符串表索引节头 */
} Elf64_Ehdr;

目标效果

代码

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include<string.h>
int main(int argc,char* argv[])
{
        if(argc < 2){ exit(0); }
        FILE *fp;
        Elf64_Ehdr elf_header;
        fp = fopen(argv[2],"r");
        if(fp == NULL) {exit(0); }
        int readfile;
        readfile = fread(&elf_header,sizeof(Elf64_Ehdr),1,fp);
        if(readfile == 0){ exit(0); }                                              //无论如何,先去读文件头
        if(!strcmp(argv[1],"-h")){                                                          //-h展示文件头
	        if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E')
	        {
		      printf("头标志: ");
		      for(int x =0;x<16;x++)
		      {
		              printf("%02x ",elf_header.e_ident[x]);
		      }
		      printf("\n");
	    	  char*file_type[4]={"Null","可重定位文件","可执行文件","共享目标文件"};
		      printf("文件类型: \t\t%s\n",file_type[elf_header.e_type]);
		      printf("运行平台: \t\t%hx\n",elf_header.e_machine);
		      printf("入口虚拟RVA: \t\t0x%lx\n",elf_header.e_entry);
		      printf("程序头文件偏移: \t%ld(bytes)\n",elf_header.e_phoff);
		      printf("节头表文件偏移: \t%ld(bytes)\n",elf_header.e_shoff);
		      printf("ELF文件头大小: \t\t%d\n",elf_header.e_ehsize);
		      printf("ELF程序头大小: \t\t%d\n",elf_header.e_phentsize);
		      printf("ELF程序头表计数: \t%d\n",elf_header.e_phnum);
		      printf("ELF节头表大小: \t\t%d\n",elf_header.e_shentsize);
		      printf("ELF节头表计数: \t\t%d\n",elf_header.e_shnum);
		      printf("字符串表索引节头: \t%d\n",elf_header.e_shstrndx);
	        }
        }
        
        return 0;
}

第二步:-s,展示section。

根据阅读分析,程序通过节头表(段表)的偏移在文件中寻找偏移。所以我们的思路也一样,通过读elf头找elf_header.e_shoff,但别忘了,段表是一种数组结构,由段表*段表数量决定。

段表的样子:

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;

使用的函数:

利用fseek函数,把位置指针整到段表位置。fread函数类似上面那样,把其读到我们要的位置。

rewind函数,在我们读完段表后,把位置指针再调到文件开头,恢复原状。

可以看到,sectionheader第一项叫节区名称,也就是这段的名字,它对应的是在段表字符串表(shstrtab)中的偏移,字符串表是一串字符串,每个名字之间用0隔开,其中偏移为0位置默认为0。这个字符串表也是一个段(节),同样表示在段表之中。那么如何寻找这个段?elfheader中最后一项elf_header.e_shstrndx指的就是其在段表中的下标。

代码示例:

	if(!strcmp(argv[1],"-s")){
	    int shnum, x;
	    Elf64_Shdr *shdr = (Elf64_Shdr*)malloc(sizeof(Elf64_Shdr) * elf_header.e_shnum);//准备空间
	    fseek(fp, elf_header.e_shoff, SEEK_SET);                       //找到段表所在位置
	    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;                                       //名字指针,为下面做准备
	    fread(shstrtab, shdr[elf_header.e_shstrndx].sh_size, 1, fp);//读进来
	    printf("节类型\t节地址\t节偏移\t节大小\t节的名称\n");
	    for(shnum = 1; shnum < elf_header.e_shnum; shnum++){
	        names=shstrtab+shdr[shnum].sh_name;
	        printf("%x\t%lx\t%lx\t%lx\t%s\n",shdr[shnum].sh_type,
	        shdr[shnum].sh_addr,shdr[shnum].sh_offset,shdr[shnum].sh_size,names);
	    }
	}

把它塞到程序的合适位置。不过这还不够,其sh_type位有多种类型,把数字一一对应起来。

经过不断改良,变成这样:

	if(!strcmp(argv[1],"-s")){
	    int shnum, x;
	    Elf64_Shdr *shdr = (Elf64_Shdr*)malloc(sizeof(Elf64_Shdr) * elf_header.e_shnum);//准备空间
	    fseek(fp, elf_header.e_shoff, SEEK_SET);                       //找到段表所在位置
	    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;                                       //名字指针,为下面做准备
	    fread(shstrtab, shdr[elf_header.e_shstrndx].sh_size, 1, fp);//读进来
	    printf("[Num]\t节类型\t\tflag\t\t节地址\t节偏移\t节大小\t节的名称\n");
	    for(shnum = 1; shnum < elf_header.e_shnum; shnum++){
	        char *type[12]={"无效段\t\t","程序段\t\t","符号表\t\t","字符串表\t","重定位表\t","符号哈希表\t","动态链接信息\t","提示行信息\t","无内容\t\t","重定位信息\t","保留\t\t","动态链接符号表\t"};
	        char* flag[8]={"Null\t\t","可写\t\t","ALLOC\t\t","可写&ALLOC\t","可执行\t\t","可写&可执行\t","ALLOC&可执行\t","可写&执行&ALLOC\t"};
	        names=shstrtab+shdr[shnum].sh_name;
	        printf("[%02d]\t",shnum);
	        if(shdr[shnum].sh_type<12)printf("%s",type[shdr[shnum].sh_type]);
	        else printf("gnu相关\t\t");
	        if(shdr[shnum].sh_flags<8)printf("%s",flag[shdr[shnum].sh_flags]);
	        else printf("我也不知道\t");                      //跑的时候发现有的不太对劲,但是我也不知道那是啥,没查到
	        printf("%lx\t%lx\t%lx\t%s\n",shdr[shnum].sh_addr,shdr[shnum].sh_offset,shdr[shnum].sh_size,names);
	    }
	}

效果:

 flag位有的我也不知道那是嘛情况,就挺无奈的...暂时留个坑吧。

第三步: -s 展示符号表

符号表总共有俩,一个是.symbol,.dynsym。分别是符号表和动态符号表。

既然要读符号表,符号总得有个名吧,那名在哪里?

这个名保存在符号表(.dynsym的保存在.shstrtab)跟上面那个段表一样,也是采用了偏移的方式。

符号表结构:

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Xword	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Xword	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to another section */
  Elf64_Word	sh_info;		/* Additional section information */
  Elf64_Xword	sh_addralign;		/* Section alignment */
  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */
} Elf64_Shdr;

思路:先找到这俩符号表,把它们读进来,之后再通过段表信息找到这俩段,再分别把它们读进来,之后再慢慢分析。

代码:

    Elf64_Sym *Symbol=NULL;
    char *strtab = NULL;
    char *dynstrtab = NULL;
    if (!strcmp(argv[1], "-s"))
    {
        int shnum;
        int temp;
        for (shnum = 1; shnum < elf_header.e_shnum; shnum++)
        {
            if (shdr[shnum].sh_type != 3)
                continue;
            if (!strcmp(shstrtab + shdr[shnum].sh_name, ".shstrtab"))
                continue;
            if (!strcmp(shstrtab + shdr[shnum].sh_name, ".strtab"))
            {
                strtab = (char *)malloc(sizeof(char) * shdr[shnum].sh_size);
                if (strtab == NULL)
                {
                    printf("malloc error");
                    return 0;
                }
                fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                temp = fread(strtab, shdr[shnum].sh_size, 1, fp);
                if (temp == 0)
                {
                    printf("fread eroor");
                    return 0;
                }
                rewind(fp);
            }
            if (!strcmp(shstrtab + shdr[shnum].sh_name, ".dynstr"))
            {
                dynstrtab = (char *)malloc(sizeof(char) * shdr[shnum].sh_size);
                if (dynstrtab == NULL)
                {
                    printf("malloc error");
                    return 0;
                }
                fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                temp = fread(dynstrtab, shdr[shnum].sh_size, 1, fp);
                if (temp == 0)
                {
                    printf("fread eroor");
                    return 0;
                }
                rewind(fp);
            }
        } //目前检测str正常
        if (strtab == NULL)
        {
            printf("没找到字符串表");
            return 0;
        }
        for (shnum = 1; shnum < elf_header.e_shnum; shnum++)
        {
            char *Binding[3] = {"LOCAL", "GLOBAL", "WEAK"};
            char *type[5] = {"未知", "变量", "函数", "段", "文件"};
            if (shdr[shnum].sh_type != 2 && shdr[shnum].sh_type != 11)
                continue;
            if (shdr[shnum].sh_type == 2)
            { //如果是普通的符号段
                fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                printf("符号表偏移:0x%lx\t", shdr[shnum].sh_offset);
                Symbol = (Elf64_Sym *)malloc(sizeof(char) * shdr[shnum].sh_size);
                temp = fread(Symbol, shdr[shnum].sh_size, 1, fp);
                if (temp == 0)
                {
                    printf("fread eroor");
                    return 0;
                }
                rewind(fp);
                printf("表名:%s\n", shstrtab + shdr[shnum].sh_name);
                printf("  Idx:   VALUE:  SIZE:  Type:   Bind:\tSHNDX:\tNAME:\n");
                int num = shdr[shnum].sh_size / sizeof(Elf64_Sym);
                for (int i = 1; i < num; i++)
                {
                    printf("  %3d : ", i);
                    printf("  0x%-8lx%-7lx", Symbol[i].st_value, Symbol[i].st_size);
                    printf("%s\t %s\t", type[Symbol[i].st_info & 0xF], Binding[Symbol[i].st_info >> 4]);
                    printf("%d\t%s\n", Symbol[i].st_shndx, strtab + Symbol[i].st_name);
                }
            }
            else
            { //动态链接的符号段
                fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                printf("符号表偏移:0x%lx\t", shdr[shnum].sh_offset);
                Elf64_Sym *DynSymbol = (Elf64_Sym *)malloc(sizeof(char) * shdr[shnum].sh_size);
                temp = fread(DynSymbol, shdr[shnum].sh_size, 1, fp);
                if (temp == 0)
                {
                    printf("fread eroor");
                    return 0;
                }
                rewind(fp);
                printf("表名:%s\n", shstrtab + shdr[shnum].sh_name);
                printf("  Idx:   VALUE:  SIZE:  Type:   Bind:\tSHNDX:\tNAME:\n");
                int num = shdr[shnum].sh_size / sizeof(Elf64_Sym);
                for (int i = 1; i < num; i++)
                {
                    printf("  %3d : ", i);
                    printf("  0x%-8lx%-7lx", DynSymbol[i].st_value, DynSymbol[i].st_size);
                    printf("%s\t %s\t", type[DynSymbol[i].st_info & 0xF], Binding[DynSymbol[i].st_info >> 4]);
                    printf("%d\t%s\n", DynSymbol[i].st_shndx, dynstrtab + DynSymbol[i].st_name);
                }
            }
        }
    }

 效果:

 

第四步:-l  展示程序头表

section header用于描述section的特性,而program header用于描述segment的特性,目标文件(也就是文件名以.o结尾的文件)不存在program header,因为它不能运行。一个segment包含一个或多个现有的section,相当于从程序执行的角度来看待这些section。

其和段表一样,都在elf头中便设好了其的位置以及大小。所以还是先把它读进来再说。

其结构:

typedef struct
{
  Elf64_Word	p_type;			/* Segment type */
  Elf64_Word	p_flags;		/* Segment flags */
  Elf64_Off	p_offset;		/* Segment file offset */
  Elf64_Addr	p_vaddr;		/* Segment virtual address */
  Elf64_Addr	p_paddr;		/* Segment physical address */
  Elf64_Xword	p_filesz;		/* Segment size in file */
  Elf64_Xword	p_memsz;		/* Segment size in memory */
  Elf64_Xword	p_align;		/* Segment alignment */
} Elf64_Phdr;

p_type的类型

#define PT_NULL     0       /* Program header table entry unused */
#define PT_LOAD     1       /* Loadable program segment */
#define PT_DYNAMIC  2       /* Dynamic linking information */
#define PT_INTERP   3       /* Program interpreter */
#define PT_NOTE     4       /* Auxiliary information */
#define PT_SHLIB    5       /* Reserved */
#define PT_PHDR     6       /* Entry for header table itself */
#define PT_TLS      7       /* Thread-local storage segment */
#define PT_NUM      8       /* Number of defined types */
#define PT_LOOS     0x60000000  /* Start of OS-specific */
#define PT_GNU_EH_FRAME 0x6474e550  /* GCC .eh_frame_hdr segment */
#define PT_GNU_STACK    0x6474e551  /* Indicates stack executability */
#define PT_GNU_RELRO    0x6474e552  /* Read-only after relocation */
#define PT_LOSUNW   0x6ffffffa
#define PT_SUNWBSS  0x6ffffffa  /* Sun Specific segment */
#define PT_SUNWSTACK    0x6ffffffb  /* Stack segment */
#define PT_HISUNW   0x6fffffff
#define PT_HIOS     0x6fffffff  /* End of OS-specific */
#define PT_LOPROC   0x70000000  /* Start of processor-specific */
#define PT_HIPROC   0x7fffffff  /* End of processor-specific */

这里像之前用数组就不太合适了,所以后面用switch

这里主要分三部分:

第一部分:文件类型,入口

第二部分:展示segment表

第三部分:统计segment包括的section

第三部分主要参考的别人的源码。不过这里我也有一点不明白,为什么某一个segment可以只包括section的一部分?

代码:

    if (!strcmp(argv[1], "-l"))
    {
        int size = elf_header.e_phentsize; //先读程序头表
        Phdr = (Elf64_Phdr *)malloc(sizeof(Elf64_Phdr) *elf_header.e_phnum );
        fseek(fp, elf_header.e_phoff, SEEK_SET);
        int temp = fread(Phdr, sizeof(Elf64_Phdr) *elf_header.e_phnum, 1, fp);
        if (temp == 0)
        {
            printf("fread eroor");
            return 0;
        }
        printf("文件类型:");
        switch (elf_header.e_type)
        {
        case 0:
            printf(" No file type\n");
            return 0;
        case 1:
            printf(" Relocatable file\n");
            return 0;
        case 2:
            printf(" Executable file\n");
            break;
        case 3:
            printf(" Shared object file\n");
            break;
        case 4:
            printf(" Core file\n");
            break;
        default:
            printf(" ERROR\n");
        }
        printf("入口点位置 0X%0lX\n", elf_header.e_entry);
        printf("共有 %d 程序头, 偏移位置 %lu\n", elf_header.e_phnum, elf_header.e_phoff);
        printf("Program Headers:\n");
        printf(" %-16s %-16s %-16s %-16s", "Type", "Offset", "VirtAddr", "PhysAddr");
        printf(" %-16s %-16s %-16s %-6s\n", "FileSiz", "MemSiz", "Flags", "Align");
        int i;
        for (i = 0; i < elf_header.e_phnum; i++)
        {	
            switch (Phdr[i].p_type)
            {
            case 0:
                printf("NULL\t\t");
                break;
            case 1:
                printf("LOAD\t\t");
                break;
            case 2:
                printf("DYNAMIC\t\t");
                break;
            case 3:
                printf("INTERP\t\t");
                break;
            case 4:
                printf("NOTE\t\t");
                break;
            case 5:
                printf("SHLIB\t\t");
                break;
            case 6:
                printf("PHDR\t\t");
                break;
            case 7:
                printf("TLS\t\t");
                break;
            case 8:
                printf("NUM\t\t");
            case 0x60000000:
                printf("LOOS\t\t");
                break;
            case 0x6474e550:
                printf("GNU_EH_FRAME\t");
                break;
            case 0x6474e551:
                printf("GNU_STACK\t");
                break;
            case 0x6474e552:
                printf("GNU_RELRO\t");
                break;
            case 0x6ffffffa:
                printf("LOSUNW\t\t");
                break;
            case 0x6ffffffb:
                printf("SUNWSTACK\t");
                break;
            case 0x6fffffff:
                printf("HIOS\t\t");
                break;
            case 0x70000000:
                printf("LOPROC\t\t");
                break;
            case 0x7fffffff:
                printf("PT_HIPROC\t");
                break;
             default:
             	printf("0x%x\t",Phdr[i].p_type);
            }
            printf("  %-16lx %-16lx %-16lx %-16lx %-16lx ", Phdr[i].p_offset, Phdr[i].p_vaddr, Phdr[i].p_paddr, Phdr[i].p_filesz, Phdr[i].p_memsz);
            switch (Phdr[i].p_flags)
            {
            case PF_X:
                printf("%-16s %-lX\n", " E", Phdr[i].p_align);
                break;
            case PF_W:
                printf("%-16s %-lX\n", " W ", Phdr[i].p_align);
                break;
            case PF_R:
                printf("%-16s %-lX\n", "R ", Phdr[i].p_align);
                break;
            case PF_X | PF_W:
                printf("%-16s %-lX\n", " WE", Phdr[i].p_align);
                break;
            case PF_X | PF_R:
                printf("%-16s %-lX\n", "R E", Phdr[i].p_align);
                break;
            case PF_W | PF_R:
                printf("%-16s %-lX\n", "RW ", Phdr[i].p_align);
                break;
            case PF_X | PF_R | PF_W:
                printf("%-16s %-lX\n", "RWE", Phdr[i].p_align);
                break;
            default:
                printf("\n");
                break;
            }
            if(Phdr[i].p_type==3){
            	char* temp=(char*)malloc(sizeof(char)*114514);
            	fseek(fp, Phdr[i].p_offset, SEEK_SET);
        		fread(temp, Phdr[i].p_memsz, 1, fp);
            	printf("                                 [Requesting program interpreter: %s]\n", temp);
            }
        }
        printf("-------------------------------------------------------------------\n");
          printf("Section to Segment mapping:\n");
	printf("  Segment...\n");
	for(int i=0;i<elf_header.e_phnum;++i)
    	{
        printf("   %-7d", i);
        for(int n = 0;n<elf_header.e_shnum;++n)
        {
            Elf64_Off temp = shdr[n].sh_addr + shdr[n].sh_size;
            if((shdr[n].sh_addr>Phdr[i].p_vaddr && shdr[n].sh_addr<Phdr[i].p_vaddr + Phdr[i].p_memsz)  ||
                    (temp > Phdr[i].p_vaddr && temp<=Phdr[i].p_vaddr + Phdr[i].p_memsz))
            {
                printf("%s ", (char*)(shdr[n].sh_name +shstrtab ));
            }
        }
        printf("\n");
    	}
    }

效果:

 

 

最后

像help之类没必要就不写了,目前先完成了64位下的-h,-S,-s,-l,后续有时间再试一下32位的,不过原理都一样。这个写的还是很粗糙的。之前学习了一下makefile语法,准备正经写一下,但发现分步不算太大,于是最终没有分开。很多变量按理说放在全局更合适,可能写32完整版时程序结构会有大改动,那么先这样吧。第一次正经写程序,还请多多包涵。

完整版:

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <string.h>
int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        exit(0);
    }
    FILE *fp;
    Elf64_Ehdr elf_header;
    fp = fopen(argv[2], "r");
    if (fp == NULL)
    {
        exit(0);
    }
    int readfile;
    readfile = fread(&elf_header, sizeof(Elf64_Ehdr), 1, fp);
    if (readfile == 0)
    {
        exit(0);
    } //无论如何,先去读文件头
    if (!strcmp(argv[1], "-h"))
    { //-h展示文件头
        if (elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E')
        {
            printf("头标志: ");
            for (int x = 0; x < 16; x++)
            {
                printf("%02x ", elf_header.e_ident[x]);
            }
            printf("\n");
            char *file_type[4] = {"Null", "可重定位文件", "可执行文件", "共享目标文件"};
            printf("文件类型: \t\t%s\n", file_type[elf_header.e_type]);
            printf("运行平台: \t\t%hx\n", elf_header.e_machine);
            printf("入口虚拟RVA: \t\t0x%lx\n", elf_header.e_entry);
            printf("程序头文件偏移: \t%ld(bytes)\n", elf_header.e_phoff);
            printf("节头表文件偏移: \t%ld(bytes)\n", elf_header.e_shoff);
            printf("ELF文件头大小: \t\t%d\n", elf_header.e_ehsize);
            printf("ELF程序头大小: \t\t%d\n", elf_header.e_phentsize);
            printf("ELF程序头表计数: \t%d\n", elf_header.e_phnum);
            printf("ELF节头表大小: \t\t%d\n", elf_header.e_shentsize);
            printf("ELF节头表计数: \t\t%d\n", elf_header.e_shnum);
            printf("字符串表索引节头: \t%d\n", elf_header.e_shstrndx);
        }
    }
    Elf64_Shdr *shdr = (Elf64_Shdr *)malloc(sizeof(Elf64_Shdr) * elf_header.e_shnum); //准备空间
    fseek(fp, elf_header.e_shoff, SEEK_SET);                                          //找到段表所在位置
    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;                                                           //名字指针,为下面做准备
    fread(shstrtab, shdr[elf_header.e_shstrndx].sh_size, 1, fp);                      //读进来
    rewind(fp);

    if (!strcmp(argv[1], "-S"))
    { //-S
        int shnum, x;
        printf("[Num]\t节类型\t\tflag\t\t节地址\t节偏移\t节大小\t节的名称\n");
        for (shnum = 1; shnum < elf_header.e_shnum; shnum++)
        {
            char *type[12] = {"无效段\t\t", "程序段\t\t", "符号表\t\t", "字符串表\t", "重定位表\t", "符号哈希表\t", "动态链接信息\t", "提示行信息\t", "无内容\t\t", "重定位信息\t", "保留\t\t", "动态链接符号表\t"};
            char *flag[8] = {"Null\t\t", "可写\t\t", "ALLOC\t\t", "可写&ALLOC\t", "可执行\t\t", "可写&可执行\t", "ALLOC&可执行\t", "可写&执行&ALLOC\t"};
            names = shstrtab + shdr[shnum].sh_name;
            printf("[%02d]\t", shnum);
            if (shdr[shnum].sh_type < 12)
                printf("%s", type[shdr[shnum].sh_type]);
            else
                printf("gnu相关\t\t");
            if (shdr[shnum].sh_flags < 8)
                printf("%s", flag[shdr[shnum].sh_flags]);
            else
                printf("我也不知道\t"); //跑的时候发现有的不太对劲,但是我也不知道那是啥,没查到
            printf("%lx\t%lx\t%lx\t%s\n", shdr[shnum].sh_addr, shdr[shnum].sh_offset, shdr[shnum].sh_size, names);
        }
    }
    Elf64_Sym* Symbol=NULL;
    char *strtab = NULL;
    char *dynstrtab=NULL;
        if(!strcmp(argv[1],"-s")){
            int shnum;
            int temp;
            for(shnum=1;shnum<elf_header.e_shnum;shnum++){
                if (shdr[shnum].sh_type != 3)
                    continue;
                if (!strcmp(shstrtab + shdr[shnum].sh_name, ".shstrtab"))
                    continue;
                if(!strcmp(shstrtab + shdr[shnum].sh_name, ".strtab")){
                    strtab = (char *)malloc(sizeof(char) * shdr[shnum].sh_size);
                    if(strtab==NULL){
                    printf("malloc error");
                    return 0;
                    }
                    fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                    temp=fread(strtab, shdr[shnum].sh_size, 1, fp); 
                    if(temp==0){
                    printf("fread eroor");
                    return 0;
                    }
                    rewind(fp);
                }
                if(!strcmp(shstrtab + shdr[shnum].sh_name, ".dynstr")){
                    dynstrtab=(char*)malloc(sizeof(char)*shdr[shnum].sh_size);
                    if(dynstrtab==NULL){
                    printf("malloc error");
                    return 0;
                    }
                    fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                    temp=fread(dynstrtab, shdr[shnum].sh_size, 1, fp); 
                    if(temp==0){
                    printf("fread eroor");
                    return 0;
                    }
                    rewind(fp);
                }
          	}                                                //目前检测str正常
            if(strtab==NULL){
            printf("没找到字符串表");
            return 0;
            }
            for(shnum=1;shnum<elf_header.e_shnum;shnum++){
                char*Binding[3]={"LOCAL","GLOBAL","WEAK"};
                char*type[5]={"未知","变量","函数","段","文件"};
                if(shdr[shnum].sh_type!=2&&shdr[shnum].sh_type!=11)continue;
                if(shdr[shnum].sh_type==2){  //如果是普通的符号段
                    fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                    printf("符号表偏移:0x%lx\t",shdr[shnum].sh_offset);
                    Symbol=(Elf64_Sym*)malloc(sizeof(char)*shdr[shnum].sh_size);
                    temp=fread(Symbol, shdr[shnum].sh_size, 1, fp); 
                    if(temp==0){
                    printf("fread eroor");
                    return 0;
                    }
                    rewind(fp);
                    printf("表名:%s\n",shstrtab + shdr[shnum].sh_name);
                    printf("  Idx:   VALUE:  SIZE:  Type:   Bind:\tSHNDX:\tNAME:\n");
                    int num=shdr[shnum].sh_size/sizeof(Elf64_Sym);
                    for(int i=1;i<num;i++){
                    printf("  %3d : ",i);
                    printf("  0x%-8lx%-7lx",Symbol[i].st_value,Symbol[i].st_size);
                    printf("%s\t %s\t",type[Symbol[i].st_info&0xF],Binding[Symbol[i].st_info>>4]);
                    printf("%d\t%s\n",Symbol[i].st_shndx,strtab+Symbol[i].st_name);
                    }
                }
                else{//动态链接的符号段
                    fseek(fp, shdr[shnum].sh_offset, SEEK_SET);
                    printf("符号表偏移:0x%lx\t",shdr[shnum].sh_offset);
                    Elf64_Sym* DynSymbol=(Elf64_Sym*)malloc(sizeof(char)*shdr[shnum].sh_size);
                    temp=fread(DynSymbol, shdr[shnum].sh_size, 1, fp);
                    if(temp==0){
                    printf("fread eroor");
                    return 0;
                    } 
                    rewind(fp);
                    printf("表名:%s\n",shstrtab + shdr[shnum].sh_name);
                    printf("  Idx:   VALUE:  SIZE:  Type:   Bind:\tSHNDX:\tNAME:\n");
                    int num=shdr[shnum].sh_size/sizeof(Elf64_Sym);
                    for(int i=1;i<num;i++){
                    printf("  %3d : ",i);
                    printf("  0x%-8lx%-7lx",DynSymbol[i].st_value,DynSymbol[i].st_size);
                    printf("%s\t %s\t",type[DynSymbol[i].st_info&0xF],Binding[DynSymbol[i].st_info>>4]);
                    printf("%d\t%s\n",DynSymbol[i].st_shndx,dynstrtab+DynSymbol[i].st_name);
                    }
                }
            }
          }
          Elf64_Phdr *Phdr = NULL;
    if (!strcmp(argv[1], "-l"))
    {
        int size = elf_header.e_phentsize; //先读程序头表
        Phdr = (Elf64_Phdr *)malloc(sizeof(Elf64_Phdr) *elf_header.e_phnum );
        fseek(fp, elf_header.e_phoff, SEEK_SET);
        int temp = fread(Phdr, sizeof(Elf64_Phdr) *elf_header.e_phnum, 1, fp);
        if (temp == 0)
        {
            printf("fread eroor");
            return 0;
        }
        printf("文件类型:");
        switch (elf_header.e_type)
        {
        case 0:
            printf(" No file type\n");
            return 0;
        case 1:
            printf(" Relocatable file\n");
            return 0;
        case 2:
            printf(" Executable file\n");
            break;
        case 3:
            printf(" Shared object file\n");
            break;
        case 4:
            printf(" Core file\n");
            break;
        default:
            printf(" ERROR\n");
        }
        printf("入口点位置 0X%0lX\n", elf_header.e_entry);
        printf("共有 %d 程序头, 偏移位置 %lu\n", elf_header.e_phnum, elf_header.e_phoff);
        printf("Program Headers:\n");
        printf(" %-16s %-16s %-16s %-16s", "Type", "Offset", "VirtAddr", "PhysAddr");
        printf(" %-16s %-16s %-16s %-6s\n", "FileSiz", "MemSiz", "Flags", "Align");
        int i;
        for (i = 0; i < elf_header.e_phnum; i++)
        {	
            switch (Phdr[i].p_type)
            {
            case 0:
                printf("NULL\t\t");
                break;
            case 1:
                printf("LOAD\t\t");
                break;
            case 2:
                printf("DYNAMIC\t\t");
                break;
            case 3:
                printf("INTERP\t\t");
                break;
            case 4:
                printf("NOTE\t\t");
                break;
            case 5:
                printf("SHLIB\t\t");
                break;
            case 6:
                printf("PHDR\t\t");
                break;
            case 7:
                printf("TLS\t\t");
                break;
            case 8:
                printf("NUM\t\t");
            case 0x60000000:
                printf("LOOS\t\t");
                break;
            case 0x6474e550:
                printf("GNU_EH_FRAME\t");
                break;
            case 0x6474e551:
                printf("GNU_STACK\t");
                break;
            case 0x6474e552:
                printf("GNU_RELRO\t");
                break;
            case 0x6ffffffa:
                printf("LOSUNW\t\t");
                break;
            case 0x6ffffffb:
                printf("SUNWSTACK\t");
                break;
            case 0x6fffffff:
                printf("HIOS\t\t");
                break;
            case 0x70000000:
                printf("LOPROC\t\t");
                break;
            case 0x7fffffff:
                printf("PT_HIPROC\t");
                break;
             default:
             	printf("0x%x\t",Phdr[i].p_type);
            }
            printf("  %-16lx %-16lx %-16lx %-16lx %-16lx ", Phdr[i].p_offset, Phdr[i].p_vaddr, Phdr[i].p_paddr, Phdr[i].p_filesz, Phdr[i].p_memsz);
            switch (Phdr[i].p_flags)
            {
            case PF_X:
                printf("%-16s %-lX\n", " E", Phdr[i].p_align);
                break;
            case PF_W:
                printf("%-16s %-lX\n", " W ", Phdr[i].p_align);
                break;
            case PF_R:
                printf("%-16s %-lX\n", "R ", Phdr[i].p_align);
                break;
            case PF_X | PF_W:
                printf("%-16s %-lX\n", " WE", Phdr[i].p_align);
                break;
            case PF_X | PF_R:
                printf("%-16s %-lX\n", "R E", Phdr[i].p_align);
                break;
            case PF_W | PF_R:
                printf("%-16s %-lX\n", "RW ", Phdr[i].p_align);
                break;
            case PF_X | PF_R | PF_W:
                printf("%-16s %-lX\n", "RWE", Phdr[i].p_align);
                break;
            default:
                printf("\n");
                break;
            }
            if(Phdr[i].p_type==3){
            	char* temp=(char*)malloc(sizeof(char)*114514);
            	fseek(fp, Phdr[i].p_offset, SEEK_SET);
        		fread(temp, Phdr[i].p_memsz, 1, fp);
            	printf("                                 [Requesting program interpreter: %s]\n", temp);
            }
        }
        printf("-------------------------------------------------------------------\n");
          printf("Section to Segment mapping:\n");
	printf("  Segment...\n");
	for(int i=0;i<elf_header.e_phnum;++i)
    	{
        printf("   %-7d", i);
        for(int n = 0;n<elf_header.e_shnum;++n)
        {
            Elf64_Off temp = shdr[n].sh_addr + shdr[n].sh_size;
            if((shdr[n].sh_addr>Phdr[i].p_vaddr && shdr[n].sh_addr<Phdr[i].p_vaddr + Phdr[i].p_memsz)  ||
                    (temp > Phdr[i].p_vaddr && temp<=Phdr[i].p_vaddr + Phdr[i].p_memsz))
            {
                printf("%s ", (char*)(shdr[n].sh_name +shstrtab ));
            }
        }
        printf("\n");
    	}
    }
    return 0;
}

参考资料:

《程序员的自我修养》

elf.h - elf/elf.h - Glibc source code (glibc-2.31) - Bootlin

​​​​​​程序的本质之二ELF文件的文件头、section header和program header_tanglinux的博客-CSDN博客

 ELF文件格式解析器 原理 + 代码_游戏逆向 (yxfzedu.com)

C/C++ 实现ELF结构解析工具 - lyshark - 博客园 (cnblogs.com) 


版权声明:本文为u014377094原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。