Buffer overflows 缓冲区溢出

}  Buffer overflows 缓冲区溢出

}  软件安全最大的威胁----缓冲区溢出

}  缓冲区溢出导致的问题已经数十年了,最著名的例子是1988年的网络蠕虫

}  1999年CERT/CC公示显示,所有严重的安全漏洞有50%是缓冲区溢出导致的,数据显示缓冲区溢出所占比重逐年增加而不是逐年减少。

}  缓冲区溢出问题的现状

}  CERT/CC公告显示在过去的11年间安全漏洞的数量,同时也显示了直接由缓冲区溢出而导致的安全漏洞的数量。

}  数据显示,缓冲区溢出导致的问题不但没好转,而且还变得越来越普遍。

}                      安全问题(CERT)


}              缓冲区溢出所占的比例





}  糟糕的编程方式

}  缓冲区溢出的根本原因是C和C++本身是不安全的编程语言,没有对数组和指针引用边界检测。

}  C语言的标准库有许多的不安全的字符串操作函数:

}  Strcpy()

}  Strcat()

}  Sprintf()

}  Gets()

}  Scanf()

}  对于编写安全攸关代码的程序员来说,学习缓冲区溢出是势在必行的,最好的防卫措施是进行良好的培训。

}  什么是缓冲区溢出

}  大多数的实际应用程序会在内存单元来存储信息,C语言允许程序员在运行时以两种方式开辟内存:堆(heap)和栈(stack),堆上存储数据都是使用”malloc()”或者”new()”来完成,栈上存放的数据通常包括非静态的局部变量和传值的参数。大多数其他数据都存储在全局静态区域里。

}  缓冲区:为大量的连续的同类型数据分配一段存储区域

}  缓冲区溢出:程序员写的数据超过了缓冲区的边界,将会覆盖邻近的区域

}  超过缓冲区边界读写操作会导致多种不同行为:

}  1、程序以怪异方式运行

}  2、程序不能正常运行结束

}  3、程序在执行中与正常方式没有显著区别

}  缓冲区溢出的副作用取决于4个重要的要素:

}  1)写了多少超过缓冲区边界的数据

}  2)当缓冲区写满并溢出时,什么数据被覆盖了

}  3)程序是否尝试读取缓冲区溢出时边界外被覆盖的数据

}  4)什么数据最终替换了被覆盖的内存

}  缓冲区溢出的重要的一点在于:当缓冲区溢出发生时,分配在该缓冲区附近的任何数据都有可能被修改

}  一个程序包含缓冲区溢出,却根本不表现出任何副作用,因此,标准的软件安全测试很难发现这种缓冲区溢出。

}  为什么缓冲区溢出是一个安全问题

}  通过缓冲溢出造成安全问题的方式是堆栈溢出攻击,以特定的编程错误为攻击目标:不小心的使用了程序运行时分配在堆栈中的数据缓冲区。

}  攻击者通过堆栈溢出利用缓冲区溢出这一弱点来执行恶意代码,覆写堆栈将攻击权转移到攻击代码中。

}  通常攻击者利用缓冲区溢出是为了获得机器上的交互会话(shell)

}  程序正义高权限级别运行,,那么攻击者在交互式会话中也将得到这种权限。最成功的缓冲区溢出是那些获取超级用户或者权限的shell堆栈溢出攻击。

}  成功的缓冲区溢出攻击都是以实现运行权限提升为最终目的

}  7.3缓冲区溢出的防御

}  防御性编程是保护你的代码免受缓冲区攻击的重要方法,标准C语言库中,有许多的调用是极易发生缓冲区溢出,使用中应该注意避免。不做任何参数检查的字符串操作是造成缓冲区溢出的的最大根源

}  永远不要使用gets()函数,gets()函数从标准输入中读取用户输入的文本,直到文件结尾才会停止读取,从不做边界检查,造成缓冲区溢出是可能的。

}  fgets()功能与gets()相同,代替gets(),它有一个用于限制接受字符数量的参数,提供了一种避免缓冲溢出机制

}  7.4主要的陷阱

}  有许多标准函数存在安全隐患,这些函数需要给它们传入任意的输入作为参数,这可能导致溢出。

}  Strcpy()函数是把元字符串复制到缓冲区,当并没有具体说明要复制的字符数,复制的字符个数直接取决于源字符串中字符数,如果你知道目标缓冲区的大小,那么就可以直接添加明确的检查:

}  不安全的函数列举:

}  Strcpy()

}  Strcat()

}  Sprintf()

}  Gets()

}  Scanf()

}  Fscanf()

}  Vfscanf()

}  Vsprintf()

}  Vscanf()

}  Vsscanf()

}  Streadd()

}  Strecpy()

}  Strtrns()

}  Strcpy和strncpy的比较

}  当src比dst大时,并不会抛出错误,只是在目标缓冲区的空间停止复制, strncpy会返回-1.

}  当src比dst大时,将留出空间以便在dst字符串的最后面放一个null字符

}  当src小于目标缓冲区或者一样大时,strncpy()函数不会以null字符来结束。

}  sprintf()和vsprintf()

}  函数sprintf()和vsprintf()设置文本格式的,并将文本保存到缓冲区中,它们可以来模仿strcpy()的功能,意味着sprintf()和vsprintf()与使用strcpy()函数一样会造成缓冲区溢出。

}  文件系统会将文件名的长度限制在一定数量范围内

}  Scanf()函数

}  Scanf()函数家族都容易导致目标缓冲区溢出(scanf(),sscanf(),fscanf()和vfscanf()).

}  读入的字符超过缓冲区空间就会发生缓冲区溢出,可以用下面的代码来避免。

}  %和s之间的255明确说明最多将255个字符存储到可变缓冲变量buf中。超出的部分将不会被复制。

}  7.5内部缓冲区溢出

}  Realpath()函数接受包含相对路径的字符串,并通过绝对路径的方式将它转换成指向同一文件的字符串,该函数有两个参数,第一是将被转换的字符串,第二是存储结果的缓冲区。

}  syslog(),getopt(),getpass()等函数,都可能产生内部静态缓冲区溢出问题。

}  如果不得不使用这些函数,最佳解决方案是设置传递给这些函数的输入长度的阀值。

}  7.6更多的输入溢出

}  边界检查的代价是效率,c和c++自身不会自动做边界检查,这实在是糟糕的,一般来讲,C语言大多数情况下注重的是效率,高效率的代价是,C语言程序员必须十分警觉,并且有极强的安全意识来防止程序出现问题,即使这样代码不出问题是很难做到的。

}  自变量检查不会严重影响程序的效率,所以,应该总是进行边界检查,在将数据复制到自己的缓冲区前,检查数据的长度,同样检查以确保不要将过大的数据传递给另一个库,因为我们不能相信其他人的代码。

}  7.7其他风险

}  即使是系统调用安全的版本,也不是完全安全的,(比如:strcpy()安全的strncpy()函数),即使“安全”的调用也会留下未终止的字符串。

}  如果不留意缓冲区大小,许多函数会出毛病,避免系统调用,getenv(),使用getenv()最大的问题:永远不能假定特定环境变量满足任何特定长度。环境变量永远值得考虑。

}  特别需要注意的是第三方压缩打包软件,不要对他人开发的软做乐观假设,因为我们没有仔细检查每个平台上每个常见库,我们不能保证这个调用不出问题。即使检查了,我们还是应持怀疑的态度。

}  7.8帮助工具

}  栈破坏是最严重的缓冲区溢出攻击,尤其当被破坏的栈运行在特权模式下,一个很好的解决方案是使用非执行栈,很多操作系统都有非执行栈的补丁,包括Linux和Solaris,在那些同时有栈溢出和堆溢出的程序中,非执行栈会轻松击败,栈溢出会产生杠杆效应,是的程序跳转到正在执行的放在堆中的代码处,被执行的代码放在堆中,非执行栈会破坏那些依赖于它们行为的程序。

}  还有一种方法是使用类型安全语言,比如Java。

}  针对C程序设计一种能进行数组边界检查的编辑器,有一种对gcc进行扩展名为GNUC的编译器,这种编译器拥有可以阻止所有缓冲区溢出的优点,包括堆栈溢出,缺点是:对于一些密集指针且运行速度有较高要求的程序。

}  栈保护工具Stackguard实现了一种比常规边界检查更有效的技术,它把一些数据放在被分配的栈的尾部,而后在缓冲区溢出之前检查那些数据是否还在那里。这种模式称为金丝雀。

}  Stackguard并没有常规的边界检查那么安全,但他还是有用的。

}  同常规边界测试相比,Stackguard主要缺点是不能阻止堆溢出,就作用而言, Stackguard类似的工具包括完整性检查包,如rational公司Purify,这种工具甚至可以防止堆溢出,但是性能开销关系,基本上没人再生成代码时使用Purify。

}  另一种技术就是安全版本来替代那些易出错的调用,该技术被libsafe所采用,该方法是只适用于软件安全源扫描数据库。

}  需要小心使用或者避免使用的编程结构

}  7.9堆破坏和栈破坏

}  我们并不关心如何将虚拟地址空间映射底层架构的确切机制,真正关心的是理论上被允许访问的占用大块连续空间的进程,在某些情况下这部分内存会被人滥用。

}  内存常被划分为一些不同的区域:

}  1.程序参数和程序环境。

}  2.程序栈。栈的大小会随着程序的执行不断变化,通常会变小,趋近于堆。

}  3.堆。随着程序的执行会增长,趋近于栈。

}  4.块存储段(BBS)。包含全局有效的数据。

}  5.数据段包含已初始化的全局有效数据(全局变量)。

}  6.文本段包含只读程序代码。

}  BBS、数据段和文本段构成了静态内存,意味着程序运行之前这些段的大小都是固定的。

}  跟静态内存相比,堆栈都是动态的,这意味着所占用的内存会随着程序的执行而变化,运行时内存分配包括两类:栈分配和堆分配。在C语言中通过malloc()来访问堆在c++中操作new完成。

}  在C语言中,堆和栈都有可能发生数据溢出。数据溢出会覆盖栈帧的返回地址,如果攻击者将攻击代码放到分配到栈的变量中,而后在构造一个新的返回地址以跳转到他们的代码,这样攻击者可以做任何想做的事,攻击者通常想去获得一个交互的shell。

}  7.10堆溢出

}  理论上实现堆溢出很容易,但由于多种原因,攻击者造成堆溢出是一件很困难的事,首先攻击者需要找到安全攸关的变量,这个过程就很困难了,尤其是潜在的攻击者没有源代码的时候,其次即使安全攸关的得关键变量确定之后,攻击者还需要制造一个缓冲区,来覆盖目标变量,否则没有办法制造溢出,并进入到变量地址空间,操作系统或者库版本的改变会使堆溢出更加困难。

}  下面举例说明:

}  从程序片段下手

}  7.11栈溢出

}  堆溢出主要的问题是很难找到安全攸关的区域,栈溢出却没有这么麻烦,栈上总有可被利用的安全攸关的区域覆盖—返回地址

}  栈溢出过程:

}  1.在栈结构中找出一块分配在栈中会溢出的缓冲区,并且这个缓冲区允许覆盖它的返回地址。

}  2.在我们所攻击的函数返回时,在跳转内存空间上放置一些恶意代码。

}  3.改写栈里的返回地址,使之跳转到恶意代码。

}  首先找出可以被利用溢出的缓冲区,在感兴趣的空间找出参数或者本地变量与返回地址之间的关系。

}  7.11.1破译堆栈

}  通过这些工作,我们现在对栈帧的内容应该有了充分的了解,栈帧的内容:

}  低地址

}           局部变量

}           原始基址

}           返回地址

}           函数的参数

}   高地址

}  堆栈向着内存地址为0的方向增长,并且以前的栈帧在函数的下面,现在知道,如果一个局部变量溢出,我们就可以重写当前执行函数的返回函数。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值