CVE-2020-14364 QEMU 逃逸 漏洞分析 (含完整 EXP)

king 2020-09-19
系统与内网安全 发布于 2020-09-19 12:44:50 阅读 349 评论 0

CVE-2020-14364 qemu逃逸漏洞是一个位于QEMU USB模块的数组越界漏洞,可以轻松实现一定范围内任意地址读写从而实现逃逸并RCE。8月25日网上公开了POC部分代码与利用思路,本文以此为参照,记录本地利用复现EXP过程.

准备工作

编译

本文使用的是1.5.3版本,从官网下载得到源码,关闭优化并开启调试信息,方便调试

启动

  • 与常见的qemu启动脚本相比,在首行加上一个gdb –args 即可用gdb启动qemu

  • 需要启用usb并添加一个usb设备

本次复现中使用的启动脚本如下,添加了一个usb2.0设备

#!/bin/sh
gdb --args \
./qemu-system-x86_64 \
-m 128M \
-usb \
-device usb-ehci,id=ehci \
-device usb-tablet,bus=ehci.0 \
-kernel ./bzImage \
-initrd  ./rootfs.img \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kalsr uid=0" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-smp cores=2,threads=1 \
-enable-kvm \
-cpu kvm64,+smep

定位

通过lspci命令,查看usb设备所在的总线号。此处是03.0

分析

首先简单概览一下漏洞情况

usb_process_one


对usb包进行处理的函数,参数是USBPacket结构,根据Packet中的pid进行设置、读、写三个操作中的一个

do_token_setup

  • s->setup_len的值来自setup_buf

  • s->setup_len的长度检查没通过时,未对setup_len清零&未设置s->setup_status

  • 处理下一个USBPacket时,s->setup_len不会被重置

do_token_in&out

  • 两者的关键部分类似,主要在于对len的检测是若大于p->iov.size则会直接设置为p->iov.size,并且p->iov.size是我们发送USBPacket中的可控数据,因此这个检测可以轻松bypass

  • s->setup_state需要提前设置为SETUP_STATE_DATA

  • s->setup_buf[0]也需要提前构造,并决定了是读取还是写入

usb_packet_copy

缓冲区复制的方向是读还是写,由USBPacket *p->pid决定

QH&QTD

参照网上一篇博客的EHCI驱动分析,可以简单了解下概念

利用基础

关于QEMU类漏洞利用的交互方式,可以参考这篇文章中的MMIO部分,本文不再赘述

qemu pwn-基础知识:xz.aliyun.com/t/6562

交互

通过mmio方式的write操作时,会调用ehci_opreg_write

mmio_write时,根据addr与val会触发不同的功能与交互。(注意此处的addr还需要加上0x20的偏移,具体原因可自行查看源码)

发包

发送一个基本USBPacket的过程如下,需要构造的部分主要是红框中的部分

qtd->token包含了p->pid和 p->iov.size ,而p->pid决定了 USBPacket 进入 do_token_setup / in / out

越界读写

step1

仅需构造正常的packet,在do_token_setup中设置s->setup_state = SETUP_STATE_DATA ,为后面做准备

step2

继续进入do_token_setup,设置s->setup_len为0x5000,sizeof(s->data_buf)的数值为0x2000,因此可以造成数组越界。且此时s->setup_state仍然为2(SETUP_STATE_DATA)

step3

由于在step1中 s->setup_buf[0]被设置为了0,因此现在只能进入do_token_out中进行写入操作

观察一下在data_buf后方,越界写入可劫持的数据成员:


struct USBDevice {
    DeviceState qdev;
    USBPort *port;
    char *port_path;
    void *opaque;
    uint32_t flags;

    /* Actual connected speed */
    int speed;
    /* Supported speeds, not in info because it may be variable (hostdevs) */
    int speedmask;
    uint8_t addr;
    char product_desc[32];
    int auto_attach;
    int attached;

    int32_t state;
    uint8_t setup_buf[8];
    uint8_t data_buf[4096];
    int32_t remote_wakeup;
    int32_t setup_state;
    int32_t setup_len;
    int32_t setup_index;

    USBEndpoint ep_ctl;
    USBEndpoint ep_in[USB_MAX_ENDPOINTS];
    USBEndpoint ep_out[USB_MAX_ENDPOINTS];

    QLIST_HEAD(, USBDescString) strings;
    const USBDesc *usb_desc; /* Overrides class usb_desc if not NULL */
    const USBDescDevice *device;

    int configuration;
    int ninterfaces;
    int altsetting[USB_MAX_INTERFACES];
    const USBDescConfig *config;
    const USBDescIface  *ifaces[USB_MAX_INTERFACES];
};

注意到data_buf后方的setup_index与setup_len,并且setup_buf位于其前方

usb_packet_copy(p, s->data_buf + s->setup_index, len);

usb_packet_copy的起始位置是data_buf的地址加上setup_index,因此若将其越界改为负数,下次写入时就可以从setup_buf开始写入,控制setup_buf[0]的数值,使得可以进入do_token_in进行越界读取操作

通过越界写入改写setup_index,注意通过p->iov.size控制越界范围,不要破坏额外的数据,否则会造成crash

第二次写入时将setup_buf[0]设置为了 USB_DIR_IN (0x80),并同时继续越界写入设置setup_index,使下次usb_packet_copy的指针指向data_buf后方的s->ep_ctl.dev

通过dev可以获取到s->data_buf的地址,以这个地址为base加减setup_index即可实现0xffffffff范围的任意读写,已经足以完成逃逸

leak text

观察data_buf后方,固定偏移处存在一些指向text段的指针,可以借此得到代码段地址,以及位于其中的system PLT地址

RCE

观察ehci_update_irq,可以发现它调用了EHCIState->irq结构体中的函数指针,并且其参数也在irq结构体内。通过之前的越界读写得到EHCIState地址,劫持irq结构即可完成逃逸并任意代码执行

EXP演示

本地测试环境,开启ASLR

结语

EXP只在已知qemu二进制文件的情况下可用,会开源上传至逐日实验室的Github仓库(可直接点击文末链接进行跳转),仅供用于学习参考

由于漏洞复现过程中较为匆忙,先前对QEMU的研究也不够深入,因此本文中若有谬误也请师傅们谅解

参考链接

1.QEMU CVE-2020-4364漏洞分析(含POC演示):

www.freebuf.com/vuls/247829.html

2.linux EHCI DRIVER之中断处理函数ehci_irq()分析(二):

https://blog.csdn.net/yaongtime/article/de...

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!
请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!