内存标签技术的安全性分析

阅读量    246251 |   稿费 200

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

一、概述

内存标签是一种可以帮助发现软件中存在的漏洞,同时也能帮助缓解漏洞和漏洞利用的关键技术。在本篇文章中,我们重点探讨内存标签技术与漏洞和漏洞利用缓解技术相关的安全性问题。
概括来说,ARM的内存标签扩展(MTE)工作原理如下:
1、每个16字节对齐的内存区域中,都有一个与之关联的4位标签,即“内存标签”。
2、指针具有一个存储在指针保留位的4位标签,即“地址标签”。
3、在跟踪指针时,会将地址标签与正在访问的内存地址相关联的内存标签进行比较。如果标签不匹配,将会引发异常,这通常会导致崩溃。
下面将详细说明内存标签技术的关键价值所在,我们着眼于分析该技术对近几年流行漏洞缓解所造成的影响:
1、堆超过运行/超过读取(相邻部分)。该类型漏洞占Microsoft所有内存安全CVE漏洞的约13%。缓解方式为对相邻堆内存访问的持久保护。
2、释放后使用(UAF)。该类型漏洞占Microsoft所有内存安全CVE漏洞的约26%。如果所有标签位都被堆分配器所使用,除非攻击者与未初始化的内存或类型混淆漏洞结合利用,否则攻击者的预计成功概率为6%。如果没有内存标签这样的技术,那么其实无法从整体上缓解这一类的漏洞。
3、堆越界读取/越界写入(不相邻部分)。该类型漏洞占Microsoft所有内存安全CVE漏洞的约27%。如果所有标签位都被堆分配器所使用,除非攻击者与未初始化的内存或类型混淆漏洞结合利用,否则攻击者的预计成功概率为6%。如果没有内存标签这样的技术,该类型的漏洞可能会被部分缓解。作为替代方案,需要使用更加安全的语言(例如Rust)或使用更加安全的C++编码时间(例如GSL span)来重写代码。
基于上述的这些数据,似乎内存标签具有比较大的价值。该技术可以提供动态发现软件漏洞的能力,并能针对近年来向微软报告的内存安全漏洞中的13%提供可靠和持久的保护,并且可以为其他具有挑战性的漏洞提供一定程度的缓解,有助于以更低的成本来检测漏洞。我们将对这一点进行更详细的分析。

 

二、影响类别

在本章中,我们将探讨内存标签预期会产生的影响,通常会与特定类型的原语和漏洞利用的原语相关。

2.1 特定类型的原语

我们要考虑的第一个影响类别与初始内存安全冲突相关,而这通常是由特定类型的漏洞所导致。因此,我们将其称为特定类型的原语。举例来说,堆缓冲区溢出会导致攻击者可以访问相邻内存(越界)。每种类型的内存安全漏洞都会映射到一组初始原语。如果可以可靠地阻止这些初始原语,那么就能够针对这种特定的漏洞类型实现完全的缓解。
下面列出了可以借助内存安全漏洞实现的原始原语。需要注意的是,某些类型的漏洞(例如类型混淆)可能会取决于可实现的特定原语(例如相邻内存访问和非相邻内存访问)而有所不同。此外,例如竞争条件这样的临时问题可能会影响到初始原语。
空间类
1、相邻的内存访问
最开始的不安全内存访问,是针对于与被访问对象相邻的部分。当攻击者可能会将不安全的内存访问移动到紧邻的对象时,往往会发生这种情况。例如,传统的缓冲区溢出。
2、不相邻的内存访问
不安全的内存访问,也可以是针对于不与被访问对象相邻的部分。当攻击者可以影响不安全的内存访问的初始位置(例如:引用了不相邻的对象)时,就会发生这种情况。例如:通过索引控制的数组访问。
3、任意内存访问
最初的不安全内存访问可以引用内存中的任何地址。因此我们大多不会将其视为漏洞导致的第一个原语。但是,如果从不受信任的来源读取值,并将其用作内存访问的基址的话,可能会发生这种情况。
4、对象内的内存访问
最初的不安全内存访问始终存在与要访问的最高级对象中。当攻击者可能导致不安全的内存访问的初始位移引用最高级对象内的另一个字段时,可能会发生这样的情况。例如,通过传统的溢出方式就可以实现,其中的数组是结构的成员字段,而其他字段与数组字段相邻。另一个例子是类型混淆漏洞,其中一种类型的对象被错误地解释为另一个号总不兼容类型的对象,允许错误地访问和使用对象字段。
临时类
1、未初始化的内存使用
最初的不安全内存访问是针对尚未初始化的内存。
2、释放后使用(释放状态)
最初的不安全内存访问是对已经释放但尚未重新分配的内存的访问。
3、释放后使用(重新分配状态)
最初的不安全内存访问是对已经释放并已经重新分配(可能会分配为其他类型)的内存的访问。

2.2 漏洞利用原语

我们要考虑的第二个影响类别,与攻击者在使用针对特定类型的原语之后可以实现的后续漏洞利用原语有关。可以根据攻击者能够通过这类原语的组合序列以实现的不同控制状态转换来对这些原语进行建模,最终得到其最终状态。对于每个漏洞利用原语来说,都有5个属性:
1、内存访问方法:这里是指读取、写入或执行的内存访问类型。
2、基址:这里是指用于内存访问的基址。
3、偏移量:这里是指相对于内存访问基址的偏移量。
4、范围:这里是指相对于基址和偏移量而言的,要访问的字节数。
5、内容:这里是指读取、写入或执行的内容。
内存访问的基址、偏移量、范围和内容,可能会使下面四种状态之一:受控值(被攻击者控制)、固定(使用常数,攻击者无法直接修改)、未初始化、未知。为了简单起见,这一节中将原语简化为相对(基址不受控制)内存访问与任意(基址受攻击者控制)内存访问之间的转换。如果攻击者可以通过受控制的偏移量的方式来实现对内存的完全寻址,就认为是存在任意内存访问漏洞。
1、从相对地址写入转换到相对地址读取:损坏的内存用于后续读取的偏移量或范围。
2、从相对地址写入转换为任意地址读取:损坏的内存用于后续读取的基址。
3、从相对地址写入转换为任意地址写入:损坏的内存用于后续写入的基址。
4、从任意地址写入转换为相对地址读取:损坏的内存用于后续读取的偏移量或范围。
5、从任意地址写入转换为任意地址读取:损坏的内存用于后续读取的基址。
6、从相对地址读取转换为相对地址写入:读取值用于后续写入的偏移量或范围。
7、从相对地址读取转换为任意地址读取:读取值用于后续读取的基址。
8、从相对地址读取转换为任意地址写入:读取值用于后续写入的基址。
9、从相对地址读取转换为任意地址执行:读取值用于后续执行的基址。
10、从任意地址读取转换为任意地址执行:读取值用于后续执行的基址。
因此,初始漏洞导致的特定类型原语与漏洞利用原语的关联性如下:
1、相邻的内存访问:相对地址写入、相对地址读取
2、非相邻的内存访问:相对地址写入、相对地址读取
3、任意内存访问:任意地址读取、任意地址写入
4、对象内内存访问:相对地址写入、相对地址读取
5、未初始化的内存使用/泄露:相对地址读取、相对地址写入、任意地址读取、任意地址写入
6、释放后使用(释放状态):相对地址读取、相对地址写入、任意地址读取、任意地址写入
7、释放后使用(已分配状态):相对地址读取、相对地址写入、任意地址读取、任意地址写入

 

三、内存标签技术

本章假设内存标签的设计和实现具有以下属性:
1、内存的每个16字节对齐区域都有4位与之关联的标签(内存标签)。
2、4个保留的虚拟地址位用于存储分配器分配的与指针关联的地址标签。
3、在发生内存访问时,指针中的地址标签必须与分配给要引用的内存区域的内存标签相匹配。如果不匹配,将会引发异常,这将导致进程终止。
4、修改堆分配器,以初始化所分配的内存标签,并为分配期间返回的指针设置相应的地址标签。
a)所有堆分配都是16字节对齐的,并且分配的大小是16的倍数。
b)堆分配器将确保相邻的分配始终使用不同的标签,从而避免相邻内存的可互相访问。
c)标签是随机分配的。
5、不存在特殊的标签状态,指针中的地址标签值必须与要访问的内存区域的所有内存标签值相匹配。
a)某些软件实现可能需要在场景中禁用标签检查,而这一过程将会影响整体的安全性和实现的复杂程度。其中的一种情况是跨信任边界映射共享内存(例如:内核映射用户空间内存),在这一过程中不能接受标签检查失败。MTR通过名为标签检查覆盖(PSTATE.TCO)的处理器状态来支持这种有选择性的禁用,该状态在设置为1时会禁用标签检查。
b)此外,并非所有的内存访问都会生成标签检查。例如,在ARM体系结构手册中提供了被归类为MTE6标签但未进行检查的内存访问列表。
在我们的设计中,我们认为下列内容不包含在内:
1、栈和全局缓冲区不会被标记(例如:它们将使用零标记),因为这些地方很少会作为漏洞利用中最初损坏的目标,并且这有助于简化对内存标签支持的设计和实现。
2、释放分配时,不会重新标记堆分配,并且不会将堆分配设置为保留的“释放”状态,因为这样的性价比较低。相反,堆分配在释放时将保留先前的标签。

3.1 对特定类型原语的影响

下表总结了特定类型的原语(即初始原语)对于映射到每个原语的CVE数量占比影响。其中统计的内存安全CVE漏洞数量,选取在2015-2019年期间发现的漏洞。
空间类
1、相邻的内存访问(占比约13%)
完全缓解。对于堆分配的内存,越界访问相邻的内存将会始终出错。由于攻击者无法修改初始访问地址的标签位,因此无论攻击者是否掌握相邻分配的标签,都无法实现攻击。
2、不相邻的内存访问(占比约27%)
一定概率的缓解。越界访问非相邻的内存将有可能失败。如果堆分配器使用了所有标签位,那么初始内存访问成功的概率将为6%左右。
如果攻击者掌握了非相邻分配使用的标签,那么他们可以创建条件,以确保可以成功实现非相邻内存的访问。
无法缓解对非堆内存的非相邻越界访问。
3、任意内存访问(占比约2%)
小幅度缓解。如果地址标签相匹配,那么尝试访问带有标签的内存(例如:堆)的过程可能会失败,但尝试访问不带标签的内存(例如:栈、全局变量等)的过程将会成功。这意味着,实际上还需要ASLR来共同提供保护。
4、对象内的内存访问(暂无可用统计数据)
没有缓解。由于整个对象具有相同的标签,因此可以读取和写入对象内容。这是必要的,以便memcpy这类的操作可以复制原始的旧数据对象。这类漏洞可能泄露有效的内存,从而帮助建立可靠的漏洞利用。
临时类
1、未初始化的内存使用(占比约12%,超过一半属于信息泄露漏洞)
没有缓解,或者仅有一定概率的缓解。这一点取决于漏洞的性质。未初始化的内存泄漏往往能有效泄露出有效的内存标签,或者泄露不适用标签的内存地址(即:栈、全局变量等)。但是,这里还存在其他技术可以解决此类问题,例如InitAll7、Pool Zeroing等。
2、释放后使用-释放状态(全部UAF占比约26%)
没有缓解。但由于攻击者通常需要重新分配释放的内存,因此这一点通常对攻击者来说没有帮助。
3、释放后使用-重新分配状态(全部UAF占比约26%)
一定概率的环节。通过悬空指针使用先前释放的内存的过程可能会失败。如果所有标签位都由堆分配器使用,那么内存访问的成功概率仅为6%左右。在攻击者掌握重新分配给内存的标记与分配给原始分配的标签匹配,那么他们可以创建条件,以确保释放后能成功利用。
对于仅有一定概率能够缓解的原语,我们需要考虑结合漏洞利用原语,同时需要考虑攻击者如何能组合利用这些原语。
在探究对漏洞利用原语的影响之前,我们需要首先考虑攻击者可能采用的策略,推断攻击者会如何利用这些尚未完全缓解的初始原语,这对于我们的防御来说很有帮助。
3.1.1 发现分配到内存的标签
如果能够掌握分配给内存分配的标签,攻击者往往就能够可靠地实现不相邻的内存访问、任意内存访问,并能够使用已经释放并重新分配的内存。而这些信息,可能用于后续的原语。
要实现这种攻击,需要攻击者满足以下条件:
1、能够读取所需内存分配的标签;
2、能够创造条件,使不安全的内存访问使用的指针地址标签与分配给所需内存分配的内存标签相匹配。
我们估计这一步骤对下面这些漏洞利用将会有所帮助:
1、未初始化的内存漏洞:可能允许攻击者读取指针(包括地址标签位)。可能允许攻击者有效地引发类型混淆,从而导致某些代码跟随未初始化的指针,恰好指向有效的对象。
2、对象内内存访问:攻击者可能读取对象中包含的指针,然后破坏指针(或其他数据),而不会出现标签不匹配的情况。
3、泄露内存标签或损坏内存。
3.1.2 发现OPTS-OUT标签的内存
我们在这里假设内存标签仅适用于堆。这意味着,如果攻击者可以找到零标签内存(例如:全局映像、栈、从堆外部分配的其他内存)的虚拟地址,那么攻击者可以通过覆盖指针并使用已知地址标签的方式来破坏这一部分内存。
3.1.3 修改分配给内存的标签
如果分配给内存区域的标签可以修改为攻击者指定的值,那么攻击者就能够可靠地执行非相邻的内存访问、任意内存访问、使用已释放并已重新分配的内存。要实现这种攻击,需要攻击者满足以下条件:
1、具备将所需内存分配标签设置为特定值的能力;
2、能够创造条件,使不安全的内存访问使用的指针地址标签与分配给所需内存分配的内存标签相匹配。
3.1.4 可预测标签的部分覆盖
在分配内存区域的块时,分配标签交替模式的堆分配器可以更容易地利用部分直真覆盖来读取或写入区域内的其他块。如果攻击者能够修改指针的低位,使得指针引用的分配时相邻两个块的倍数,同时保留相同标签,那么就可能会发生这种情况,可以确保内存成功访问。为了缓解这种利用方式,应该使用不重复、不可预测的标签。
3.1.5 猜测标签分配
由于分配器可用于随机标签分配的范围数量有限,可以重复进行多次漏洞利用的攻击者可能会以相当高的成功率猜测到分配的标签。这意味着,内存标签为各种原语所提供的保护不一定能适用于所有的情况下。然而,即使攻击者成功利用了这一方法,由于失败的尝试会生成崩溃转储,这一攻击过程也非常容易被检测。
3.1.6 侧信道(Side Channel)
可能存在影响内存标签的侧信道漏洞。我们认为下列方式是可行的,应该纳入到威胁建模之中并引起足够关注:
1、攻击者可以破坏ASLR并可靠地定位映像、栈、对和其他内存区域。这些侧信道已经存在于许多上下文值中,例如JavaScript—>浏览器进程、用户模式—>内核模式。
2、攻击者可以推断内存的内存标签值,而不会触发内存标签冲突。
3、攻击者可以推断出指针的地址标签值,而不会触发内存标签冲突。
其中的1和2不会破坏针对一阶原语的内存标签。
其中的2和3可以增加非相邻越界访问、释放后利用的成功概率。通过这种侧信道的方式,唯一能够保证无法访问相邻内存的机制将会失效。例如,由于进程内存在侧信道,浏览器应用程序可能会存在被利用的原语。这些通道可以用于读取浏览器渲染器进程中的内存。因此,像是浏览器这样的应用程序将无法确保内存标签缓解技术的有效性。

3.2 对漏洞利用原语的影响

我们假设攻击者成功使用了特定类别的原语,那么在这时,就需要考虑攻击者如何利用这些原语实现对目标应用程序的更多控制。
3.2.1 NTDLL竞争条件
如果攻击者可以获得绕过内存标签的特定类别原语,那么就可以构建自己的漏洞利用程序,尽量减少失败的尝试次数。针对这一状况,我们还没有进行太多的研究,但攻击者很可能会对其开展长期而深入的研究。我们在这里假设,满足攻击条件的一个最低门槛,是NTDLL的条件竞争。
我们预设映像(包括全局变量)都不在内存标签的范围之内。
NTDLL会跟踪以下内容的全局变量:
1、所有堆全局变量,从中可以读取所有堆结构,可以从中读取所有堆分配和标签。
2、进程中所有其他映像的基址。
如果攻击者可以找到NTDLL,并使用任意读取原语来读取其全局变量,我们就可以假定攻击者可以使用相匹配的正确标签来读取进程的全部地址空间。我们知道,攻击者在编写漏洞利用时,会尽可能降低访问失败的次数,而不会倾向于去进行已知标签、保证能访问成功的内存访问。
我们以下面的结构为例,这样的结构可能存在于任何二进制文件中:
struct
{
PVOID ArbitraryPtr; // Attacker can cause reads and writes through this pointer
// Arbitrary read/write if it can be corrupted.
CHAR LocalArray[10]; // Local array, attacker can trigger reads from this array.
SIZE_T LocalArrayIndex; // Index to read from the local array. Not bounds checked
// at runtime since the code knows it will never exceed 9.
// This assumption is not true if memory corruption happens.
PVOID NtdllPtr; // Pointer to some code in ntdll
} MyStruct;
如果攻击者能够损坏上述结构,则他们可以实现:
1、破坏LocalArrayIndex,然后使用另一个API与该对象进行交互,并读取LocalArray范围以外的内容。这样的相对地址越界读取允许攻击者泄露NtdllPtr,从而掌握NTDLL的地址。
2、破坏ArbitraryPtr,将地址偏移量设置到NTDLL。这样一来,攻击者就可以读取NTDLL全局变量,并将地址泄露给堆跟踪结构。
3、使用堆跟踪结构的地址损坏ArbitraryPtr,并泄露每个堆分配和相应标签的地址。
通过这种方式,很快就将其转换为了任意读写原语。在我们的示例中,唯一可能发生问题的位置就是对该结构的首次访问。如果攻击者盲目地使用相对地址写入来破坏结构,他们成功的概率只有6%。如果攻击者在掌握一些信息的前提下更加可靠地破坏这一结构,那么就会有100%的成功概率,后续的所有过程也都迎刃而解。关于访问安全,具体如下:
1、LocalArray中的相对地址读取:如果超出对象内的边界范围,则不再受到内存标签的保护。
2、通过ArbitraryPtr读取/写入到NTDLL全局变量:映像全局变量不会受到内存标签的保护。
3、通过ArbitraryPtr读取/写入到堆结构:指向堆结构的指针被泄露(包括标签位),因此ArbitraryPtr在指针中将具有正确的标签位。
3.2.2 NTDLL竞争条件的收获
通过这样的尝试,我们可以得出以下结论:
1、内存标签最有可能实现对初始原语的保护。
2、如果初始原语被成功利用,那么就应该假设内存标签已经失效。
3、在某些情况下,攻击者需要进行多次访问尝试。我们建议将这些方案作为一种备选方案,但不要将其视为100%可靠的方案。
3.2.3 对第N阶原语的影响
下面列举了内存标签对一阶以上原语的影响。需要注意的是,我们在分析过程中会将这些原语进行单独分析,但实际上,可以将多个原语的前提条件进行组合。举例来说,如果两个原语各自有2个前提条件,但这并不代表攻击者必须要尝试4次内存访问。
1、非相邻的内存访问
(1)从相对地址写入到相对地址读取
示例:

// write “controlled” to &corrupted 
wbuf[offset_to_corrupted] = controlled; 
// relative read using corrupted index 
rvalue = rbuf[corrupted];

分析:
a) 分配给&corrupted的内存标签必须与wbuf的地址标签匹配,否则将产生错误。
b) 分配给&rbuf[corrupted]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
(2)从相对地址写入到任意地址读取
示例:

// write “controlled” to &corrupted_rptr 
wbuf[offset_to_corrupted_rptr] = controlled; 
// arbitrary read using corrupted pointer 
read_value = *corrupted_rptr;

分析:
a) 分配给&corrupted_rptr的内存标签必须与wbuf的地址标签匹配,否则将产生错误。
b) 分配给*corrupted_rptr的内存标签必须与corrupted_rptr的地址标签匹配,否则将产生错误。
(3)从相对地址写入到任意地址写入
示例:

// write “controlled” to &corrupted_wptr 
wbuf[offset_to_corrupted_wptr] = controlled; 
// arbitrary write using corrupted pointer 
*corrupted_wptr = write_value;

分析:
a) 分配给&corrupted_wptr的内存标签必须与wbuf的地址标签匹配,否则将产生错误。
b) 分配给*corrupted_wptr的内存标签必须与corrupted_wptr的地址标签匹配,否则将产生错误。
(4)从相对地址写入到任意地址执行
示例:

// write “controlled” to &corrupted_fptr 
wbuf[offset_to_corrupted_fptr] = controlled; 
// arbitrary execute using corrupted function pointer 
(*corrupted_fptr)(...);

分析:
a) 分配给&wbuf[offset_to_corrupted_fptr]的内存标签必须与wbuf的地址标签匹配,否则将产生错误。
(5)从相对地址读取到相对地址写入
示例:

// read “controlled” from offset in memory 
controlled = rbuf[offset_to_controlled]; 
// relative write using controlled as displacement 
wbuf[controlled] = write_value;

分析:
a) 分配给&controlled的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
b) 分配给&wbuf[controlled]的内存标签必须与wbuf的地址标签匹配,否则将产生错误。
(6)从相对地址读取到任意地址写入
示例:

// read “controlled_wptr” from offset in memory 
controlled_wptr = rbuf[offset_to_controlled_wptr]; 
// arbitrary write using controlled pointer 
*controlled_wptr = write_value;

分析:
a) 分配给&rbuf[offset_to_controlled_wptr]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
b) 分配给*controller_wptr的内存标签必须与controller_wptr的地址标签匹配,否则将产生错误。
(7)从相对地址读取到任意地址读取
示例:

// read “controlled_rptr” from offset in memory 
controlled_rptr = rbuf[offset_to_controlled_rptr]; 
// arbitrary read using controlled pointer 
value = *controlled_rptr;

分析:
a) 分配给&rbuf[offset_to_controlled_rptr]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
b) 分配给*controlled_rptr的内存标签必须与controller_rptr的地址标签匹配,否则将产生错误。
(8)从相对地址读取到任意地址执行
示例:

// read “controlled_fptr” from offset in memory
controlled_fptr = rbuf[offset_to_controlled_fptr];
// arbitrary execute using controlled function pointer
(*controlled_fptr)(...);

分析:
a) 分配给&rbuf[offset_to_controlled_fptr]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。

2、未初始化使用
从相对/任意地址读取/写入到相对/任意地址读取/写入/执行
示例:

// relative write to an uninitialized write ptr 
uninitialized_wptr[offset_to_controlled] = value; 
// relative read using a controlled index 
read_value = rbuf[controlled];

分析:
a) 攻击者需要能够将uninitialized_wptr初始化为所需的地址标签,并且必须与用于&controlled的内存标签相匹配,否则会产生错误。
b) 分配给&rbuf[controlled]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。

3、释放后使用(重新分配)
(1)从相对地址写入到相对地址读取
示例:

// relative write by writing to a field of a dangling ptr 
dangling->offset_to_controlled = write_value; 
// relative read using a controlled displacement 
read_value = rbuf[controlled];

分析:
a) 攻击者必须能够放置&controlled,使得&dangling->offset_to_controlled与之重叠,并且地址标签位需要与&controlled的内存标签相匹配。
b)分配给&rbuf[controlled]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
(2)从相对地址写入到任意地址读取
示例:

// arbitrary read by writing to a field of a dangling ptr 
dangling->offset_to_controlled_rptr = write_value; 
// arbitrary read using controlled pointer 
read_value = *controlled_rptr;

分析:
a) 攻击者必须能够放置&controlled_rptr,使得&dangling->offset_to_controlled_rptr与之重叠,并且地址标签位需要与&controlled_rptr的内存标签相匹配。
b)分配给*controlled_rptr的内存标签必须与controlled_rptr的地址标签匹配,否则将产生错误。
(3)从相对地址写入到任意地址写入
示例:

// arbitrary write by writing to a field of a dangling ptr 
dangling->offset_to_controlled_wptr = write_value; 
// arbitrary write using controlled pointer 
*controlled_wptr = write_value;

分析:
a) 攻击者必须能够放置&controlled_wptr,使得&dangling->offset_to_controlled_wptr与之重叠,并且地址标签位需要与&controlled_wptr的内存标签相匹配。
b)分配给*controlled_wptr的内存标签必须与controlled_wptr的地址标签匹配,否则将产生错误。
(4)从任意地址写入到相对地址读取
示例:

// relative read by writing to an arbitrary address 
*dangling->ptr_to_controlled_offset = write_value; 
// relative read using controlled offset 
read_value = rbuf[controlled_offset];

分析:
a) 攻击者必须能够放置&controlled_offset,使得&dangling->ptr_to_controlled_offset与之重叠,并且地址标签位需要与&rbuf的内存标签相匹配。
b)分配给&rbuf[controlled_offset]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
(5)从任意地址写入到任意地址读取
示例:

// arbitrary read by writing to an arbitrary address 
*dangling->ptr_to_controlled_ptr = write_value; 
// arbitrary read using controlled ptr 
read_value = *controlled_rptr;

分析:
a) 攻击者必须能够放置&controlled_rptr,使得&dangling->ptr_to_controlled_ptr与之重叠,并且地址标签位需要与controlled_rptr的内存标签相匹配。
b)分配给
controlled_rptr的内存标签必须与controlled_rptr的地址标签匹配,否则将产生错误。
(6)从相对地址读取到相对地址写入
示例:

// read “controlled” from offset in memory 
controlled = dangling->offset_to_controlled; 
// relative write using “controlled” 
wbuf[controlled] = write_value;

分析:
a) 攻击者必须能够放置&controlled,使得&dangling->offset_to_controlled与之重叠,并且地址标签位需要与&controlled的内存标签相匹配。
b)分配给&wbuf[controlled]的内存标签必须与rbuf的地址标签匹配,否则将产生错误。
(7)从相对地址读取到任意地址读取
示例:

// read “controlled_rptr” from offset in memory 
controlled_rptr = dangling->offset_to_controlled_ptr; 
// arbitrary read using “controlled_rptr” 
read_value = *controlled_rptr;

分析:
a) 攻击者必须能够放置受控数据,使得&dangling->offset_to_controlled_ptr与之重叠,并且地址标签位需要与&controlled_ptr的内存标签相匹配。
b)分配给*controlled_rptr的内存标签必须与controlled_rptr的地址标签匹配,否则将产生错误。
(8)从相对地址读取到任意地址写入
示例:

// read “controlled_wptr” from offset in memory 
controlled_wptr = dangling->offset_to_controlled_ptr; 
// arbitrary write using “controlled_wptr” 
*controlled_wptr = write_value;

分析:
a) 攻击者必须能够放置受控数据,使得&dangling->offset_to_controlled_ptr与之重叠,并且地址标签位需要与&controlled_wptr的内存标签相匹配。
b)分配给*controlled_wptr的内存标签必须与controlled_wptr的地址标签匹配,否则将产生错误。
(9)从相对地址读取到任意地址执行
示例:

// read “controlled_fptr” from offset in memory 
controlled_fptr = dangling->offset_to_controlled_ptr; 
// arbitrary execute using “controlled_fptr” 
(*controlled_fptr)(...);

分析:
a) 攻击者必须能够放置受控数据,使得&dangling->offset_to_controlled_ptr与之重叠,并且地址标签位需要与&controlled_fptr的内存标签相匹配。
(10)从任意地址读取到任意地址执行
示例:

// read “controlled_fptr” from pointer in memory 
controlled_fptr = *dangling->ptr_to_controlled_fptr; 
// arbitrary execute using “controlled_fptr” 
(*controlled_fptr)(...);

分析:
a) 攻击者必须能够放置受控数据,使得&dangling->ptr_to_controlled_fptr与之重叠,并且地址标签位需要与&controlled_fptr的内存标签相匹配。
b)分配给*ptr_to_controlled_ptr的内存标签必须与ptr_to_controlled_ptr的地址标签匹配,否则将产生错误。

 

四、总结

我们认为,本文中所讨论的内存标签的实现方式可能会为同时发现漏洞和缓解漏洞提供一定的帮助:
1、内存标签可以提供大规模发现更多不同类型的内存安全漏洞的能力。
2、内存标签可以持久地缓解比较常见的一些内存安全漏洞(即:相邻堆内存越界读取/写入),这类漏洞占近几年来所有内存安全漏洞的13%左右。
3、内存标签可以在一定概率上缓解其他大多数类别的内存安全漏洞(例如:释放后使用、不相邻的堆内存越界读取/写入),这类漏洞占近几年来所有内存安全漏洞的53%左右。
4、由于内存标签可以提供一定概率的保护,因此如果攻击者希望不断尝试,目标可能会遭受到暴力攻击。此外,如果针对大量目标进行漏洞利用尝试,攻击者成功的概率将会增加。
5、我们不应该假设内存标签在一定概率上提供的保护对于每次内存访问来说都是有效的。换而言之,如果攻击者成功访问了其中的一个内存标签,那么后续成功访问其他内存区域的概率就明显增加。
6、如果存在可以利用的侧信道(例如:Web浏览器),内存标签提供的一定概率的保护有可能会失效。在这种情况下,攻击者可能会利用侧信道来发现已经分配的标签。

 

五、附录

自2015年到2019年,我们收集了微软针对不同类型的内存安全CVE漏洞的统计数据,详细数据如下:
CVE漏洞数量:3353
内存安全CVE数量:2117
栈损坏CVE数量:39
堆越界读取CVE数量:385
堆损坏CVE数量:454
释放后使用CVE数量:557但是
类型混淆CVE数量:294
未初始化使用CVE数量:250
未初始化使用(仅导致信息泄露)CVE数量:157
相邻空间安全漏洞CVE数量:303
相邻空间安全漏洞(堆相关)CVE数量:278
非相邻空间安全漏洞CVE数量:595
非相邻空间安全漏洞(堆相关)CVE数量:563
任意指针取消引用CVE数量:41

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多