麻省理工学院的课程“计算机系统安全”。 第3讲:缓冲区溢出:漏洞利用和保护,第1部分

麻省理工学院。 讲座课程#6.858。 “计算机系统的安全性。” Nikolai Zeldovich,James Mickens。 2014年


计算机系统安全是一门有关开发和实施安全计算机系统的课程。 讲座涵盖了威胁模型,危害安全性的攻击以及基于最新科学研究的安全技术。 主题包括操作系统(OS)安全性,功能,信息流管理,语言安全性,网络协议,硬件安全性和Web应用程序安全性。

第1课:“简介:威胁模型” 第1 部分 / 第2 部分 / 第3部分
第2课:“控制黑客攻击”, 第1 部分 / 第2 部分 / 第3部分
第3讲:“缓冲区溢出:漏洞利用和保护” 第1 部分 / 第2 部分 / 第3部分

欢迎来到有关缓冲区溢出漏洞利用的讲座。 今天我们将结束对松散边界的讨论,然后继续讨论其他缓冲区溢出保护方法。



接下来,我们将讨论今天讲座的印刷材料,这些材料专门用于盲返回定向编程(BROP) -盲反向定向编程。 即使攻击者没有目标二进制文件,也可以执行这种利用技术。 这些漏洞旨在破坏64位系统堆栈中的“ canaries”。 因此,如果您初次阅读这些材料时像我一样,您应该会喜欢看克里斯托弗·诺兰的电影。 只是大脑爆炸!

我们将考虑这些小工具如何正常工作。 因此,我希望到讲座结束时您将能够理解讲座材料中描述的所有这些高科技。 但是首先,正如我说的,我们将结束对Baggy界限的讨论。 考虑一个非常简单的示例。

假设我们要分配一个指针p并为其分配44字节的大小。 还假定插槽大小为16个字节。

当我们分配malloc函数时会发生什么? 您已经知道,在这种情况下, Baggy边界系统将尝试以2n对数补充此分布。 因此,对于我们的44个字节的指针,将分配64个字节的内存。 但是插槽大小为16个字节,因此我们将创建64/16 = 4个边界表,每个边界表16个字节。 这些条目中的每一个都将放置在大小分布日志中。

接下来,分配另一个指针char * q = p + 60 。 我们看到这个值超出范围,因为p的大小是44个字节,这里是60个字节。 但是, Baggy边界有效,因此在这种情况下,不会发生任何不好的情况,尽管程序员不应该这样做。

现在,我们假设下一步是分配另一个指针,该指针将等于char * r = q + 16 。 现在这实际上将导致错误,因为偏移量大小将为60 + 16 = 76,这比Baggy边界系统分配的4个插槽(4x16 = 64字节)大12个字节。 而这个多余的部分实际上超过了插槽的一半。



如果您还记得,在这种情况下, Baggy边界系统立即响应严重的同步错误,这将导致程序崩溃并实际上将其停止。

因此,让我们假设我们只有两行:

char * p = malloc(44)
字符* q = p + 60

而且代码没有第三行。 相反,我们将这样做:

字符* s = q + 8

在这种情况下,指针的值将为60 + 8 = 68位,这将比Baggy bounds指定的边界大4个字节。 实际上,尽管该值超出了范围,但这不会导致严重错误。 我们在这里所做的是为指针设置了一个高阶位。 因此,如果随后有人尝试取消引用他,这将导致严重错误。



最后要做的是分配另一个指针:

char * t = s-32

实际上,我们这样做-将指针返回到边框。 因此,如果最初的s超出了范围,那么现在我们将其返回到为指针创建的最初分配的卷。 因此,现在t的成分将不再具有高阶位,并且可以轻松地取消引用。



听众:程序如何知道r的剩余量大于堆栈的一半?

Mickens教授:请注意,当我们创建r时 ,我们获得了一个工具代码,该代码将在所有带有指针的操作中工作。 这样我们就可以知道q的位置,并且知道它在Baggy范围内 。 因此,当我们执行q + 16的操作时, Baggy边界工具会知道此初始值来自何处。 然后,如果出现此原始大小的偏移量,则Baggy边界将轻松确定该偏移量大于插槽大小的½。

原则上,使用指针执行操作时,应查看它们是否超过分配的大小。 在某个时候,您有一个指针位于Baggy bounds的边界 ,然后发生某种事情,使其超出了边界。 因此,恰好在这种情况发生时,我们将发现某种“编织”出代码的方式。

希望这是可以理解的。 这是对功课的非常简短的概述,但我希望您能轻松理解它。

因此,我们有一个如下所示的指针:

char * p = malloc(256) ,然后添加指针char * q = p + 256 ,之后我们将尝试取消对该指针的引用。

那么会发生什么呢? 好吧,请注意256是2n序列,因此它将在Baggy范围内 。 因此,当我们再添加256位时,这意味着我们将另一遍传递到Baggy边界的末尾。 与前面的示例一样,此行足够好,但是会导致为q设置一个更高阶的位。 因此,当我们尝试取消引用它时,一切都会爆炸并且必须致电我们的保险代理。 清楚吗?



从这两个示例中,您可以了解Baggy bounds系统的工作原理 。 正如我在上一讲中提到的那样,如果您可以使用静态代码分析来找出一组特定的指针操作是安全的,那么您实际上不需要对每个指针操作进行检测。 我将推迟对某些静态分析的进一步讨论,但可以说您不一定总是需要执行这些数学操作,我们已经在前面进行过检查。

Piazza中提到的另一个问题是:如何确保Baggy边界与以前的非工具库的兼容性。 这个想法是,当Baggy边界初始化边界表时,它们确定所有记录必须在31位之内。 因此,当我们读取边界表时,其中的每个记录都表示形式为2n + 31的值 。 因此,初始化大小为31位的初始边界时,我们假设每个指针的最大可能大小为2n + 31 。 让我给你一个非常简单的例子,这将使这一点变得清楚。

假设我们有一个用于堆的内存空间。 该存储空间由两个部分组成。 在顶部,我们已经使用非工具代码分配了一个堆,在下面是已经使用工具代码分配了一个堆。 那么, 放宽界限会做什么? 您还记得,该系统具有插槽的概念,其大小为16位。 因此,边界表将由2个部分组成,从31位开始。

但是,在运行工具代码时,它将实际上使用Baggy边界算法为表的这一行设置适当的值。



当指针来自内存空间的顶部时,它始终设置为最大可能的2n + 31范围。 这意味着Baggy边界永远不会认为来自非工具库的“指针”指针操作可以超出边界。

这个想法是,在工具代码中,我们将始终对指针执行这些比较,但是如果我们为形式为2n + 31的非工具代码设置指针写入边界,那么我们将永远不会出现解引用错误。 也就是说,我们在Baggy界限代码条目和先前库的非仪器记录之间具有良好的交互作用。

这意味着我们拥有这个系统,这很好,因为使用非工具库时它不会使程序崩溃,但是它有问题。 问题在于,我们永远无法确定由非工具代码生成的指针的边界。 因为例如在此指针获得太多或太少的空间时,我们永远都不会设置高位。 因此,对于使用非仪器代码时发生的操作,我们实际上无法确保其内存安全性。 您也无法确定何时传递超出工具代码到非工具代码大小限制的指针。 在这种情况下,可能会发生无法想象的事情。 如果您从工具代码中拉出了这样的指针,则它的高位设置为1。因此,它似乎具有巨大的尺寸。

我们知道,如果只是将此代码放置在工具代码中,则可以在返回边界时在某些点清除此标志。 但是,如果我们仅将这个巨大的地址传递给非仪器代码,那么它可以做一些难以想象的事情。 它甚至可能使该指针返回到边界,但是我们将永远没有机会清除这个高位。 因此,即使使用此处显示的电路,我们仍然会遇到问题。

受众:如果我们有工具代码来分配内存,它是否使用与属性代码相同的malloc函数?

教授:这是一个微妙的问题。 如果我们在这里考虑这种情况,那么这是严格遵守的,因为我们有两个内存区域,每个内存区域都遵循为此建立的规则。 但是原则上,这将取决于使用所选编程语言的代码。 想象一下,例如,在C ++中,您可以分配自己的限定符。 因此,这取决于代码的某些细节。

观众:预选赛如何检查限制是否设置为31位?

教授:在较低级别,分发算法起作用,因此当您调用未知系统时,指针会向上移动。 因此,如果您有几个分配器,那么它们都会尝试分配内存,每个分配器都有自己的内存块,它们基本上可以正确地为自己保留。 因此,在现实生活中,它可能比高层次上更加分散。

因此,我们上面检查的所有内容都与32位系统中的Baggy边界的操作有关。 考虑使用64位系统时会发生什么。 在这样的系统中,您实际上可以摆脱边界表,因为我们可以在指针本身中存储一些有关边界的信息。

考虑一下常规指针在Baggy边界中的外观。 它由3部分组成。 第一个零部分分配了21个位,该大小分配了另外5个位,这是日志的主要大小,另外38个是通常地址的位。



之所以不会大量限制您正在使用的程序的地址大小,是因为操作系统和/或位于指针的前2个部分中的设备的大多数高阶位由于各种原因而不允许使用该应用程序。 因此,事实证明,我们并没有大大减少系统中使用的应用程序数量。 这就是常规指针的样子。

当我们只有这些指针之一时会发生什么? 好吧,在32位系统上,我们所能做的只是设置一个高阶位,并希望这个东西永远不会超过插槽大小的一半。 但是,既然有了所有这些额外的地址空间,您就可以直接在此指针中将偏移量放置在OOB(边界)边界之外。 因此,我们可以执行如图所示的操作,将指针分为4部分并重新分配其大小。

因此,我们可以获得13个偏移量边界位,也就是说,记下该OOB指针应距其应有的距离。 再一次,您可以将此处指示的对象的实际大小设置为5,将零部分的其余部分设置为21-13 = 8位。 然后跟随我们38位的地址部分。 在此示例中,您将看到使用64位系统的优势。



请注意,这里我们具有常规指针的通常大小,在两种情况下,该大小均为64位,并且其描述是基本的。 这很好,因为在使用“粗”指针时,我们将需要很多单词来描述它们。
我还注意到,非工具代码在这里可以轻松应用,因为它的工作原理和使用的大小与常规指针相同。 例如,我们可以将这些内容放入struct中 ,并且此struct的大小将保持不变。 因此,当我们有机会在64位环境中工作时,这非常好。

受众:为什么在第二种情况下,偏移量位于尺寸的前面,而不是在前一种情况下,如果偏移量较大,会发生什么?

教授:我认为在某些情况下,我们有一些必须解决的局限性问题。 例如对于如果有更多的位,则会出现问题。 但基本上,我认为您看不懂其中某些原因是没有原因的。 除非我现在不考虑的某些严格条件规定了零部分的大小,否则硬件可能会出现问题。

因此,您仍然可以在Baggy bounds系统中启动缓冲区溢出,因为应用上述方法不能解决所有问题,对吗? 如果您使用的是非仪器代码,则可能会遇到另一个问题,因为我们将无法检测到非仪器代码中的任何问题。 您可能还会遇到动态内存分配系统引起的内存漏洞。 如果您还记得的话,在上一讲中,我们讨论了这个指向free malloc的奇怪指针,而Baggy边界无法阻止此类事情的发生。

在上一讲中,我们还讨论了代码指针没有与之关联的边界这一事实。 假设我们有一个结构,其中内存缓冲区位于底部,指针位于顶部,并且缓冲区溢出。 我们假定缓冲区溢出仍在Baggy范围内 。 然后,您必须重新定义此函数指针。 否则,如果我们尝试使用它,则可以将其发送给恶意代码以攻击内存的受控部分。 在这种情况下,边界将无济于事,因为我们没有与这些函数指针关联的边界。

那么,使用Baggy边界的价格是多少? 实际上,这个价格只有4个组成部分。



首先是空间。 因为如果使用“粗”指针,那么很明显,必须使指针变大。 如果您使用的是刚刚讨论的Baggy边界系统,则应保存一个边框表。 该表的插槽大小可让您控制此表的大小,直到用尽分配给它的内存的可能性。

此外,您还增加了CPU的负载,这被迫使用指针执行所有这些工具操作。 由于对于每个或几乎每个指针,都必须使用相同的操作模式检查边界,这将减慢程序的执行速度。

错误警报也存在问题。 我们已经讨论了程序生成超出边界但从未尝试取消引用它们的指针可能发生的情况。 严格来说,这不是问题。 如果它们超出插槽大小的1/2(至少在32位解决方案中),则松散边界将标记这些“ 越界 ”标志。

您将在大多数安全工具中看到的是,错误警报会降低人们使用这些工具的可能性。 因为在实践中,我们都希望我们关心安全性,但是什么真正使人兴奋呢? 他们希望能够将自己的愚蠢照片上传到Facebook,他们希望加快上传过程,等等。 因此,如果您确实希望使用安全工具,则它们应该具有零错误警报。 尝试捕获所有漏洞通常会导致错误警报,这会烦扰开发人员或用户。

它还导致需要编译器支持的非生产性支出。 因为您必须将所有工具都添加到系统中,所以绕过了指针检查等等。

因此,要使用Baggy边界系统,我们必须付出一定的代价,其中包括过度使用空间,CPU负载增加,错误警报以及需要使用编译器。

到此结束宽松边界的讨论。

现在我们可以想到另外两种缓解缓冲区溢出的策略。 实际上,它们更容易解释和理解。

这些方法之一称为不可执行内存 。 他的主要思想是,交换硬件将为内存中的每个页面指示RWX的 3位(读,写和执行)。 , , . 2 , , , , .

, . , , , , - . , . « W X » , , , , , . , , . . , , . ?

- , . , . , , .

, , , , . , , . .

, . – just-in-time , .

-, JavaScript . JavaScript, , - - «» , - «» , x86 . , .
. , , just-in-time W , X . , , .

— . , , , .



, , . GDB, , . , . , . .

, . , , , , , .

: , , — . , , , , , , , - . , , .

, , , GDB , , , , . .
, , , , . - , - , . , . , , .

, ? , . , , . , , , , - , .

27:10

:

麻省理工学院的课程“计算机系统安全”。 3: « : », 2


.

感谢您与我们在一起。 你喜欢我们的文章吗? 想看更多有趣的资料吗? 通过下订单或将其推荐给您的朋友来支持我们,为我们为您开发的入门级​​服务器的独特模拟,为Habr用户提供30%的折扣: 关于VPS(KVM)E5-2650 v4(6核)的全部真相10GB DDR4 240GB SSD 1Gbps从$ 20还是如何划分服务器? (RAID1和RAID10提供选件,最多24个内核和最大40GB DDR4)。

戴尔R730xd便宜2倍?在荷兰和美国,我们有2台Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100电视(249美元起) 阅读有关如何构建基础架构大厦的信息。 使用价格为9000欧元的Dell R730xd E5-2650 v4服务器的上等课程?

Source: https://habr.com/ru/post/zh-CN416839/


All Articles