缓冲区溢出攻击
- 缓冲区溢出概述
- 基础概念
- 缓冲区溢出根源
- 缓冲区溢出危害性&普遍性
- 缓冲区溢出攻击原理
- 内存分配模式
- 缓冲区溢出攻击
- 缓冲区溢出攻击原理
- 缓冲区溢出攻击分类
- 堆栈溢出
- 堆栈相关知识
- 攻击原理
- 堆溢出攻击
- 堆简介
- 堆溢出
- DWORD SHOOT
- BSS段溢出
- 缓冲区溢出攻击防御措施
- 防御策略
- 缓冲区溢出攻击防御技术
缓冲区溢出概述
基础概念
- 缓冲区或缓存 (Buffer):用户在程序运行时在计算机中申请得的一段连续的内存,保存给定类型的数据.
- 缓冲区溢出(Buffer Overflow):计算机程序向缓冲区内填充的数据位数超过缓冲区本身的容量,溢出的数据覆盖在合法数据上。
- 缓冲区溢出攻击:向程序的缓冲区写入超出其长度的内容,造成缓冲区的溢出,从而破坏程序的正常执行流程,使程序转而执行其他的指令,以达到攻击的目的。
缓冲区溢出根源
- 溢出的根源在于编程:如果缓冲区被写满,而程序没有去检查缓冲区边界,也没有停止接收数据,就会发生缓冲区溢出。
- Unix和 Windows系统由于要实现更好的性能和功能,往往在数据段中动态地放入可执行的代码。
- C/C++语言问题:对数组下标访问边界不做检查或者少做检查。
- 程序员的编程习惯:忽略对输入数据进行严格的边界检查。
缓冲区溢出危害性&普遍性
- 危害性:缓冲区溢出漏洞可以使一个匿名的Internet用户有机会获得一台主机的部分或全部的控制权.
- 普遍性:
- 缓冲区溢出攻击占远程网络攻击的绝大多数,操作系统中超过50%的安全漏洞都是由内存溢出引起的。
- 任何平台、任何程序都可能存在缓冲区溢出的漏洞
缓冲区溢出攻击原理
内存分配模式
- 内存分配模式分为三种:静态分配、堆栈分配、堆分配
- 静态分配:在进程创建时由系统一次性分配的整块静态内存,这块空间在进程运行期间保持不变。
- 静态分配内容包括:正文( TEXT)段:指令、数据( DATA)段:初始化的全局静态数据、BSS段:未初始化的全局数据、栈空间
- 堆栈(Stack)分配:调用程序的地址信息,函数参数的内存分配。
- 整个堆栈空间已在进程创建时分配好。进程刚启动时,堆栈空间是空的,里面无实体。
- 在进程运行期间,对实体的堆栈分配是进程自行生成(压栈)和释放(弹出)实体,系统并不参与。
- 只要压入的实体的总长度不超过堆栈空间大小,堆栈分配就与系统无关。若超过,就会引发堆栈溢出错误。
- 堆(Heap)分配:当进程需要生成实体时,向系统申请分配空间;不再需要该实体时,可以向系统申请回收这块空间。
- 堆分配使用特定的函数:malloc();new()
- 堆分配的空间利用率最高
- 三种内存分配模式比较
静态分配 | 栈分配 | 堆分配 | |
---|---|---|---|
空间的生成 | 进程创建时 | 进程创建时 | 即用即分配 |
实体生成时间 | 进程创建时 | 进程运行时 | 进程允许时 |
实体生成者 | 操作系统 | 进程 | 进程申请/系统实施 |
生命期 | 永久 | 临时 | 完全可控 |
访问方式 | 标识 | 标识 | 指针 |
- 进程内存布局
- 进程内存布局
缓冲区溢出攻击
- 缓冲区溢出的目的在于扰乱具有某些特权的运行程序的执行流程,让攻击者取得程序的控制权。
- 两个步骤:
- 在程序的地址空间(堆栈、堆、BSS段等)里植 入攻击代码,或植入攻击代码所需的攻击参数(如果攻击代码已存在于目标程序中)。
- 改变程序的执行流程,转去执行攻击代码
缓冲区溢出攻击原理
- 代码注入攻击:攻击者向缓冲区写入的数据包含攻击代码(可执行的二进制代码,通常称为“shellcode”),当发生缓冲区溢出时,溢出的数据覆盖掉一个可执行程序的入口地址(如函数的返回地址,函数指针变量等),使得该地址指向shellcode,从而当程序试图通过该入口地址执行代码时,就会执行攻击者的shellcode。
- ROP 攻击:攻击代码已经在被攻击的程序中(通常是一些系统函数,如system(), exec()等),攻击者为攻击代码传递它所需要的参数,然后用一个系统函数的地址覆盖可执行代码的入口地址,使程序用预设的参数调用系统函数。比如用“cmd”作为参数调用system()函数,也称为ret2libc(Return-to-libc)
缓冲区溢出攻击分类
- 堆栈溢出
- 堆溢出
- BSS段溢出
- 格式化字符串溢出攻击
堆栈溢出
堆栈相关知识
- SP(堆栈指针):指向堆栈的顶部;堆栈的增长方向:向下增长(向内存低地址)
- 堆栈布局
- 函数调用过程
- 假设有一个程序,其函数调用顺序如下:
main() -> func_1() -> func_2() -> func_3()
攻击原理
- 攻击的原理:通过往程序缓冲区写入超过其边界的内容,造成缓冲区溢出,使得程序转而执行攻击者指定的代码,通常是为攻击者打开远程连接的ShellCode。
- 关键点:
- 存在能够被攻击的数据缓存
- 要有被执行的攻击代码
堆溢出攻击
堆简介
- 当需要较大的缓冲区或在写代码时不知道包含在缓冲区中对象的大小,常常要使用堆。
- 堆没有压栈和入栈操作,而是分配和回收内存。
- C语言中使用malloc()和free()函数实现内存的动态分配和回收,C++语言使用new()和delete()函数来实现相同的功能。
- 堆的特点:
- 堆的增长方向:从底到高(与栈相反)
- 堆的分配和释放可以由用户自由控制
- 堆的空间不一定连续
- 堆申请函数返回指向堆内存的指针,后续对于内存的读写等操作需要通过此指针进行
- 不同的系统有着不同的堆管理机制
- 堆表位于堆区的起始位置用来索引堆块,表中包含索引堆块的大小、位置、状态等信息
- 堆表分为两种:空闲双向链表Freelist(空表 128条)和快速单向链表Lookaside(快表最多只有四项)
- 堆块分配可以分为三类:快表分配、普通空表分配和零号空表(free[0]分配)
- 堆块的释放操作包括将堆块状态改为空闲,链入相应的堆表。
- 堆块合并:当堆管理系统发现两个空闲堆块相邻时,就会进行堆块合并操作,包括,将堆块从链表中卸下、合并、调整新堆块块首信息、重新链入空表。
堆溢出
- 堆溢出:指程序向某个堆块中写入的字节数超过堆块本身可使用的字节数,从而导致数据溢出,并覆盖到物理相邻的高地址的下一个堆块。
- 前提条件:程序向堆上写入数据、写入的数据大小没有被良好地控制
- 利用策略:覆盖与其物理相邻的下一个 chunk 的内容、利用堆中的机制(如 unlink 等 )来实现任意地址写入或控制堆块中的内容等效果,从而来控制程序的执行流。
DWORD SHOOT
- DWORD SHOOT:用精心构造的数据去溢出下一个块的块首,改写块首中的前向指针和后向指针,这种能够向内存任意位置写入任意数据的机会叫DWORD SHOO
int remove (ListNode * node)
{
node -> blink -> flink = node -> flink;
node -> flink -> blink = node -> blink;
}
BSS段溢出
- BSS段存放全局和静态的未初始化变量,变量与变量之间是连续存放的,没有保留空间。
- 字符数组即是位于BSS段:
static char buf1[16],buf2[16];
- 如果先向buf2中写入16个字符A,之后再往buf1中写入24个B,由于变量之间是连续存放的,静态字符数组buf1溢出后,就会覆盖其相邻区域字符数组buf2的值。利用这一点,攻击者可以通过改写BSS中的指针或函数指针等方式,改变程序原先的执行流程,使指针跳转到特定的内存地址并执行指定操作
缓冲区溢出攻击防御措施
防御策略
- 系统管理上的防御策略:
- 关闭不需要的特权程序
- 及时给程序漏洞打补丁
- 程序开发中的防御策略:
- 编写正确的代码
- 非执行的缓冲区
- 数组边界检查
- 程序指针完整性检查
- 关闭不需要的特权程序:缓冲区溢出只有在获得更高的特权时才有意义,关闭一些不必要的特权程序就可以降低被攻击的风险。
- 及时给程序漏洞打补丁:及时补上漏洞,无疑极大的增强系统抵抗攻击的能力
- 编写正确的代码: 在所有拷贝数据的地方进行数据长度和有效性的检查,确保目标缓冲区中数据不越界并有效。
- 非执行的缓冲区:通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码。
- 数组边界检查:防止所有的缓冲区溢出的产生和攻击。方法包括:C的数组边界检查;内存存取检查(Purify 工具):使用“目标代码插入”技术来检查所有的内存存取;类型-安全语言。
- 指针完整性检查:阻止由于函数返回地址或函数指针的改变而导致的程序执行流程的改变。
缓冲区溢出攻击防御技术
- 两类防护技术
- 被动防护技术:典型代表有插入canary 值、存储RETADDR 值、指针前后加guardzone 和低脂指针
- 主动防护技术:更换动态链接库、加密指针型数据、随机化内存地址、去堆栈布局可预测性