image-20230314100936800

一、基础

ELF文件 整体架构

​ elf文件主要分了几部分:头部、程序头、节表头以及各种节.

​ elf头部中存储了程序头和节表头的位置,节头中又存储了各个节的位置,以此进行索引.

​ 所以可以回答第2个问题的一部分, 这些节(我认为题目应该说的是节的意思, 段和节经常混用) 在ELF中的位置是可以任意调换的,因为在头部中指定了它们的位置,只要索引信息没问题就可以.

img

链接视图与执行时视图

​ elf静态文件是链接视图,当加载到内存后,会有些许变化.

image-20230322090941304

​ 在链接视图中,节表是必须的,需要根据它来定位各个节,以及保存节的属性,而程序头表则是可选的,程序头表的内容是加载进内存后的属性(是否可选要看该文件的类型??).

​ 在执行视图中,程序头表是必须的,节表就是可选的了.

​ 在静态文件中,有各种不一样的程序的节(section),比如.text节 .bss节,在加载进内存后,加载器会将相同的节属性(比如只读)合并一个段(segment)

ELF文件头 ehdr

​ 详细内容:https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html

​ 结构体定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define EI_NIDENT 16

typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */ elf文件类型
Elf64_Half e_machine; /* Architecture */ CPU架构
Elf64_Word e_version; /* Object file version */ 指定ELF版本,一般都为1
Elf64_Addr e_entry; /* Entry point virtual address */ 代码运行的入口
Elf64_Off e_phoff; /* Program header table file offset */ 程序头表在文件中的偏移
Elf64_Off e_shoff; /* Section header table file offset */ 节头表在文件中的偏移
Elf64_Word e_flags; /* Processor-specific flags */ 在e_machine指定的处理器下的一些特性
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */ 程序头表每个条目的大小
Elf64_Half e_phnum; /* Program header table entry count */ 程序头表中条目的树木
Elf64_Half e_shentsize; /* Section header table entry size */ 节头表每个条目的大小
Elf64_Half e_shnum; /* Section header table entry count */ 节头表中条目的数量
Elf64_Half e_shstrndx; /* Section header string table index */ 每个节都有一个名称,这些名称存储在.shstrtab节中,e_shstrndx指定这个特殊的节所在节头表的下表
} Elf64_Ehdr;

​ 可以用010editor加载插件后清晰的看到结构(需要在templates里面安装和加载一下elf模版)

image-20230322094455562

e_ident

​ 一个16字节大小的数组

image-20230322094613842

​ 这个下标范围标错了吧….

ELF节表头 shdr

​ e_shoff是 0x19a8(小端)

image-20230322100247070

​ 可以看到0x19a8开始,存储的是各个节的内容

image-20230322100349315

​ 结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Section header.  */

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

typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */ 名称,值是在string表的索引
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;

sh_name

​ 这里存储的是节名的下标,节名实际存在于.shstrtab中,这里存的是在shstrtab中的下标.

节 section

.text节

​ 保存了程序代码指令的代码节. 一段可执行程序如果存在Phdr, .text字节就会存在于text段中(如果不存在呢??)

.rodata节

​ 保存只读数据,如一行c语言代码中的字符串 printf(“hello world\n”); 因为是只读,所以也放到了text段

.data节

​ 保存了初始的全局变量等数据.存在于data段

.bss节

​ 保存了未进行初始化的全局数据,存在于data段.

.plt

​ 包含了动态链接器调用从共享库导入的函数所必需的相关代码. 存在于text段中,同样保存了代码.

image-20230303132136453

.got

​ 保存了全局偏移表.这个存的是变量

image-20230228164021947

.got.plt

​ 这个存的是函数引用

.dynsym

​ 保存了从共享库导入的动态符号信息,该节保存在text段中

.dynstr

​ 保存了动态符号字符串表,表中存放了一系列字符串,这些字符串表示符号的名称,以空字符00作为终止符

ELF程序头 phdr (segment段)

​ 程序头中描述了可执行文件的段信息,即程序如何加载到内存以及内存中的布局.

​ 程序头可以通过elf文件头的e_phoff(程序头表偏移量)字段来得到位置

​ 它主要有5种类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Program segment header.  */

typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags , I.E execute|read|write */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

typedef struct {
Elf64_Word p_type; //类型
Elf64_Word p_flags; // 读写执行权限
Elf64_Off p_offset; //文件中的偏移
Elf64_Addr p_vaddr; //虚拟地址
Elf64_Addr p_paddr; //物理地址
Elf64_Xword p_filesz; //在文件中的大小
Elf64_Xword p_memsz; //在内存中的大小
Elf64_Xword p_align; //
} Elf64_Phdr;

PT_LOAD

​ 一个可执行文件至少有一个该类型的段.PT_LOAD表述的是可装载的段,这种类型的段会被装载或者映射到内存中,

编译器如何对节段进行组织安排

​ 这里所说的是编译环节,从源代码到二进制文件.

ELF装载过程

​ 这里是指把二进制文件加载进内存的过程

问题

一个elf程序或者动态库是否必须有某些节,他们固定的名字和含义

image-20230323221121391

编译器如何对这些段进行编排和组织,顺序是否可以任意调换

image-20230323221148505

是否可以欺骗程序加载器或者反汇编引擎(例如,增、删、改text段)

image-20230323221203956

文件头52????

magic number16字节 还剩36字节

8*2 + 4 * 2 + 4 + 4 * 2 = 36

0x3e = 62

测试

删除一些段,程序是否还能正常运行? 程序必须的段 节是哪些??

怎么增删改text段等 欺骗编译器等

代码编写

自己也要编译成32位才行

gcc -m32 1.c && ./a.out test

可以参考readelf

先把32位的都给写好,然后再加上64位的

1.改进,32位 64位,根据文件头判断,然后再进行解析

解析段和节,名称 起始和结束位置, 大小,权限

fatal error: elf32.h: No such file or directory
#include <elf32.h>

fatal error: bits/libc-header-start.h: No such file or directory

https://stackoverflow.com/questions/54082459/fatal-error-bits-libc-header-start-h-no-such-file-or-directory-while-compili

e_ident 怎么输出多位, c

怎么定义空格字符呢?

gcc -m32 1.c 需要编译成32位的,目前也只能解析32位程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <elf.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>

// 解析文件头
// int check_elf_head(file)
// {
// printf("just a test%d\n",file[0]);
// return 0;
// }



int main(int argc, char **argv)
{

int fd, i;
uint8_t *mem;
struct stat st;
char *StringTable, *interp;

Elf32_Ehdr *Elf_header;
Elf32_Phdr *Pro_header;
Elf32_Shdr *Section_header;

if (argc < 2)
{
printf("usage: %s <executable> \n", argv[0]);
exit(0);
}

if ((fd = open(argv[0], O_RDONLY)) < 0) //什么情况下会这样呢? 和权限好像没关系
{
perror("open");
exit(-1);
}

if (fstat(fd, &st) < 0)
{
perror("fstat");
exit(-1);
}


mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);


// 64位还是32位
if (mem[4] == 0x2)
printf("it is 64 bit %d\n",mem[4]);
else
printf("it is 32 bit %d\n",mem[4]);
// check_elf_head(mem);

//头部信息
Elf_header = (Elf32_Ehdr *)mem;
printf("\n");
printf("e_ident: \t%10x\n",Elf_header->e_ident[16]);
printf("Type: \t%d\n",Elf_header->e_type);
printf("Machine: \t%d\n",Elf_header->e_machine); /* Architecture */
printf("Version: \t%#02x\n",Elf_header->e_version);
printf("Entry point address: \t%#02x\n",Elf_header->e_entry);
printf("Start of program headers: \t%d(bytes)\n",Elf_header->e_phoff);
printf("Start of section headers: \t%d(bytes)\n",Elf_header->e_shoff);
printf("Flags: \t%#02x\n",Elf_header->e_flags);
printf("Size of this header: \t%d(bytes)\n",Elf_header->e_ehsize);
printf("Size of program headers: \t%d(bytes)\n",Elf_header->e_phentsize);
printf("Number of program headers: \t%d\n",Elf_header->e_phnum);
printf("Size of section headers: \t%d(bytes)\n",Elf_header->e_shentsize);
printf("Number of section headers: \t%d\n",Elf_header->e_shnum);
printf("Section header string table index:\t%d\n",Elf_header->e_shstrndx);


// 解析节信息 readelf -S
Section_header = (Elf32_Shdr *)&mem[Elf_header->e_shoff];
StringTable = &mem[Section_header[Elf_header->e_shstrndx].sh_offset];
printf("Section header list:\n\n");
char a = '|';

// size flag
// for (i = 1; i < Elf_header->e_shnum; i++) {
// printf("%-20s\t\t%-10x\t\t0x%x\t\t%x\t\t%c\n",&StringTable[shdr[i].sh_name],shdr[i].sh_type,shdr[i].sh_addr,shdr[i].sh_size,shdr[i].sh_flags);
// }

printf("[Nr] Name%-20c\t\tType%-10c\tAddr\t\tOff\tSize\tES Flg Al",a);
for(i = 1; i<Elf_header->e_shnum; i++)
{
/*printf("\n[%-2d] %-20s %-10x %08x %06x %06x %02x %02x %02x "\
*/ printf("\n[%-2d] %-20s\t\t%-10x\t%08x\t%06x\t%06x\t%02x %02x %02x "\
, i,&StringTable[Section_header->sh_name],Section_header->sh_type,Section_header->sh_addr,\
Section_header->sh_offset,Section_header->sh_size,Section_header->sh_entsize,\
Section_header->sh_flags,Section_header->sh_addralign);
Section_header++;
}

// 解析段信息
// readelf -l
Pro_header = (Elf32_Phdr *)&mem[Elf_header->e_phoff];

printf("\n");

printf("\n/*****Program Headers:*****/\n");
printf("starting at offset: %d\n",Elf_header->e_phoff);
printf("Number of program headers: %d\n",Elf_header->e_phnum);
printf("Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg\n");

for(i = 0; i<Elf_header->e_phnum; i++)
{
printf("%d %-#6x %-#x %-#x %-#5x %-#5x %-#x\n",Pro_header->p_type,Pro_header->p_offset,\
Pro_header->p_vaddr,Pro_header->p_paddr\
,Pro_header->p_filesz,Pro_header->p_memsz,Pro_header->p_flags);
Pro_header++;
}

return 0;
}

参考

ELF(5)手册

ELF官方规范文档

https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html

https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html

https://blog.csdn.net/qfanmingyiq/article/details/124295287

https://zhuanlan.zhihu.com/p/401446080?utm_id=0

https://github.com/jianhong-li/ElfReader

https://www.52pojie.cn/forum.php?mod=viewthread&tid=591986&highlight=elf%2B%BD%E2%CE%F6