漏洞利用幽灵'msg_msg'以及VED的防护策略 November 13, 2022 | 4 最小读取

漏洞利用幽灵'msg_msg'以及VED的防护策略

为什么是结构体 msg_msg

结构体长度可以由用户空间决定

首先, msg_msg 结构体的长度可以由用户空间间接地控制,这意味着 msg 可以落在指定类型的 cache 上。任何大小从 sizeof(struct msg_msg) 到 PAGE_SIZE 的 object 存在 UAF/double free 的漏洞时,msg_msg 都可以申请到该 cache 上实现堆喷漏洞利用。下面是 msgsnd() 的原型:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

msgsz 将传递给 alloc_msg, 并最终使用 kmalloc 分配到相应的 cache 上:

static struct msg_msg *alloc_msg(size_t len)
{
    struct msg_msg *msg;
    ...
    size_t alen;

    alen = min(len, DATALEN_MSG);
    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT); // alloc msg
    if (msg == NULL)
        return NULL;

    ...

    len -= alen;
    ...
    while (len > 0) {
        struct msg_msgseg *seg;
        ...

        alen = min(len, DATALEN_SEG);
        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); // alloc msg_seg
        if (seg == NULL)
            goto out_err;
        ...
    }

    return msg;
    ...
}

因此在漏洞利用中,很容易通过精心计算 msg_msg 的长度,使其落在存在 UAF/double free 漏洞的 object 附近, 进一步完成堆喷和内存污染。

实现任意读

由于内核会将 msgsnd() 传入的缓存切割成较短的段,并且用指针来连结成链表:

struct msg_msg {
    struct list_head m_list;
    long m_type;
    size_t 
    
    ;       /* message text size */
    struct msg_msgseg *next;
    void *security;
    /* the actual message follows immediately */
};

struct msg_msgseg {
    struct msg_msgseg *next;
    /* the next part of the message follows immediately */
};

为了读取 msg 的数据缓存, load_msg 实现了这样一个循环来读取每一个切割成的段:

struct msg_msg *load_msg(const void __user *src, size_t len)
{
    ...
    if (copy_from_user(msg + 1, src, alen))
        goto out_err;

    /* read segments one by one */
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        src = (char __user *)src + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_from_user(seg + 1, src, alen))
            goto out_err;
    }

    ...

    return msg;
    ...
}

缓存的长度并不确定, 读取大小完全决定于 size_t m_ts, 一旦size_t m_tsstruct msg_msgseg *next" 被污染, 用户空间使用 msgrcv() 就能实现任意读。甚至只污染 size_t m_ts` 的情形下,也能实现越界读取。 这意味着被污染的 msg 可以用来做信息泄漏。

这里有一些使用 msg_msg 达成信息泄漏的例子: Four Bytes of Power: Exploiting CVE-2021-26708 in the Linux kernel - Achieving arbitrary read: overwrite next and size_t m_ts achieve arbitrary read.

CVE-2021-22555: Turning \x00\x00 into 10000$ - Exploring struct msg_msg: out-of-bound read.

Linux Kernel Exploit Development: 1day case study: out-of-bound read.

可被用来实现任意 free

以下这些例子,展现如何在使用已有的代码路径和被污染的 msg 达成任意free:

任意释放已经非常接近完成提权,因为他能够实现在任意的结构体的 UAF, 比如一个带有函数指针的结构体被污染以后,就能实现控制流劫持来完成ROP。更直接的是,最近有许多研究人员发现直接对 cred_jar cache 的堆喷能够实现提权。 使用任意 free 来释放当前进程的 cred 指针,在用户空间触发新的高权限进程,比如:sshd, su, sudo, 其中的某个 cred object 会落在当前进程 cred 指针所指的位置,这样提权就已经完成了。

关于 DirtyCred 的一些故事

cred_jar 堆喷现在被一些研究人员叫做 “DirtyCred”, 其作者声称是他们发现的新方法,实际上则是已经被使用并且在此之前被多个研究人员公开发布。我们曾经揭露过该作者如何收集 Syzkaller 社区和 HardenedLinux 社区的 Harbian-QA 项目的信息,重新实现或者重现这些内容,重命名他们,然后撰写论文声称是他们自己独立发明的,并且在论文引用上分毫不提对社区经验的借鉴。同样的情况, DirtyCred 也是类似的, 可以知道这种方法已经被大量的研究人员使用

时间表:

Mar 19, 2020: xfrm_poc_RE_challenge Vitaly Nikolenko

Mar 1, 2022: Exploiting CVE-2021-26708 (Linux kernel) with sshd by HardenedVault

Aug 13, 2022: HardenedLinux’s tweet

Aug 16, 2022: Reviving Exploits Against Cred Structs - Six Byte Cross Cache Overflow to Leakless Data-Oriented Kernel Pwnage

Sep 8, 2022: Vitaly Nikolenko’s tweet

VED 一些不完全的缓解措施

msg_msg 的完整性检查

VED 维护了一个 msg_msg 的列表,计算每个 msg_msg 内容的 hash。 使用 msgrcv() 读取或释放struct msg_msgseg next, size_t m_tsvoid *security' 被污染的 msg_msg 将会被 VED 探测到。但是我们的检查并没有包含 stuct list_head m_list`, 这意味着如果该指针被污染并被释放,VED 并不能检查出来,因为他是由 msg_queue 来维护的。 CVE-2021-22555: Turning \x00\x00 into 10000$ - Exploring struct msg_msg 就是这样一个例子. 但 VED 使用其他办法来进行防御。

越界读检查

VED 添加了越界读取的检查。如果说读取目标缓存的长度和读取长度是不匹配的, VED 就能检查到污染。在漏洞利用中’size_t m_ts’的污染较容易达成越界读取, 并且实现 KASLR 绕过或者泄漏堆地址。VED 的检查能够有效检查到越界读取, 但是这个防御也是不完整的,精心制作的 msg 仍可能绕过。比如说, 存在 UAF/double free 漏洞的结构体,msg_msg 结构体, 需要泄漏目标结构体, 三者的长度都是一致的,仍可以绕过 VED 的检查。

这两个缓解措施均是可被绕过的,VED 目前的版本是基于 LKRG 实现的,检查的完整性与性能的平衡是需要考虑的。更加严密的 msg 是可能的,但是 kprobe 的检查点数量,计算量需要多的多。另外则是虽然这两个缓解措施均可被绕过,但是叠加两者,由于其中的检查是相互交叉的,比方说完整性检查包含的 struct msg_msgseg *next, size_t m_ts,和越界检查。这使得漏洞利用需要依赖于原代码路径来实现堆喷,和更依赖同类型的 object 之间的污染,使得 msg 的漏洞利用困难程度上升。当然这些专门的绕过对资深的内核黑客来说不会是多大的问题。VED 也在探索更加完整并且平衡性能损耗的方案。

效果

由于目前的漏洞利用并不会针对上述防御进行绕过,VED针对公开的 exploit,仍然能够轻松检查出来,尤其是在利用较早期的infoleak阶段就能达成防御,这是我们测试过的一些漏洞 POC:

1:CVE-2022-25636: 完整性校验

2:CVE-2021-26708: 完整性校验

3:CVE-2021-22555:越界读检查

4:CVE-2021-22555: 越界读检查

Demo

alter-text