源代码
file:elfDemo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#include <stdio.h>
int
global_init_var
=
10
;
int
global_uninit_var;
void func (
int
sum
) {
printf(
"%d\n"
,
sum
);
}
void main(void) {
static
int
local_static_init_var
=
20
;
static
int
local_unstatic_var;
int
local_init_val
=
30
;
int
local_uninit_var;
func(gobal_init_var
+
local_init_val
+
local_static_init_var);
}
|
通过以下五条命令可得到5个目标文件
(图片 ‘&&’ 命令 把四五条命令合为一条)
1
2
3
4
5
|
gcc elfDemo.c
-
o elfDemo.
exec
gcc
-
static elfDemo.c
-
o elfDemo_static.
exec
gcc
-
c elfDemo.c
-
o elfDemo.rel
gcc
-
c
-
fPIC elfDemo.c
-
o elfDemo_pic.rel
gcc
-
shared elfDemo_pic.rel
-
o elfDemo.dyn
|
通过file结果可以看到ELF文件分为三种主要类型:
1.可执行文件(.exec);
经过链接的,可执行的目标文件,通常也被称为程序。
2.可重定位文件(.rel);
由源文件编译而成,但尚未链接的目标文件,通常以“.o”作为扩展名。用于与其他目标文件进行链接以构成可执行文件或动态链接库,通常是一段位置独立的代码。
3.共享目标文件(.dyn);
动态链接库文件。用于在链接过程中与其他动态链接库或可重定位文件一起构成新的目标文件,或在可执行文件加载时,链接到进程中作为运行代码的一部分。
其实还有第四种:核心存储文件(Core Dump file)。作为进程意外终止时进程地址空间的转存,也是ELF文件的一种。使用gdb读取这类文件可以辅助调试或查找程序崩溃的原因。
1
2
|
ELF被统称为目标文件,但与我们通常理解“.o”文件不同。我们可称目标文件为各种ELF文件称“.o”文件为可重定位文件。
我们可从两种视角审视一个:链接视角,通过节来划分;运行视角,通过段来划分。
|
通常目标文件都会包含代码(.text),数据(.data),BSS(.bss)三个节。
代码节:用于保存可执行的机器指令。
数据节:用于保存已初始化的全局变量和局部静态变量。
BSS节:用于保存未初始化的全局变量和静态变量。
除此之外,简化的目标文件还应包含一个文件头(File Header)。
程序被加载后,程序指令和数据被映射到只读和可读写的两个虚拟区域。
ELF文件头位于目标文件最开始的位置,包含描述整个文件的一些基本信息(ELF文件类型,版本/ABI版本,目标机器,程序入口,段表和节表的位置和长度等)。文件头部存在魔术字符(7f 45 4c 46),字符串“\177ELF”,当文件被映射到内存时,可以通过搜索该字符确定映射地址,这在dump内存时非常有用。
Elf64_Ehdr结构体如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef struct
{
unsigned char e_ident[EI_NIDENT];
/
*
Magic number
and
other info
*
/
Elf64_Half e_type;
/
*
Object
file
type
*
/
Elf64_Half e_machine;
/
*
Architecture
*
/
Elf64_Word e_version;
/
*
Object
file
version
*
/
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
*
/
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
*
/
} Elf64_Ehdr;
|
1
2
3
4
5
6
7
8
9
10
11
12
|
typedef struct {
Elf64_Half sh_name;
/
*
section name
*
/
Elf64_Half sh_type;
/
*
section
type
*
/
Elf64_Xword sh_flags;
/
*
section flags
*
/
Elf64_Addr sh_addr;
/
*
virtual address
*
/
Elf64_Off sh_offset;
/
*
file
offset
*
/
Elf64_Xword sh_size;
/
*
section size
*
/
Elf64_Half sh_link;
/
*
link to another
*
/
Elf64_Half sh_info;
/
*
misc info
*
/
Elf64_Xword sh_addralign;
/
*
memory alignment
*
/
Elf64_Xword sh_entsize;
/
*
table entry size
*
/
} Elf64_Shdr;
|
下面我们来看看.text,.data,.bss节
输入指令 objdump -x -s -d elfDemo.rel
得到
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
|
elfDemo.rel: 文件格式 elf64
-
x86
-
64
elfDemo.rel
体系结构:i386:x86
-
64
, 标志
0x00000011
:
HAS_RELOC, HAS_SYMS
起始地址
0x0000000000000000
节:
Idx Name Size VMA LMA
File
off Algn
0
.text
0000005b
0000000000000000
0000000000000000
00000040
2
*
*
0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1
.data
00000008
0000000000000000
0000000000000000
0000009c
2
*
*
2
CONTENTS, ALLOC, LOAD, DATA
2
.bss
00000008
0000000000000000
0000000000000000
000000a4
2
*
*
2
ALLOC
3
.rodata
00000004
0000000000000000
0000000000000000
000000a4
2
*
*
0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4
.comment
0000002c
0000000000000000
0000000000000000
000000a8
2
*
*
0
CONTENTS, READONLY
5
.note.GNU
-
stack
00000000
0000000000000000
0000000000000000
000000d4
2
*
*
0
CONTENTS, READONLY
6
.note.gnu.
property
00000020
0000000000000000
0000000000000000
000000d8
2
*
*
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
7
.eh_frame
00000058
0000000000000000
0000000000000000
000000f8
2
*
*
3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000
l df
*
ABS
*
0000000000000000
elfDemo.c
0000000000000000
l d .text
0000000000000000
.text
0000000000000000
l d .data
0000000000000000
.data
0000000000000000
l d .rodata
0000000000000000
.rodata
0000000000000004
l O .data
0000000000000004
local_static_init_var.
1
0000000000000004
l O .bss
0000000000000004
local_unstatic_var.
0
0000000000000000
g O .data
0000000000000004
global_init_var
0000000000000000
g O .bss
0000000000000004
global_uninit_var
0000000000000000
g F .text
000000000000002b
func
0000000000000000
*
UND
*
0000000000000000
printf
000000000000002b
g F .text
0000000000000030
main
Contents of section .text:
0000
f30f1efa
554889e5
4883ec10
897dfc8b
....UH..H....}..
0010
45fc89c6
488d0500
00000048
89c7b800
E...H......H....
0020
000000e8
00000000
90c9c3f3
0f1efa55
...............U
0030
4889e548
83ec10c7
45fc1e00
00008b15
H..H....E.......
0040
00000000
8b45fc01
c28b0500
00000001
.....E..........
0050
d089c7e8
00000000
90c9c3
...........
Contents of section .data:
0000
0a000000
14000000
........
Contents of section .rodata:
0000
25640a00
%
d..
Contents of section .comment:
0000
00474343
3a202855
62756e74
75203131
.GCC: (Ubuntu
11
0010
2e332e30
2d317562
756e7475
317e3232
.
3.0
-
1ubuntu1
~
22
0020
2e303429
2031312e
332e3000
.
04
)
11.3
.
0.
Contents of section .note.gnu.
property
:
0000
04000000
10000000
05000000
474e5500
............GNU.
0010
020000c0
04000000
03000000
00000000
................
Contents of section .eh_frame:
0000
14000000
00000000
017a5200
01781001
.........zR..x..
0010
1b0c0708
90010000
1c000000
1c000000
................
0020
00000000
2b000000
00450e10
8602430d
....
+
....E....C.
0030
06620c07
08000000
1c000000
3c000000
.b..........<...
0040
00000000
30000000
00450e10
8602430d
....
0.
...E....C.
0050
06670c07
08000000
.g......
Disassembly of section .text:
0000000000000000
<func>:
0
: f3
0f
1e
fa endbr64
4
:
55
push
%
rbp
5
:
48
89
e5 mov
%
rsp,
%
rbp
8
:
48
83
ec
10
sub $
0x10
,
%
rsp
c:
89
7d
fc mov
%
edi,
-
0x4
(
%
rbp)
f:
8b
45
fc mov
-
0x4
(
%
rbp),
%
eax
12
:
89
c6 mov
%
eax,
%
esi
14
:
48
8d
05
00
00
00
00
lea
0x0
(
%
rip),
%
rax
# 1b <func+0x1b>
17
: R_X86_64_PC32 .rodata
-
0x4
1b
:
48
89
c7 mov
%
rax,
%
rdi
1e
: b8
00
00
00
00
mov $
0x0
,
%
eax
23
: e8
00
00
00
00
call
28
<func
+
0x28
>
24
: R_X86_64_PLT32 printf
-
0x4
28
:
90
nop
29
: c9 leave
2a
: c3 ret
000000000000002b
<main>:
2b
: f3
0f
1e
fa endbr64
2f
:
55
push
%
rbp
30
:
48
89
e5 mov
%
rsp,
%
rbp
33
:
48
83
ec
10
sub $
0x10
,
%
rsp
37
: c7
45
fc
1e
00
00
00
movl $
0x1e
,
-
0x4
(
%
rbp)
3e
:
8b
15
00
00
00
00
mov
0x0
(
%
rip),
%
edx
# 44 <main+0x19>
40
: R_X86_64_PC32 global_init_var
-
0x4
44
:
8b
45
fc mov
-
0x4
(
%
rbp),
%
eax
47
:
01
c2 add
%
eax,
%
edx
49
:
8b
05
00
00
00
00
mov
0x0
(
%
rip),
%
eax
# 4f <main+0x24>
4b
: R_X86_64_PC32 .data
4f
:
01
d0 add
%
edx,
%
eax
51
:
89
c7 mov
%
eax,
%
edi
53
: e8
00
00
00
00
call
58
<main
+
0x2d
>
54
: R_X86_64_PLT32 func
-
0x4
58
:
90
nop
59
: c9 leave
5a
: c3 ret
|
1
2
3
4
5
6
7
|
*
*
代码节
*
*
:
可以看到Contents of section .text的部分为
16
进制数据,共
0x4e
个字节,最左一列是偏移量,中间四列是内容,最右边一列是ascii码形式。Disassembly of section .text部分是反汇编的结果。
*
*
数据节和只读数据节
*
*
:
可以看到.data保存已经初始化的全局变量和局部静态变量。
.rodata节保存只读数据,包括只读变量和字符串常量。例如源代码调用的printf()函数时的“
%
d\n”为只读数据,因此保存在.radata节中。
*
*
bss节
*
*
:
用于保存未初始化的全局变量和局部静态变量。可以看到该节没有contents属性,这表示该节在文件中实际上并不存在,只是为变量预留了位置而已,因此该节的sh_offset域也就,没有意义了。
|
下面是几种常见的节
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
|
.rodata1
和rodata类似,只存放只读数据
.comment
存放编译器版本信息,如字符串
"GCC:(GNU)4.2.0"
.debug
存放调试信息
.shstrtab
节区头部表名字的字符串表(Section Header String Table)
.plt
过程链接表(Procedure Linkage Table),用来保存长跳转格式的函数调用
.got
全局偏移表(Global Offset Table),在地址无关代码中才需要,所有只读段需要修复的位置都间接引用到此表, 因此只读段自身就无需修复,只需修复此got表即可。.got表是在编译期间确定,静态链接期间生成的,而.plt表是在静态链接期间确定,静态链接期间生成的。可执行文件通常不论如何编译都有got表,这是因为是否加入got表是由编译(cc1)期间决定的,而可执行文件默认连接的多个目标文件默认都有got表元素.
.got.plt
实际上其本质是从.got表中拆除来的一部分,当开启延迟绑定(Lazy Binding)时,会将plt表中的长跳转(函数)的重定位信息单独放到此表中,以满足后续实际的延迟绑定.
.symtab
(静态链接)符号表的作用是保存当前 目标文件中所有对符号的定义和引用.
*
符号表中UND的符号不是当前目标文件定义的,也就是对符号的引用
*
符号表中其他非UND的符号,全部是定义在当前目标文件中的,也就是对符号的定义
默认所有非.L开头的符号都要输出,.L开头的不输出
.strtab
静态链接字符串表,其中记录的是静态链接符号表中使用到的字符串,这些字符串仅供静态链接符号表使用,strip的时候会将.symtab和.strtab两个段完全清除。符号表的第一个元素必须是 STN_UNDEF,其代表一个未定义的符号索引,此符号表项内部所有值都为
0
.group
是用来记录多个节区的相关信息的,比如说代码段引用了数据段,这种信息是为了保证二进制处理时候不会操作错误的,像是elf自动生成的,细节见链接
https:
/
/
docs.oracle.com
/
cd
/
E19683
-
01
/
816
-
1386
/
6m7qcoblj
/
index.html
section groups
动态链接相关节区
.interp
.interp整个段的内容就是一个字符串,此字符串为系统中动态链接器的路径,如:
/
lib
/
ld
-
linux
-
aarch64.so.
1
linux的可执行文件加载时会去寻找可执行文件所需的动态链接器
.dynamic
.interp保存的是动态链接器路径,.dynamic中保存的是动态链接器用到的基本信息,如动态链接符号表(.dynsym),字符串表(.dynstr),重定位表(.rela.dyn
/
rela.plt),依赖的运行时库,库查找路径等
.rela.dyn
记录所有变量的动态链接重定位信息(.rela.plt记录的是函数),与.rela.plt一起,是系统中唯二的两张动态链接重定位表。.rela.dyn记录除了.plt段之外所有段的动态链接重定位信息,若开启了地址无关代码,那么这些信息都应该只与.got段的地址有关.
.rela.plt
过程连接表的动态链接重定位表,只要有过程链表,通常就会有此表,因为plt导致了绝对跳转,那么所有plt表中所有需要动态链接
/
重定位的绝对地址(可能在.got.plt或.got中,依赖于是否开启延迟绑定),都需要通过.rela.plt记录,此表中记录所有全局函数(长跳转函数)的动态链接重定位信息,与.rela.dyn一起,是系统中唯二的两张动态链接重定位表。.rela.plt实际上记录的是.plt段的动态链接重定位信息,若未开启lazy binding,则这这些信息应该都只与.got段的地址有关;若开启lazy binding,则这些信息应该都只与.got.plt段的地址有关;
需要动态链接重定位的原因主要是模块有导入符号的存在,这些符号在运行时才能确定, 地址无关代码并不能改变未定符号的本质(即不影响模块是否需要动态链接重定位), 但地址无关代码可以让重定位变得简单(如仅重定位 .got
/
.data
/
.got.plt)
.dynsym
动态链接符号表,其格式和.symtab一样,readelf
-
s会尝试同时输出.dynsym和.symtab,如右图.
动态链接符号表是静态链接符号表的子集,其只保留了与动态链接相关的符号信息,所有模块内部符号则不保留(因此静态符号表是可以被strip的,其只对于目标文件有用).
动态链接符号表中未定义的符号(符号引用),又称为导入符号(类似导入表)
动态链接符号表中已定义的符号(符号定义),又称为导出符号(类似导出表)
全局符号默认是直接导出到动态链接重定位表的
.dynstr
动态链接符号表用到的字符串表,其与静态链接字符串表(.strtab)分开的原因应该是.strtab是可以完全strip的
|
Elf64_Sym结构体如下
1
2
3
4
5
6
7
8
9
|
typedef struct
{
Elf64_Word st_name;
/
*
Symbol name (string tbl index)
*
/
unsigned char st_info;
/
*
Symbol
type
and
binding
*
/
unsigned char st_other;
/
*
Symbol visibility
*
/
Elf64_Section st_shndx;
/
*
Section index
*
/
Elf64_Addr st_value;
/
*
Symbol value
*
/
Elf64_Xword st_size;
/
*
Symbol size
*
/
}Elf64_Sym;
|
重定位是连接符号定义和符号引用的过程。可重定位文件在构建可执行文件或共享目标文件时,需要把节中的符号引用换成这些符号在进程空间中的虚拟地址。包含这些转换信息的数据就是重定位项。
1
2
3
4
5
6
7
8
9
10
11
12
|
typedef struct
{
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
typedef struct
{
Elf64_Addr r_offset;
/
*
Address
*
/
Elf64_Xword r_info;
/
*
Relocation
type
and
symbol index
*
/
Elf64_Sxword r_addend;
/
*
Addend
*
/
} Elf64_Rela;
|
Elf64_Rel和Elf64_Rela结构体中,r_offset是在重定位时需要被修改的符号的偏移。r_info的type部分指示如何修改引用,symobl指示应修改引用为哪个符号。r_addend用于对被修改的引用做偏移调整。
下面我们从运行视角审视。
输入指令 readelf -l elfDemo.exec
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
|
Elf 文件类型为 DYN (Position
-
Independent Executable
file
)
Entry point
0x1060
There are
13
program headers, starting at offset
64
程序头:
Type
Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR
0x0000000000000040
0x0000000000000040
0x0000000000000040
0x00000000000002d8
0x00000000000002d8
R
0x8
INTERP
0x0000000000000318
0x0000000000000318
0x0000000000000318
0x000000000000001c
0x000000000000001c
R
0x1
[Requesting program interpreter:
/
lib64
/
ld
-
linux
-
x86
-
64.so
.
2
]
LOAD
0x0000000000000000
0x0000000000000000
0x0000000000000000
0x0000000000000628
0x0000000000000628
R
0x1000
LOAD
0x0000000000001000
0x0000000000001000
0x0000000000001000
0x00000000000001b1
0x00000000000001b1
R E
0x1000
LOAD
0x0000000000002000
0x0000000000002000
0x0000000000002000
0x0000000000000114
0x0000000000000114
R
0x1000
LOAD
0x0000000000002db8
0x0000000000003db8
0x0000000000003db8
0x0000000000000260
0x0000000000000270
RW
0x1000
DYNAMIC
0x0000000000002dc8
0x0000000000003dc8
0x0000000000003dc8
0x00000000000001f0
0x00000000000001f0
RW
0x8
NOTE
0x0000000000000338
0x0000000000000338
0x0000000000000338
0x0000000000000030
0x0000000000000030
R
0x8
NOTE
0x0000000000000368
0x0000000000000368
0x0000000000000368
0x0000000000000044
0x0000000000000044
R
0x4
GNU_PROPERTY
0x0000000000000338
0x0000000000000338
0x0000000000000338
0x0000000000000030
0x0000000000000030
R
0x8
GNU_EH_FRAME
0x0000000000002008
0x0000000000002008
0x0000000000002008
0x000000000000003c
0x000000000000003c
R
0x4
GNU_STACK
0x0000000000000000
0x0000000000000000
0x0000000000000000
0x0000000000000000
0x0000000000000000
RW
0x10
GNU_RELRO
0x0000000000002db8
0x0000000000003db8
0x0000000000003db8
0x0000000000000248
0x0000000000000248
R
0x1
Section to Segment mapping:
段节...
00
01
.interp
02
.interp .note.gnu.
property
.note.gnu.build
-
id
.note.ABI
-
tag .gnu.
hash
.dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03
.init .plt .plt.got .plt.sec .text .fini
04
.rodata .eh_frame_hdr .eh_frame
05
.init_array .fini_array .dynamic .got .data .bss
06
.dynamic
07
.note.gnu.
property
08
.note.gnu.build
-
id
.note.ABI
-
tag
09
.note.gnu.
property
10
.eh_frame_hdr
11
12
.init_array .fini_array .dynamic .got
|
运行一个可执行文件时,首先需要将该文件和动态链接库装载到进程空间中,形成一个进程镜像。每个进程都拥有独立的虚拟地址空间,它的布局由记录在段头表中的程序头决定的。ELF文件头的e_phoff域给出了段头表位置。可以看到每个段包含了一个或多个节,实际上系统不关心每个节的内容,而是关心它们的权限,将不同权限的节分组即可同时装载多个节。
通常一个可执行文件至少有一个PT_LOAD类型的段,用于描述可装载的节,而动态链接的可执行文件则包含两个,将.data和.text分开存放。动态段PT_DYNAMIC包含了一些动态链接器所必需的信息(共享库列表,GOT表,重定位表...)。PT_NOTE类型的段保存了系统相关的附加信息。PI_INTERP段将位置和大小信息存放在一个字符中,是对程序解释器位置的描述。PT_PHDR段保存了程序头表本身的位置和大小。
1
2
3
4
5
6
7
8
9
10
|
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;
|
进程镜像中还需要用到栈(stark),堆(heap)vDSO等空间,这些空间同样需要通过权限来进行访问控制,从而保证程序运行时的安全。动态链接的可执行文件装载完成后,还需要进行动态链接才能顺利执行。
更多【(pwn零基础入门到进阶)第一章 二进制文件 & 2 ELF文件格式】相关视频教程:www.yxfzedu.com