背景知识
地址空间(Address space,Prozess Adressraum)
地址空间可以被看作是一个巨大的 一维字节数组,在程序运行时(与它潜在的大小相比),其中只有少数位置存有数据。由于地址空间占用非常稀疏,操作系统会将其划分为大小相等的 页(Pages),其中只有被操作系统释放(映射)的页才可以被访问。
程序从硬盘被加载到低地址空间(对应的页由操作系统或程序加载器自动提供)。
用于执行的机器代码存放在 文本段(Text Segment) 中,静态初始化的变量和字符串常量存放在 数据段(Data Segment) 中。静态变量如果在程序开始时尚未被赋值,则会放在 BSS 段 中,并由操作系统填充为零字节。
程序所需的动态链接库也由这三类段组成,并由程序加载器加载到更高的内存地址。由于这些库所需的内存是通过 mmap 系统调用 向操作系统申请的,因此它们也被称为 MMap 段。
在程序初始化过程中,还会额外保留两个区域:
- 栈(Stack):用于自动管理的变量。
- 堆(Heap):用于动态分配的变量。
栈在每次函数调用时会扩展一个 栈帧(Stack Frame)。在栈帧的内存区域中存储有当前函数的局部变量,以及一些管理信息,例如 返回地址。返回地址记录了当前执行的函数是从哪一个程序地址被调用的。随着函数调用深度的增加,栈会从高地址向低地址方向增长。
当需要在函数执行完毕后变量仍然存在时,就必须使用 堆 来进行动态内存分配(因为栈上的变量会在函数返回时自动释放)。在这种情况下,程序可以通过 libc 提供的分配器 使用 malloc 函数向系统申请内存。如果可能,分配器会返回对程序启动时预留的堆区域的引用;如果堆空间不足,分配器会通过 mmap 系统调用 向操作系统请求一个新的 MMap 段,并返回对该段的引用。
C 标准库(libc)除了包含内存分配器之外,还提供了许多常用函数,以便简化与操作系统的交互。
在程序执行过程中,如果访问了一个无效的地址(即未映射的页),操作系统会向程序发送一个 段错误(Segmentation Fault) 信号。如果程序没有对此进行处理,就会导致程序终止。
下面介绍一些基本的漏洞以及对应的攻击方法:
栈溢出(Stack buffer overflow)
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。(https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/stackoverflow-basic/)
漏洞
结构体字段劫持
最简单的攻击目的便是修改写入变量附近的某个变量的值。比如说在下面这个例子里:
1 |
|
我们写入的变量为user这个struct里的name,但是由于输入长度限制为200,而实际的name的存储空间仅为64,并且struct里的内容的存储空间是连续的,所以可以通过输入
1 | 'A'*64 + '\x01\x00\x00\x00' |
将原本的is_admin
的值修改为(被覆盖掉为)1。(注意大部分架构都是使用的小端序,并且int的大小为4个Byte,所以是\x01\x00\x00\x00
)
覆盖程序的返回地址
这种攻击可以成功实施的前提是需要确保修改后的这个地址所在的段具有可执行权限。
例题:
Format String Vulnerability
模板
可以利用Python的Pwntools库。
1 | from pwn import * |
推荐刷题顺序
- Pwn相关基础知识:
- Buffer Overflow
- 修改目标变量的值:
- 修改返回地址(以调用目标函数):
- ret2libc:
- 攻防世界 level0 Writeup
- 攻防世界 level2 Writeup
- HTB You_know_0xDiablos Writeup
- TJCTF 2025 pwn/i-love-birds Writeup
- TUM Binary Exploitation qualification challenge (这题在利用Buffer Overflow漏洞修改返回地址的基础上多了一个需要绕过的检测。)
- ret2reg:
- 3
- ret2libc:
- 字符串格式化漏洞(可以看我的这篇博客:格式化字符串(Format String)漏洞介绍):
- emmm