Introduction
This post will give some more debugging details on CVE-2015-5165. Based on the poc code in [1], we make some modification to the code according to the information of local machine.
As we know, QEMU is an application running on the host machine. The goal of the VM escape in the guest machine is that we have to retrieve the base address of text segment of QEMU application on the host machine and the base address of the virtual memory that are mapped to emulate the physical memory of guest machine.
The final result of the information leakage is given as the cover image of this post.
Vulnerability Analysis
The vulnerable code is located in the /hw/net/rtl8139.c
uint8_t *saved_buffer = s->cplus_txbuffer; int saved_size = s->cplus_txbuffer_offset; int saved_buffer_len = s->cplus_txbuffer_len; /* ip packet header */ ip_header *ip = NULL; int hlen = 0; uint8_t ip_protocol = 0; uint16_t ip_data_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; } }
With the following debugging script:
set pagination off break hw/net/rtl8139.c:2173 commands x/10gx saved_buffer p/x ip x/8gx ip p/x *ip end cont
Together with the debugging info:
Thread 3 "qemu-system-x86" hit Breakpoint 1, rtl8139_cplus_transmit_one (s=0x5644f0222140) at /home/dango/Security/qemu/hw/net/rtl8139.c:2173 warning: Source file is more recent than executable. 2173 if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) { 0x7fd094060980: 0x5452563412005452 0x0045000856341200 0x7fd094060990: 0x06400040adde1300 0xa8c0010108c0adde 0x7fd0940609a0: 0xfecaefbeadde0201 0x1050bebafecabeba 0x7fd0940609b0: 0x00000000addeadde 0x0000000000000000 0x7fd0940609c0: 0x0000000000000000 0x0000000000000000 $1 = 0x7fd09406098e 0x7fd09406098e: 0x0040adde13000045 0x010108c0adde0640 0x7fd09406099e: 0xefbeadde0201a8c0 0xbebafecabebafeca 0x7fd0940609ae: 0x0000addeadde1050 0x0000000000000000 0x7fd0940609be: 0x0000000000000000 0x0000000000000000 $2 = {ip_ver_len = 0x45, ip_tos = 0x0, ip_len = 0x1300, ip_id = 0xadde, ip_off = 0x40, ip_ttl = 0x40, ip_p = 0x6, ip_sum = 0xadde, ip_src = 0x10108c0, ip_dst = 0x201a8c0}
At this point, we can observe that the variable saved_buffer is actually the malformed packet sent in our exploit.
According to the macro defined as below
#define IP_HEADER_LENGTH(ip) (((ip->ip_ver_len) & 0xf) << 2)
We can find that the value of variable ip_data_len will be 0xffff since it is of type uint16_t.
Furthermore, variable ip_data_len is later used to compute the length of TCP data that are copied into a buffer:
int tcp_data_len = ip_data_len - tcp_hlen; int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen; int is_last_frame = 0; for (tcp_send_offset = 0; tcp_send_offset = tcp_data_len) { is_last_frame = 1; chunk_size = tcp_data_len - tcp_send_offset; } memcpy(data_to_checksum, saved_ip_header + 12, 8); if (tcp_send_offset) { memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size); } /* more code follows */ }
With the following debugging script:
set pagination off break hw/net/rtl8139.c:2243 commands p/x tcp_data_len cont end break hw/net/rtl8139.c:2267 commands p/x tcp_send_offset p/x chunk_size end cont
Together with the debugging info:
Thread 3 "qemu-system-x86" hit Breakpoint 1, rtl8139_cplus_transmit_one (s=0x5644f0222140) at /home/dango/Security/qemu/hw/net/rtl8139.c:2243 2243 for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size) $1 = 0xffeb Thread 3 "qemu-system-x86" hit Breakpoint 2, rtl8139_cplus_transmit_one (s=0x5644f0222140) at /home/dango/Security/qemu/hw/net/rtl8139.c:2267 2267 memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size); $2 = 0x5b4 $3 = 0x5b4
We can find that the actual length to be sent it 0xffeb, which will leak almost 64KB data into the buffer.
The next problem is how we are going to get the base address of text segment and mapped PHY_MEM.
Information Disclosure
In my exploit, my strategy to leak the base address of text segment and the base address of PHY_MEM is different from the strategy in [1].
To leak the base address of text segment, I did not seek to search for a complete object of class Object_Property in memory. Instead I turn to search for one specific function pointer, which is supposed to be a member variable of object of class Object_Property. In my exploit, I pick function property_get_str to leak the base address.
To leak the base address of text segment, I get the base address of PHY_MEM based on the weak ASLR provided by the system. According to my multiple trials, the 12th least significant byte of the base address of PHY_MEM is 7. More importantly, all addresses that satisfy this condition point belong to next adjacent heap of PHY_MEM.
Based on the observations discussed as above, I rewrite the code of original exploit and get the final result as demonstrated in the cover image.
Exploit
The full code of my exploit is given on my github repository [2].
Conlusion
Though I finally rewrite the exploit based on my understanding on the vulnerability, but I do not think that I have a deep understanding on how QEMU emulates the drive operation in the guest machine. I think I need to refer to the source code of Linux Kernel and give a deep discussion on QEMU internals.
Reference
[1] http://www.phrack.org/papers/vm-escape-qemu-case-study.html
[2] https://github.com/dangokyo/QEMU_ESCAPE/blob/master/CVE_2015_5165.c