名词解释

  • exploit
    • 用于攻击的脚本与方案
  • payload
    • 攻击载荷,是对目标进程被劫持控制流的数据
  • shellcode
    • 调用目标的shell的代码

GOT 表(Global Offset Table)原理解析

1. 核心概念:程序的“动态通讯录”

Linux 开启了 ASLR(地址随机化)且采用动态链接,程序在编译时,根本不知道外部库(如 libc.so 中的 printfsystem)最终会被加载到内存的哪个具体位置。

GOT 表就是用来存放这些外部符号“真实物理/虚拟地址”的指针数组。

2. 构成拆解:变量表与函数表

GOT 在底层被划分为两个权限和用途不同的节区:

  • .got(变量偏移表)
    • 存放内容:外部全局变量的绝对地址(例如 stdinstdout)。
    • 解析时机:程序启动(Load)阶段,动态链接器会一次性把它全部填好。
  • .got.plt(函数偏移表)
    • 存放内容:外部库函数的绝对地址(例如 putsread)。
    • 解析时机:为了不拖慢程序启动速度,采用延迟绑定机制,用到哪个函数,才去查哪个函数的地址。

3. 运行原理:延迟绑定(Lazy Binding)

以程序调用 puts 为例,理解 PLT(过程链接表)与 .got.plt 的配合:

  • 第一次调用(查号码并保存)

    1. 业务代码发起调用,跳转到 puts@plt
    2. PLT 去查 .got.plt 中的对应表项。
    3. 关键点:此时 .got.plt 里存的不是真实地址,而是一条跳回 PLT 的指令。
    4. PLT 顺势调用动态链接器(_dl_runtime_resolve)。
    5. 链接器在内存中找到 puts 的真实地址,将其覆写进 .got.plt 的对应位置,并执行 puts
  • 第二次调用(直接拨号)

    1. 业务代码跳转到 puts@plt
    2. PLT 去查 .got.plt
    3. 发现里面已经是真实地址,直接跳转过去执行,不再经过链接器。

4. 安全攻防:RELRO 保护机制

由于延迟绑定需要写入动作,.got.plt 的可写性成为了劫持程序执行流(GOT Hijacking)的核心攻击面。

  • No RELRO.got.got.plt 均可写。
  • Partial RELRO(默认策略):.got 初始化后设为只读.got.plt 保持可写。攻击者可通过漏洞将 .got.plt 中的 puts 地址覆写为 system 的地址。
  • Full RELRO:彻底关闭延迟绑定。程序启动时强行解析所有函数地址,随后将 .got.got.plt 全部设为只读,直接阻断 GOT 劫持。

栈溢出

栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。

栈溢出的前提是:程序向栈上写入数据;数据的长度不受控制

最简单的栈溢出就是通过溢出,覆盖程序的返回地址,将返回地址覆盖为system('/bin/sh')的地址

未开启 NX 保护

先来说说NX保护机制,NX典型的例子有ret2syscall和libc,为了便于理解为什么有这种保护机制以及为什么需要这种保护机制,我去网上找了一些NX的原理。

NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存 页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

通过学习以及个人的理解,我认为NX保护机制其实就是让我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,而是通过其他途径绕过保护机制然后得到shell,例如ret2syscall是利用程序中的 gadgets 来获得 shell,libc则是控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置(即函数对应的 got表项的内容)。