roger 发表于 2020-6-15 20:59:41

手写ELF结构解析工具

  ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.
  读取ELF头: 首先需要先来编译一个简单的ELF文件,然后将文件编译并连接.
# cat lyshark.c
#include <stdio.h>

int main()
{
printf("hello lyshark");
return 0;
}
# gcc -c lyshark.c
# gcc -o lyshark lyshark.o
  Linux系统中有一个默认命令readelf -h可以解析指定文件的头结构.
# readelf -h lyshark
ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                           ELF6464位程序
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
# hexdump -s 0 -n 64 -C lyshark

000000007f 45 4c 46 02 01 01 0000 00 00 00 00 00 00 00|.ELF............|
0000001002 00 3e 00 01 00 00 0030 04 40 00 00 00 00 00|..>.....0.@.....|
0000002040 00 00 00 00 00 00 0040 19 00 00 00 00 00 00|@.......@.......|
0000003000 00 00 00 40 00 38 0009 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;   /* 一个字节数组用来确认文件是否是一个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字节
#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,"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 == 0x7F || elf_header.e_ident == 'E')
      {
                printf("头标志: ");
                for(int x =0;x<16;x++)
                {
                        printf("%x ",elf_header.e_ident);
                }
                printf("\n");
      }
      return 0;
}
  编译并运行即可读取出文件头部的前16个字节的字节数组,我们最需要关注的就是开头前4个字节,其标志着PE文件的开始
# gcc -std=c99 -o elf elf.c
# ./elf lyshark
头标志: 7f 45 4c 46 2 1 1 0 0 0 0 0 0 0 0 0
  除此之外,读取其他头结构数据,代码与上方类似,只需要稍微改动一下就好.
      if(elf_header.e_ident == 0x7F || elf_header.e_ident == '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);
      }
  运行后,就可以读取到所有的节头数据.
# 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个字节,就是节表所在位置.
# hexdump -s 144 -n 100 -C lyshark
0000009038 02 40 00 00 00 00 001c 00 00 00 00 00 00 00|8.@.............|
000000a01c 00 00 00 00 00 00 0001 00 00 00 00 00 00 00|................|
000000b001 00 00 00 05 00 00 0000 00 00 00 00 00 00 00|................|
000000c000 00 40 00 00 00 00 0000 00 40 00 00 00 00 00|..@.......@.....|
000000d00c 07 00 00 00 00 00 000c 07 00 00 00 00 00 00|................|
000000e000 00 20 00 00 00 00 0001 00 00 00 06 00 00 00|.. .............|
000000f010 0e 00 00                                       |....|
  编程实现简单的节表读取,只需要在上方代码基础上进行修改即可.
      if(elf_header.e_ident == 0x7F || elf_header.e_ident == '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.sh_offset, SEEK_SET);
                char shstrtab.sh_size];
                char *names = shstrtab;
                temp = fread(shstrtab, shdr.sh_size, 1, fp);
                printf("节类型\t节地址\t节偏移\t节大小\t节名称\n");
                for(shnum = 0; shnum < elf_header.e_shnum; shnum++)
                {
                        names = shstrtab;
                        names=names+shdr.sh_name;
                        printf("%x\t%x\t%x\t%x\t%s \n",shdr.sh_type,shdr.sh_addr,shdr.sh_offset,shdr.sh_size,names);
                }

      }
  Linux系统中也可以使用objdump命令读取程序的节表信息.
# objdump -h lyshark
lyshark:   file format elf64-x86-64
Sections:
Idx Name          Size      VMA               LMA               File offAlgn
0 .interp       0000001c00000000004002380000000000400238000002382**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 0000002000000000004002540000000000400254000002542**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 0000002400000000004002740000000000400274000002742**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash   0000001c00000000004002980000000000400298000002982**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym       0000006000000000004002b800000000004002b8000002b82**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr       0000003f00000000004003180000000000400318000003182**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version0000000800000000004003580000000000400358000003582**1
  当然objdump命令,还可以排查文件的SO加载情况.
# objdump -p /usr/bin/git | grep NEEDED
NEEDED               libpcre.so.1
NEEDED               libz.so.1
NEEDED               libpthread.so.0
NEEDED               libc.so.6

Cherster230 发表于 2020-6-18 10:33:51

谢谢版主分享
页: [1]
查看完整版本: 手写ELF结构解析工具