缓冲区溢出:防止缓冲区溢出杜绝如今最常见的程序缺陷




  什么是缓冲区溢出?
  缓冲区以前可能被定义为“包含相同数据类型例子个连续计算机内存块”在 C 和 C缓冲区通常是使用和诸如 malloc 这样内存分配例程来实现极其常见缓冲区种类是简单溢出 是指数据被添加到分配给该缓冲区内存块的外
  
  如果攻击者能够导致缓冲区溢出那么它就能控制其他值虽然存在许多利用缓冲区溢出思路方法不过最常见思路方法还是“stack-smashing”攻击Elias Levy (又名为 Aleph One)篇经典文章“Smashing the Stack for Fun and Profit”解释了 stack-smashing 攻击Elias Levy 是 Bugtraq 邮件列表(请参阅 参考资料 以获得相关链接)前任主持人
  
  为了理解 stack-smashing 攻击(或其他任何缓冲区攻击)是如何进行您需要了解些有关计算机在机器语言级实际如何工作知识在类 UNIX 系统上每个进程都可以划分为 3个主要区域:文本、数据和堆栈文本区域 包括代码和只读数据通常不能对它执行写入操作数据区域 同时包括静态分配内存(比如全局和静态数据)和动态分配内存(通常称为 堆)堆栈区域 用于允许/思路方法;它用于记录完成的后返回位置中使用本地变量传递参数以及从返回值每当就会使用个新 堆栈帧 来支持该了解这些的后让我们来考察个简单
  
  清单 1. 个简单
  
  void function1( a, b, c) {
    char buffer1[5];
    gets(buffer1); /* DON'T DO THIS */
  }
  
  void {
   function(1,2,3);
  }
  
  假设使用 gcc 来编译清单 1 中简单在 X86 上 Linux 中运行并且紧跟在对 gets的后中止此时内存内容看起来像什么样子呢?答案是它看起来类似图 1其中展示了从左边低位地址到右边高位地址排序内存布局
  
  内存底部    内存顶部 
   buffer1 sfp ret a b c 
  <--- 增长 --- [ ] [ ] [ ] [ ] [ ] [ ] ... 
  堆栈顶部    堆栈底部 
  
  许多计算机处理器包括所有 x86 处理器都支持从高位地址向低位地址“倒”增长堆栈因此每当更多数据将被添加到左边(低位地址)直至系统堆栈空间耗尽在这个例子中 function1它将 c 值压入堆栈然后压入 b 最后压入 a 的后它压入 (ret)值这个值在 function1完成时告诉 function1 返回到 何处它还把所谓“已保存帧指针(saved frame poersfp)”记录到堆栈上;这并不是必须保存内容此处我们不需要理解它在任何情况下function1在启动以后它会为 buffer1预留空间这在图 1 中显示为具有个低地址位置
  
  现在假设攻击者发送了超过 buffer1 所能处理数据接下来会发生什么情况呢?当然C 和 C 员不会自动检查这个问题因此除非员明确地阻止它否则下个值将进入内存中“下个”位置那意味着攻击者能够改写 sfp(即已保存帧指针)然后改写 ret(返回地址)的后当 function1 完成时它将“返回”—— 不过不是返回到 而是返回到攻击者想要运行任何代码
  
  通常攻击者会使用它想要运行恶意代码来使缓冲区溢出然后攻击者会更改返回值以指向它们已发送恶意代码这意味着攻击者本质上能够在个操作中完成整个攻击!Aleph On 文章(请参阅 参考资料)详细介绍了这样攻击代码是如何创建例如个 ASCII 0 压入缓冲区通常是很困难而该文介绍了攻击者般如何能够解决这个问题
  
  除了 smashing-stack 和更改返回地址外还存在利用缓冲区溢出缺陷其他途径和改写返回地址区别攻击者可以 smashing-stack(使堆栈上缓冲区溢出)然后改写局部变量以利用缓冲区溢出缺陷缓冲区根本就不必在堆栈上 —— 它可以是堆中动态分配内存(也称为“malloc”或“”区域)或者在某些静态分配内存中(比如“global”或“”内存)基本上如果攻击者能够溢出缓冲区边界麻烦或许就会找上你了 然而最危险缓冲区溢出攻击就是 stack-smashing 攻击如果对攻击者很脆弱攻击者获得整个机器控制权就特别容易
  
  为什么缓冲区溢出如此常见?
  在几乎所有计算机语言中不管是新语言还是旧语言使缓冲区溢出任何尝试通常都会被该语言本身自动检测并阻止(比如通过引发个异常或根据需要给缓冲区添加更多空间)但是有两种语言不是这样:C 和 C 语言C 和 C 语言通常只是让额外数据乱写到其余内存任何位置而这种情况可能被利用从而导致恐怖结果更糟糕用 C 和 C 编写正确代码来始终如地处理缓冲区溢出则更为困难;很容易就会意外地导致缓冲区溢出除了 C 和 C 使用得 非常 广泛外上述这些可能都是不相关事实;例如Red Hat Linux 7.1 中 86% 代码行都是用 C 或 C 编写因此大量代码对这个问题都是脆弱实现语言无法保护代码避免这个问题
  
  在 C 和 C 语言本身中这个问题是不容易解决该问题基于 C 语言根本设计决定(特别是 C 语言中指针和处理方式)由于 C 是最兼容 C 语言超集它也具有相同问题存在些能防止这个问题 C/C 兼容版本但是它们存在极其严重性能问题而且旦改变 C 语言来防止这个问题它就不再是 C 语言了许多语言(比如 和 )在语法上类似 C但它们实际上是区别语言将现有 C 或 C 改为使用那些语言是项艰巨任务
  
  然而其他语言用户也不应该沾沾自喜有些语言存在允许缓冲区溢出发生“转义”子句Ada 般会检测和防止缓冲区溢出(即针对这样尝试引发个异常)但是区别可能会禁用这个特性 般会检测和防止缓冲区溢出但是它允许员将某些例程定义为“不而这样代码 可能 会导致缓冲区溢出因此如果您使用那些转义机制就需要使用 C/C 所必须使用相同种类保护机制许多语言都是用 C 语言来实现(至少部分是用 C 语言来实现 )并且用任何语言编写所有本质上都依赖用 C 或 C 编写因此所有都会继承那些问题所以了解这些问题是很重要
  
  导致缓冲区溢出常见 C 和 C
  从根本上讲将数据读入或复制到缓冲区中任何时候它需要在复制 的前 检查是否有足够空间能够容易看出来异常就不可能会发生 —— 但是通常会随时间而变更从而使得不可能成为可能
  
  遗憾C 和 C 附带大量危险(或普遍使用库)甚至连这点(指检查空间)也无法做到对这些任何使用都是个警告信号除非慎重地使用它们否则它们就会成为缺陷您不需要记住这些列表;我真正目是介绍说明这个问题是多么普遍这些包括 strcpy(3)、strcat(3)、sprf(3)(及其同类 vsprf(3))和 gets(3)scanf集(scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3) 和 vfscanf(3))可能会导致问题使用个没有定义最大长度格式是很容易(当读取不受信任输入时使用格式“%s”总是)
  
  其他危险包括 realpath(3)、getopt(3)、getpass(3)、streadd(3)、strecpy(3) 和 strtrns(3) 从理论上讲snprf应该是相对 —— 在现代 GNU/Linux 系统中确是这样但是非常老 UNIX 和 Linux 系统没有实现 snprf 所应该实现保护机制
  
  Microsoft 库中还有在相应平台上导致同类问题其他(这些包括 wcscpy、_tcscpy、_mbscpy、wcscat、_tcscat、_mbscat 和 CopyMemory)注意如果使用 Microsoft MultiByteToWideChar 还存在个常见危险 —— 该需要个最大尺寸作为数目但是员经常将该尺寸以字节计(更普遍需要)结果导致缓冲区溢出缺陷
  
  另个问题是 C 和 C 对整数具有非常弱类型检查般不会检测操作这些整数问题由于它们要求员手工做所有问题检测工作因此以某种可被利用方式不正确地操作那些整数是很容易特别是当您需要跟踪缓冲区长度或读取某个内容长度时通常就是这种情况但是如果使用个有符号值来这个长度值会发生什么情况呢 —— 攻击者会使它“成为负值”然后把该数据解释为个实际上很大正值吗?当数字值在区别尺寸的间转换时攻击者会利用这个操作吗?数值溢出可被利用吗? 有时处理整数方式会导致缺陷
  
  防止缓冲区溢出新技术
  当然要让员 不 犯常见是很难而让(以及员)改为使用另种语言通常更为困难那么为何不让底层系统自动保护避免这些问题呢?最起码避免 stack-smashing 攻击是件好事 stack-smashing 攻击是特别容易做到
  
  般来说更改底层系统以避免常见安全问题是个极好想法我们在本文后面也会遇到这个主题事实证明存在许多可用防御措施些最受欢迎措施可分组为以下类别:
  
  基于探测思路方法(canary)防御这包括 StackGuard(由 Immunix 所使用)、ProPolice(由 OpenBSD 所使用)和 Microsoft /GS 选项
  
  非执行堆栈防御这包括 Solar D
Tags:  什么是缓冲区溢出 发现缓冲区溢出攻击 缓冲区溢出攻击 缓冲区溢出

延伸阅读

最新评论

发表评论