地址空间配置随机加载(Address space layout randomization,缩写ASLR,又称地址空间配置随机化、地址空间布局随机化)是一种防范内存损坏漏洞被利用的计算机安全技术。详细一点,就是地址空间配置随机加载是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
所以对于攻击者来说,绕过地址空间布局随机化的防御体系是他们执行所有内存攻击漏洞的先决条件。这意味着,关于攻破地址空间布局随机化的研究话题也是一个比较热门的领域。另外根据我的推测,对地址空间布局的攻击将来也会变得异常复杂。
本文会介绍有关地址空间布局的一些基本事实,重点是Windows实现。除了介绍地址空间布局在改善安全状况方面所做的贡献之外,我们还旨在为防御者提供有关如何改善其程序安全性的建议,并为研究人员提供更多有关地址空间布局的工作原理和调查其局限性的想法的见解。
当程序错误地将攻击者控制的数据写入目标内存区域或目标内存范围之外时,就会发生内存损坏漏洞(Memory corruption vulnerability)。这可能会使程序崩溃,更糟糕的是,攻击者可以完全控制系统。尽管苹果,谷歌和微软等大型公司都努力在缓解内存破坏漏洞,但数十年来这个攻击一直在困扰着它们。
由于这些漏洞很难被发现,而且一旦发生就会危及整个操作系统,所以安全专业人员设计了一种漏洞安全保护机制,以阻止程序被利用。另外使用这种漏洞安全保护机制,如果发生内存损坏漏洞时,就可以限制所造成的损害。本文将介绍一种被称为“silver bullet”的方法,防护人员使用这种方法可以让漏洞利用变得非常困难,该防护机制可以将错误的代码留在原处,从而为开发人员提供了用内存安全的语言修复或重写代码所需的时间。不幸的是,没有什么防护方法是完美的,但是地址空间布局随机化是可用的最佳缓解措施之一。
地址空间布局随机化的工作方式打破了开发人员在运行时对程序和库位于内存中的位置所做的假设,一个常见的示例是面向返回的编程(ROP)中使用的小工具的位置,该位置通常用于抵御数据执行保护(DEP)的防御。地址空间布局随机化混合了易受攻击进程的地址空间(主程序、其动态库、堆栈和堆、内存映射文件等),因此必须针对受害进程的地址空间专门定制利用有效载荷当时的布局。如果攻击者编写一种蠕虫,将其带有硬编码内存地址的内存破坏漏洞随机地发送到它可以找到的每台设备,这种传播注定会失败。只要目标进程启用了地址空间布局随机化,漏洞利用的内存偏移就将与地址空间布局随机化选择的内存偏移不同。这会使易受攻击的程序发生崩溃,而不是被利用。
地址空间布局随机化是在Windows Vista中被引入的, 也就是说Vista之前的版本没有ASLR。更糟糕的是,他们竭尽全力在所有进程和设备上保持一致的地址空间
Windows Vista和Windows Server 2008是最早支持地址空间布局随机化兼容可执行文件和库的版本,所以有人可能会认为以前的版本根本没有地址空间布局随机化,而只是将DLL加载到当时方便的任何位置,这个位置是可以预测的,但在两个进程或设备之间不一定相同。不幸的是,这些老的Windows版本反而无法实现我们所说的“地址空间布局一致性”。下表显示了Windows XP Service Pack 3某些核心DLL的“首选基地址”。
Windows DLL包含一个首选的基地址,如果没有地址空间布局随机化,则在可能的情况下使用。
创建进程时,Vista之前的Windows版本会尽可能在其首选基址处加载程序所需的每个DLL。例如,如果攻击者在ntdll的0x7c90beef处找到了有用的ROP小工具,则攻击者可以假定它将一直在该地址可用,直到以后的Service Pack或安全补丁要求重新组织DLL为止。这意味着对Vista之前的Windows的攻击可以将来自常见DLL的ROP小工具链接在一起,以禁用DEP,DEP是这些版本中唯一的内存损坏防护机制。Data Execution Prevention(DEP),即数据执行保护,是Windows上的可执行空间保护策略,能够在内存上执行额外检查以阻止数据页(如默认的堆页、各种堆栈页及内存池页)执行恶意代码,防止缓冲区溢出攻击。缓冲区溢出攻击的根源在于计算机对数据和代码没有明确区分,当程序溢出成功转入数据页的shellcode时,会成功执行恶意指令。而DEP的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode尝试在数据页面上执行指令时,CPU 会抛出异常,而不执行恶意指令。微软从Windows XP SP2开始提供DEP支持,操作系统通过在内存的页面表(Page Table)设置内存页的NX/XD属性标记,来指明不能从该内存执行代码。当该标识位设置为0时表示这个页面允许执行指令,设置为1时表示该页面不允许执行指令。
为什么Windows需要支持首选基地址?因为Windows dll的设计与ELF共享库等其他设计之间的性能需要进行权衡。由于Windows DLL不是位于独立位置的,特别是在32位计算机上,如果Windows DLL代码需要引用全局变量,则该变量的运行时地址将被硬编码到计算机代码中。如果DLL加载的地址与预期的地址不同,则将执行重定位以修复此类硬编码的引用。如果DLL被加载为它的首选基址,则不需要重新定位,并且DLL的代码可以直接从文件系统映射到内存中。
直接将DLL文件映射到内存,这样做对性能的提高有好处,因为它避免了在需要DLL的任何页面之前将它们读入物理内存。首选基址的一个更好的理由是确保内存中只需要一个DLL副本,如果没有它们,那么运行的三个程序将共享一个公共DLL,但是每个程序在不同的地址加载该DLL,那么内存中就会有三个DLL副本,每个副本都被重新定位到不同的库。这样一来,使用共享库的主要好处就会被抵消掉。除了安全性方面的好处外,地址空间布局随机化还以一种更优雅的方式完成了同样的任务,即确保已加载DLL的地址空间不会重叠,并且只将DLL的一个副本加载到内存中。由于地址空间布局随机化在避免地址空间重叠方面比静态分配的首选负载地址做得更好,所以手动分配首选基地址在支持地址空间布局随机化的操作系统上不提供任何优化,并且在开发生命周期中不可能再被用到。
总结起来就是:
1. Windows XP和Windows Server 2003及更早版本不支持地址空间布局随机化,显然,这些版本已经多年不受支持了,应该早就停止在生产环境中使用了。他们可能没有意识到,完全相同的程序可能更安全,也可能更不安全,这取决于运行的操作系统版本。那些仍然拥有地址空间布局随机化和非地址空间布局随机化支持Windows版本的开发人员应该相应地响应CVE报告,因为同样的漏洞可能在Windows 10上无法利用,但在Windows XP上却可以利用。这同样适用于Windows 10而不是Windows 8.1或Windows 7,因为每个版本的ASLR都变得更加强大。
2. 审核老版程序代码库,以避免被首选加载地址误导。老版程序仍可以使用Microsoft Visual C ++ 6之类的老工具进行维护,这些开发工具包含有关首选加载地址的作用和重要性的过时文档。由于这些老工具无法将映像标记为与地址空间布局随机化兼容,因此,一个懒惰的开发人员不必费心更改默认的DLL地址,实际上会更好,因为冲突会迫使映像重新定位到无法预测的位置!
Windows在跨进程甚至跨用户的同一位置会加载多个映像实例,只有重新启动才能保证所有映像的地址均具有新的随机基地址。
在地址空间布局随机化的Linux实现中使用的ELF映像可以在共享库中使用与位置无关的可执行文件和与位置无关的代码,以便在每次启动时为主程序及其所有库提供新的随机地址空间,共享同一设备的代码在多个进程之间可以进行切换,即使它被加载到不同的地址也是如此。 不过,Windows ASLR无法以这种方式工作。相反,第一次使用内核时,内核会为每个DLL或EXE映像分配一个随机的加载地址,并且在加载DLL或EXE的其他实例时,它们会收到相同的加载地址。如果映像的所有实例均已卸载,并且该映像随后又被加载,则该映像可能会,也可能不会接收到相同的基址。只有重新启动,才能保证系统范围内所有映像接收到的都是最新基址。
由于Windows DLL不使用与位置无关的代码,因此可以在进程之间共享其代码的唯一方法是始终将其加载在同一地址。为了实现这一点,内核会选择一个地址(例如32位系统上的0x78000000),并在其下面的随机地址处加载DLL。如果某个进程加载了最近使用的DLL,则系统可能会重新使用先前选择的地址,因此会在内存中重新使用该DLL的先前副本,该实现解决了为每个DLL提供一个随机地址并确保DLL不会同时重叠的问题。
对于EXE,不必担心两个EXE重叠,因为它们永远不会被加载到同一进程中。即使映像大于0x100000字节,将EXE的第一个实例加载为0x400000,将第二个实例加载为0x500000也没有问题, Windows只选择在给定EXE的多个实例之间共享代码。
总结起来就是:
1. 任何在崩溃后自动重启的Windows程序都特别容易受到暴力攻击,这时地址空间布局随机化的防护也会失败。
考虑远程攻击者可以按需执行的程序,例如CGI程序,或者仅在超级服务器需要时才执行的连接处理程序(例如inetd)。另一种可能性是,Windows服务与看门狗配对,当它崩溃时会重新启动该服务。此时,攻击者可以利用Windows ASLR的工作原理来尽可能加载EXE的基地址。如果程序崩溃,并且该程序的另一个副本保留在内存中,或者该程序迅速重新启动,并且在可能的情况下,接收到相同的ASLR基址,则攻击者可以假定新实例仍为加载到相同的地址,此时,攻击者将尝试使用相同的地址。
2. 如果攻击者能够发现DLL在任何进程中的加载位置,那么他就知道DLL在所有进程中的加载位置。
假如一个运行网络服务的系统同时具有两个漏洞:一个在调试消息中泄漏指针值,但没有缓冲区溢出;一个在缓冲区溢出但不泄漏指针。如果泄漏的程序揭示了kernel32.dll的基址,并且攻击者知道该DLL中的一些有用的ROP小工具,则可以使用相同的内存偏移量来攻击包含溢出的程序。因此,看似无关的易受攻击程序可以链接在一起,以首先克服ASLR,然后启动漏洞利用程序。
3. 要提升特权,可以首先使用低特权帐户绕过ASLR。
假设后台服务公开了仅供本地用户可访问的命名管道,并且存在缓冲区溢出。要确定该程序的主程序和DLL的基地址,攻击者只需在调试器中启动另一个副本。然后,可以使用从调试器确定的偏移量来开发有效载荷,以利用高特权进程。发生这种情况是因为Windows在保护EXE和DLL的随机基址时不会尝试将用户彼此隔离。
本文翻
转载请注明:IT运维空间 » 安全防护 » 关于Windows上地址空间布局随机化防御机制的分析(上)
发表评论