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

PE格式:实现ELF结构解析工具的更多相关文章

  1. Win32汇编-编写PE结构解析工具

    汇编语言(assembly language)是一种用于电子计算机.微处理器.微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地 ...

  2. 手写PE结构解析工具

    PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如 ...

  3. innodb结构解析工具---innodb_ruby

    1.下载ruby并安装ruby: ftp://ftp.ruby-lang.org/pub/ruby/ ftp://ftp.ruby-lang.org/pub/ruby/ruby-2.3-stable. ...

  4. Linux可执行文件格式-ELF结构详解

    表1. ELF文件类型分类 ELF文件类型 说明 实例 Relocatable File 可重定位文件 未链接之前的ELF文件,可用于链接可执行文件或静态链接库 Linux下的".o&quo ...

  5. PE格式第九讲,资源表解析

    PE格式第九讲,资源表解析 一丶熟悉Windows管理文件的方法 首先,为什么标题是这个,主要是为了下边讲解资源方便,因为资源结构体很乱.如果直接拿出来讲解,那么就会很晕. 1.windows管理文件 ...

  6. Golang Json文件解析为结构体工具-json2go

    代码地址如下:http://www.demodashi.com/demo/14946.html 概述 json2go是一个基于Golang开发的轻量json文件解析.转换命令行工具,目前支持转换输出到 ...

  7. PE头结构解析(代码实现)

    PE头结构解析(代码实现) 图表实现在:https://www.cnblogs.com/juicyhumberger/articles/17064764.html #include "std ...

  8. (转)AVI文件格式解析+AVI文件解析工具

    AVI文件解析工具下载地址:http://download.csdn.net/detail/zjq634359531/7556659 AVI(Audio Video Interleaved的缩写)是一 ...

  9. WinHex分析PE格式(1)

    最近在一直努力学习破解,但是发现我的基础太差了,就想学习一下PE结构.可是PE结构里的结构关系太复杂,看这老罗的WiN32汇编最后一章 翻两页又合上了..把自己的信心都搞没了.感觉自己的理解能力不行, ...

  10. PE格式第七讲,重定位表

    PE格式第七讲,重定位表 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶何为重定位(注意,不是重定位表格) 首先, ...

随机推荐

  1. 【GO】Go语言介绍 Go开发环境搭建 第一个helloworld 变量命名规范 变量的定义和使用

    今日内容 1 Go语言介绍 #Go语言介绍 Go 即 Golang,是Google公司2009年11月正式对外公开的一门编程语言 Go是[静态 强类型]语言,是区别于解析型语言的编译型语言 静态:类型 ...

  2. ME5A报表增强

    一.ME5A报表新增长文本字段 结构中添加字段 在该方法下添加对应的取值逻辑 二.ME5A点击科目分配按钮界面添加字段 结构中添加字段 对应位置写取值逻辑 定期更文,欢迎关注 TRANSLATE wi ...

  3. 关于“Github上传以及Clone时发生的 Failed to connect to github.com port 443: Timed out 问题解法记录

    本文是记录关于如何解决 "Github上传以及Clone时发生的 Failed to connect to github.com port 443: Timed out 错误" 看 ...

  4. 第九届蓝桥杯(2018)C/C++大学A组省赛题解

    第一题:分数 1/1 + 1/2 + 1/4 + 1/8 + 1/16 + - 每项是前一项的一半,如果一共有20项, 求这个和是多少,结果用分数表示出来. 类似:3/2 当然,这只是加了前2项而已. ...

  5. kafka集群七、java操作kafka(有密码验证)

    系列导航 一.kafka搭建-单机版 二.kafka搭建-集群搭建 三.kafka集群增加密码验证 四.kafka集群权限增加ACL 五.kafka集群__consumer_offsets副本数修改 ...

  6. js根据对象数组中某一属性值,合并相同项,并对某一属性累加处理

    https://www.cnblogs.com/mahao1993/p/13491430.html

  7. shell脚本(9)-流程控制for

    一.循环介绍 for循环叫做条件循环,或者for i in,可以通过for实现流程控制 二.for语法 1.for语法一:for in for var in value1 value2 ...... ...

  8. java项目实践-webapp-mytomcat-day16

    目录 1. http协议 2. 自定义的web框架 3. 具体实现 4. 启动 1. http协议 CS架构 建立连接"三次握手" 断开连接 "四次挥手" 三次 ...

  9. 带你熟悉NLP预训练模型:BERT

    本文分享自华为云社区<[昇思技术公开课笔记-大模型]Bert理论知识>,作者: JeffDing. NLP中的预训练模型 语言模型演变经历的几个阶段 word2vec/Glove将离散的文 ...

  10. ORA-00947:Not enough values (没有足够的值)

    1.问题 2.解决方式 大概率是关系表实际列数大于你所填的元素个数,请检查是否有疏漏的列即可. 我这里是以为代理键直接忽略不写即可,没有标明具体插入列,但是还是得标明才行 --创建图书目录表TITLE ...