缓冲区溢出安全编程教与学

2017-07-10 07:32刘浩贺文华彭智朝贺劲松
电脑知识与技术 2017年14期
关键词:堆栈

刘浩+贺文华+彭智朝+贺劲松

摘要:缓冲区溢出是一种非常普遍、非常危险的漏洞,常被黑客和病毒利用,是信息安全的重要隐患之一。因此,在“信息安全理论与技术”课程教学中,缓冲区溢出安全编程的教与学一直受师生们的重视。基于缓冲区溢出的工作原理与攻击技术,通过师生共同探讨,给出了一些关于C语言程序编写过程中防御缓冲区溢出的方法,以提高安全编程能力。

关键词:缓冲区溢出;堆栈;安全编程;Bss;heap

中图分类号:TP309 文献标识码:A 文章编号:1009-3044(2017)14-0102-04

1概述

自从二十世纪末以来,由于其破坏性大与广泛性,缓冲区溢出漏洞得到了信息安全领域学者们的普遍关注。当前,相关研究统计表明,全球每年发生的安全威胁事件以指数增长。由于缓冲区溢出漏洞不受操作系统不同的限制、能作用于不同的应用程序之上,作为网络攻击一种主要形式,缓冲区溢出攻击次数超过了所有网络系统攻击总数的五分之四。

在程序设计中,缓冲区就是应用程序用来保存用户输入数据和代码的临时数据的内存空间。作为一种系统攻击的手段,缓冲区溢出(Buffer Overflow),就是在程序中的缓冲区内写入超出正常长度的内容,使缓冲区产生溢出,破坏程序的堆栈,让程序跳转去执行别的指令,从而达到系统攻击的目标。

正常情况,利用缓冲区溢出漏洞攻击者并不只是想让程序本身崩溃,而是想通过这种攻击来达到提升权限,获得对系统更多的访问和控制权。一般而言,缓冲区溢出本身并不会导致系统安全问题,但如果该溢出能够跳转到以超级权限(如root权限)运行命令的区域,使程序去运行一个shell或执行某些特权代码,那么该程序将以超级用户的权限控制了计算机系统。显然,缓冲区溢出漏洞是计算机系统的重大安全隐患。缓冲区溢出的根本原因就是程序设计时没有考虑用户输入参数与运行边界的相关检查。

当应用程序运行时,其内存中的映像被分为数据段、代码段以及堆栈段三个部分。数据段包括运行文件中的BSS Sec-tion与Data Section,其用于存放程序运行的静态变量与各种数据。代码段就是运行文件中的Text Section(图1),其中包括只读数据与运行代码。一般该段在内存中被标记为只读,所有企图修改该数据的指令都将引发一个Segmentation Violation错误。

具体到C程序中,如图1所示,.stack、.heap、.bss以及.data区都被分配在缓冲区,并且和程序执行流相关的函数返回地址、函数指针等数据结构也会被分配在区域,因此,一旦发生到缓冲区溢出攻击时,控制程序执行流的敏感数据结构则有可能发生改变,将导致正在执行的程序发生转向,从而去执行非法代码。

依据程序数据在内存中增长方式的不同,缓冲区溢出可分堆溢出和栈溢出两种情况。所谓堆溢出,就是数据分配从低地址向高地址方向增长,溢出点可能发生在.bss、.daat、.heap区,基于堆溢出的攻击有覆盖分配在该区域的函数指针等。反之,栈溢出就是数据分配从高地址向低地址方向增长,溢出点发生在.stack区,基于栈溢出的攻击有覆盖函数返回地址等。

在模块化程序设计中,各种函数调用会经常出现,如调用Win32 API、C运行库等。并且编译器几乎都会将这些调用编译为Call语句,执行该指令时,IP被设为调用函数的入口地此,调用后的返回地址会被压入堆栈,而且针对函数调用带有的局部变量与入口参数,这些数据也会被编译器生成为一些指令存入堆栈(也有通过寄存器传递的)。通常称因一个函数调用所导致必需在堆棧中存放的返回地址与相关数据等构成一个堆栈帧(Stack Frame)。

2缓冲区溢出基本原理

缓冲区溢出攻击的基本原理就是向程序中输入超出正常长度的内容,由于越过缓冲区长度界限造成缓冲区的溢出,程序的堆栈会被破坏而出现特殊的问题,使程序运行跳转去执行其它指令。

下面我们举一个缓冲区溢出例子来说明其基本原理:

首先我们看一下未执行strcpy时(已经调用函数funcl)堆栈中的情况(如图2所示)。

该程序是一个典型的缓冲区溢出编码错误。并没有经过边界检查,函数将一个字符串拷贝至另一内存区域。在执行strcpy时,256字节的‘A(ox41)被程序拷入buffer中,但是buffer的长度只有16字节,那么buffer后面的240字节内容将被覆盖掉,这些字节包括RET地址、EBP、large_string地址。字符‘A的十六进制为0×41,因此函数的返回地址被变为了0×41414141,明显超出了程序的地址空间,所以系统将报“Segmentation Vio-lation”错误。这就是所谓的缓冲区溢出。

3缓冲区溢出攻击方式

当非法用户操作程序时,若所进行的操作超出了程序的运行范围,程序所用的数据会被添加到分配给该缓冲区内存块之外,将导致缓冲区溢出,这时候就会出现数据泄漏或侵占其它的数据空间。

1)向缓冲区写人超出正常长度的字符。

如上例通过向缓冲区中写入超出正常长度的字符来产生缓冲区溢出,导致程序崩溃。

2)攻击者可用任意数据覆盖堆栈中变量的内容。

安全漏洞的一个经典例子是基于口令的认证,首先从本地数据库中读取口令并存储在本地变量中,然后用户输入口令,程序比较这两个字符串,从而比较结果为二者相等。

3)覆盖堆栈中保存的寄存器。

通过输入超长的字符从而覆盖指令指针IP,攻击者可以利用函数结尾的RET来执行程序中的任意程序代码。一般而言,不是利用程序本身的代码,而是植入攻击者自己的机器代码(一般称之为Shellcode,即外壳代码)。为此把机器代码写到变量中并复制到堆栈中,把保存的IP地址改变为攻击代码的开始地址。当函数执行完毕返回时,RET从堆栈中获得IP的值并写入CPU的IP寄存器,于是运行攻击代码。

4)覆盖函数指针以执行第三方代码。

攻击者把机器代码Shellcode放在一全局或本地变量或编程环境中,并使函数指针指向这段程序代码。当用函数指针调用函数时,执行的将不是函数代码而是攻击代码。

4安全编程

1)编写正确的代码

前面提到过,解决缓冲区溢出问题的第一步是,人们必须更加小心地进行计算机的编程。程序员只要增加能够处理过长字符串的指令,就能够防止对自己产品的攻击。下面我们共同探讨容易导致缓冲区溢出的系统调用,并给出正确的、安全的使用方法。

①gets(char*s)

本函数的功能是从标准输入来读入数据到静态缓冲区中,有名的bug是Morris Internet Worm在fingerd中开发的,利用此漏洞可通过网络在计算机上执行命令。正确的fgets(char *s,im size,FILE*stream)使用方式是通过严格规定输入数据长度从而安全的读取数据。如本例中通过使用sizeof(Array Buff)等指定数据长度,如12字节,fgets()将读入1~12个字节并在最后加一个NULL字符。

然而,实际编程中程序员容易把整个if语句直接写成了i++的错误编程方式。

如前所述,还有很多函数不进行边界检查,包括scanf(3)、strcpy(3)/strcat(3)、getwd(3)等等,在此不再一一敘述,请同学们自行探索研究。

所谓静态发现技术,就是为了降低程序被攻击的可能性,在程序设计过程中,根据一定的约束规则来发现源码里潜在的漏洞之处,便于程序员发现并改进。显然使用静态发现技术,系统需要维护一个不断更新的与漏洞有关的规则库。静态发现技术常用的工具有BOON、Flawfinder、ITS4、RAST等。

2)使用Libsafe

朗讯技术公司的Arash Baratloo、Timothy Tsai和Navjot Singh等针对这些易受缓冲区溢出攻击的Libc函数进行二次开发,开发出了封装这些库函数的动态载入库Libsafe[4],来解析那些不安全的Libc库函数,并用Libsafe中实现的安全函数替代,让Libsafe实现边界检查,以达到确保任一缓冲区溢出都被控制在堆栈帧之内,从而保证了代码的安全,解决了缓冲区溢出攻击问题。

3)不可执行的缓冲区技术

根据缓冲区溢出的基本原理,所谓不可执行的缓冲区技术,就是使可能被攻击程序的数据段地址空间不可执行,植入到被攻击程序输入缓冲区中的代码不可能被非法用户执行。

合法程序并不需要在堆栈中存放可执行代码,因此完全可以让操作系统使程序的堆栈段不可执行。目前,Solaris与Linux为此发布了安全补丁。正常情况下,合法程序几乎都不会在堆栈中存放代码,那么这种做法也就不会产生有关兼容性方面的问题。然而Linux系统中有特例的情况,其可执行的代码必须被存放在堆栈中,在此不再一一叙述,请同学们自行探索研究。

不可执行缓冲区技术能够有效地抑制把代码植入自动变量的缓冲区溢出攻击,但是对于其它形式的攻击却无效果。

4)数组边界检查

根据缓冲区溢出的基本原理可知,要实现缓冲区溢出攻击则需要改变程序的执行流程,使程序代码不按约定的流程执行。如果给局部变量分配的内存空间没被溢出,改变程序运行状态也就无从谈起。为此,我们可以利用一些编译器或工具对程序进行数组边界检查,就是在对数组进行读写操作时,必须将对数组的操作控制在正确的内存范围内。最简单的方法就是检查所有对数组的操作。当前,Paul Kelly与Richard Jones联合开发的GCC补丁、Purify以及Compaq C编译器等都能实现对数组边界的检查功能。

5)程序指针完整性检查

相对于边界检查,所谓程序指针完整性检查,就是在程序指针被引用之前检测它是否有改变。若非法用户改变了程序的指针,并且系统事先检测到了指针的改变,那么该指针将不会再使用。目前有以下三个研究方向。

FreeBSD系统有一套能通过监测CPU堆栈来确定缓冲区溢出的libc,可有效地保护libc中当前有效的记录函数,有效地防卫了基于libc库函数的攻击,然而不能抑制其它方式的攻击。

StackGuard通过不允许改动活动函数的返回地址RET来防止某些类型的缓冲区溢出攻击。实现方式有函数返回前检测返回地址RET的改动和禁止对返回地址RET写。分析与实验数据表明,对于各种系统的缓冲区溢出攻击,StackGuard都有很好的保护作用,并具有较好的系统性能与兼容性。并且,StackGuard能有效抵御各类不同基于堆栈的攻击。

PointGuard通过在所有的代码指针之后放置附加字节来检验指针在被调用之前的合法性,实质上是StackGuard的推广。此外动态防御技术还有ProPolice、StackShield、PaX等。

5结束语

作为一种危害性大、应用广的安全漏洞,缓冲区溢出攻击被非法用户广泛应用于不同的操作系统与应用软件中。通过缓冲区溢出攻击,能导致程序运行失败、重新启动以及系统当机等后果。尤其是若利用它执行非授权指令,非法用户甚至能够取得系统特权,进而执行一些非法操作。由于这种攻击能使得非法用户完全控制某一台主机,因此构成了对计算机系统很大的安全威胁与隐患。

在“信息安全理论与技术”课程教学中,我们十分重视缓冲区溢出安全编程的教与学。通过分析缓冲区溢出的基本原理和攻击技术,师生共同探讨,举一反三,理论与上机操作相结合,提出了一些在C语言程序编写过程中防御缓冲区溢出的方法,以提高学生安全编程能力和软件开发水平。

猜你喜欢
堆栈
基于行为监测的嵌入式操作系统堆栈溢出测试*
基于SpringBoot结果集序列化过滤插件的研究与实现
Stacking算法在医疗健康数据中的应用研究
应用EDAC容错技术的星载软件堆栈溢出实时检测方法
嵌入式软件堆栈溢出的动态检测方案设计*
基于堆栈自编码降维的武器装备体系效能预测
一种航天器软件进程堆栈使用深度的动态检测方法
多边形图形的环状扫描线种子填充算法
一种用于分析MCS-51目标码堆栈深度的方法
Cx51程序设计的堆栈空间计算方法