PLT(过程链接表)属于代码段(.text 相关节),GOT(全局偏移表)属于数据段(.data/.bss 相关节) —— 这个分段设计是动态链接机制的核心基础,也直接决定 Pwn 攻击的核心思路(比如为什么能劫持 GOT 而不能直接劫持 PLT)。

结合 CTF Pwn 视角,我们深入拆解这个设计的「本质原因」和「攻击关联」:

一、先明确:代码段 vs 数据段的核心差异(Pwn 关键)

ELF 文件的段(Segment)是操作系统加载内存时的权限控制单位,核心差异在于 权限属性 和 用途,这直接影响漏洞利用的可行性:

段类型 核心权限 典型节(Section) 用途 Pwn 相关性
代码段(Text Segment) 只读 + 可执行(RX) .text(程序自身代码)、.plt(PLT 表) 存储可执行指令(CPU 能直接运行的代码) 不可写 → 无法直接修改 PLT 指令,只能通过「跳转目标劫持」(如 GOT)
数据段(Data Segment) 可读 + 可写(RW) .data(初始化全局变量)、.bss(未初始化全局变量)、.got.plt(GOT 表) 存储变量、指针等可修改数据 可写 → 漏洞利用的核心目标(任意写漏洞修改 GOT 指针)

⚠️ 关键提醒:x86-64 系统中,代码段绝对禁止「可写」(否则会触发内存保护机制,如 NX 保护),而数据段绝对禁止「可执行」(否则会触发 DEP 保护)—— 这是现代系统的基础安全策略,也倒逼 Pwn 攻击只能围绕「修改数据段的指针」来实现。

二、PLT 为什么在代码段?

PLT 的本质是「跳板指令集合」,每个 PLT 条目都是一段固定的汇编代码(比如之前提到的 jmp [GOT地址] + push 符号索引 + jmp 通用入口),其设计必须满足「可执行」和「不可修改」:

  1. 需要可执行:PLT 是函数调用的「跳板」,CPU 必须能直接执行其中的 jmppush 指令 —— 只有代码段(RX 权限)能满足「可执行」要求;
  2. 不需要可写:PLT 指令是编译时固定的(所有动态链接程序的 PLT 结构都一致),运行时不会变化 —— 代码段的「只读」属性刚好匹配,同时避免被恶意修改(但 Pwn 中无需修改 PLT,只需劫持它的跳转目标)。

举个 Pwn 实战例子:

  • 你在 ret2plt 攻击中,利用的是 plt[puts] 这个「可执行指令地址」(代码段权限允许执行),通过 call plt[puts] 触发函数调用 —— 这正是因为 PLT 在代码段,具备可执行权限。

三、GOT 为什么在数据段?

GOT 的本质是「指针数组」,存储外部函数的真实内存地址,其设计必须满足「可修改」和「可读」:

  1. 需要可写:动态链接的「延迟绑定」核心是「第一次调用时更新 GOT 指针」—— 动态链接器(_dl_runtime_resolve)需要将解析出的真实函数地址(如 libc+printf_offset)写入 GOT 表,覆盖初始的 PLT 跳转地址(如 0x4004f6);
  2. 需要可读:PLT 中的 jmp [GOT地址] 指令需要读取 GOT 中存储的指针 —— 数据段的「可读」属性满足此要求。

这正是 Pwn 中「GOT 劫持」的核心前提:GOT 在数据段(RW 权限),存在任意写漏洞时,可直接修改其中的指针,将函数调用目标劫持到 shellcodeonegadget

举个 Pwn 实战例子:

  • 若程序存在格式化字符串漏洞,你可以通过 %n 直接修改 got[printf] 的值(将其改为 system 地址)—— 这正是因为 GOT 在数据段,具备可写权限;
  • 若 GOT 在代码段(只读),则无法修改,GOT 劫持攻击直接失效。

四、这个设计对 Pwn 攻击的直接影响(核心结论)

  1. 攻击目标锁定:因为 PLT 不可写、GOT 可写,所以动态链接相关的攻击几乎都围绕「GOT 表」展开(GOT 劫持、GOT 地址泄露),而 PLT 仅作为「触发工具」(如 ret2plt 泄露 GOT 地址);
  2. 漏洞利用逻辑:任意写漏洞(栈溢出、堆溢出、格式化字符串)的核心利用思路,就是「找到可写的 GOT 地址 → 修改其存储的指针 → 让后续函数调用跳转到恶意地址」;
  3. 调试与分析技巧
    • 静态分析时,用 readelf -S ./pwn 可看到 .plt 属于 PROGBITS 类型且权限为 AX(可执行),.got.plt 属于 PROGBITS 类型且权限为 WA(可写);
    • 动态调试时,用 vmmap 查看内存权限,代码段(含 PLT)的权限是 r-x,数据段(含 GOT)的权限是 rw-

五、实战验证:用工具确认 PLT/GOT 的段归属

1. 静态分析(readelf)

查看 ELF 节的属性,确认 .plt 和 .got.plt 所属的段和权限:

bash

运行

1
readelf -S ./pwn | grep -E ".plt|.got.plt"

典型输出(x86-64 动态链接 ELF):

plaintext

1
2
3
[13] .plt              PROGBITS         00000000004004e0  000004e0  00000040  00  AX  0   0 16
[24] .got PROGBITS 0000000000601000 00001000 00000028 00 WA 0 0 8
[25] .got.plt PROGBITS 0000000000601028 00001028 00000038 00 WA 0 0 8
  • 关键标识:.plt 的 AX 表示「可执行(A)+ 可读(X,隐含)」(即 RX 权限),属于代码段;
  • .got.plt 的 WA 表示「可写(W)+ 可读(A,隐含)」(即 RW 权限),属于数据段。

2. 动态调试(pwndbg)

查看内存中 PLT 和 GOT 的权限,验证段属性:

python

运行

1
2
3
4
5
6
7
# 查看 plt[printf] 的地址和内存权限
plt_printf = elf.plt['printf']
vmmap plt_printf # 输出类似:0x400000-0x400600 r-xp 0x000000 08:01 1234 ./pwn → r-x(代码段)

# 查看 got[printf] 的地址和内存权限
got_printf = elf.got['printf']
vmmap got_printf # 输出类似:0x601000-0x602000 rw-p 0x000000 08:01 1234 ./pwn → rw-(数据段)

总结(Pwn 核心记忆点)

  1. 核心结论:PLT(代码段,RX)= 不可写的跳板指令 → 只能用不能改;GOT(数据段,RW)= 可写的指针数组 → 漏洞利用的核心目标
  2. 攻击逻辑:动态链接相关的 Pwn 攻击,本质都是「利用 GOT 的可写性,篡改其指针,让 PLT 的跳转目标指向恶意代码」;
  3. 实战应用:无论是 GOT 劫持、ret2plt 泄露,还是 ret2dlresolve 伪造解析,都依赖这个分段设计 —— 理解「PLT 不可写、GOT 可写」,就能瞬间想通大部分动态链接相关的攻击原理。

意为:system@got 是一块内存地址(属于全局偏移表 GOT 的一个表项),这块地址中存储的内容就是 system 函数的真实内存地址(即 &system
&system 的作用是定位 system 函数的指令,
1. CPU 拿到 &system 这个地址(比如 0xf7e4d200);
2. 从这个地址开始,依次读取内存中的二进制指令并执行;
3. 这些指令的集合,就是 system 函数的完整功能实现。
![[Screenshot 2025-11-23 221513.png]]![[Screenshot 2025-11-27 112949.png]]