ILD

ELF重定向分析(1):无共享库依赖的可执行文件
作者:Herbert Yuan 邮箱:yuanjp@hust.edu.cn
发布时间:2018-1-1 站点:Inside Linux Development

通过分析各种不同elf文件的elf和反汇编信息来学习重定向。这里使用x86架构。


1 工具

通过make来编译可重定向目标文件、可执行文件和共享库。并调用readelf和objdump将elf信息和反汇编信息输出到相应文件。


Makefile文件如下:

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
CC := cc
READELF := readelf
OBJDUMP := objdump
CFLAGS := -m32
 
srcs := exe.c exe_lib.c lib.c lib_lib.c
 
objs = $(srcs:%.c=obj/%.o)
objs_pic = $(filter obj/lib%.o,$(srcs:%.c=obj/%_pic.o))
objs_pie = $(filter obj/exe%,$(srcs:%.c=obj/%_pie.o))
 
bins_name := liblib.so liblib_pic.so exe exe_pie exe_lib exe_lib_pie \
        liblib_lib.so liblib_lib_pic.so
 
bins = $(bins_name:%=obj/%)
 
elfs := $(objs) $(objs_pic) $(objs_pie) $(bins)
 
elfdumps := $(elfs:obj/%=readelf/%.readelf)
objdumps := $(elfs:obj/%=objdump/%.objdump)
 
dirs := obj readelf objdump
 
all: $(dirs) $(objs) $(objs_pic) $(objs_pie) $(bins) $(elfdumps) $(objdumps)
 
.PHONY: $(dirs)
$(dirs):
    [ -d $@ ] || mkdir $@
 
$(objs):obj/%.o:%.c
    $(CC) $(CFLAGS) -c -o $@ $<
 
$(objs_pic):obj/%_pic.o:%.c
    $(CC) $(CFLAGS) -fpic -c -o $@ $<
 
$(objs_pie):obj/%_pie.o:%.c
    $(CC) $(CFLAGS) -fpie -c -o $@ $<
 
obj/exe: obj/exe.o
    $(CC) $(CFLAGS) -nostdlib -o $@ $<
 
obj/exe_pie: obj/exe_pie.o
    $(CC) $(CFLAGS) -fpie -nostdlib -o $@ $<
 
obj/exe_lib: obj/exe_lib.o
    $(CC) $(CFLAGS) -nostdlib -o $@ $< -Lobj -llib
 
obj/exe_lib_pie: obj/exe_lib_pie.o
    $(CC) $(CFLAGS) -fpie -nostdlib -o $@ $< -Lobj -llib
 
obj/liblib.so: obj/lib.o
    $(CC) $(CFLAGS) -shared -o $@ $<
 
obj/liblib_pic.so: obj/lib_pic.o
    $(CC) $(CFLAGS) -shared -fpic -o $@ $<
     
obj/liblib_lib.so: obj/lib_lib.o
    $(CC) $(CFLAGS) -shared -o $@ $< -Lobj -llib
 
obj/liblib_lib_pic.so: obj/lib_lib_pic.o
    $(CC) $(CFLAGS) -shared -fpic -o $@ $< -Lobj -llib
 
$(elfdumps):readelf/%.readelf:obj/%
    $(READELF) -a $< > $@
 
$(objdumps):objdump/%.objdump:obj/%
    $(OBJDUMP) -d $< > $@
 
clean:
    rm -fr $(dirs)


分析下面几种类型,所有的编译如上述Makefile文件所示。


2 PIC vs PIE

gcc手册如是说:


-fpic

Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine. Such code accesses all constant addresses through a global offset table (GOT). The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system). If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that ‘-fpic’ does not work; in that case, recompile with ‘-fPIC’ instead. (These maximums are 8k on the SPARC, 28k on AArch64 and 32k on the m68k and RS/6000. The x86 has no such limit.) Position-independent code requires special support, and therefore works only on certain machines. For the x86, GCC supports PIC for System V but not for the Sun 386i. Code generated for the IBM RS/6000 is always position-independent. When this flag is set, the macros __pic__ and __PIC__ are defined to 1.


-fPIC

If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on AArch64, m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines. When this flag is set, the macros __pic__ and __PIC__ are defined to 2.


-fpie

-fPIE

These options are similar to ‘-fpic’ and ‘-fPIC’, but generated position independent code can be only linked into executables. Usually these options are used when ‘-pie’ GCC option is used during linking. ‘-fpie’ and ‘-fPIE’ both define the macros __pie__ and __PIE__. The macros have the value 1 for ‘-fpie’ and 2 for ‘-fPIE’

简单的说,-fpic用于共享库,-fpie用于可执行文件,在写下面的内容时,错误的使用-fpic创建可执行文件,发现有部分代码是位置相关的。


3 无共享库依赖的可执行文件

访问本地静态变量、本地静态函数、本地全局变量、本地全局函数,查看可重定向情况。


exe.c

1
2
3
4
5
6
7
8
9
10
11
12
int g_c = 1;
int g_d() {
    return 1;
}
static int c = 1;
static int d() {
    return 1;
}
int _start() 
{
    return c + d() + g_c + g_d();
}


3.1 位置相关代码

先来看可重定向目标文件exe.o的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
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000038 00  AX  0   0  1
  [ 2] .rel.text         REL             00000000 000210 000018 08   I 10   1  4
  [ 3] .data             PROGBITS        00000000 00006c 000008 00  WA  0   0  4
   
Relocation section '.rel.text' at offset 0x210 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000020  00000301 R_386_32          00000000   .data
00000027  00000a01 R_386_32          00000004   g_c
0000002f  00000b02 R_386_PC32        0000000a   g_d
 
Symbol table '.symtab' contains 13 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS exe.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000004     4 OBJECT  LOCAL  DEFAULT    3 c
     6: 0000000a    10 FUNC    LOCAL  DEFAULT    1 d
     7: 00000000     0 SECTION LOCAL  DEFAULT    6 
     8: 00000000     0 SECTION LOCAL  DEFAULT    7 
     9: 00000000     0 SECTION LOCAL  DEFAULT    5 
    10: 00000000     4 OBJECT  GLOBAL DEFAULT    3 g_c
    11: 00000000    10 FUNC    GLOBAL DEFAULT    1 g_d
    12: 00000014    36 FUNC    GLOBAL DEFAULT    1 _start


重定向section为.rel.text,是.text section的重定向信息。


静态变量的重定向

第一条是R_386_32,表示是绝对地址。符号是.data,这是.data section的地址,静态变量使用这种重定向,静态本地变量c存储在.data section,但是它是本地的,可以通过.data的偏移来访问。但是在链接阶段,该目标文件中的.text和.data的相对位置是可能变化的,通过.data的地址来进行重定向。

看c在符号表中的条目:"5: 00000004 4 OBJECT LOCAL DEFAULT 3 c"。它位于section 3,即.data section中,偏移为value值0x4。

在来看.text偏移0x20处的反汇编信息:

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
obj/exe.o:     file format elf32-i386
  
Disassembly of section .text:
 
00000000 <g_d>:
   0:    55                       push   %ebp
   1:    89 e5                  mov    %esp,%ebp
   3:    b8 01 00 00 00           mov    $0x1,%eax
   8:    5d                       pop    %ebp
   9:    c3                       ret    
 
0000000a <d>:
   a:    55                       push   %ebp
   b:    89 e5                  mov    %esp,%ebp
   d:    b8 01 00 00 00           mov    $0x1,%eax
  12: 5d                       pop    %ebp
  13: c3                       ret    
 
00000014 <_start>:
  14: 55                       push   %ebp
  15: 89 e5                  mov    %esp,%ebp
  17: 53                       push   %ebx
  18: e8 ed ff ff ff           call   a <d>
  1d: 89 c2                  mov    %eax,%edx
  1f: a1 04 00 00 00           mov    0x4,%eax
  24: 01 c2                  add    %eax,%edx
  26: a1 00 00 00 00           mov    0x0,%eax
  2b: 8d 1c 02                 lea    (%edx,%eax,1),%ebx
  2e: e8 fc ff ff ff           call   2f <_start+0x1b>
  33: 01 d8                  add    %ebx,%eax
  35: 5b                       pop    %ebx
  36: 5d                       pop    %ebp
  37: c3                       ret


汇编代码为:"1f: a1 04 00 00 00       mov    0x4,%eax",根据ELF文档Intel Arch节中的内容,R_386_32计算地址的方法为S+A。也即c在.data中的偏移存储在指令中,为0x4。而.data的地址,通过链接阶段确定。最终的地址为S+A。


静态函数和调用者在同一个.text section,通过PC相对地址来访问,因此不需要重定向信息。


全局变量

重定向类型位R_386_32,在汇编:"26: a1 00 00 00 00       mov    0x0,%eax"中,A为0,而S则通过符号表确定,链接器通过符号表确定符号在链接阶段的绝对地址。


全局函数

因为i386指令集,函数调用是PC相对指令,因此重定向类型为:R_386_PC32。和R_386_32不同之处,其计算重定向值的方法为:S+A-P。


最终可执行文件没有可重定向section:

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
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .note.gnu.build-i NOTE            080480d4 0000d4 000024 00   A  0   0  4
  [ 2] .text             PROGBITS        080480f8 0000f8 000038 00  AX  0   0  1
  [ 3] .eh_frame_hdr     PROGBITS        08048130 000130 000024 00   A  0   0  4
  [ 4] .eh_frame         PROGBITS        08048154 000154 00007c 00   A  0   0  4
  [ 5] .data             PROGBITS        0804a000 001000 000008 00  WA  0   0  4
  [ 6] .comment          PROGBITS        00000000 001008 000034 01  MS  0   0  1
  [ 7] .shstrtab         STRTAB          00000000 001196 00005b 00      0   0  1
  [ 8] .symtab           SYMTAB          00000000 00103c 000120 10      9  12  4
  [ 9] .strtab           STRTAB          00000000 00115c 00003a 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
 
There are no section groups in this file.
 
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x001d0 0x001d0 R E 0x1000
  LOAD           0x001000 0x0804a000 0x0804a000 0x00008 0x00008 RW  0x1000
  NOTE           0x0000d4 0x080480d4 0x080480d4 0x00024 0x00024 R   0x4
  GNU_EH_FRAME   0x000130 0x08048130 0x08048130 0x00024 0x00024 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
 
 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .text .eh_frame_hdr .eh_frame 
   01     .data 
   02     .note.gnu.build-id 
   03     .eh_frame_hdr 
   04


3.2 位置无关代码

使用-fpie选项,编译目标文件和链接可执行文件。


思考:位置无关代码依赖于PC相对位置,因为如果使用绝对位置的话,.text段总是要被重定向的,绕不过去。对于PC相对位置函数调用通常不同架构都是支持的,但是有些架构不支持访问PC相对变量的指令,x86就是如此,因此需要插入一段代码获取PC寄存器的值。而x64架构支持访问PC相对变量的指令


来看exe_pie.o的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
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .group            GROUP           00000000 000034 000008 04     14  16  4
  [ 2] .group            GROUP           00000000 00003c 000008 04     14  19  4
  [ 3] .text             PROGBITS        00000000 000044 000059 00  AX  0   0  1
  [ 4] .rel.text         REL             00000000 000324 000048 08   I 14   3  4
  [ 5] .data             PROGBITS        00000000 0000a0 000008 00  WA  0   0  4
  [ 6] .bss              NOBITS          00000000 0000a8 000000 00  WA  0   0  1
  [ 7] .text.__x86.get_p PROGBITS        00000000 0000a8 000004 00 AXG  0   0  1
  [ 8] .text.__x86.get_p PROGBITS        00000000 0000ac 000004 00 AXG  0   0  1
  [ 9] .comment          PROGBITS        00000000 0000b0 000035 01  MS  0   0  1
  [10] .note.GNU-stack   PROGBITS        00000000 0000e5 000000 00      0   0  1
  [11] .eh_frame         PROGBITS        00000000 0000e8 0000a4 00   A  0   0  4
  [12] .rel.eh_frame     REL             00000000 00036c 000028 08   I 14  11  4
  [13] .shstrtab         STRTAB          00000000 000394 000096 00      0   0  1
  [14] .symtab           SYMTAB          00000000 00018c 000140 10     15  14  4
  [15] .strtab           STRTAB          00000000 0002cc 000058 00      0   0  1
 
Relocation section '.rel.text' at offset 0x324 contains 9 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000004  00001002 R_386_PC32        00000000   __x86.get_pc_thunk.ax
00000009  0000110a R_386_GOTPC       00000000   _GLOBAL_OFFSET_TABLE_
00000018  00001002 R_386_PC32        00000000   __x86.get_pc_thunk.ax
0000001d  0000110a R_386_GOTPC       00000000   _GLOBAL_OFFSET_TABLE_
0000002d  00001302 R_386_PC32        00000000   __x86.get_pc_thunk.bx
00000033  0000110a R_386_GOTPC       00000000   _GLOBAL_OFFSET_TABLE_
00000040  00000309 R_386_GOTOFF      00000000   .data
00000048  00000e09 R_386_GOTOFF      00000000   g_c
00000050  00000f02 R_386_PC32        00000000   g_d
 
Symbol table '.symtab' contains 20 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS exe.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    3 
     3: 00000000     0 SECTION LOCAL  DEFAULT    5 
     4: 00000000     0 SECTION LOCAL  DEFAULT    6 
     5: 00000004     4 OBJECT  LOCAL  DEFAULT    5 c
     6: 00000014    20 FUNC    LOCAL  DEFAULT    3 d
     7: 00000000     0 SECTION LOCAL  DEFAULT    7 
     8: 00000000     0 SECTION LOCAL  DEFAULT    8 
     9: 00000000     0 SECTION LOCAL  DEFAULT   10 
    10: 00000000     0 SECTION LOCAL  DEFAULT   11 
    11: 00000000     0 SECTION LOCAL  DEFAULT    9 
    12: 00000000     0 SECTION LOCAL  DEFAULT    1 
    13: 00000000     0 SECTION LOCAL  DEFAULT    2 
    14: 00000000     4 OBJECT  GLOBAL DEFAULT    5 g_c
    15: 00000000    20 FUNC    GLOBAL DEFAULT    3 g_d
    16: 00000000     0 FUNC    GLOBAL HIDDEN     7 __x86.get_pc_thunk.ax
    17: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    18: 00000028    49 FUNC    GLOBAL DEFAULT    3 _start
    19: 00000000     0 FUNC    GLOBAL HIDDEN     8 __x86.get_pc_thunk.bx


分析:

多出两个Section:.text.__x86.get_pc_thunk.ax和.text.__x86.get_pc_thunk.bx,分别对应函数__x86.get_pc_thunk.ax和__x86.get_pc_thunk.bx,x86架构不能直接访问PC寄存器的值,这两个函数用来将PC寄存器的值读入eax和ebx。

.rel.text包含了.text section的重定向信息,可以发现没有R_386_32(绝对地址)类型。


来看exe_pie.o的反汇编信息:

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
obj/exe_pie.o:     file format elf32-i386
 
 
Disassembly of section .text:
 
00000000 <g_d>:
   0:    55                       push   %ebp
   1:    89 e5                  mov    %esp,%ebp
   3:    e8 fc ff ff ff           call   4 <g_d+0x4>
   8:    05 01 00 00 00           add    $0x1,%eax
   d:    b8 01 00 00 00           mov    $0x1,%eax
  12: 5d                       pop    %ebp
  13: c3                       ret    
 
00000014 <d>:
  14: 55                       push   %ebp
  15: 89 e5                  mov    %esp,%ebp
  17: e8 fc ff ff ff           call   18 <d+0x4>
  1c: 05 01 00 00 00           add    $0x1,%eax
  21: b8 01 00 00 00           mov    $0x1,%eax
  26: 5d                       pop    %ebp
  27: c3                       ret    
 
00000028 <_start>:
  28: 55                       push   %ebp
  29: 89 e5                  mov    %esp,%ebp
  2b: 53                       push   %ebx
  2c: e8 fc ff ff ff           call   2d <_start+0x5>
  31: 81 c3 02 00 00 00      add    $0x2,%ebx
  37: e8 d8 ff ff ff           call   14 <d>
  3c: 89 c2                  mov    %eax,%edx
  3e: 8b 83 04 00 00 00      mov    0x4(%ebx),%eax
  44: 01 c2                  add    %eax,%edx
  46: 8b 83 00 00 00 00      mov    0x0(%ebx),%eax
  4c: 8d 1c 02                 lea    (%edx,%eax,1),%ebx
  4f: e8 fc ff ff ff           call   50 <_start+0x28>
  54: 01 d8                  add    %ebx,%eax
  56: 5b                       pop    %ebx
  57: 5d                       pop    %ebp
  58: c3                       ret    
 
Disassembly of section .text.__x86.get_pc_thunk.ax:
 
00000000 <__x86.get_pc_thunk.ax>:
   0:    8b 04 24                 mov    (%esp),%eax
   3:    c3                       ret    
 
Disassembly of section .text.__x86.get_pc_thunk.bx:
 
00000000 <__x86.get_pc_thunk.bx>:
   0:    8b 1c 24                 mov    (%esp),%ebx
   3:    c3                       ret


结合.rel.text中的重定向条目来分析,g_d()和d()中也有重定向条目,没啥用,这里不分析。


_start():


2c: e8 fc ff ff ff       call   2d <_start+0x5>

8048124: e8 2c 00 00 00       call   8048155 <__x86.get_pc_thunk.bx>

[5] 偏移0x2d,类型R_386_PC32,符号__x86.get_pc_thunk.bx,将PC寄存器的值读入ebx。

PC指向下一条指令,所以0x8048155-0x8048129=0x2c,重定向结果为0x2c。执行后ebx的值PC地址。

注意:实际的计算方法是S+A-P,这里的P是重定向条目指定的偏移,而不是PC寄存器的值,链接器不处理架构相关的细节,它不清除当前指令多长,不同的架构PC也由不同的含义,相关信息由编译器生成合适的加数A。所以实际的计算是:0x8048155-0x8048125+0xfffffffc(-4)=0x2c


31: 81 c3 02 00 00 00     add    $0x2,%ebx

8048129: 81 c3 d7 1e 00 00     add    $0x1ed7,%ebx

[6] 偏移0x33,类型R_386_GOTPC,符号_GLOBAL_OFFSET_TABLE_,重定向为GOT表相对PC的地址。

在exe_pie的符号表中,    

13: 0804a000     0 OBJECT  LOCAL  DEFAULT    5 _GLOBAL_OFFSET_TABLE_

所以实际的重定向为GOT+A-P:0x804a000-0x804812b+0x2=0x1ed7。

执行后ebx的值为GOT的地址。


37: e8 d8 ff ff ff       call   14 <d>

3c: 89 c2                 mov    %eax,%edx

这样edx为d()。


3e: 8b 83 04 00 00 00     mov    0x4(%ebx),%eax

8048136: 8b 83 10 00 00 00     mov    0x10(%ebx),%eax

[7] 偏移0x40,类型R_386_GOTOFF,符号.data,重定向为.data section相对于GOT的偏移。

S+A-GOT,如上,GOT为0x804a000,.data可以通过section头确定为0x804a00c

[ 6] .data             PROGBITS        0804a00c 00100c 000008 00  WA  0   0  4

所以重定向值为:0x804a00c-0x804a000+0x4=0x10。

这里A存储的是c是.data中的偏移,S-GOT为.data相对GOT的偏移,ebx存储为GOT的绝对地址。

因此ebx+A+S-GOT就是静态变量c的地址。这样eax为c的值。


44: 01 c2                 add    %eax,%edx

这样edx的值为d()+c


46: 8b 83 00 00 00 00     mov    0x0(%ebx),%eax

804813e: 8b 83 0c 00 00 00     mov    0xc(%ebx),%eax

[8] 偏移0x48,类型R_386_GOTOFF,符号g_c,重定向为g_c相对于GOT表的偏移。

由于ebx已经存储了GOT的绝对地址,这样就获得g_c的地址。这样就可将g_c的值拷贝到%eax。


4c: 8d 1c 02             lea    (%edx,%eax,1),%ebx

这样ebx的值为d()+c+g_c,只差一个g_d()了。


4f: e8 fc ff ff ff       call   50 <_start+0x28>

8048147: e8 ac ff ff ff       call   80480f8 <g_d>

9 偏移0x50,类型R_386_PC32,符号g_d,重定向为g_d()相对PC的偏移,通过call相对位置函数调用。

思考

这里是编译为可执行文件,因此使用R_386_PC32是可以的,可执行文件这个偏移是固定的。但是使用-fpic用于动态链接库时就不可以了,因此全局函数可能在共享库中,加载位置是变化的,因此偏移是变化的,产生的重定向条目为R_386_PLT32。通过PLT条目访问。


54: 01 d8                 add    %ebx,%eax

eax为最终的值。


思考

可重定向目标文件只有.rel.text重定向信息,没有GOT和PLT,只有在链接为可执行文件或共享库时,链接器才根据重定向类型创建GOT和PLT。

pie编译的可执行文件是不需要.got的,只需要GOT的地址,用来做相对地址引用。objdump -s exe_pie,查看,.got.plt section只有保留的3个条目,每个条目全0。

1
2
Contents of section .got.plt:
 804a000 00000000 00000000 00000000           ............


3.3 补充:函数指针

如上,调用本地全局函数,都是通过R_386_PC32重定向实现的,那么访问函数指针是啥情况来,exe.c添加相关代码:

1
2
3
4
5
6
7
8
9
10
#if 1
void *static_func_ptr()
{
    return (void *)d;
}
void *global_func_ptr()
{
    return (void *)g_d;
}
#endif


3.3.1 位置相关代码

exe.o的elf信息,多出两个重定向条目

1
2
0000003c  00000201 R_386_32          00000000   .text
00000046  00000b01 R_386_32          00000000   g_d


exe.o的反汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
00000038 <static_func_ptr>:
  38: 55                       push   %ebp
  39: 89 e5                  mov    %esp,%ebp
  3b: b8 0a 00 00 00           mov    $0xa,%eax
  40: 5d                       pop    %ebp
  41: c3                       ret    
 
00000042 <global_func_ptr>:
  42: 55                       push   %ebp
  43: 89 e5                  mov    %esp,%ebp
  45: b8 00 00 00 00           mov    $0x0,%eax
  4a: 5d                       pop    %ebp
  4b: c3                       ret

非常简单,静态函数取.text的绝对地址,然后加数为静态函数在.text中的偏移。全局函数直接重定向为全局函数的地址。


3.3.2 位置无关代码

exe_pie.o的elf信息,多出几个重定向条目

1
2
3
4
5
6
0000005d  00001002 R_386_PC32        00000000   __x86.get_pc_thunk.ax
00000062  0000110a R_386_GOTPC       00000000   _GLOBAL_OFFSET_TABLE_
00000068  00000209 R_386_GOTOFF      00000000   .text
00000072  00001002 R_386_PC32        00000000   __x86.get_pc_thunk.ax
00000077  0000110a R_386_GOTPC       00000000   _GLOBAL_OFFSET_TABLE_
0000007d  00000f09 R_386_GOTOFF      00000000   g_d


exe_pie.o相关反汇编信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0000000a <d>:
。。。
 
00000059 <static_func_ptr>:
  59: 55                       push   %ebp
  5a: 89 e5                  mov    %esp,%ebp
  5c: e8 fc ff ff ff           call   5d <static_func_ptr+0x4>
  61: 05 01 00 00 00           add    $0x1,%eax
  66: 8d 80 14 00 00 00      lea    0x14(%eax),%eax
  6c: 5d                       pop    %ebp
  6d: c3                       ret    
 
0000006e <global_func_ptr>:
  6e: 55                       push   %ebp
  6f: 89 e5                  mov    %esp,%ebp
  71: e8 fc ff ff ff           call   72 <global_func_ptr+0x4>
  76: 05 01 00 00 00           add    $0x1,%eax
  7b: 8d 80 00 00 00 00      lea    0x0(%eax),%eax
  81: 5d                       pop    %ebp
  82: c3                       ret


可执行文件exe_pie的反汇编信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
08048151 <static_func_ptr>:
 8048151:  55                       push   %ebp
 8048152:  89 e5                  mov    %esp,%ebp
 8048154:  e8 22 00 00 00           call   804817b <__x86.get_pc_thunk.ax>
 8048159:  05 a7 1e 00 00           add    $0x1ea7,%eax
 804815e:  8d 80 0c e1 ff ff      lea    -0x1ef4(%eax),%eax
 8048164:  5d                       pop    %ebp
 8048165:  c3                       ret    
 
08048166 <global_func_ptr>:
 8048166:  55                       push   %ebp
 8048167:  89 e5                  mov    %esp,%ebp
 8048169:  e8 0d 00 00 00           call   804817b <__x86.get_pc_thunk.ax>
 804816e:  05 92 1e 00 00           add    $0x1e92,%eax
 8048173:  8d 80 f8 e0 ff ff      lea    -0x1f08(%eax),%eax
 8048179:  5d                       pop    %ebp
 804817a:  c3                       ret


获取静态函数指针的套路:

获取PC地址,获取GOT相对PC的地址,获取.text相对GOT的偏移。这样就能获取.text的绝对地址,而静态函数在.text中的偏移是固定的,在编译.o时就确定了,这个偏移就是加数A。这样就得到了静态函数的地址。


获取全局函数指针的套路:

获取PC地址,获取GOT相对PC的地址,获取全局函数地址相对GOT的偏移,这就获取到全局函数的地址。


参考

ELF:Intel architecture. https://insidelinuxdev.net/article/a00c0b.html

ELF:Intel architecture and System V Release 4 Dependencies. https://insidelinuxdev.net/article/a032be.html

The Intel386 psABI version draft for clarifying R_386_GOT32 and R_386_GOT32X relocations. https://github.com/hjl-tools/x86-psABI/wiki/intel386-psABI-draft.pdf


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.