Day 02 程序保护机制即绕过思路
ELF 文件的常见保护机制及绕过思路
在 Pwn题目中,ELF(Executable and Linkable Format)文件通常会开启多种保护机制,以抵御常见的漏洞利用手段。理解这些保护机制的原理、检测方法及绕过思路,是成功解题的核心前提。本文将系统梳理 PWN 题目中主流的 ELF 保护机制,并详细分析对应的绕过方案。
前置须知
system("sh")
、system("/bin/sh")
、execve("/bin/sh", 0, 0)
等函数可以用来获取 shell 权限。得到shell权限之后就可以cat flag
了。
检测方式
1 | checksec pwn # pwn为目标ELF文件 |
输入以上代码会在终端里显示出ELF的保护机制。
主要保护机制及绕过思路
一、NX(No-eXecute)保护:栈 / 堆不可执行
1. 保护原理
NX(No-eXecute,不可执行)是最基础的 ELF 保护机制之一,其核心作用是标记栈、堆等数据区域为 “不可执行”。当程序尝试将数据区域(如栈上的 shellcode)当作代码执行时,CPU 会触发异常,直接终止程序运行,从而阻止攻击者通过注入 shellcode 并执行来获取权限。
2. 检测方法
- 工具检测:使用
checksec
,若输出中包含NX: NX enabled
,则表示开启 NX 保护;若为NX: NX disabled
,则未开启。
3. 绕过思路
NX 保护的核心是 “阻止数据区域执行”,因此绕过思路围绕 “不依赖数据区域执行代码” 展开,主流方案如下:
1.Ret2Libc:利用系统库函数
原理:程序运行时会加载
libc
(C 标准库),而libc
中包含system("/bin/sh")
、execve
等可直接获取 shell 的函数。通过漏洞(如栈溢出)控制程序的返回地址,将其指向libc
中的system
函数地址,并构造参数(如"/bin/sh"
的地址),让程序执行system("/bin/sh")
,从而绕过 NX。适用场景:程序泄露了库函数地址,通过该地址可以计算出libc库的base(基址),从而构造指向libc的payload.
2.Ret2Shellcode(仅特定场景)
- 原理:写入shellcode,计算机会执行机器码。
- 适用场景:若程序存在 “可执行的数据区域”(如自定义的
rwx
权限段、未开启 NX 的部分区域),可将 shellcode 写入该区域,再通过漏洞控制返回地址跳转到 shellcode 执行。
3.Ret2DLOPEN/Ret2DLRESOLVE:动态链接利用
原理:利用 ELF 的动态链接机制,通过构造
dlopen
(加载动态库)和dlsym
(获取库中函数地址)的调用流程,动态加载libc
并执行system
函数,无需提前泄露libc
基地址。适用场景:程序未开启 Full RELRO 保护(GOT 表可写),且无法直接泄露
libc
地址时。
二、ASLR(Address Space Layout Randomization):地址空间随机化
1. 保护原理
ASLR 是操作系统级的保护机制,其作用是在程序每次运行时,随机化 ELF 文件的代码段、数据段、堆、栈以及libc
等动态库的加载地址。攻击者无法提前预测关键地址(如system
函数、"/bin/sh"
字符串地址),从而阻止 Ret2Libc 等依赖固定地址的利用方式。
2. 绕过思路
ASLR 的核心是 “地址随机”,绕过思路围绕 “获取随机地址的具体值” 或 “利用地址固定的区域” 展开:
1.信息泄露漏洞:获取关键地址
原理:通过程序中的信息泄露漏洞(如格式化字符串漏洞、栈溢出导致的内存泄漏、UAF 漏洞),读取内存中已加载的关键地址(如 GOT 表中
libc
函数的真实地址、栈地址、堆地址),再根据地址偏移计算出目标函数(如system
)或字符串(如"/bin/sh"
)的地址。常见泄露场景:
格式化字符串漏洞:通过
%p
输出栈上的libc
地址或栈地址;栈溢出泄露:覆盖返回地址前,先泄露栈上保存的
libc
函数地址(如__libc_start_main
的返回地址);GOT 表泄露:通过漏洞读取 GOT 表中已解析的
libc
函数地址(如puts
的 GOT 地址)。
2.地址爆破:小范围随机地址尝试
原理:若 ASLR 随机化的地址空间较小(如仅低 12 位随机,对应 4KB 内存页),可通过循环发送 payload,尝试所有可能的地址组合,直到命中正确地址。
适用场景:
栈地址随机化范围小(如仅栈基址低 12 位随机);
程序可重复运行且无次数限制(如远程题目未限制连接次数)。
3.利用静态地址区域
原理:若程序未开启 PIE 保护(代码段地址固定),则 ELF 自身的代码段、数据段地址固定,可利用这些区域中的 gadget(代码片段)或字符串构造利用链,无需依赖
libc
地址。适用场景:未开启 PIE 保护的程序,且程序自身存在可利用的 gadget 和字符串。
三、PIE(Position-Independent Executable):PIE偏移
1. 保护原理
PIE 是 ELF 文件级的保护机制,其作用是让 ELF 文件的代码段、数据段、BSS 段等在加载时使用相对地址,而非固定地址。结合 ASLR 后,程序每次运行时,整个 ELF 文件的加载基地址会随机化,导致攻击者无法提前预测代码段中的 gadget 地址、数据段中的字符串地址等。
2. 检测方法
- 使用
checksec
,若输出中包含PIE: PIE enabled
(或PIE: 0x400000
等具体基址,代表部分随机),则开启 PIE;若为PIE: PIE disabled
,则未开启。
3. 绕过思路
PIE 的核心是 “ELF 自身地址随机”,绕过思路与 ASLR 类似,需先获取 ELF 的加载基地址:
泄露 ELF 基地址
原理:通过漏洞泄露 ELF 代码段或数据段中的某个已知偏移地址(如
main
函数地址、某个全局变量地址),再根据该地址与 ELF 基地址的偏移,计算出 ELF 的加载基地址。示例:若泄露的
main
函数真实地址为0x55a3b7201120
,ida
显示main
的偏移为0x1120
,则 ELF 基地址为0x55a3b7201120 - 0x1120 = 0x55a3b7200000
。
四、RELRO(Relocation Read-Only):重定位表只读保护
1. 保护原理
RELRO 通过限制 ELF 的 GOT(Global Offset Table,全局偏移表)和 PLT(Procedure Linkage Table,过程链接表)的可写性,防止攻击者通过修改 GOT 表来劫持函数执行流程(如将puts
的 GOT 表项修改为system
地址)。RELRO 分为两种级别:
保护级别 | 原理 | 防护强度 |
---|---|---|
Partial RELRO(部分 RELRO) | 仅将 GOT 表的部分区域设为只读,GOT 表的前半部分(.got)仍可写,后半部分(.got.plt)只读 | 较弱,可修改.got 区域 |
Full RELRO(完全 RELRO) | 将整个 GOT 表设为只读,且在程序启动时提前解析所有动态链接函数(关闭延迟绑定),GOT 表无任何可写区域 | 较强,无法修改 GOT 表 |
2. 检测方法
- 工具检测:使用
checksec
,若输出中包含RELRO: Full RELRO
,则开启完全 RELRO;若为RELRO: Partial RELRO
,则开启部分 RELRO;若为RELRO: No RELRO
,则未开启。
3. 绕过思路
RELRO 的核心是 “GOT 表只读”,绕过思路需根据保护级别区分:
1.Partial RELRO:修改可写 GOT 区域
原理:Partial RELRO 下,GOT 表的
.got
区域(存储已解析的函数地址)仍可写,攻击者可通过栈溢出、格式化字符串等漏洞修改该区域的函数地址(如将__libc_start_main
的 GOT 地址修改为system
地址),实现函数劫持。示例:通过格式化字符串漏洞,利用
%n
将__libc_start_main
的 GOT 表项修改为system
地址,当程序再次调用__libc_start_main
时,实际执行system
。
2.Full RELRO:放弃 GOT 表,寻找其他漏洞
原理:Full RELRO 下 GOT 表完全只读,无法修改,因此需放弃 “GOT 表劫持” 思路,转而利用其他类型的漏洞或代码段中的 gadget:
利用栈溢出 + Ret2Gadget:通过栈溢出控制返回地址,跳转到代码段或
libc
中的 gadget 组合(如pop ret
、pop rdi ret
),构造system("/bin/sh")
的调用流程。利用堆漏洞:若程序存在堆漏洞(如 UAF、Double Free、Off-by-One),可通过篡改堆块元数据(如
fd
、bk
指针),实现任意地址写或控制程序执行流,无需依赖 GOT 表。
五、Stack Canary(栈金丝雀):栈溢出检测
1. 保护原理
Stack Canary(栈金丝雀)是一种栈溢出检测机制,其核心逻辑是:在程序启动时,生成一个随机的 “金丝雀” 值,存储在栈帧的 rbp下方(栈溢出的必经之路)。函数返回前,程序会检查金丝雀值是否被篡改,若被篡改(说明发生栈溢出),则立即终止程序。
2. 检测方法
- 工具检测:使用
checksec
,若输出中包含Canary: Canary found
,则开启 Stack Canary;若为Canary: No canary found
,则未开启。
3. 绕过思路
Stack Canary 的核心是 “随机值保护栈帧”,绕过思路围绕 “获取金丝雀值” 展开:
泄露金丝雀值
原理:通过漏洞(如格式化字符串漏洞、栈溢出导致的部分内存泄露)读取栈上存储的金丝雀值,在构造栈溢出 payload 时,将泄露的金丝雀值填充到对应的位置,避免触发
__stack_chk_fail
。常见泄露场景:
- 格式化字符串漏洞:栈上的金丝雀值可通过
%p
直接输出(需确定金丝雀在栈上的偏移);
- 格式化字符串漏洞:栈上的金丝雀值可通过
注意事项:金丝雀值在程序每次运行时随机(结合 ASLR),因此需在同一次程序运行中完成 “泄露金丝雀 + 构造 payload 溢出”。
gdb调试
调试验证:通过gdb
动态调试,获取关键地址,从而构造payload。
vmmap
:查看进程的内存映射情况,获取各个段的地址信息。reg rsp
:查看当前栈顶指针的值。(system函数调用前会检查rsp向0x10对齐)