小菜谈谈之缓冲区溢出

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012494321/article/details/52987382

在介绍缓冲区溢出之前先介绍相关概念

一、缓冲区

所谓缓冲区是指内存中存放数据的地方,可以更为抽象地理解为一段可读可写的内存区域。以进程地址空间分布图来说明缓冲区位置,如图1。

 

                    图1 进程地址空间分布

(1)代码段:存储用户程序的所有可执行代码,在程序正常执行的情况下,程序计数器(PC指针)只会在代码段和操作系统地址空间(内核态)内寻址。
(2)数据段:存储用户程序的全局变量(静态数据)、常量等。
(3)堆空间:存储程序运行时动态申请的内存数据等,并在用完之后归还给堆区。动态分配和回收是堆区的特点。堆的数据增长方向是高地址方向。
(4)栈空间:存储用户程序的函数栈帧(包括参数、局部数据等动态数据),实现函数调用机制,以保证调用函数结束后继续执行父函数的指令。栈的数据增长方向是低地址方向。
除了代码段和受操作系统保护的数据区域,其他的内存区域都可能作为缓冲区,因此缓冲区溢出的位置可能在数据段,也可能在堆、栈段。最常见的应该是栈段。

二、寄存器

寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。以下介绍缓冲区溢出中用到的寄存器。
(1)esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,指向当前堆栈储存区域的顶部。该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,指向当前堆栈储存区域的底部。该指针永远指向系统栈最上面一个栈帧的底部。(ebp在当前栈帧内位置固定,故函数中对大部分数据的访问都基于ebp进行)
(3)eip:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。eip是在缓冲区溢出中对我们最有用的寄存器,可以说如果控制了eip寄存器的内容,就控制了进程——我们让eip指向哪里,CPU就会去执行哪里的指令。eip可被jmp、call和ret等指令隐含地改变(事实上它一直都在改变)(ret指令就是把当前栈顶保存的返回值地址弹到eip中)。
ESP和EBP之间的内存空间为当前栈帧,EBP标识了当前栈帧的底部,ESP标识了当前栈帧的顶部。

三、函数栈帧

栈的主要功能是实现函数的调用。每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址,即下一条指令),一些关键的寄存器值(地址)保存在栈内,函数的实际参数和局部变量(包括数据、结构体、对象等)也保存在栈内。这些数据统称为函数调用的栈帧,而且是每次函数调用都会有个独立的栈帧,这也为递归函数的实现提供了可能,如图2。
 
                                                          图2 函数栈针结构

四、函数调用

函数调用大致包括以下几个步骤以及函数调用时栈的结构变化(图3,图4):

(1)参数入栈:调用者函数把被调用者函数的参数从右向左依次压入栈中

(2)返回地址入栈:调用者函数使用call指令调用被调用函数,并把call指令的下一条指令的地址(即EIP中的内容)当成返回地址,供函数返回时继续执行(这个压入栈压栈操作隐含在call指令中)

(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处

(4)栈帧调整:在被调用函数中,被调用函数会先保存调用者函数的栈底地址(push ebp),已备调用结束回到调用函数时使用;然后将调用者的esp值赋给被调用者的ebp(mov ebp,esp),更新栈帧底部。

(5)给新栈帧分配空间:在被调用函数中,从ebp(新栈帧底部)的位置处开始存放被调用函数中的局部变量和临时变量,按栈的延伸方向依次入栈(先定义的变量先入栈,后定义的变量后入栈),把esp减去所需空间的大小即新栈栈顶。

 

                                                       图3 函数调用栈结构变化(1)


注:ebp寄存器的位置非常重要,向上(栈底方向)能获取返回地址,向下(栈顶方向)能获取函数的局部变量值,而该地址处又存放着上一层函数调用时的ebp值。
 
                                           图4 函数调用栈结构变化(2)

五、缓冲区溢出攻击

每当使用call指令进行函数调用时,都会将原来的eip寄存器中的值(返回地址)压栈,然后将新的函数指针写入eip寄存器,这是由机器自动执行的,保存原eip的同时,将新的执行地址写入eip。一旦函数调用完毕,需要将原eip值(返回地址)取出加载到eip寄存器,那么,如果返回地址被修改(比如被修改成为恶意程序的入口地址),函数返回后就会执行恶意程序。这就是缓冲区溢出攻击。缓冲区溢出攻击的原理就是通过篡改函数的返回地址从而让程序执行不应该执行的代码,如图5。
缓冲区溢出攻击大致可以分为三个步骤:
(1)向有漏洞程序的缓冲区注入攻击字符串;
(2)利用其对内存合法性检查不严格改写特定数据(如返回地址),使得程序的执行流程跳转到shellcode;
(3)执行shellcode使攻击者获得被攻击主机的控制权,继而以一定的方式控制被攻击主机。
 
                                    图5 栈溢出时的栈结构

六、数据执行保护DEP原理

分析缓冲区溢出攻击,其根源在于计算机没有对数据区和代码区明确区分,但这是计算机体系结构设计问题,无法修改。针对这一问题,增加了保护机制——DEP(数据执行保护),DEP将数据所在内存页标识为不可执行,使得程序在数据页(如默认的堆页、各种堆栈页以及内存池页)上无法执行。
当DEP保护机制被使用后,由于恶意代码是存放在系统的数据页面(堆栈页面)上,当程序溢出成功函数返回时,指令寄存器EIP将跳转到恶意代码入口地址,此时该页面是非可执行的,DEP将触发系统异常而导致程序终止,如图6。
 
                                                          图6 DEP工作原理

七、Ret21ibc-绕过数据保护机制

        Ret2libc攻击机制就是针对DEP保护机制中而设定的,DEP可以看到关键是在函数返回时EIP跳转到了非可执行页面时被DEP检测到。那么Ret2libc的攻击原理是,攻击者设定的函数的返回地址并不直接指向恶意代码,而是指向一个已存在的系统函数的入口地址。由于系统函数所在的页面权限是可执行的,这样就不会触发DEP异常。
        一般恶意代码shellcode的功能是通过执行/bin/sh,而系统函数库(libc)里面有许多非常有用的函数,如 system 函数就是通过/bin/sh命令去执行一个用户命令或脚本的,同样可以启动shellcode。如果攻击者将EIP地址改为system 函数的入口地址,再通过构造system 函数的调用参数传入自己的参数,就可以执行恶意代码程序了。因此函数返回libc库(return to libc)就是关键,这也是Ret2libc名字的由来。攻击者实现Ret2libc攻击的堆栈结构如图7。
 

                                  图7 Ret2libc攻击的堆栈结构

        另一个可以绕过DEP数据保护机制的方法是通过跳转VirtualProtect函数来修改恶意代码所在内存页面的执行权限,然后再将控制转移到恶意代码。VirtuaIProlect是 Windows系 统 kernel32.dll提供的函数,其功能是修改调用进程所在虚拟地址空间 (virtual address)的内存区域的保护权限。

       攻击者特别构造将恶意代码的入口地址作为VirtualProtect函数退出时的返回地址。由于在VirtualProtect的执行过程中,恶意代码所在的页面被修改为可执行权限,这样当VirtualProtect返回时,EIP再跳转到恶意代码时就不会出发任何DEP异常。如图8。 
 
                             图8 使用VirtualProtect攻击的堆栈结构

八、ASLR/dynamicbase链接选项

        在上面的Ret2libc攻击方式介绍中,最为关键的一点是攻击者事先知道了特定的系统函数如system或VirtualProtect的入口地址。在WindowsXP或 Windows 2000上,这些函数的入口地址是固定的,即攻击者可以确定的。
        ASLR的原理就是在当一个应用程序或动态链接库(如kernel32.dll)被加载时,系统就会将其加载的基址随机设定。ASLR是系统一级的特性,系统动态库(如kernel32.dll)是在系统每次启动时被随机设定的。这样,攻击者就无法事先预知动态库的基址,也就无法事先确定特定函数(VirtualProtect函数)的入口地址了。
一旦使用了ASLR/dynamicbase链接选项,Windows程序默认加载的大量dll中(kernel32.dll,ntdll.dl等)都会受到ASLR机制的保护。所以建议开发人员都开启此功能。Windows Vista在使用ASLR功能的时输出结果如图9(系统函数重启后地址随机分布):
 

                           图9 ASLR功能输出结果

        ASLR的有一定的局限性,首先,ASLR安全特性只在WindowsVista和其后的 Windows版本 (如WindowsServer2008)中实现。其次,ASLR是需要和 DEP配合使用的。如果CPU不提供对于DEP的硬件支持,或者应用程序没有选择被DEP保护的话,恶意代码一旦执行,就可以通过程序进程表结构来获得特定DLL的加载基址。

九、小结

        以上是在遇到缓冲区溢出问题时的知识总结,攻防博弈在安全方向上体现更深。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数,使得缓冲区接收的输入数据超过了缓冲区本身的容量,而导致数据溢出到被分配空间之外的内寸空间,使得溢出的数据覆盖了其他内存空间的数据。
       缓冲区溢出是一种非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统宕机、重新启动等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。
       因此程序员要养成安全编码的思想,应该熟悉那些可能会产生漏洞或需要注意输入参数长度的函数。除此之外用户应该注意防范:1.关闭不需要的端口或服务。2.及时安装软件厂商的补丁。3.以所需的最小权限运行软件。


猜你喜欢

转载自blog.csdn.net/u012494321/article/details/52987382