看我如何跨虚拟机实现Row

消息来源: 51CTO         消息类型: 行业新闻         发布日期:2017-10-12

前言

row-hammer是一种能在物理层面上造成RAM位翻转的硬件漏洞。Mark Seaborn 和Thomas Dullien 两人首次发现可以利用Row-hammer漏洞来获取内核权限。Kaveh Razavi等人通过利用操作系统的“内存重复删除”特性能够有效控制比特位翻转,他们将Row-hammer漏洞利用推向了一个新的台阶。如果他们知道私密文件的内容,那么他们可以利用比特翻转来加载私密文件(比如authorized_keys),并通过削弱authorized_keys文件中的RSA模块,他们能够生成相应的私钥并在共同托管的受害者VM上进行身份验证。

看我如何跨虚拟机实现Row Hammer攻击和权限提升

在这篇文章中,我们的目的是展示不同的攻击情形。在本文我们破坏了正在运行的程序状态,而不是破坏内存加载的文件。libpam是一个很容易遭受攻击的目标,因为它再类unix系统上提供身份验证机制。通过在攻击者的VM中运行row-hammer攻击实例,我们能够通过破坏pam_unix.so模块状态在邻近的受害者VM上成功进行身份验证。在下文中,我们假设两台相邻的虚拟机运行Linux(攻击者VM +受害者VM),而且都托管在KVM虚拟机管理程序上,具体如下图所示:

http://p7.qhimg.com/t01ca8180f978be5750.png

Row-hammer

DRAM芯片由周期性刷新的单元行组成,当CPU对存储器的一个字节请求读/写操作时,数据首先被传送到行缓冲器。在读/写请求执行之后,行缓冲器的内容会被复制回原始行,频繁的读写操作(放电和再充电)可能导致相邻行单元上出现较高的放电率从而引发错误。如果在丢失其电荷之前不刷新它们,则会在相邻的存储器行中引起位翻转。

以下代码足以产生位翻转,该代码从两个不同的存储器行交替读取数据。这种操作是必需的,否则我们只能从行缓冲区读取数据,并且将无法重新激活一行数据,而且还需要使用cflush指令以避免从CPU的缓存中读取数据。Mark Seaborn和Thomas Dullien注意到,如果我们“侵略”其相邻行的数据(行k-1和行k + 1),那么row-hammer效应在受害者行k上将会被放大,具体如下图所示:

row-hammer效应在受害者行k上将会被放大

CHANNELS, RANKS, BANKS AND ADDRESS MAPPING

在包含2个channel的电脑配置中,最多可以插入两个内存模块。一个存储器模块由该模块两侧的存储器芯片组成,而且一个存储器芯片组成一个存储体,一个存储体代表一个存储单元矩阵。下面以我的电脑为例来说明,我的电脑配备了8 GB RAM,具体参数如下所示:

  • 2个channel。
  • 每个channel包含1个内存模块。
  • 每个内存模块包含2个rank。
  • 每个rank包含8个内存芯片。
  • 每个芯片包含8个bank。
  • 每个bank代表2^15行x 2^10列x8 bits。
  • 因此,可用的RAM大小计算如下:

    			
    1. 2 modules * 2 ranks * 2^3 chips * 2^3 banks * 2^15 rows * 2^10 columns * 1 byte = 8 GB 

    当CPU访问一个字节内存时,内存控制器负责执行该读请求。Mark Seaborn已经确定了英特尔Sandy Bridge CPU的物理地址映射机制。该映射与下面给出的内存配置相匹配:

  • 位0-5:字节的低6位用于索引行。
  • 位6:用于选择channel。
  • 位7-13:字节的高7位用于索引列。
  • 位14-16:((addr>>14)&7)^((addr>>18)&7)用于选择bank。
  • 位17:用于选择rank。
  • 位18-33:用于选择行。
  • 正如这篇文章所述,即使CPU请求单个字节,内存控制器也不会寻址芯片并返回8个字节,CPU会使用地址的3 LSB位来选择正确的位。在这篇文章的其余部分,我们将使用上文提供的物理映射机制。

    行选择

    Row-hammer需要选择属于同一个bank的行数据,如果我们无法将虚拟地址转换为物理地址,那么就无法浏览行数据。假如我们已经知道了底层的物理地址映射,那么怎么可以获取一对映射到同一个bank而不同行的地址呢?实际上,从VM的角度来看,物理地址只是QEMU虚拟地址空间中的偏移量,因此为了达到从VM进行“攻击”的目的,我们只要获取Transparent Huge Pages (THP)就可以了。

    THP是一个Linux功能,后台运行的内核线程会尝试分配2 MB的巨大页面。如果我们申请分配一个2 MB大小的缓冲区,那么客户端的内核线程将返回给我们一个THP。主机中的QEMU虚拟内存也是如此,在一段时间后也将被THP替代。正是因为有了THP,我们才可以获得2 MB的连续物理内存,并且一个THP包含了很多行数据,因此我们可以浏览行数据了。

    根据先前提出的物理地址映射,由于行数据以MSB位(位18-33)寻址,那么一个THP包含了8*(2*2^20/2^18)行数据。但是,给定行中的地址是属于不同的channels, banks 以及ranks的。

    从VM进行Row-hammer攻击

    在攻击者VM中,我们尝试分配一个THP缓冲区,对于大小为2 MB的每个内存块,我们通过读取页面映射文件来检查它是否包含在大页面中。然后,对于THP页面中的每两行(r,r+2),我们通过改变channel位和rank位来”敲击”每对地址。但是请注意,物理地址映射中的排列方案使得选择属于同一个bank的地址对具有以下要求:

    令( r_i,b_i )表示行i的地址中标识行和存储体的3个LSB位。对于固定channel和rank,我们从行i开始依次“敲击”行j(j=i+2)中满足以下条件

    	
    1. > r_i^b_i=r_j^b_j 

    的地址开始。

    对于给定的bank b_i,在8个给定的bank b_j中只有三个满足上述条件,具体如下图所示:

    以下是我们优化后的row-hammer代码:

    	
    1. static int 
    2. hammer_pages(struct ctx *ctx, uint8_t *aggressor_row_prev, uint8_t *victim_row, 
    3.              uint8_t *aggressor_row_next, struct result *res) 
    4.     uintptr_t aggressor_row_1 = (uintptr_t)(aggressor_row_prev); 
    5.     uintptr_t aggressor_row_2 = (uintptr_t)(aggressor_row_next); 
    6.   
    7.     uintptr_t aggressor_ch1, aggressor_ch2 , aggressor_rk1, aggressor_rk2; 
    8.     uintptr_t aggressors[4], aggressor; 
    9.   
    10.     uint8_t *victim; 
    11.   
    12.     uintptr_t rank, channel, bank1, bank2; 
    13.   
    14.     int i, p, offset, ret = -1; 
    15.   
    16.     /* Loop over every channel */ 
    17.     for (channel = 0; channel < ctx->channels; channel++) { 
    18.         aggressor_ch1 = aggressor_row_1 | (channel << ctx->channel_bit); 
    19.         aggressor_ch2 = aggressor_row_2 | (channel << ctx->
    服务热线
    025-8660 3700

    微信公众号