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博客