缓冲区溢出

  • 知识背景
  • 栈溢出及防护
  • 堆溢出及防护

一、知识背景

关于栈溢出的知识背景

1. 典型操作系统的内存布局:

典型操作系统的内存布局

分段的意义:
缓存命中率高、访问控制

2. 进程的内存布局:

进程的内存布局

3. 堆栈的区别:

地址增长方向

heap - 堆 地址从小到大
stack - 栈 地址从大到小

支持及作用

栈时具有硬件直接支持的,栈存储数据和控制流信息。
堆是由操作系统的库函数予以支持的,堆存储数据。

4. ESP与EIP:

esp - 栈指针寄存器

esp指向栈顶(存储栈顶的地址)
push则esp减
pop则esp增

eip - 指令指针寄存器

eip指向.test中存放的代码指令,指向的指令为即将被执行的指令。

5. 栈的工作原理:

栈的作用:为函数的调用和执行维护存储结构。

函数调用时:存储数据
函数返回时:恢复数据

栈帧:调用函数将在栈上创建一个结构化空间,称为栈帧。

锚点:函数被调用时的栈指针位置。可用于对函数的局部数据寻址、返回到调用者。

call - return的实现:

1)存储参数
push arg2
push arg1
2)存储函数f返回后需要执行的指令地址RA(push RA)
call f
3)存储返回位置
把ebp原来存储的值压栈,将esp此时的位置X存入ebp。
push %ebp
mov %esp %ebp
4)执行函数f的指令
X可帮助函数f的局部变量寻址。
5)返回
将ebp存储的值(即返回位置)告诉esp,并回复ebp原来的数据。此外,通过此时栈顶的数据RA,定位到下一条需要执行的指令。
mov %ebp %esp
pop %ebp
retn

6. 缓冲区:

copy-on-write:

资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。

很多时候,函数执行中需要局部分配较大的线性内存空间。


函数执行分配空间
增长方向

栈增值方向为从高到低,缓冲区的增长从低到高。

要求

缓冲区应该足够大,写入数据之前应该先检查是否仍有剩余空间可用。

关于堆溢出的知识背景

1. 堆的创建和维护:

通过malloc实现


malloc

2. 堆的工作原理:

malloc创建的区域首先是堆的场地(Arena),进程申请堆空间时,malloc从场地中划分对应大小的区域供其使用。同一场地内可能存在多个堆段。

注意:

Main Arena没有heap_info
有多个heap的Thread Arena只有一个malloc_state

场地的数量限制 - 与cpu内核数量有关:

32位系统:#_of_Arena = 2 * #_of_cores + 1
64位系统:#_of_Arena = 8 * #_of_cores + 1

堆块的形态:

N表示此块是否属于main arean; M表示此块是否是mmap()创建的; P表示前块是否正在使用。


堆块形态
链表结构
显式链表结构bin:

bin是记录free chunk的链表数据结构,在glibc中可分为fastbinsY和bins。
fastbin主要用于高效的分配和回收比较小的内存块,采用LIFO形式的单链表结构。


bin结构
顶块top chunk:

不属于任何bin。
当前所有空闲块(无论哪种bin)全都尺寸不合时,由顶块应急。
顶块比请求尺寸大——分割供给使用,剩余部分为新顶块。
顶块比请求尺寸小——全堆无适合块,扩展堆/分配新堆。

二、栈溢出及防护

1. 主要原因:忽略了边界检查

-> 开发者认为被操作对象合法
-> 二进制代码底层细节复杂,容易使人忽略问题的存在
-> 一些重要的遗产代码在最初的设计中存在此类缺陷

2. 现有的栈溢出防护技术

数据执行保护(W⊕X,NX-bit):

机制:
对内存页面增加一个标识比特,使它要么可改写,要么可执行
缺陷:
只要不试图执行数据区内容,则既不能阻止溢出,又不能发现溢出。

栈帧上的“金丝雀”:

机制:
栈上设置检查点,检查点数值异常,则代表栈溢出发生。
缺陷:
开销大,对遗产代码不起作用。

影子栈:

机制:
构造一个额外的动态数据区域,称为影子站。当call发生时,在返回地址入栈的同时,也将其写入影子堆栈。每当return发生时,在弹出并转向返回地址前,将其与影子堆栈的栈顶比较。
缺陷:
开销大,无法保护遗产代码。

分离的控制/数据栈:

机制:
修改操作系统的内存布局,分配两个栈空间,分别用于控制参数和数据的存储。
缺陷:
无法保护遗产代码,不适用于完全由汇编语言构成的函数。

三、堆溢出及防护

1. 与栈溢出的区别

溢出方向等于堆增长方向。首先破坏(虚拟地址意义上的)下一个堆块的构造。

2. linux上的典型堆溢出利用方式

  • 攻击fastbin
  • 攻击unlink
攻击fastbin:

基于fastbin块LIFO的特点,我们可以先申请,然后释放,再申请就可以得到原来地址的块。
但是这不能满足我们的需求,我们需要将堆分配在可控地址。
我们可以通过堆溢出更改已经申请块的fd,使其指向我们可控的地址,并且在可控地址上伪造假的fastbin结构。
释放,再申请两次,第2次就可以得到分配在可控地址上的块。(覆盖fd)


攻击fastbin
攻击unlink:

unlink攻击技术就是利用”glibc malloc”的内存回收机制,将second chunk给unlink掉,并且,在unlink的过程中使用shellcode地址覆盖掉free函数(或其他函数也行)的GOT表项。这样当程序后续调用free函数的时候(如上面代码[5]),就转而执行shellcode了。显然,核心就是理解glibc malloc的free机制。

一旦涉及到free内存,那么就意味着有新的chunk由allocated状态变成了free状态,此时glibc malloc就需要进行合并操作——向前以及(或)向后合并。这里所谓向前向后的概念如下:将previous free chunk合并到当前free chunk,叫做向后合并;将后面的free chunk合并到当前free chunk,叫做向前合并。


攻击思路
攻击思路

3. windows堆溢出攻击的主要形式

  • 利用向量化异常处理(VEH)
  • 利用系统默认异常处理函数(UEF)
  • Heap spray
  • Bitmap Flipping攻击
  • Bitmap XOR攻击
  • Heap Cache攻击

4. 堆溢出防御

堆依靠系统库实现其维护,因此,堆保护 = 系统库升级。

针对unlink的保护:

Double Free检测
next size非法检测
双链表冲突检测

你可能感兴趣的:(缓冲区溢出)