栈溢出
1.1什么是栈
栈是程序运行时的一块临时内存区域,遵循先进后出规则,用来存放局部变量、函数返回地址、ebp栈基址、函数临时参数。
函数调用时开辟栈帧,执行完毕自动回收栈帧。
1.2为什么会栈溢出
程序接收用户输入时,没限制输入长度。输入的数据塞满规定的缓冲区之后,多余内容会顺着内存往后写,我们就能操控程序接下来运行什么代码。当然,最关键的就是改掉函数执行完要跳转的地址。
栈里简单布局(32位程序):低地址→缓冲区(我们输入内容)→旧ebp→返回地址→高地址,我们填数据把缓冲区塞满,继续往后写,最后精准把「返回地址」改成我们想要的地址。
如图,我们只需要把ret_addr覆盖成我们想要的地址,就可以完成攻击。

1.3什么函数可以栈溢出
最常见的是gets、scanf这类不会自动截断输入的函数,当然,凡是不会主动控制写入缓冲区数据大小、无脑把所有数据塞进数组的函数,都能造成栈溢出。
1.4缓解措施
1.4.1 PIE(Position-Independent-Executable,位置无关可执行文件)
编译器提供PIE保护选项,开启时,程序会被胡乱加载到内存的随机位置,并且,这个位置只有操作系统才知道。
- PIE:位置无关可执行文件,编译器的编译选项,随机化ELF主程序的加载基地址,基于ASLR实现
- ASLR:地址空间布局随机化,操作系统内核功能,随机化libc、动态共享库的加载及地址,之影响系统库,不控制主程序
1.4.2 NX(DEP , No-eXecute,不可执行保护)
通过修改段的权限设置,将没有必要的权限移除,使得攻击者不能胡乱劫持程序执行流或修改代码
- 内存分段:程序运行时,内存会被分为一块块段,每一段有不同的权限(可读,可写,可执行
- 常见分段:代码段(.text):可读,不可写,可执行,存放汇编指令、函数(system、main)
数据段(.date):可读,可写,不可执行,存放已初始化的全局变量
只读数据段(.rodate):只读,存放常量字符串(“/bin/sh”)
栈(stack):可读,可写,默认不可执行(NX开启)
堆(heap):可读,可写,不可执行
1.4.3 Canary(栈金丝雀)
在缓冲区和函数返回地址直接按放置一串随机值,栈溢出时,程序检测该随机值被修改,程序直接终止,防止攻击者覆盖返回地址。
攻击手法
2.1 ret2text
2.1.1 定义
溢出篡改返回地址,直接跳转到程序自身.text代码段里已经写好的功能代码,不用额外注入代码、不用调用外部库。
最经典的是程序内置system(“/bin/sh”)后门函数。
2.1.2 前提
- 程序二进制内部自带可直接拿shell的目标函数。
- 无Canary金丝雀防护。
2.1.3 如何攻击
- IDA或GDB查找目标后门函数的地址。
- 构造载荷:垃圾填充字节 × 偏移长度 + 垃圾字节覆盖ebp + 后门函数地址
- 把payload发送给程序,函数结束执行ret时直接跳转到后门函数执行。
2.1.4 利弊
- 优点:实现最简单。
- 缺点:局限性极强,只有程序自带后门才能使用,绝大多数题目没有这类内置后门。
2.2 ret2shellcode
2.2.1 定义
往栈缓冲区写入一段能直接弹出shell的机器码(shellcode),再修改返回地址跳转到缓冲区,让CPU直接执行我们手写的机器指令。
2.2.2 前提
- NX防护必须关闭,栈内存拥有可执行权限。
- 知道存放shellcode的缓冲区栈地址。
2.2.3 如何攻击
- 生成适配系统架构的shellcode。
- 构造payload:前置填充垃圾数据,后面拼接shellcode,同时把返回地址改成shellcode所在的缓冲区地址。
- 发送payload,函数结束跳转至缓冲区,CPU运行shellcode拿到shell。
2.2.4 利弊
- 优点:灵活性极高,shellcode可以自定义执行任意命令,不需要依赖libc库中的函数与字符串。
- 缺点:开启NX防护无法使用,开启PIE地址随机化后,栈地址不固定。
2.3 ret2syscall
2.3.1 定义
不调用libc封装好的函数,利用ROP指令控制寄存器,手动构造execve(“/bin/sh”)系统调用打开shell。
2.3.2 前提
- 程序内有对应的gadget片段,可以控制rax、rdi、rsi、rdx这些系统调用必备寄存器.
- 内存中存在/bin/sh字符串。
2.3.3 如何攻击
- 搜集pop rax;pop rdi;ret等用来修改寄存器的小指令片段.
- 排布ROP链,给寄存器赋值:rax赋值execve调用号、rdi存放/bin/sh地址。
- 拼接syscall指令触发系统调用。
2.3.4 利弊
- 优点:不依赖libc版本,NX防护不影响执行。
- 缺点:需要大量合适的gadget,搜集繁琐。
2.4 ret2libc
2.4.1 定义
程序运行会加载libc动态链接库,库内自带system函数、/bin/sh字符串;先泄露libc运行时的基地址,计算出system真实地址,最后调用system(“/bin/sh”)获取shell。
2.4.2 前提
- GOT表可读取。
- 能构造基础ROP链调用puts/write这类输出函数。
2.4.3 如何攻击
- 第一轮ROP:调用puts打印puts@GOT地址,拿到libc内puts的运行地址,计算libc基地址。
- 依靠基地址换算出system函数、/bin/sh字符串的真实内存地址。
- 第二轮溢出构造ROP,控制参数执行system(“/bin/sh”)拿到交互式shell。
2.4.4 利弊
- 优点:适用范围最广。
- 缺点:需必须配套对应版本的libc文件。
2.5 ret2csu
2.5.1 定义
专门针对64位程序设计,当找不到pop rdx传参gadget时,控制rdx传参寄存器,完成函数参数传递。
2.5.2 前提
- 64位ELF程序,自带完整的__libc_csu_init函数。
2.5.3 如何攻击
- 提取CSU的两段核心gadget:一段批量pop寄存器,一段调用目标函数。
- 在栈上排布数据。
- 调用puts/write泄露GOT地址,后续正常走ret2libc流程拿shell。
2.5.4 利弊
- 优点:通用性极强。
- 缺点:ROP链排布冗长,代码篇幅长,调试排查问题比较麻烦。
2.6 栈迁移
2.6.1 定义
缓冲区分配的空间极小,放不下完整的ROP攻击链,利用leave; ret指令修改ebp寄存器,把整个栈整体搬迁到BSS段这类空间充足、地址稳定的内存区域,在新栈写入完整ROP。
2.6.2 前提
- 程序内存在leave; ret指令。
- BSS段具备读写权限。
2.6.3 如何攻击
- 第一次溢出:把ebp修改为BSS段空闲地址,拼接leave; ret;指令执行后栈指针esp整体切换到BSS。
- 第二次向新的BSS栈写入完整的ROP攻击链。
- 依托新栈执行地址泄露、调用system拿到shell。
2.6.4 利弊
- 优点:解决缓冲区空间不足的致命限制。
- 缺点:本身只是栈空间转移手段,不能单独拿shell。
评论(0)
暂无评论