在信息安全和编程中,缓冲区溢出是一种异常,其中程序在将数据写入缓冲区时会超出缓冲区边界并覆盖相邻的显存位置。缓冲区是留出的用于储存数据的显存区域,一般是在将数据从程序的一个部份联通到另一部份或在程序之间联通时使用的。若果假定所有输入都大于特定大小,但是缓冲区被创建为该大小,则形成更多数据的异常事务可能造成其写入缓冲区的末尾。
缓冲区溢出概念
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢出的数据覆盖在合法数据上,理想的情况是程序检测数据宽度并不容许输入超过缓冲区宽度的字符,并且绝大多数程序就会假定数据宽度总是与所分配的存储空间相匹配,这就为缓冲区溢出埋下隐患,操作系统所使用的缓冲区,又被称为"堆栈"。在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出。
缓冲区溢出功击之所以成为一种常见安全功击手段其缘由在于缓冲区溢出漏洞太普遍了,而且便于实现。并且,缓冲区溢出成为远程功击的主要手段其缘由在于缓冲区溢出漏洞给与了功击者他所想要的一切:植入而且执行功击代码。被植入的功击代码以一定的权限运行有缓冲区溢出漏洞的程序,因而得到被功击主机的控制权。
缓冲区溢出漏洞解读
当你使用例如“C”或“C++”之类的语言开发程序并使用gcc使用以下命令对其进行编译时:
gcc -o program program.c
你晓得gcc怎样将你的代码从“C”转换为计算机可执行的机器语言吗?简而言之,我们可以说该过程分3个步骤完成:
“C”中的代码将转换为汇编语言,该汇编语言是二补码以后的最低级语言。此时,汇编代码被翻译成二补码。可执行文件是“链接的”,换句话说,链接是由代码使用的库构建的。
如今让我们看一下汇编器的基础知识,由于这些语言对于理解开发过程至关重要。
section .text global _start _start: push rdx mov rdi, 0x4444444444444444 ; v_addr mov rsi, 0x5555555555555555 ; len mov rdx, 0x7 ; RWX mov rax, 10 ; mprotect0x80483dc syscall mov rcx, 0x2222222222222222 mov rsi, 0x3333333333333333 mov rdx, 0x6666666666666666 ; random_int mov rdi, rsi jmp _loop _loop: cmp rcx, 0x0 je _end lodsb not al xor al, dl stosb loop _loopon _end: pop rdx mov rax, 0x1111111111111111 jmp rax
前面这段代码的目的只是向你展示它的外型。如你所见,代码是由例如push,mov,cmp等指令组成的。
; Note, in assembler everything behind a semicolon is considered as a comment call 0x80483dc; Call function at address 0x80483dc push 0x0; puts the value 0x0 on the stack pop ebx; put what is at the top of the stack in ebx mov eax, 0x1; puts 0x1 in eax
如你所见,并不复杂。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对其进行反编译,objdump是大多数现代GNU/Linux发行版中提供的线性反汇编工具。
objdump -M intel -d code
输出内容:
你所看见的一切都是正确的!从前面的屏幕截图中linux telnetd密钥处理缓冲区溢出漏洞adobe air linux,我们将重点置于“01119
让我们继续检测一下我们程序的准确函数:
推送rbp,将rbp装入堆栈;
movrbp,rsp,将rsp装入rbp;
movDWORDPTR[rbp-0x4],0xf将0xf(十六补码为15)倒入rbp;
moveax,0x0将0装入eax;
poprbp将哪些置于栈顶中;
如你所见,没有定义变量。我们甚至可以说也没有变量名,然而最终我们在rbp中确实有15个。
让我们看一下这段代码:
输出内容:
从里面的输出中,我们将了解每位会话的大小。到目前为止,你可以听到引用的bss列的大小为“8”。
如今让我们瞧瞧假如通过添加新变量来更改代码:
输出内容:
如你所见,bss从8个字节降低到12个字节,我们可以轻松地觉得我们的全局变量储存在bss中,由于他的值确实降低了。
如今,我们将在主函数中包含一个静态变量,以进行另一项测试,之后瞧瞧会发生哪些。
输出内容:
如你所见,变量没有像上面的示例那样储存在bss中,而是储存在从512字节变为516字节的数据中。看上去很有趣嵌入式linux,不是吗?
进行最终测试,以了解假如初始化全局变量会发生哪些。
输出内容:
我们得到相同的结果,你晓得为何吗?全局变量(假如已初始化)将被装入数据中。我觉得假如此时早已开发了足够的内容,可以让你了解变量的储存位置和储存方法。
显存怎样运作?
如今,我们将不谈论你的数学显存,而是谈论RAM及其怎样由操作系统管理。在计算机上运行的进程须要显存,而在计算机中,显存量是有限的。
为此,进程必须找寻可用的显存能够工作。假定有多个进程同时运行。假如两个进程想要访问相同的显存区域,将会发生哪些?并且,假如某个进程写入了一个显存区域,这么另一个进程将用其数据覆盖该相同的显存区域,这么第一个进程将考虑找到其数据,但它将找到第二个进程的数据。这可能是一个很大的问题,不是吗?
通过为每位进程分配一定范围的虚拟显存,在32位系统上限制为4GB,在64位系统上限制为8GB,这是操作系统的主要函数拿来解决此问题的位置。
每位进程将才能使用所需的显存地址,而毋须害怕其他进程,操作系统的内核将设法链接虚拟显存和实际显存。
栈和堆
如今,我们将继续进行一些特别重要的事情,堆可以由程序员操纵。这是写入动态分配的显存区域malloc()或calloc()的显存部份。
此储存区域没有固定大小,它按照我们的要求降低或降低,我们可以通过分配或释放算法保留或删掉块以供将来使用。堆大小越大,显存地址越大,但是它们与堆栈中的显存地址越接近。与堆栈不同,不仅化学显存限制外,堆中变量的大小不受限制。
程序中的任何地方都可以使用表针访问堆中储存的变量,堆栈的大小也可变,并且堆栈的大小降低得越大,显存地址降低的越多,因而接近堆的底部。函数的堆栈框架是堆栈中的一个储存区域,在其中储存了调用此函数所需的所有信息,该函数还有局部变量。
理解堆栈的概念
让我们从LIFO开始讲起,它并不代表任何复杂的事情,由于我们之前早已听到过。LIFO代表后进先出。这就是说,放在栈上的最后一件事是我们要发布的第一个东西,尤其是通过pop和push听到的。
最终缓冲区溢出
最后,我们步入开发部份。让我们来看下边的一小段代码。
该代码看上去完全正常,在学习“C”语言时必须使用scanf函数。并且,假如我们看一下此示例中的堆栈,该如何办。
[buffer (100)] [int a] [saved ebp] [saved eip]
你可能想晓得保存的ebp和eip是哪些?虽然,我们不在意“保存的ebp”,我们感兴趣的是“保存的eip”。你还记得eip包含哪些吗?下一条要执行的指令的地址。假如我们修改此地址,我们可以执行任何操作!
然而,怎样修改此值?这很简单!你会发觉scanf不会检测接收到的字符数!让我们用以下一段代码来演示。
若果在执行scanf时给出的值太大(比如,多个“A”),则会造成缓冲区溢出。因而,该程序将向我们返回分段错误。并且,假如我们通过有效地址修改“A”值,则可以跳转到任何位置,非常是在adminfunction()上。
缓冲区溢出和远程堆溢出的区别,以iOSMail顾客端MFMutable中的远程堆溢出为例。
在剖析代码流时,我们确定了以下内容:
1.以原始MIME格式下载电子电邮时,会调用函数[MFDAMessageContentConsumerConsumerData:length:format:mailMessage:],但是该函数也会多次调用,直至电子电邮以交换模式下载为止。它将创建一个新的NSMutableData对象,并为属于同一电子电邮/MIME消息的任何新流数据调用appendData:。对于其他合同(比如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失败,理想情况下,一封足够大的电邮将不可防止地造成失败。并且,我们觉得,可以使用其他可以用尽资源的方法来触发漏洞。这种方法可以通过多部份、RTF和其他格式来实现,我们会在稍后再介绍。
另一个影响可借助性的重要诱因是硬件尺寸:iPhone6拥有1GB显存;iPhone7有2GB显存;iPhoneX有3GB显存;
较旧的设备具有较小的化学RAM和较小的虚拟显存空间,因而没有必要用尽所有的RAM来触发这个漏洞,当mmap在可用的虚拟显存空间中找不到给定大小的连续显存时,它都会失败。
我们早已确定,MacOS不会同时遭到这两个漏洞的功击。
在iOS12中,触发漏洞更容易,由于数据流传输是在同一过程中完成的,由于默认电邮应用程序(MobileMail)会处理更多的资源,因而用尽分配的虚拟显存空间(尤其是UI),而在iOS13中,MobileMail将数据流传递到后台进程(即短信)。它把资源集中在解析电子电邮上,进而增加了虚拟显存空间意外用尽的风险。
因为MobileMail/maild并未明晰设置电子电邮大小的最大限制,因而可以设置自定义电子电邮服务器并发送包含几GB纯文本的电子电邮。iOSMIME/消息库在流式传输数据时将数据平均分成大概0x100000字节,因而完全可以不下载整个电子电邮。
请注意,这只是怎样触发此漏洞的一个示例。功击者无需发送这种电子电邮即可触发此漏洞,而且采用多部份、RTF或其他格式的其他方法也可以使用标准大小的电子电邮实现相同的目标。
目前,苹果修补了iOS13.4.5beta中的两个漏洞,如以下屏幕截图所示:
为了减轻这种漏洞,你可以使用最新版的Beta。倘若未能使用Beta版,不过要禁用电邮应用程序,并使用不易受功击的Outlooklinux telnetd密钥处理缓冲区溢出漏洞,EdisonMail或Gmail。
本文翻译自:#WhatisaBufferOverflowandHowHackersExploittheseFlawspart1-2倘若转载,请标明原文地址