首页
社区
课程
招聘
[原创]Relocate、PLT、GOT And Lazy Binding
发表于: 2023-3-7 19:24 20702

[原创]Relocate、PLT、GOT And Lazy Binding

2023-3-7 19:24
20702

本篇文章将会尝试解释重定位,PLT,GOT以及延迟绑定,并且通过一个简单的例子动态调试追踪延迟绑定的过程。

Relocation is the process of connecting symbolic references with symbolic definitions. Relocatable files must have information that describes how to modify their section contents, thus allowing executable and shared object files to hold the right information for a process's program image. Relocation entries are these data.

即:

以32位系统为例(为了便于理解,本文后续内容均会以32位系统场景描述),其关键的数据结构如下:

在程序加载时,会通过自己的.rel section,告诉连接器需要重定位的位置,在后续会详细的展示一个32位程序重定位的过程。

像上面那种在程序加载时,通过.relsection,让编译器基于重定位信息计算出调用函数在程序中的实际位置的加载方式,一般被称为静态链接,如果程序使用了外部的库函数时,整个库函数都会被直接编译到程序中。

可以思考一下它的缺点,以及对应的改正方法:

动态链接技术的提出就是为了解决这个问题,在程序运行时,将共享库和程序本身进行链接,同时,内存里的程序可以共享同一个库文件,这样既节省了硬盘存储空间,同样节省了内存空间。

静态链接与动态链接主要区别如下图所示。(本图参照《CTF竞赛权威指南》所画)

为了做到动态编译,首先需要生成位置无关代码(Posistion-Independent Code,PIC),通过PIC一个共享库可以被多个进程共享。

同时想要完成动态链接在源程序中还需要有:

因为数据段和代码段之间的距离是一个运行时常量,他们之间的偏移是固定的,于是这里就有了全局偏移表(GOT,Global Offset Table),它位于数据段的开始,用于保存全局变量以及库函数(外部函数)的引用,每一条8个字节,在程序加载时会完成重定位,并填入符号的绝对地址。GOT一般被拆成了两个section,不需要延迟绑定,用于存储全局变量,加载到内存中只需要被读取的.got,以及为了存储库函数需要延迟绑定写入的.got.plt

而同时为了完成延迟绑定还需要将外部函数的值在运行时写入.got.plt,因此又引入了过程链接表(PLT,Procedure Liknage Table)。PLT是由代码片段组成,用于将地址无关函数转移到绝对地址。每一个被调用的库函数,都会映射到一组PLT 和 GOT。如下图所示:

Lazy Binding,即延迟绑定,指的是只有当函数被调用的时候才进行函数绑定,这种方式加快的程序的启动速度。

为了完成延迟绑定的过程,PLT和GOT需要配合完成一些事情。

假设程序中存在一个puts函数被调用,在PLT 中的表项为puts@plt,在GOT中表项为`puts@got.plt。为了完成延迟绑定,在第一次执行的时候会完成以下事情:

在后续调用,就会直接到GOT表项取得puts函数的地址。

整理一下,PLT和GOT中存在着通用的指令和后续的指令如下所示:

整个过程草图如下所示

环境:

源代码:

编译:

通过objdump查看目标文件汇编代码:

print_hello函数如下:

main 函数如下:

通过objdump查看最终汇编代码:

print_hello函数如下:

main函数如下:

对比目标代码和最终代码的反汇编代码,可以看到,在print_hello函数中,调用call函数时对应内容是有经过重定向的,如下图所示。

在目标代码中:


查看对应重定位表,可以看到:

offset 为$5$ 的符号是 __x86.get_pc_thunk.bx, offset为 $17$的符号是puts

而在最终代码中:

查看编译之后程序的符号:

对于 __x86.get_pc_thunk.bx, 采用的是S + A - P的方式计算偏移:

那么还剩下一个问题,puts函数此时在符号表中的值是0,又应该怎么计算呢?

前面有讲到,Linux下符号动态链接默认采用的是延迟绑定的方式,也就是说,在程序运行时用到改符号的时候才会去解析它的地址(值)。

开始动态调试,启动后在puts@plt下断点:

可以看到PLT表的内容和上述所说的基本吻合,首先是一个jump指令,此时根据上面信息可以得知ebx寄存器存储的是GOT表地址,所以这条jump指令就跳转到GOT表中。第二条push 序号入栈,第三条jump 指令调转到的位置就是PLT[0]

查看jump地址的信息:


跳入的是.got.plt且表现值存储的值是0x56556056即刚才的push指令。

继续查看0x56556030处的内容,ebx存储的仍然是GOT表的地址,因此此处指令仍然是上面所描述的push GOT[1] jmp GOT[2]

继续调试,发现他进入的即是_dl_runtime_resolve()函数。

到这里有关PLT,GOT延迟绑定的整个过程已经基本梳理完毕,在后续有关更多PLT,GOT攻击利用的时候,再来详细讨论_dl_runtime_resolve()函数。

好的,下面我们结合源码分析一下_dl_runtime_resolve()函数的实现。

在GNU C库的源码中,_dl_runtime_resolve()函数的定义位于elf/dl-runtime.c文件中。该函数的主要作用是在程序运行期间解析动态链接函数。

函数的定义如下:

其中,ElfW(Addr)是一个宏定义,根据编译时指定的目标架构不同而不同。例如,在32位x86架构上,它被定义为unsigned int

函数中的ref参数是一个指向函数指针的指针,它指向需要被解析的动态链接函数的地址。

函数的实现过程如下:

这里的D_PTR()宏是GNU C库中的一个宏定义,用于读取动态链接器内部数据结构中的字段。DT_PLTGOT表示动态链接器中GOT表的地址。

然后,从ref参数指向的地址中读取一个偏移量,该偏移量是一个相对于共享对象基地址的偏移量,表示需要解析的动态链接函数在共享对象中的位置。

这里的DT_RELA表示动态链接器中重定位表的地址,通过它可以找到需要解析的动态链接函数的重定位项。

接着,通过解析重定位项,获取目标函数的地址。

其中,_dl_fixup()函数是用于解析重定位项的函数,它会根据重定位项中的信息,计算出目标函数的地址,并返回给调用者。

最后,将目标函数的地址返回给调用者,完成动态链接函数的解析过程。

综上所述,_dl_runtime_resolve()函数的实现过程中,主要涉及了动态链接器的内部数据结构和重定位表的解析。它是实现动态链接的核心函数之一,可以在程序运行期间解析动态链接函数,实现代码的灵活性和可重用性。

 
// 隐式加法重定位,重定位处的汇编一般会是 e8 fc ff ff ff
typedef struct {
    Elf32_Addr r_offset;
    uint32_t   r_info;
} Elf32_Rel;
 
// 显式加法重定位,重定位时需要计算的加数存储在 r_addend
typedef struct {
    Elf32_Addr r_offset;
     uint32_t   r_info;
     int32_t    r_addend;
 } Elf32_Rela;
// 隐式加法重定位,重定位处的汇编一般会是 e8 fc ff ff ff
typedef struct {
    Elf32_Addr r_offset;
    uint32_t   r_info;
} Elf32_Rel;
 
// 显式加法重定位,重定位时需要计算的加数存储在 r_addend
typedef struct {
    Elf32_Addr r_offset;
     uint32_t   r_info;
     int32_t    r_addend;
 } Elf32_Rela;
 
 
 
 
 
 
 
 
PLT[0]: push GOT[1]
        jmp GOT[2]
PLT[1]: __libc_start_main()
PLT[2]: jmp GOT[4]
        push 0
        jmp PLT[0]
...
 
GOT[0]: .dynmic 地址
GOT[1]: relor
GOT[2]: _dl_runtime_resolve()
GOT[3]: sys startup  # 针对不同环境此处内容可能不同
GOT[4]: PLT[2]第二条指令地址
PLT[0]: push GOT[1]
        jmp GOT[2]
PLT[1]: __libc_start_main()
PLT[2]: jmp GOT[4]
        push 0
        jmp PLT[0]
...
 
GOT[0]: .dynmic 地址
GOT[1]: relor
GOT[2]: _dl_runtime_resolve()
GOT[3]: sys startup  # 针对不同环境此处内容可能不同
GOT[4]: PLT[2]第二条指令地址
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
 
Linux null 5.15.0-67-generic #74-Ubuntu SMP Wed Feb 22 14:14:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
 
Linux null 5.15.0-67-generic #74-Ubuntu SMP Wed Feb 22 14:14:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
#include <stdio.h>
 
int print_hello() {
        printf("hello PLT and GOT\n");
}
 
int main() {
        print_hello();
        return 0;
}
#include <stdio.h>
 
int print_hello() {
        printf("hello PLT and GOT\n");
}
 
int main() {
        print_hello();
        return 0;
}
gcc main.c -o test -save-temps -m32 -g -Wl,-z,lazy
gcc main.c -o test -save-temps -m32 -g -Wl,-z,lazy
objdump -M intel -d main.o
objdump -M intel -d main.o
00000000 <print_hello>:
   0:   53                      push   ebx
   1:   83 ec 14                sub    esp,0x14
   4:   e8 fc ff ff ff          call   5 <print_hello+0x5>
   9:   81 c3 02 00 00 00       add    ebx,0x2
   f:   8d 83 00 00 00 00       lea    eax,[ebx+0x0]
  15:   50                      push   eax
  16:   e8 fc ff ff ff          call   17 <print_hello+0x17>
  1b:   83 c4 18                add    esp,0x18
  1e:   5b                      pop    ebx
  1f:   c3                      ret
00000000 <print_hello>:
   0:   53                      push   ebx
   1:   83 ec 14                sub    esp,0x14
   4:   e8 fc ff ff ff          call   5 <print_hello+0x5>
   9:   81 c3 02 00 00 00       add    ebx,0x2
   f:   8d 83 00 00 00 00       lea    eax,[ebx+0x0]
  15:   50                      push   eax
  16:   e8 fc ff ff ff          call   17 <print_hello+0x17>
  1b:   83 c4 18                add    esp,0x18
  1e:   5b                      pop    ebx
  1f:   c3                      ret
00000024 <main>:
  20:   55                      push   ebp
  21:   89 e5                   mov    ebp,esp
  23:   83 e4 f0                and    esp,0xfffffff0
  26:   e8 fc ff ff ff          call   27 <main+0x7>
  2b:   b8 00 00 00 00          mov    eax,0x0
  30:   c9                      leave
  31:   c3                      ret
00000024 <main>:
  20:   55                      push   ebp
  21:   89 e5                   mov    ebp,esp
  23:   83 e4 f0                and    esp,0xfffffff0

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-3-8 22:45 被安和桥南编辑 ,原因: 添加样例
上传的附件:
收藏
免费 13
支持
分享
最新回复 (2)
雪    币: 47147
活跃值: (20430)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
感谢分析,你这个简单的例子上传一份,我来部署到 http://kctf.kanxue.com
2023-3-8 10:34
1
雪    币: 3345
活跃值: (3368)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
3
kanxue 感谢分析,你这个简单的例子上传一份,我来部署到 http://kctf.kanxue.com
已经添加啦
2023-3-8 22:46
1
游客
登录 | 注册 方可回帖
返回
//