我们可以通过链接将两个或者多个不同的目标文件组成一个可执行文件。链接由链接器完成,根据发生时间不同可分为编译时链接,加载时链接和运行时链接。
file:
1
2
3
4
5
6
7
8
|
/
/
main.c
extern
int
shared;
extern void func(
int
*
a,
int
*
b);
int
main() {
int
a
=
100
;
func(&a, &shared);
return
0
;
}
|
1
2
3
4
5
6
7
8
|
/
/
func.c
int
shared
=
1
;
int
tmp
=
0
;
void func(
int
*
a,
int
*
b) {
tmp
=
*
a;
*
a
=
*
b;
*
b
=
tmp;
}
|
命令:gcc -static -fno-stack-protector main.c func.c -save-temps --verbose -o func.ELF
你将得到
当前链接器采用相似节合并的方法将main.o和func.o两个目标文件链接成一个可执行文件。此方法将不同目标文件相同属性的节合并为一个节(main.o和func.o的.text合并为新的.text...)。首先对各个节的长度,属性和偏移进行分析,然后将输入目标文件中的符号表的符号定义与符号引用统一生成全局符号表,最后读取输入文件的各类信息对符号进行解析,重定位等操作。相似节合并就发生在重定位时。完成后程序中的每条指令和全局变量就有唯一的运行时内存地址了。
为了构造可执行文件,链接器必须完成两个重要工作:
1.符号解析
将每个符号(函数,全局变量,静态变量)的引用与其定义进行关联。
2.重定位
将每个符号的定义与一个内存地址进行关联,然后修改这些符号的引用,使其指向这个内存地址。
对比func.ELF(静态链接可执行文件)和func.ELF-main.o(中间产物)的.text,.data和.bss节
指令 objdump -h func.ELF-main.o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func.ELF
-
main.o: 文件格式 elf64
-
x86
-
64
节:
Idx Name Size VMA LMA
File
off Algn
0
.text
00000030
0000000000000000
0000000000000000
00000040
2
*
*
0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1
.data
00000000
0000000000000000
0000000000000000
00000070
2
*
*
0
CONTENTS, ALLOC, LOAD, DATA
2
.bss
00000000
0000000000000000
0000000000000000
00000070
2
*
*
0
ALLOC
3
.comment
0000002c
0000000000000000
0000000000000000
00000070
2
*
*
0
CONTENTS, READONLY
4
.note.GNU
-
stack
00000000
0000000000000000
0000000000000000
0000009c
2
*
*
0
CONTENTS, READONLY
5
.note.gnu.
property
00000020
0000000000000000
0000000000000000
000000a0
2
*
*
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
6
.eh_frame
00000038
0000000000000000
0000000000000000
000000c0
2
*
*
3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
|
指令 objdump -h func.ELF
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
|
func.ELF: 文件格式 elf64
-
x86
-
64
节:
Idx Name Size VMA LMA
File
off Algn
0
.note.gnu.
property
00000030
0000000000400270
0000000000400270
00000270
2
*
*
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
1
.note.gnu.build
-
id
00000024
00000000004002a0
00000000004002a0
000002a0
2
*
*
2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2
.note.ABI
-
tag
00000020
00000000004002c4
00000000004002c4
000002c4
2
*
*
2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3
.rela.plt
00000240
00000000004002e8
00000000004002e8
000002e8
2
*
*
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4
.init
0000001b
0000000000401000
0000000000401000
00001000
2
*
*
2
CONTENTS, ALLOC, LOAD, READONLY, CODE
5
.plt
00000180
0000000000401020
0000000000401020
00001020
2
*
*
4
CONTENTS, ALLOC, LOAD, READONLY, CODE
6
.text
00094e58
00000000004011c0
00000000004011c0
000011c0
2
*
*
6
CONTENTS, ALLOC, LOAD, READONLY, CODE
7
__libc_freeres_fn
000014d0
0000000000496020
0000000000496020
00096020
2
*
*
4
CONTENTS, ALLOC, LOAD, READONLY, CODE
8
.fini
0000000d
00000000004974f0
00000000004974f0
000974f0
2
*
*
2
CONTENTS, ALLOC, LOAD, READONLY, CODE
9
.rodata
0001cadc
0000000000498000
0000000000498000
00098000
2
*
*
5
CONTENTS, ALLOC, LOAD, READONLY, DATA
10
.stapsdt.base
00000001
00000000004b4adc
00000000004b4adc
000b4adc
2
*
*
0
CONTENTS, ALLOC, LOAD, READONLY, DATA
11
.eh_frame
0000b8b0
00000000004b4ae0
00000000004b4ae0
000b4ae0
2
*
*
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
12
.gcc_except_table
00000106
00000000004c0390
00000000004c0390
000c0390
2
*
*
0
CONTENTS, ALLOC, LOAD, READONLY, DATA
13
.tdata
00000020
00000000004c17b0
00000000004c17b0
000c07b0
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA, THREAD_LOCAL
14
.tbss
00000048
00000000004c17d0
00000000004c17d0
000c07d0
2
*
*
3
ALLOC, THREAD_LOCAL
15
.init_array
00000008
00000000004c17d0
00000000004c17d0
000c07d0
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA
16
.fini_array
00000008
00000000004c17d8
00000000004c17d8
000c07d8
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA
17
.data.rel.ro
00003788
00000000004c17e0
00000000004c17e0
000c07e0
2
*
*
5
CONTENTS, ALLOC, LOAD, DATA
18
.got
00000098
00000000004c4f68
00000000004c4f68
000c3f68
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA
19
.got.plt
000000d8
00000000004c5000
00000000004c5000
000c4000
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA
20
.data
000019e8
00000000004c50e0
00000000004c50e0
000c40e0
2
*
*
5
CONTENTS, ALLOC, LOAD, DATA
21
__libc_subfreeres
00000048
00000000004c6ac8
00000000004c6ac8
000c5ac8
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA
22
__libc_IO_vtables
00000768
00000000004c6b20
00000000004c6b20
000c5b20
2
*
*
5
CONTENTS, ALLOC, LOAD, DATA
23
__libc_atexit
00000008
00000000004c7288
00000000004c7288
000c6288
2
*
*
3
CONTENTS, ALLOC, LOAD, DATA
24
.bss
00005980
00000000004c72a0
00000000004c72a0
000c6290
2
*
*
5
ALLOC
25
__libc_freeres_ptrs
00000020
00000000004ccc20
00000000004ccc20
000c6290
2
*
*
3
ALLOC
26
.comment
0000002b
0000000000000000
0000000000000000
000c6290
2
*
*
0
CONTENTS, READONLY
27
.note.stapsdt
00001648
0000000000000000
0000000000000000
000c62bc
2
*
*
2
CONTENTS, READONLY
|
可以看到尚未链接的目标文件main.o的VMA(虚拟地址)都是0。而链接完成后的func.ELF的VMA及LMA(加载地址)都不为0,一般情况下VMA和LMA是相等的。func.ELF完成了虚拟地址的分配,相似节也被合并。
使用指令 objdump -d -M intel --section=.text func.ELF-main.o 查看反汇编代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func.ELF
-
main.o: 文件格式 elf64
-
x86
-
64
Disassembly of section .text:
0000000000000000
<main>:
0
: f3
0f
1e
fa endbr64
4
:
55
push rbp
5
:
48
89
e5 mov rbp,rsp
8
:
48
83
ec
10
sub rsp,
0x10
c: c7
45
fc
64
00
00
00
mov DWORD PTR [rbp
-
0x4
],
0x64
13
:
48
8d
45
fc lea rax,[rbp
-
0x4
]
17
:
48
8d
15
00
00
00
00
lea rdx,[rip
+
0x0
]
# 1e <main+0x1e>
1e
:
48
89
d6 mov rsi,rdx
21
:
48
89
c7 mov rdi,rax
24
: e8
00
00
00
00
call
29
<main
+
0x29
>
29
: b8
00
00
00
00
mov eax,
0x0
2e
: c9 leave
2f
: c3 ret
|
可以看到main()函数的地址从0x00开始,对func()函数的调用在偏移0x29处,0xe8是call指令的操作码,后面四个字节是调用函数相对于调用指令的下一条指令的偏移量。此时符号没有重定位,相对偏移为0x00000000,call指令的下一条指令mov指令的地址为0x29,因此call指令调用的地址为0x29+(-0)=0x29,这只是个临时地址,编译器尚不知道func()函数的实际地址,于是把地址计算的工作交给了链接器;链接器根据上一步的结果对重定位符号的地址进行修正。同理0x17处对shared的取值也以0x0代替。
接下来看func.ELF中的符号地址 (查看20行就可)
指令 objdump -d -M intel --section=.text func.ELF | grep -A 20"<main>"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
0000000000401745
<main>:
401745
: f3
0f
1e
fa endbr64
401749
:
55
push rbp
40174a
:
48
89
e5 mov rbp,rsp
40174d
:
48
83
ec
10
sub rsp,
0x10
401751
: c7
45
fc
64
00
00
00
mov DWORD PTR [rbp
-
0x4
],
0x64
401758
:
48
8d
45
fc lea rax,[rbp
-
0x4
]
40175c
:
48
8d
15
8d
39
0c
00
lea rdx,[rip
+
0xc398d
]
# 4c50f0 <shared>
401763
:
48
89
d6 mov rsi,rdx
401766
:
48
89
c7 mov rdi,rax
401769
: e8
07
00
00
00
call
401775
<func>
40176e
: b8
00
00
00
00
mov eax,
0x0
401773
: c9 leave
401774
: c3 ret
0000000000401775
<func>:
401775
: f3
0f
1e
fa endbr64
401779
:
55
push rbp
40177a
:
48
89
e5 mov rbp,rsp
40177d
:
48
89
7d
f8 mov QWORD PTR [rbp
-
0x8
],rdi
401781
:
48
89
75
f0 mov QWORD PTR [rbp
-
0x10
],rsi
|
可以看到调用func()函数的指令call位于0x401769,其下一条指令mov位于0x40176e,因此相对于mov指令偏移量为0x07的地址为0x40176e+0x07=0x401775,刚好就是func()函数的地址。
可重定位文件中最重要的就是要包含重定位表,用于告诉链接器如何修改节的内容。每一个重定位表对应一个需要被重定位的节。
指令 objdump -r func.ELF-main.o
1
2
3
4
5
6
7
8
9
10
11
|
func.ELF
-
main.o: 文件格式 elf64
-
x86
-
64
RELOCATION RECORDS FOR [.text]:
OFFSET
TYPE
VALUE
000000000000001a
R_X86_64_PC32 shared
-
0x0000000000000004
0000000000000025
R_X86_64_PLT32 func
-
0x0000000000000004
RELOCATION RECORDS FOR [.eh_frame]:
OFFSET
TYPE
VALUE
0000000000000020
R_X86_64_PC32 .text
|
例如.rel.text的节保存.text的重定位表。.rel.text包含两个重定位入口,shared的类型R_X86_64_PC32用于相对寻址,cpu将指令中编码的32值加上PC(下一条指令地址)的值得到有效地址。func的类型R_X86_64_PLT32也用于相对寻址,而PLT是一个新的函数入口表的格式(为了和旧版本区别改了标记。gcc 现在有个参数可以关掉 plt )。
X86_64 中的重定位类型(部分)这部分知识可以自行补充
1
2
3
4
|
X86_64 中的重定位类型 含义 地址在指令中占用的字节数
R_X86_64_PC32
32
位 PC 相对地址
4
R_X86_64_64
64
位绝对地址
8
R_X86_64_32
32
位绝对地址
4
|
后缀名为.a的文件就是静态链接库文件,如libc.a。一个静态链接库可以视为一组目标文件经过压缩打包后形成的合集执行各种编译任务时,需要许多不同的目标文件,为了方便管理,人们使用ar工具将这些目标文件进行了压缩,编号和索引,这就形成了libc.a。
更多【(pwn零基础入门到进阶)第一章 二进制文件 & 3 静态链接】相关视频教程:www.yxfzedu.com