当前位置: 首页 > 科技观察

什么是缓冲区溢出以及如何利用它

时间:2023-03-12 13:05:39 科技观察

在信息安全和编程中,缓冲区溢出是一种异常现象,其中程序在将数据写入缓冲区时超出缓冲区边界并覆盖相邻的内存位置。缓冲区是为存储数据而预留的内存区域,通常在将数据从程序的一部分移动到另一部分或在程序之间移动时使用。如果假设所有输入都小于特定大小,并且缓冲区被创建为该大小,则产生更多数据的异常事务可能会导致它被写入缓冲区的末尾。缓冲区溢出概念缓冲区溢出是指当计算机向缓冲区中填充的数据位超过缓冲区本身的容量时,溢出的数据被覆盖在合法的数据上。理想的情况是程序检查数据长度,不允许输入超过缓冲区。然而,大多数程序都假设数据长度始终与分配的存储空间相匹配,这就产生了缓冲区溢出的隐患。操作系统使用的缓冲区也称为“堆栈”。.在每一次操作过程之间,指令都会被暂存在“栈”中,“栈”也会出现缓冲区溢出。缓冲区溢出攻击之所以成为一种常见的安全攻击手段,是因为缓冲区溢出漏洞过于普遍且容易实施。而且,缓冲区溢出之所以成为远程攻击的主要手段,是因为缓冲区溢出漏洞给了攻击者想要的一切:植入和执行攻击代码。植入的攻击代码以一定权限运行存在缓冲区溢出漏洞的程序,从而获得对被攻击主机的控制权。当您使用“C”或“C++”等语言开发程序并使用以下命令使用gcc编译它时:gcc-oprogramprogram.c您知道gcc是如何将您的代码从“C”转换为机器语言可执行文件的一台电脑?简而言之,我们可以说这个过程分3个步骤完成:“C”中的代码将被转换为汇编语言,这是二进制语言之后的最低级别。至此,汇编代码被翻译成二进制。可执行文件是“链接的”,换句话说,链接是由代码使用的库建立的。现在让我们看一下汇编程序的基础知识,因为这种语言对于理解开发过程至关重要。section.textglobal_start_start:pushrdxmovrdi,0x4444444444444444;v_addrmovrsi,0x5555555555555555;lenmovrdx,0x7;RWXmovrax,10;mprotect0x80483dcsyscallmovrcx,0x2222222222222222movrsi,0x3333333333333333movrdx,0x6666666666666666;random_intmovrdi,rsijmp_loop_loop:?cmprcx,0x0je_endlodsbnotalxoral,dlstosbloop_loopon_end:poprdxmovrax,0x1111111111111111jmprax上面这段代码的目的只是向你展示它的样子。如您所见,代码由push、mov、cmp等指令组成。注意,在汇编分号后面的所有内容都被视为注释call0x80483dc;Callfunctionataddress0x80483dcpush0x0;eax或ebx这些就是我们所说的寄存器。在其中存储一些值,如地址、数字等。对于32位处理器,寄存器eax、ebx、ecx、edx、ebp、esp、eip和edi的大小为8位。还有其他寄存器,但老实说,它们暂时对我们没有任何意义。还有一件事,对于64位处理器,我们将使用相同的寄存器,只是它们将是16位而不是8位,并且“e”将替换为“r”,因此寄存器名称将变为rax、rbx、rcx、rdx、rbp、rsp、rip和rdi。内存段.data——存储全局变量的段;.bss-包含静态变量;.text-包含我们的代码,可能不够清楚,所以让我们用一些代码来说明:在上面的例子中,我们可以看到以下内容:a和b在.bss中,c放在堆栈上,并且我们的功能主要在.text。让我们看看如何反汇编程序:我们将通过编译上面的代码片段来测试它。首先,打开您的终端,然后依次使用以下命令:输出:既然我们已经编译了文件,我们将使用objdump对其进行反编译,这是大多数现代GNU/Linux发行版中提供的线性反汇编工具。objdump-Mintel-dcode输出:你看到的一切都是正确的!从上面的屏幕截图,我们关注“0000000000001119让我们继续检查我们程序的确切功能:pushrbp,将rbp放入堆栈;movrbp,rsp,将rsp放入rbp;movDWORDPTR[rbp-0x4],0xf把0xf(十六进制的15)放入rbp;moveax,0x0把0放入eax;poprbp把什么放到栈顶;可以看到,没有定义变量。我们甚至可以说没有变量名称,但我们在rbp中确实以15结束。让我们看一下这段代码:column是“8”。现在我们看看如果修改代码,增加一个新的变量:输出内容:如可以看到,bss从8个字节增加到12个字节,我们很容易认为我们的全局变量是存储在bss中,因为他的值确实增加了。现在,我们在main函数中包含一个static变量再做一次测试,看看会发生什么。输出:如你所见变量不像前面的例子那样存储在bss中,而是存储在数据中。看起来很有趣,不是吗?最终测试看看如果你初始化一个全局变量会发生什么。输出:我们得到相同的结果,你知道为什么吗?全局变量(如果已初始化)将放入数据中。我认为如果此时已经开发了足够多的内容,那么它可以让您了解变量的存储位置和存储方式。记忆如何运作?现在,我们不再谈论您的物理内存,而是要谈论RAM以及它是如何由操作系统管理的。在计算机上运行的进程需要内存,而在计算机中,内存量是有限的。因此,该进程必须找到可用内存才能完成其工作。假设有多个进程并发运行。如果两个进程想要访问同一个内存区域会发生什么?此外,如果一个进程写入内存区域,那么另一个进程将用其数据覆盖同一内存区域,那么第一个进程将考虑查找其数据,但它会查找第二个进程的数据。这可能是个大问题,不是吗?通过为每个进程分配一定范围的虚拟内存,在32位系统上限制为4GB,在64位系统上限制为8GB,这是操作系统的主要功能位置,用来解决这个问题。每个进程将能够使用它需要的内存地址而不用担心其他进程,操作系统的内核将尝试链接虚拟和实际内存。堆栈和堆现在,我们将继续讨论一些非常重要的事情,程序员可以操纵堆。这是写入malloc()或calloc()动态分配内存区域的内存部分。这个内存区域没有固定的大小,它会根据我们的要求增长或缩小,我们可以通过分配或释放算法保留或删除块以备将来使用。heapsize越大,内存地址越大,越接近栈中的内存地址。与堆栈不同,除了物理内存限制外,堆中变量的大小没有限制。在程序的任何地方,都可以使用指针来访问堆中存储的变量,栈的大小也是可变的,但是栈的大小越大,内存地址越减少越接近栈顶堆。一个函数的栈帧是栈上的一个存储区,这里存储了调用这个函数所需的所有信息,包括局部变量。理解堆栈的概念让我们从后进先出开始,它并不代表任何复杂的东西,正如我们之前所见。LIFO代表后进先出。也就是说,最后放入堆栈的东西是我们发布的第一件事,尤其是从pop和push来看。最终缓冲区溢出最后,我们来到开发部分。让我们看下面的一小段代码。代码看起来非常好,学习“C”语言时必须使用scanf函数。但是,如果我们查看此示例中的堆栈会怎样。[buffer(100)][inta][savedebp][savedeip]你可能想知道保存的ebp和eip是什么?实际上,我们并不关心“savedebp”,我们感兴趣的是“savedeip”。你还记得eip包含什么吗?下一条要执行的指令的地址。如果我们更改此地址,我们可以做任何事情!但是,如何改变这个值呢?非常简单!你会发现scanf并没有检查接收到的字符数!让我们用下面的一段代码来演示。如果给scanf的值太大(比如多个“A”),就会造成缓冲区溢出。因此,该程序将返回一个分段错误。但是,如果我们通过有效地址更改“A”值,我们可以跳转到任何位置,尤其是在adminfunction()上。缓冲区溢出和远程堆溢出的区别,以iOSMail客户端MFMutable中的远程堆溢出为例。在分析代码流程时,我们确定了以下内容:1.函数[MFDAMessageContentConsumerConsumerData:length:format:mailMessage:]在以原始MIME格式下载电子邮件时被调用,并且它也会被调用多次,直到电子邮件在下载中交换模式直到。它将创建一个新的NSMutableData对象并调用appendData:用于属于同一电子邮件/MIME消息的任何新流数据。对于其他协议(如IMAP),它使用-[MFConnectionreadLineIntoData:]代替,但逻辑和利用是相同的。2.NSMutableData设置阈值为0x200000字节,如果数据大于0x200000字节,它会将数据写入一个文件,然后使用mmap系统调用将文件映射到设备内存。阈值大小0x200000可以很容易地增加,因此每次需要添加新数据时,文件都会重新映射,文件大小以及mmap大小会越来越大。3.重映射在-[MFMutableData_mapMutableData:]内部完成,漏洞就在这个函数内部。漏洞函数的伪代码如下:-[MFMutableData_mapMutableData:]当mmap系统调用失败时调用函数MFMutableData__mapMutableData___block_invoke。MFMutableData__mapMutableData___block_invoke的伪代码如下,分配一块大小为8的堆内存,然后用分配的内存替换data->bytes指针。执行-[MFMutableData_mapMutableData:]后,进程继续执行-[MFMutableDataappendBytes:length:],在将数据复制到分配的内存时导致堆溢出。append_length是流中数据块的长度,由于MALLOC_NANO是一个可预测的内存区域,因此可以利用此漏洞。攻击者不需要用尽最后一位内存来导致mmap失败,因为mmap需要连续的内存区域。根据mmap的操作说明,如果指定了MAP_ANON且可用内存不足,mmap会失败。目标是使mmap失败,理想情况下,足够大的消息将不可避免地失败。但是,我们认为可以使用其他可能耗尽资源的技巧来触发该漏洞。这些技巧可以通过multipart、RTF和其他格式来实现,我们将在后面介绍这些格式。影响可利用性的另一个重要因素是硬件规格:iPhone6有1GB内存;iPhone7有2GB内存;iPhoneX有3GB内存;较旧的设备具有较少的物理RAM和较少的虚拟内存空间,因此没有必要用尽所有RAM来触发此错误,当mmap在可用虚拟内存空间中找不到给定大小的连续内存时,mmap将失败。我们已经确定MacOS不会同时受到这两个漏洞的影响。在iOS12中,更容易触发错误,因为数据流是在同一进程中完成的,因为默认邮件应用程序(MobileMail)处理更多资源,从而耗尽分配的虚拟内存空间(尤其是UI),而在iOS中13、MobileMail将数据流传递给后台进程(即邮件)。它将资源集中在解析电子邮件上,从而降低了意外耗尽虚拟内存空间的风险。由于MobileMail/maild没有明确设置最大电子邮件大小限制,因此可以设置自定义电子邮件服务器并发送包含几GB纯文本的电子邮件。iOSMIME/Message库在流式传输时将数据平均分成大约0x100000个字节,因此最好不要下载整个电子邮件。请注意,这只是如何触发此漏洞的一个示例。攻击者不需要发送这样的电子邮件来触发此漏洞,使用多部分、RTF或其他格式的其他技巧可以使用标准大小的电子邮件实现相同的目标。目前,苹果已经修复了iOS13.4.5测试版中的两个漏洞,如下截图所示:要缓解这些漏洞,您可以使用最新版本的测试版。如果您不能使用测试版,请禁用邮件应用程序并使用不易受攻击的Outlook、EdisonMail或Gmail。