Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2974597
  • 博文数量: 523
  • 博客积分: 11908
  • 博客等级: 上将
  • 技术积分: 5475
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-03 15:50
文章分类

全部博文(523)

文章存档

2019年(3)

2013年(4)

2012年(71)

2011年(78)

2010年(57)

2009年(310)

分类: C/C++

2009-06-03 15:53:42

一、溢出的分类
虽然多数溢出的发生是由于程序接收了比预期更多的数据,但事实上,溢出有很多不同的种类。辨别不同种类的溢出,对于开发出能识别特定类型的溢出测试用例是很重要的:
堆栈溢出。堆栈溢出是一种写入数的超出了给堆栈分配的缓冲区大小而发生的溢出。
整型溢出。用来保存某个确定数值范围的特定数据类型或CPU寄存器,当分配给它们的值超出其范围时,就出现整型溢出。在计算机分配的内存大小时发生的整型溢出通常会导致缓冲区溢出。
堆溢出。当数据被写入所分配的堆空间之外时,就发生了堆溢出。
格式化字符串攻击。当写入格式化字符串参数%n中的数据超出了目标缓冲区时,就导致格式化字符串攻击。
 
二、堆栈的工作机制
为了了解堆栈溢出,首先让我们研究一下堆栈的工作机制。堆栈的作用就好比短期内存——它存储计算机处理特定函数时所需的信息。当调用一个函数时,程序首先将调用函数所必需的的各种参数放入堆栈中。
计算机处理器使用一种能够特殊的处理器寄存器——可扩展寄存器指针(Extented Stack Point,ESP)——来跟踪堆栈的当前位置。这一步骤如图 1-1 所示。图中,函数的参数已经被压入堆栈。请注意,这个堆栈的布局中,越低的单元对应的内存地址越高。

实际的函数调用随后将返回地址压入堆栈中。返回地址提醒计算机处理器在这个特定函数运行结束后,到哪里去执行下一段代码,如图 1-2 所示。注意每次数据进入堆栈时堆栈指针式如何递减的。

一旦函数开始运行,它通常会向堆栈压入另外的数据。这是的堆栈如图 1-3 所示,一旦函数被调用,它就开始将局部变量压入堆栈中。返回地址进入堆栈时的过程如图 1-2 所示,堆栈指针的值会随着进入堆栈数据的增加而不断减少。

进入堆栈的一下诶数据可能包含攻击者能进行潜在溢出攻击的一个缓冲区。正常的输入测试及用法如图 1-4 所示,当向函数的下一个局部变量(及一个缓冲区)写入正常的输入数据是,输入数据通常会被顺序复制到该缓冲区中。
 

   一旦函数运行结束,局部变量将会西欧能够堆栈中移走(或弹出,即pop off)。(注意函数中包含的值通常仍然保留在内存中。)
重要提示:看图 1--4中输入数据,假设这些数据包含攻击者能造成潜在溢出的一个缓冲,那么在这种情况下,堆栈中的其他数据也会被覆盖,如图1-5所示。

   计算机处理器接着运行返回地指出的代码,以便调用者能从离开的地方回复运行。然而,在一处状态下会发生什么事呢?如果输入的长度大于堆栈缓冲提供的空间大小,而函数让人进行了数据复制,那么其他的便利即返回地址会被覆盖。如图1--5就是一个衣橱状况下堆栈的编写形式。
   当写入的数据超出了分配的空间时,他会覆盖包括返回地址在内的其他堆栈变量。为了利益这一缺陷,攻击者针对返回地址在做些什么呢?请看图1-6.如果攻击者能控制长数据,那么他就可以精心构造一个包含堆栈返回地址或其他数据变量的长数据,从而达到控制机器目的。
 
 
三、堆栈溢出
堆栈溢出原理
  堆栈 堆栈是一个在计算机科学中经常使用的抽象数据类型。堆栈中的物体具有一个特性: 最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先处(LIFO)队列. 堆栈中定义了一些操作. 两个最重要的是PUSH和POP。 PUSH操作在堆栈的顶部加入一 个元素。POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一。
  为什么使用堆栈?
  现代计算机被设计成能够理解人们头脑中的高级语言。 在使用高级语言构造程序时 最重要的技术是过程(procedure)和函数(function)。 从这一点来看, 一个过程调用可 以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时, 函数把控制权返回给调用之后的语句或指令。 这种高级抽象实现起来要靠堆栈的帮助。 堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返 回值也要用到堆栈。
  堆栈区域
  堆栈是一块保存数据的连续内存。 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部。 堆栈的底部在一个固定的地址。 堆栈的大小在运行时由内核动态地调整。 CPU实现指令 PUSH和POP, 向堆栈中添加元素和从中移去元素。 堆栈由逻辑堆栈帧组成。 当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑 堆栈帧被从栈中弹出。 堆栈帧包括函数的参数, 函数地局部变量, 以及恢复前一个堆栈 帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值。 堆栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现。 在我 们的例子中, 堆栈是向下增长的。 这是很多计算机的实现方式, 包括Intel, Motorola, SPARC和MIPS处理器。 堆栈指针(SP)也是依赖于具体实现的。 它可以指向堆栈的最后地址, 或者指向堆栈之后的下一个空闲可用地址。 在我们的讨论当中, SP指向堆栈的最后地址。 除了堆栈指针(SP指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定 地址的指针叫做帧指针(FP)。 有些文章把它叫做局部基指针(LB-local base pointer)。 从理论上来说, 局部变量可以用SP加偏移量来引用。 然而, 当有字被压栈和出栈后, 这 些偏移量就变了。 尽管在某些情况下编译器能够跟踪栈中的字操作, 由此可以修正偏移 量, 但是在某些情况下不能。 而且在所有情况下, 要引入可观的管理开销。 而且在有些 机器上, 比如Intel处理器, 由SP加偏移量访问一个变量需要多条指令才能实现。 因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用, 因为它们到FP的距离不会受到PUSH和POP操作的影响。 在Intel CPU中, BP(EBP)用于这 个目的。 在Motorola CPU中, 除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP。 考虑到我们堆栈的增长方向, 从FP的位置开始计算, 函数参数的偏移量是正值, 而局部 变量的偏移量是负值。 当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以 恢复)。 然后它把SP复制到FP, 创建新的FP, 把SP向前移动为局部变量保留空间。 这称为 例程的序幕(prolog)工作。 当例程退出时, 堆栈必须被清除干净, 这称为例程的收尾 (epilog)工作。 Intel的ENTER和LEAVE指令, Motorola的LINK和UNLINK指令, 都可以用于 有效地序幕和收尾工作。
  堆栈溢出
  堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据。 或者解释为 在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.
阅读(2191) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

guoqixin2009-06-26 09:52:12

good