PE格式:实现ELF结构解析工具

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.

ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.

读取ELF头: 首先需要先来编译一个简单的ELF文件,然后将文件编译并连接.

[root@localhost ~]# cat lyshark.c 
#include <stdio.h>

int main()
{
    printf("hello lyshark");
    return 0;
}
[root@localhost ~]# gcc -c lyshark.c 
[root@localhost ~]# gcc -o lyshark lyshark.o 

Linux系统中有一个默认命令readelf -h可以解析指定文件的头结构.

[root@localhost ~]# readelf -h lyshark
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64  64位程序
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V  调用约定
  ABI Version:                       0
  Type:                              EXEC (Executable file)  可执行文件
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400430            #程序的入口地址
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6464 (bytes into file)
  Flags:                             0x0               #标志
  Size of this header:               64 (bytes)        #本头大小
  Size of program headers:           56 (bytes)        #程序头大小
  Number of program headers:         9
  Size of section headers:           64 (bytes)        #节头大小
  Number of section headers:         31                #节表数量
  Section header string table index: 30                #字符串表索引节头

通过hexdump工具查看文件16进制文件头hexdump -s 0 -n 64 -C lyshark

[root@localhost ~]# hexdump -s 0 -n 64 -C lyshark

00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  30 04 40 00 00 00 00 00  |..>.....0.@.....|
00000020  40 00 00 00 00 00 00 00  40 19 00 00 00 00 00 00  |@.......@.......|
00000030  00 00 00 00 40 00 38 00  09 00 40 00 1f 00 1e 00  |....@.8...@.....|

linux系统中的节头文件保存在/usr/include/elf.h我通过查找找到了ELF64所对应的结构数据

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;

通过编程实现Magic的读取,或者说实现的是文件头e_ident[16]文件的读取,通过定义可得知文件头大小是16字节

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>

int main(int argc,char* argv[])
{
        if(argc < 2){ exit(0); }
        FILE *fp;
        Elf64_Ehdr elf_header;

        fp = fopen(argv[1],"r");
        if(fp == NULL) { exit(0); }

        int readfile;
        readfile = fread(&elf_header,sizeof(Elf64_Ehdr),1,fp);
        if(readfile == 0){ exit(0); }

        if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E')
        {
                printf("头标志: ");
                for(int x =0;x<16;x++)
                {
                        printf("%x ",elf_header.e_ident[x]);
                }
                printf("\n");
        }
        return 0;
}

编译并运行即可读取出文件头部的前16个字节的字节数组,我们最需要关注的就是开头前4个字节,其标志着PE文件的开始

[root@localhost ~]# gcc -std=c99 -o elf elf.c 
[root@localhost ~]# ./elf lyshark
头标志: 7f 45 4c 46 2 1 1 0 0 0 0 0 0 0 0 0 

除此之外,读取其他头结构数据,代码与上方类似,只需要稍微改动一下就好.

        if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E')
        {
                printf("文件类型: %hx\n",elf_header.e_type);
                printf("运行平台: %hx\n",elf_header.e_machine);
                printf("入口虚拟RVA: 0x%x\n",elf_header.e_entry);
                printf("程序头文件偏移: %d(bytes)\n",elf_header.e_phoff);
                printf("节头表文件偏移: %d(bytes)\n",elf_header.e_shoff);
                printf("ELF文件头大小: %d\n",elf_header.e_ehsize);
                printf("ELF程序头大小: %d\n",elf_header.e_phentsize);
                printf("ELF程序头表计数: %d\n",elf_header.e_phnum);
                printf("ELF节头表大小: %d\n",elf_header.e_shentsize);
                printf("ELF节头表计数: %d\n",elf_header.e_shnum);
                printf("字符串表索引节头: %d\n",elf_header.e_shstrndx);
        }

运行后,就可以读取到所有的节头数据.

[root@localhost ~]# gcc -std=c99 -o elf elf.c &&  ./elf lyshark
文件类型: 2
运行平台: 3e
入口虚拟RVA: 0x400430
程序头文件偏移: 64(bytes)
节头表文件偏移: 6464(bytes)
ELF文件头大小: 64
ELF程序头大小: 56
ELF程序头表计数: 9
ELF节头表大小: 64
ELF节头表计数: 31
字符串表索引节头: 30

读取ELF节表: 首先打开elf.h头文件,找到这个声明处Elf64_Shdr.

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;

通过使用hexdump -s 144从偏移为144的位置开始读取,向后读取100个字节,就是节表所在位置.

[root@localhost ~]# hexdump -s 144 -n 100 -C lyshark
00000090  38 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |8.@.............|
000000a0  1c 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
000000b0  01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00  |..@.......@.....|
000000d0  0c 07 00 00 00 00 00 00  0c 07 00 00 00 00 00 00  |................|
000000e0  00 00 20 00 00 00 00 00  01 00 00 00 06 00 00 00  |.. .............|
000000f0  10 0e 00 00                                       |....|

编程实现简单的节表读取,只需要在上方代码基础上进行修改即可.

        if(elf_header.e_ident[0] == 0x7F || elf_header.e_ident[1] == 'E')
        {
                int shnum, x;
                Elf64_Shdr *shdr = (Elf64_Shdr*)malloc(sizeof(Elf64_Shdr) * elf_header.e_shnum);
                temp = fseek(fp, elf_header.e_shoff, SEEK_SET);
                temp = 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;
                temp = fread(shstrtab, shdr[elf_header.e_shstrndx].sh_size, 1, fp);
                printf("节类型\t节地址\t节偏移\t节大小\t节名称\n");
                for(shnum = 0; shnum < elf_header.e_shnum; shnum++)
                {
                        names = shstrtab;
                        names=names+shdr[shnum].sh_name;
                        printf("%x\t%x\t%x\t%x\t%s \n",shdr[shnum].sh_type,shdr[shnum].sh_addr,shdr[shnum].sh_offset,shdr[shnum].sh_size,names);
                }

        }

Linux系统中也可以使用objdump命令读取程序的节表信息.

[root@localhost ~]# objdump -h lyshark
lyshark:     file format elf64-x86-64
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     0000001c  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000060  00000000004002b8  00000000004002b8  000002b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       0000003f  0000000000400318  0000000000400318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000008  0000000000400358  0000000000400358  00000358  2**1

当然objdump命令,还可以排查文件的SO加载情况.

[root@localhost ~]# objdump -p /usr/bin/git | grep NEEDED
  NEEDED               libpcre.so.1
  NEEDED               libz.so.1
  NEEDED               libpthread.so.0
  NEEDED               libc.so.6
相关文章
|
23天前
|
人工智能
歌词结构的巧妙安排:写歌词的方法与技巧解析,妙笔生词AI智能写歌词软件
歌词创作是一门艺术,关键在于巧妙的结构安排。开头需迅速吸引听众,主体部分要坚实且富有逻辑,结尾则应留下深刻印象。《妙笔生词智能写歌词软件》提供多种 AI 功能,帮助创作者找到灵感,优化歌词结构,写出打动人心的作品。
|
16天前
|
安全 程序员 API
|
10天前
|
自然语言处理 并行计算 数据可视化
免费开源法律文档比对工具:技术解析与应用
这款免费开源的法律文档比对工具,利用先进的文本分析和自然语言处理技术,实现高效、精准的文档比对。核心功能包括文本差异检测、多格式支持、语义分析、批量处理及用户友好的可视化界面,广泛适用于法律行业的各类场景。
|
1月前
|
人工智能 JavaScript 数据可视化
Cursor 、v0 和 Bolt.new:当今 AI 编程工具的全面解析与对比
本文对 Cursor AI、v0 和 Bolt.new 三大 AI 编程工具进行了全面比较,分析其各自优势与局限性,帮助开发者在不同工作流中灵活应用。
229 8
Cursor 、v0 和 Bolt.new:当今 AI 编程工具的全面解析与对比
|
1月前
|
机器学习/深度学习 搜索推荐 大数据
深度解析:如何通过精妙的特征工程与创新模型结构大幅提升推荐系统中的召回率,带你一步步攻克大数据检索难题
【10月更文挑战第2天】在处理大规模数据集的推荐系统项目时,提高检索模型的召回率成为关键挑战。本文分享了通过改进特征工程(如加入用户活跃时段和物品相似度)和优化模型结构(引入注意力机制)来提升召回率的具体策略与实现代码。严格的A/B测试验证了新模型的有效性,为改善用户体验奠定了基础。这次实践加深了对特征工程与模型优化的理解,并为未来的技术探索提供了方向。
92 2
深度解析:如何通过精妙的特征工程与创新模型结构大幅提升推荐系统中的召回率,带你一步步攻克大数据检索难题
|
16天前
|
机器学习/深度学习 自然语言处理 数据管理
GraphRAG核心组件解析:图结构与检索增强生成
【10月更文挑战第28天】在当今数据科学领域,自然语言处理(NLP)和图数据管理技术的发展日新月异。GraphRAG(Graph Retrieval-Augmented Generation)作为一种结合了图结构和检索增强生成的创新方法,已经在多个应用场景中展现出巨大的潜力。作为一名数据科学家,我对GraphRAG的核心组件进行了深入研究,并在此分享我的理解和实践经验。
38 0
|
1月前
|
域名解析 网络协议 安全
DNS查询工具简介
DNS查询工具简介
|
1月前
|
人工智能 JavaScript 数据可视化
Cursor、v0 和 Bolt.new:当今 AI 编程工具的全面解析与对比
本文深入解析了 Cursor AI、v0 和 Bolt.new 三大 AI 编程工具的特点与应用场景。Cursor 适合日常编码与团队协作,v0 专注于 UI 原型设计,Bolt.new 擅长全栈原型开发。各工具在功能上互为补充,开发者可根据需求灵活选择,以提升工作效率。
829 1
|
21天前
光纤电缆(FOC)的结构深度解析
【10月更文挑战第21天】
37 0
|
30天前
|
存储 前端开发 JavaScript
前端模块化打包工具的深度解析
【10月更文挑战第13天】前端模块化打包工具的深度解析

推荐镜像

更多