查看: 960|回复: 5

[Pwn] Pwn 基础:PLT&GOT 表以及延迟绑定机制

[复制链接]
发表于 2020-3-26 14:06:02 | 显示全部楼层 |阅读模式
PLT&GOT
linux 下的动态链接是通过 PLT&GOT 来实现的,这里做一个实验,通过这个实验来理解

使用如下源代码 test.c:
#include <stdio.h>
void print_banner()
{
    printf("Welcome to World of PLT and GOT\n");
}
int main(void)
{
    print_banner();
    return 0;
}


依次使用下列命令进行编译:
        
gcc -Wall -g -o test.o -c test.c -m32
gcc -o test test.o -m32

这样除了原有的 test.c 还有个 test.o 以及可执行文件 test
通过 objdump -d test.o 可以查看反汇编

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制

printf () 和函数是在 glibc 动态库里面的,只有当程序运行起来的时候才能确定地址,所以此时的 printf () 函数先用 fc ff ff ff 也就是有符号数的 -4 代替

运行时进行重定位是无法修改代码段的,只能将 printf 重定位到数据段,但是已经编译好的程序,调用 printf 的时候怎么才能找到这个地址呐?

链接器会额外生成一小段代码,通过这段代码来获取 printf () 的地址,像下面这样,进行链接的时候只需要对 printf_stub () 进行重定位操作就可以
.text
...
// 调用printf的call指令
call printf_stub
...
printf_stub:
    mov rax, [printf函数的储存地址] // 获取printf重定位之后的地址
    jmp rax // 跳过去执行printf函数
.data
...
printf函数的储存地址,这里储存printf函数重定位后的地址

总体来说,动态链接每个函数需要两个东西:
1、用来存放外部函数地址的数据段
2、用来获取数据段记录的外部函数地址的代码

对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表(GOT, Global Offset Table),那个存放额外代码的表称为程序链接表(PLT,Procedure Link Table)

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制
可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址

那我们可以发现,在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制

延迟绑定
只有动态库函数在被调用时,才会地址解析和重定位工作,为此可以使用类似这样的代码来实现:
//一开始没有重定位的时候将 prin[email protected] 填成 lookup_printf 的地址
void [email protected]()
{
address_good:
    jmp *[email protected]  
lookup_printf:
    调用重定位函数查找 printf 地址,并写到 [email protected]
    goto address_good;//再返回去执行address_good
}


说明一下这段代码工作流程,一开始,[email protected] 是 lookup_printf 函数的地址,这个函数用来寻找 printf () 的地址,然后写入 [email protected],lookup_printf 执行完成后会返回到 address_good,这样再 jmp 的话就可以直接跳到 printf 来执行了

也就是说这样的机制的话如果不知道 printf 的地址,就去找一下,知道的话就直接去 jmp 执行 printf 了

接下来,我们就来看一下这个 “找” 的工作是怎么实现的:

通过 objdump -d test > test.asm 可以看到其中 plt 表项有三条指令
Disassembly of section .plt:
080482d0 <[email protected]>:
 80482d0:   ff 35 04 a0 04 08       pushl  0x804a004
 80482d6:   ff 25 08 a0 04 08       jmp    *0x804a008
 80482dc:   00 00                   add    %al,(%eax)
    ...
080482e0 <[email protected]>:
 80482e0:   ff 25 0c a0 04 08       jmp    *0x804a00c
 80482e6:   68 00 00 00 00          push   $0x0
 80482eb:   e9 e0 ff ff ff          jmp    80482d0 <_init+0x28>
080482f0 <[email protected]>:
 80482f0:   ff 25 10 a0 04 08       jmp    *0x804a010
 80482f6:   68 08 00 00 00          push   $0x8
 80482fb:   e9 d0 ff ff ff          jmp    80482d0 <_init+0x28>

ps. 这里 plt 表的第一项使用 objdump 的时候给没有符号名的一项自动改成了离他最近的一项,为了避免引起误会,改成了 common,而且随着不断深入,会发现,确实可以叫 common

其中除第一个表项以外,plt 表的第一条都是跳转到对应的 got 表项,而 got 表项的内容我们可以通过 gdb 来看一下,如果函数还没有执行的时候,这里的地址是对应 plt 表项的下一条命令,即 push 0x0

(说一下怎么查看,先 gdb test 然后 b main,再 run, 再 x/x jmp的那个地址 就可以)

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制

还记得之前我们说的,在还没有执行过函数之前 [email protected] 的内容是 lookup_printf 函数的地址吗,这就是要去找 printf 函数的地址了

现在要做的是:
push   $0x0    //将数据压到栈上,作为将要执行的函数的参数
jmp    0x80482d0   //去到了第一个表项

接下来继续
080482d0 <[email protected]>:
pushl  0x804a004  //将数据压到栈上,作为后面函数的参数
jmp    *0x804a008 //跳转到函数
add    %al,(%eax)
    ...

我们同样可以使用 gdb 来看一下这里面到底是什么,可以看到,在没有执行之前是全 0

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制

当执行后他有了值

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制

这个值对应的函数是 _dl_runtime_resolve

那现在做一个小总结:
在想要调用的函数没有被调用过,想要调用他的时候,是按照这个过程来调用的
[email protected] -> [email protected] -> [email protected] -> 公共 @plt -> _dl_runtime_resolve

到这里我们还需要知道

  • _dl_runtime_resolve 是怎么知道要查找 printf 函数的
  • _dl_runtime_resolve 找到 printf 函数地址之后,它怎么知道回填到哪个 GOT 表项

第一个问题,在 [email protected] 中,我们在 jmp 之前 push 了一个参数,每个 [email protected] 的 push 的操作数都不一样,那个参数就相当于函数的 id,告诉了 _dl_runtime_resolve 要去找哪一个函数的地址

在 elf 文件中 .rel.plt 保存了重定位表的信息,使用 readelf -r test 命令可以查看 test 可执行文件中的重定位信息

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制

这里有些问题,对应着大佬博客说 plt 中 push 的操作数,就是对应函数在.rel.plt 段的偏移量,但是没对比出来

第二个问题,看 .rel.plt 的位置就对应着 [email protected] 里 jmp 的地址

在 i386 架构下,除了每个函数占用一个 GOT 表项外,GOT 表项还保留了3个公共表项,也即 got 的前3项,分别保存:

  • got [0]: 本 ELF 动态段 (.dynamic 段)的装载地址
  • got [1]:本 ELF 的 link_map 数据结构描述符地址
  • got [2]:_dl_runtime_resolve 函数的地址
动态链接器在加载完 ELF 之后,都会将这3地址写到 GOT 表的前3项

跟着大佬的流程图来走一遍:

第一次调用

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制

之后再次调用

Pwn 基础:PLT&GOT 表以及延迟绑定机制

Pwn 基础:PLT&GOT 表以及延迟绑定机制


温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
论坛交流群:672619046

0

主题

143

帖子

0

精华

中级会员

Rank: 8Rank: 8

学币
369
荣耀
0
rank
0
违规
0

    发表于 2020-3-26 19:49:40 | 显示全部楼层
    啥也不说了,楼主就是给力!

    0

    主题

    5

    帖子

    0

    精华

    初级会员

    Rank: 4

    学币
    9
    荣耀
    0
    rank
    0
    违规
    0

      发表于 2020-3-29 09:39:22 | 显示全部楼层
      太给力了,这么多好东西!

      0

      主题

      143

      帖子

      0

      精华

      中级会员

      Rank: 8Rank: 8

      学币
      369
      荣耀
      0
      rank
      0
      违规
      0

        发表于 2020-3-30 15:22:52 | 显示全部楼层
        非常不错啊,感谢楼主无私的共享精神!

        0

        主题

        42

        帖子

        0

        精华

        初级会员

        Rank: 4

        学币
        50
        荣耀
        0
        rank
        0
        违规
        0

          发表于 2020-4-7 20:40:59 | 显示全部楼层
          感谢分享
          回复 打印

          使用道具 举报

          0

          主题

          4

          帖子

          0

          精华

          初级会员

          Rank: 4

          学币
          0
          荣耀
          0
          rank
          0
          违规
          0

            发表于 2020-4-11 11:38:27 | 显示全部楼层
            吃水不忘挖井人,我也去发帖分享好东西。
            微信公众号
            快速回复 返回顶部 返回列表