ELF重定位

图片

在工作和学习中,越发发现ELF文件格式的重要性,今天简单谈谈ELF重定位。重定位技术实际上是给二进制打补丁的机制,如果使用了动态连接器,可以使用重定位在内存打热补丁。

ELF重定位重定位技术实际上是给二进制打补丁的机制,如果使用了动态连接器,可以使用重定位在内存打热补丁。https://mp.weixin.qq.com/s?__biz=MzAxNjIyNDgzNw==&mid=2247488904&idx=1&sn=78cb9a50f76b471d760e37be0527a026&chksm=9bf952a2ac8edbb499f71102b3d506f23a1be5e5859913f292bf28c4a24d369564d96149256e&token=1265574353&lang=zh_CN#rd

--问题引入--


因为32位系统和64位系统差异不大,我们采用32位架构进行分析,已知一个重定向条目定义为:

typedef struct{ Elf32_Addr r_offset; Elf32_Word r_info;} Elf32_Rel;

上述结构中,加数为隐式的,还有个结构体,添加了显式加数:

typedef struct{ Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend;} Elf32_Rela;

总共有以下四种结构体:

typedef struct {        Elf32_Addr      r_offset;        Elf32_Word      r_info;} Elf32_Rel; typedef struct {        Elf32_Addr      r_offset;        Elf32_Word      r_info;        Elf32_Sword     r_addend;} Elf32_Rela;
typedef struct {        Elf64_Addr      r_offset;        Elf64_Xword     r_info;} Elf64_Rel; typedef struct {        Elf64_Addr      r_offset;        Elf64_Xword     r_info;        Elf64_Sxword    r_addend;} Elf64_Rela;

下面给出一个简单的示例C代码:

 

//$ cat obj1.c _start(){ foo();}

上面的代码的函数foo并没有实现,将在另一个C文件中给出。对上面的C程序进行编译,生成二进制目标文件,当然,我们这里指定生成32位目标文件:

$ gcc -nostdlib obj1.c -m32 -c

对目标文件进行反汇编为:

$ objdump -d obj1.o obj1.o:     file format elf32-i386Disassembly of section .text:00000000 <_start>: 0: f3 0f 1e fb endbr32 4: 55 push %ebp 5: 89 e5 mov %esp,%ebp 7: 53 push %ebx 8: 83 ec 04 sub $0x4,%esp b: e8 fc ff ff ff call c <_start+0xc> 10: 05 01 00 00 00 add $0x1,%eax 15: 89 c3 mov %eax,%ebx 17: e8 fc ff ff ff call 18 <_start+0x18> 1c: 90 nop 1d: 83 c4 04 add $0x4,%esp 20: 5b pop %ebx 21: 5d pop %ebp 22: 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

在这个目标文件中,存在重定向条目:

 

$ readelf -r obj1.o
Relocation section '.rel.text' at offset 0x22c contains 3 entries: Offset Info Type Sym.Value Sym. Name0000000c 00000c02 R_386_PC32 00000000 __x86.get_pc_thunk.ax00000011 00000d0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_00000018 00000e04 R_386_PLT32 00000000 foo
Relocation section '.rel.eh_frame' at offset 0x244 contains 2 entries: Offset Info Type Sym.Value Sym. Name00000020 00000202 R_386_PC32 00000000 .text00000044 00000502 R_386_PC32 00000000 .text.__x86.get_pc_thu

只关注下面这一行:

 

 Offset     Info    Type            Sym.Value  Sym. Name00000018 00000e04 R_386_PLT32 00000000 foo

那么,foo对应的重定向条目为:

r_offset = 00000018r_info = 00000e04

类型为:R_386_PLT32 ,这将在后面进行介绍。在objdump的结果中:

17: e8 fc ff ff ff call 18 <_start+0x18>

e8表示call指令,fc ff ff ff 为隐式加数(因为是小端,所以这个数值为0xfffffffc,对应-4)。下图红色位置是我们需要注意的:

图片

在另一源文件obj2.c中,定义了函数foo:

//$ cat obj2.c void foo(){}

同样,进行编译和反汇编:

$ gcc -nostdlib obj2.c -m32 -c$ objdump -d obj2.o
obj2.o:     file format elf32-i386Disassembly of section .text:00000000 <foo>: 0: f3 0f 1e fb endbr32 4: 55 push %ebp 5: 89 e5 mov %esp,%ebp 7: e8 fc ff ff ff call 8 <foo+0x8> c: 05 01 00 00 00 add $0x1,%eax 11: 90 nop 12: 5d pop %ebp 13: 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

这个文件中其实也存在重定向条目,但是,我们不需要关心。

接着,趁热,编译出可执行文件:

 

$ gcc -nostdlib obj1.o obj2.o -m32 -o a.out

再次反汇编查看:

$ objdump -d a.out a.out:     file format elf32-i386Disassembly of section .text:00001000 <_start>:    1000:  f3 0f 1e fb            endbr32     1004:  55                     push   %ebp    1005:  89 e5                  mov    %esp,%ebp    1007:  53                     push   %ebx    1008:  83 ec 04               sub    $0x4,%esp    100b:  e8 13 00 00 00         call   1023 <__x86.get_pc_thunk.ax>    1010:  05 e4 2f 00 00         add    $0x2fe4,%eax    1015:  89 c3                  mov    %eax,%ebx    1017:  e8 0b 00 00 00         call   1027 <foo>    101c:  90                     nop    101d:  83 c4 04               add    $0x4,%esp    1020:  5b                     pop    %ebx    1021:  5d                     pop    %ebp    1022:  c3                     ret    00001023 <__x86.get_pc_thunk.ax>:    1023:  8b 04 24               mov    (%esp),%eax    1026:  c3                     ret    00001027 <foo>:    1027:  f3 0f 1e fb            endbr32     102b:  55                     push   %ebp    102c:  89 e5                  mov    %esp,%ebp    102e:  e8 f0 ff ff ff         call   1023 <__x86.get_pc_thunk.ax>    1033:  05 c1 2f 00 00         add    $0x2fc1,%eax    1038:  90                     nop    1039:  5d                     pop    %ebp    103a:  c3                     ret

为了清晰,我简化一下:

Disassembly of section .text:00001000 <_start>:    1017:  e8 0b 00 00 00         call   1027 <foo>00001027 <foo>:

这和编译出来的两个目标文件中的地址显然不同了,如下:

$ objdump -d obj1.o Disassembly of section .text:00000000 <_start>:  17:  e8 fc ff ff ff         call   18 <_start+0x18>
$ objdump -d obj2.o Disassembly of section .text:00000000 <foo>:  

--分析--


我们的问题正式引入,

重定位是怎么将obj1+obj2转化为可执行文件的?

在上面我们简单提到foo在obj1中的重定位类型是 R_386_PLT32 ,重定位类型有很多,可以参见

32-bit x86: ELF Relocation Types

Name 

Value 

Field 

Calculation 

R_386_NONE

0

None

None

R_386_32

1

word32

S + A

R_386_PC32

2

word32

S + A - P

R_386_GOT32

3

word32

G + A

R_386_PLT32

4

word32

L + A - P

R_386_COPY

5

None

Refer to the explanation following this table. 

R_386_GLOB_DAT

6

word32

S

R_386_JMP_SLOT

7

word32

S

R_386_RELATIVE

8

word32

B + A

R_386_GOTOFF

9

word32

S + A - GOT

R_386_GOTPC

10

word32

GOT + A - P

R_386_32PLT

11

word32

L + A

R_386_16

20

word16

S + A

R_386_PC16

21

word16

S + A - P

R_386_8

22

word8

S + A

R_386_PC8

23

word8

S + A - P

R_386_SIZE32

38

word32

Z + A

https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter6-26/index.html

上表中,字母的含义为:

  • A:用于计算可重定位字段值的加数。

  • B:共享对象在执行过程中加载到内存的基地址。

    一般情况下,共享对象文件是用0的虚拟基地址构建的,但是共享对象的执行地址是不同的。

    见程序头。

  • G:执行时重定位项的符号地址所在的全局偏移表中的偏移量。

  • GOT:全局偏移表的地址。

  • L:符号的过程链接表项的段偏移量或地址。

  • P:被重定位的存储单元的段偏移或地址,使用r_offset计算。

  • S:索引位于重定位项中的符号的值。

  • Z:索引位于重定位项的符号的大小。

对应类型 R_386_PLT32 可以找到计算方式为 L + A - P,他的意思是:计算符号的过程链接表条目的地址并指示链接编辑器创建过程链接表。仿佛一头雾水,那么我们结合汇编代码看就显得不那么灰色难懂了。再次给出简化的反汇编代码:

那么就需要根据以上信息计算“L + A - P”即可:

L = 0x1027A = 0xfffffffcP = 0x1018

为了清晰,我把他们在图上标注出来:

图片

计算:

L + A - P = 0x1027 + 0xfffffffc - 0x1018= 0x1027 - 4 - 0x1018= 0xb

计算出来的0xb 是什么?就是指令:e8 0b 00 00 00 也就是call 1027 <foo> 。

0x1027的计算过程是:

CALL指令地址 + offset + CALL指令长度= 0x1017 + 0xb + 5= 0x1027

至此,重定位过程就结束了。

--结论--


重定位技术实际上是给二进制打补丁的机制,如果使用了动态连接器,可以使用重定位在内存打热补丁。

--参考链接--


  • https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/6n33n7fct/index.html

  • 源码:https://gitee.com/rtoax/test-linux

我的分析流程见下图:

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值