14

从 PWN2OWN CVE-2022-27666 看内核页风水

 3 years ago
source link: https://paper.seebug.org/1889/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

从 PWN2OWN CVE-2022-27666 看内核页风水

4小时之前 2022年05月05日 漏洞分析

作者: f-undefined团队 v1n3gar
原文链接:https://mp.weixin.qq.com/s/JPbwYA2sS9jCMMgwBxONjg

知识点

(1)使用 msg_msg 构造任意写来篡改 modprobe_path通过 FUSE 来处理页错误(克服5.11版本之后用户没有userfaultfd权限的问题,肯定有一大波CTF题将要效仿)。

(2)由于漏洞对象位于 8-page,已经不能用常规的堆喷(slub allocator)来利用了,得利用页喷射(buddy system),作者分析了伙伴系统的源码,可以学习页喷知识(主要采用 ring_buffer 进行页喷和页风水,值得学习)。某种程度上来说本漏洞是一种 cross-cache overflow,从一个页溢出覆盖到下一个页上的cache(因为页上可以含有cache也可以是单纯的页)。

(3)采用新的弹性对象来泄露信息,也即user_key_payload弹性对象,由于长度变量和数据在一起,所以不担心溢出时覆盖到指针(限制是只能分配最多200个最长20000字节)。

影响版本:Linux-v5.17-rc8 以前,v5.17-rc8已修补。

测试版本:Linux-v5.16.14 exploit及测试环境下载地址

编译选项:所有和 INET6 / TUNNEL / XFRM / CONFIG_NET_KEY / CONFIG_NF_SOCKET_IPV6 相关的选项都勾上y,特别是以下选项。

CONFIG_XFRM_ESP=y
CONFIG_INET_ESP=y
CONFIG_INET_ESP_OFFLOAD=y
CONFIG_INET6_ESP=y
CONFIG_INET6_ESP_OFFLOAD=y

在编译时将.config中的CONFIG_E1000CONFIG_E1000E,变更为=y。参考

$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.16.14.tar.xz
$ tar -xvf linux-5.16.14.tar.xz
# KASAN: 设置 make menuconfig 设置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"。
$ make -j32
$ make all
$ make modules
# 编译出的bzImage目录:/arch/x86/boot/bzImage。

漏洞描述:位于目录 net/ipv4/esp4.cnet/ipv6/esp6.c 中的 IPsec ESP transformation 代码存在堆溢出。漏洞自2017年引入(cac2661c53f3 / 03e2a30f6a27)。

本漏洞能够在最新的 Ubuntu 21.10 上提权,来自于pwn2own 2022,能够影响 Ubuntu / Fedora / Debian。首次进行 page-level heap fengshui 和 cross-cache overflow(环境:4G内存,2 CPU)。

补丁patch 补丁引入 ESP_SKB_FRAG_MAXSIZE,大小为 32768 ,也就是 8-page,如果 allocsize 大于8页,则跳转到 COW

diff --git a/include/net/esp.h b/include/net/esp.h
index 9c5637d41d951..90cd02ff77ef6 100644
--- a/include/net/esp.h
+++ b/include/net/esp.h
@@ -4,6 +4,8 @@

 #include <linux/skbuff.h>

+#define ESP_SKB_FRAG_MAXSIZE (PAGE_SIZE << SKB_FRAG_PAGE_ORDER)
+
 struct ip_esp_hdr;

 static inline struct ip_esp_hdr *ip_esp_hdr(const struct sk_buff *skb)
diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index e1b1d080e908d..70e6c87fbe3df 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -446,6 +446,7 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *
    struct page *page;
    struct sk_buff *trailer;
    int tailen = esp->tailen;
+   unsigned int allocsz;

    /* this is non-NULL only with TCP/UDP Encapsulation */
    if (x->encap) {
@@ -455,6 +456,10 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *
            return err;
    }

+   allocsz = ALIGN(skb->data_len + tailen, L1_CACHE_BYTES);
+   if (allocsz > ESP_SKB_FRAG_MAXSIZE)
+       goto cow;
+
    if (!skb_cloned(skb)) {
        if (tailen <= skb_tailroom(skb)) {
            nfrags = 1;
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
index 7591160edce14..b0ffbcd5432d6 100644
--- a/net/ipv6/esp6.c
+++ b/net/ipv6/esp6.c
@@ -482,6 +482,7 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info
    struct page *page;
    struct sk_buff *trailer;
    int tailen = esp->tailen;
+   unsigned int allocsz;

    if (x->encap) {
        int err = esp6_output_encap(x, skb, esp);
@@ -490,6 +491,10 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info
            return err;
    }

+   allocsz = ALIGN(skb->data_len + tailen, L1_CACHE_BYTES);
+   if (allocsz > ESP_SKB_FRAG_MAXSIZE)
+       goto cow;
+
    if (!skb_cloned(skb)) {
        if (tailen <= skb_tailroom(skb)) {
            nfrags = 1;

保护机制:KASLR / SMEP / SMAP

利用总结:主要利用过程位于 loop() 函数:

(1)初始化:设置CPU affinity,设置漏洞socket(文件描述符存在 r[1]);

(2)缓解噪声(避免 order-2 从 order-3 取页 或者 order-2 的页合并到 order-3,影响到漏洞对象的排布):

  • (2-1)耗尽 order-0/1/2 的 freelist:喷射0x1000个大小为0x1000的 ring_buffer
  • (2-2)分配 (10*100*2) 个 4-page,释放一半:采用 ring_buffer
  • (2-3)释放 (2-1) 中堆喷的对象;

(3)泄露 msg_msg->next(尝试9次):堆上布局3个相邻的对象—— vul object -> user_key_payload -> msg_msg

  • (3-1)耗尽 order-3 的freelist,使得堆排布时从 order-4 取页,保证8-page 相邻:喷射 0x2000 个大小为 0x8000 的 ring_buffer
  • (3-2)耗尽 kmalloc-4k,使得分配 user_key_payload 时从 buddy system 取页(order-3):调用 setxattr() 分配 0x50*8 个 kmalloc-4k;
  • (3-3)分配3个连续的 8-page 占位对象:采用 ring_buffer 对象;
  • (3-4)释放第2个占位对象,分配1个8-page slab(分配1个 user_key_payload 和7个 setxattr());
  • (3-5)喷射 100 个 seq_operations 对象,便于之后泄露内核基址;
  • (3-6)释放第3个占位对象,分配1个8-page slab(喷射16个 msg_msg,位于 kmalloc-4k / kmalloc-32);
  • (3-7)释放第1个占位对象,分配漏洞对象,触发越界写来修改 user_key_payload->datalen
  • (3-8)通过 user_key_payload 进行越界读,泄露 msg_msg->next

(4)泄露内核基址(尝试50次):堆上布局2个相邻的对象——vul object -> msg_msg

  • (4-1)耗尽 kmalloc-4k,使得分配 msg_msg 时从 buddy system 取页(order-3):堆喷0x100个大小为0x1000 的 ring_buffer
  • (4-2)耗尽 order-3 的freelist,使得堆排布时从 order-4 取页,保证8-page 相邻:喷射 0x100 个大小为 0x8000 大小的 ring_buffer
  • (4-3)分配8*2个连续的 8-page 占位对象(占位对象):采用 ring_buffer 对象;
  • (4-4)释放第2个占位对象,分配1个8-page slab (分配 9 个 msg_msg,位于 kmalloc-4k / kmalloc-32);
  • (4-5)释放第1个占位对象,分配漏洞对象,触发越界写来修改 msg_msg->m_ts & msg_msg->next (改成上一步泄露的 msg_msg->next);
  • (4-6)通过 msg_msg 进行越界读,泄露 seq_operations->start / stop / next 指针;

(5)篡改 modprobe_path 提权(尝试50次):堆上布局2个相邻的对象——vul object -> msg_msg。 - (5-1)设置 FUSE,页错误处理地址为 fuse_evil_addr = 0x1339000(FUSE的 evil_read_pause() 函数在处理页错误时,会往该地址写入字符串 /tmp/get_rooot\x00,也即提权程序); - (5-2)耗尽 order-3 的freelist,使得堆排布时从 order-4 取页,保证8-page 相邻:喷射 0x100 个大小为 0x8000 大小的 ring_buffer; - (5-3)分配2个连续的 8-page 占位对象(占位对象):采用 ring_buffer 对象; - (5-4)释放第2个占位对象,分配1个8-page slab (分配 9 个 msg_msg,位于 kmalloc-4k / kmalloc-32)(注意,用户message地址设置为 fuse_evil_addr-8 == 0x1339000-8,以便在内核拷贝消息时触发页错误而暂停); - (5-5)释放第1个占位对象,分配漏洞对象,触发越界写来修改 msg_msg->next(改成 modprobe_path-8); - (5-6)通过写pipe来通知 FUSE 的 evil_read_pause() 函数,结束页错误处理,使得 msg_msg 消息完成拷贝,篡改 modprobe_path; - (5-7)执行错误binary文件触发modprobe,完成提权。


1. 漏洞分析

简介:漏洞来自 Linux esp6 crypto 模块,接收缓冲区是 8-page,但发送者可以发送大于 8-page 的数据,导致页溢出。

1-1 漏洞对象创建

漏洞对象创建esp6_output_head() 负责创建 receive buffer,allocsize 变量不重要,因为 skb_page_frag_refill() 会默认分配 8-page 内存(order-3 pages)。

调用栈sendmsg() -> __sys_sendmsg() -> ___sys_sendmsg() -> ____sys_sendmsg() -> sock_sendmsg() -> sock_sendmsg_nosec() -> rawv6_sendmsg() -> rawv6_push_pending_frames() -> ip6_push_pending_frames() -> ip6_send_skb() -> ip6_local_out() -> dst_output() -> xfrm6_output() -> NF_HOOK_COND() -> __xfrm6_output() -> xfrm_output() -> xfrm_output2() -> xfrm_output_resume() -> dst_output() -> ip6_output() -> NF_HOOK_COND() -> ip6_finish_output -> __ip6_finish_output -> ip6_finish_output2() -> neigh_output() -> neigh_hh_output() -> dev_queue_xmit() -> __dev_queue_xmit() -> validate_xmit_skb() -> validate_xmit_xfrm() -> esp6_xmit() -> esp_output_head() 34层,太复杂了。。。

int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *esp)
{
        ...
        struct page_frag *pfrag = &x->xfrag;          // x->xfrag->page = vul object
        int tailen = esp->tailen;
        allocsize = ALIGN(tailen, L1_CACHE_BYTES);

        spin_lock_bh(&x->lock);

        if (unlikely(!skb_page_frag_refill(allocsize, pfrag, GFP_ATOMIC))) {    // [1]
            spin_unlock_bh(&x->lock);
            goto cow;
        }
        ...
}

bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)
{
        if (pfrag->offset + sz <= pfrag->size)
        return true;
    ...
    if (SKB_FRAG_PAGE_ORDER &&
        !static_branch_unlikely(&net_high_order_alloc_disable_key)) {

        pfrag->page = alloc_pages((gfp & ~__GFP_DIRECT_RECLAIM) |               // [2] 
                      __GFP_COMP | __GFP_NOWARN |
                      __GFP_NORETRY,
                      SKB_FRAG_PAGE_ORDER);
        ...
    }
    ...
    return false;
}

1-2 越界写

漏洞对象越界null_skcipher_crypt() 函数中,内核拷贝了 N-page 数据,导致OOB。

static int null_skcipher_crypt(struct skcipher_request *req)
{
    struct skcipher_walk walk;
    int err;

    err = skcipher_walk_virt(&walk, req, false);

    while (walk.nbytes) {
        if (walk.src.virt.addr != walk.dst.virt.addr)
            // out-of-bounds write
            memcpy(walk.dst.virt.addr, walk.src.virt.addr,
                   walk.nbytes);
        err = skcipher_walk_done(&walk, 0);
    }

    return err;
}

调用栈... -> esp6_xmit() -> esp6_output_tail() -> crypto_aead_encrypt() -> crypto_authenc_encrypt() -> crypto_authenc_copy_assoc() -> crypto_skcipher_encrypt() -> null_skcipher_crypt() 可以看到,esp6_xmit() 先调用 esp6_output_head() 分配漏洞对象的 8-page 内存(地址存放在x->xfrag->page),再调用 esp6_output_tail() 将该内存赋值给 req.dst 并最后触发OOB。

static int esp6_xmit(struct xfrm_state *x, struct sk_buff *skb,  netdev_features_t features)
{
    int err;
    int alen;
    struct esp_info esp;
    bool hw_offload = true;

    ... ...
    esp.tailen = esp.tfclen + esp.plen + alen;          // esp.tailen   ->   allocsize

    if (!hw_offload || !skb_is_gso(skb)) {
        esp.nfrags = esp6_output_head(x, skb, &esp);    // [1] alloc the vulnerable object, saved at x->xfrag->page
        if (esp.nfrags < 0)
            return esp.nfrags;
    }
    ... ...
    err = esp6_output_tail(x, skb, &esp);               // [2] trigger OOB
    ... ...
}

int esp6_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *esp)
{
    ... ... 
    if (!esp->inplace) {
        int allocsize;
        struct page_frag *pfrag = &x->xfrag;
        ... ...
        page = pfrag->page;
        get_page(page);
        /* replace page frags in skb with new page */
        __skb_fill_page_desc(skb, 0, page, pfrag->offset, skb->data_len);
        pfrag->offset = pfrag->offset + allocsize;
        spin_unlock_bh(&x->lock);

        sg_init_table(dsg, skb_shinfo(skb)->nr_frags + 1);
        err = skb_to_sgvec(skb, dsg,
                       (unsigned char *)esph - skb->data,
                       assoclen + ivlen + esp->clen + alen);
    ... ...

    aead_request_set_crypt(req, sg, dsg, ivlen + esp->clen, iv);       // [2-1]  dsg  =  x->xfrag->page
    aead_request_set_ad(req, assoclen);

    ... ...
    err = crypto_aead_encrypt(req);        // [2-2] req->dst = dsg
    ... ...
}
EXPORT_SYMBOL_GPL(esp6_output_tail);

static int null_skcipher_crypt(struct skcipher_request *req)
{
    struct skcipher_walk walk;
    int err;

    err = skcipher_walk_virt(&walk, req, false);            // [3] walk->dst = req.dst

    while (walk.nbytes) {
        if (walk.src.virt.addr != walk.dst.virt.addr)
            memcpy(walk.dst.virt.addr, walk.src.virt.addr,  // [4]  trigger OOB
                   walk.nbytes);
        err = skcipher_walk_done(&walk, 0);
    }

    return err;
}

漏洞缺陷:作者利用时,发送 16-page 数据,可以溢出 8-page,问题是 esp_output_fill_trailer() 会根据消息长度和所用协议类型,在末尾添加几个字节(对我们来说是垃圾数据)。

static inline void esp_output_fill_trailer(u8 *tail, int tfclen, int plen, __u8 proto)
{
    /* Fill padding... */
    if (tfclen) {
        memset(tail, 0, tfclen);
        tail += tfclen;
    }
    do {
        int i;
        for (i = 0; i < plen - 2; i++)
            tail[i] = i + 1;
    } while (0);
    tail[plen - 2] = plen - 2;
    tail[plen - 1] = proto;
}

2. Buddy system 知识

说明:分析伙伴系统的原理是研究 page-level heap fengshui 的前提。

2-1 page allocator

页分配器的知识可以参见 page_alloc.c 源码。

简介Linux page allocator 管理内核底层的物理页,SLUB / SLAB / SLOB 内存分配器都在 Page allocator 之上。例如,当内核耗尽所有 kmalloc-4k slab之后,内存分配器会向 Page allocator 申请内存,由于 kmalloc-4k 位于 8-page slab (order 3),所以 Page allocator 会申请 8-page 内存给内存分配器。

存储结构:Page allocator 采用 free_area 结构(zone->free_area 数组,长度为 MAX_ORDER == 1,所以最大order为11)来保存空闲页,也就是个保存不同 order/size 页的数组,采用 order 来区分不同大小的页(例如,N-order 表示大小为 PAGE_SIZE<<3 的页;order-0 就表示大小为 PAGE_SIZE 的页)。free_area 中每个 order 都对应一个 free_list,从 free_list 分配或将页释放后放入 free_list

struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long       nr_free;
};

cache 与页分配:不同的slab如果耗尽了会申请不同 order 的页,例如,kmalloc-256 会从 order-0 申请页,而 kmalloc-512 会从 order-1 申请页,kmalloc-4k 会从 order-3 申请页。

1-understand-free_area

split page:如果 free_list 中没有空闲页,则 lower-order free_areahigher-order free_area 取页,higher-order free_area 将页一分为二,然后 lower-order free_area 将页返回给申请者(例如 alloc_pages())。例如,当 order-2(4-page)的 free_list 耗尽之后,就从 order-3 申请页,order-3 的页分成两个 4-page 页,位于低地址的 4-page 返回给申请者,高地址的 4-page 保存在 order-2free_list 中供下次申请。原理如下所示:

2-higher-order-split-to-lower-order

merge page:如果 free_list 中有很多空闲页,页分配器会整合两个相邻的、order相同的页,并放入 higher-order free_area 。还是以刚才的例子来看,假设 order-3 被分成两个 order-2 的页,其中一个存放在 order-2free_list,只要被分配的页又被释放回 order-2free_list,页分配器会检查新释放的页在同一 free_list 中是否存在相邻的页(这俩就被称为 buddy),存在的话就将这俩合并后放入 order-3

3-lower-order-merge-to-higher-order

对应源码:以下代码展示了页分配器如何从 free_area 中选取页以及如何从 higher-order 中取页。

static __always_inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                        int migratetype)
{
    unsigned int current_order;
    struct free_area *area;
    struct page *page;


    for (current_order = order; current_order < MAX_ORDER; ++current_order) {
        // Pick up the right order from free_area
        area = &(zone->free_area[current_order]);
        // Get the page from the free_list
        page = get_page_from_free_area(area, migratetype);
        // If no freed page in free_list, goes to high order to retrieve
        if (!page)
            continue;
        del_page_from_free_list(page, zone, current_order);
        expand(zone, page, order, current_order, migratetype);
        set_pcppage_migratetype(page, migratetype);
        return page;
    }

    return NULL;
}

static inline struct page *get_page_from_free_area(struct free_area *area,
                        int migratetype)
{
    return list_first_entry_or_null(&area->free_list[migratetype],
                    struct page, lru);
}

2-2 shaping heap

页风水目标:现在讨论下如何为 OOB write 布局堆结构。现在已知在页分配器中,每种 order 的页都保存在 free_area->free_list 中。由于不能保证在同一 free_list 中的两个页是连续的,所以即便连续申请2个同一order的页,这2个页可能相隔很远。为了更好的控制堆布局,我们需要确保 free_list 中所有的页是连续的。首先耗尽目标order的 free_list,迫使其向 higher-order 取页,这样取过来的页会被划分成两段连续的内存。

缓解噪声(保证连续):有些内核进程也会分配和释放页,影响了堆布局。回到本漏洞中来,我们的目标是布局连续的 order-3 的页,但是可能会有 order-3 的页被划分到 order-2 或者有 order-2 的页被整合到 order-3。为了缓解噪声影响,可以采取以下步骤:

(1)耗尽 order 0, 1, 2 的 free_list;(采用socket中的 ring_buffer 来堆喷—页风水

(2)分配大量的 order-2 对象 (假定为N个),这样,order-2 会向 order-3 取页;(分10个进程,每个进程喷200个 4-page 大小的 ring_buffer

(3)释放第2步中一半的对象,这样,有 N/2 个对象会存入 order-2 的 free_list

(4)释放第1步所有的对象;

第3步中,释放一半的 order-2 就避免其发生整合而被存入 order-3,这样 order-2 的 free_list 中就有 N/2 个页可以使用了,之后就不会从 order-3 取页或者整合到 order-3 了。避免我们构造连续的 8-page 时受到影响。

4-mitigate_noise


3. 漏洞利用

3-1 泄露方法

思路一(失败):利用 msg_msg,覆写 msg_msg->m_ts 构造越界读。但是测试时发现,漏洞的垃圾字节会覆写 msg_msg->next 指针(m_tsnext 相邻),导致越界读失败。

思路二:利用 user_key_payload 结构(从 ELOISE 论文中找到)。可以看到,这个结构的 datalen 长度和数据在一起,这样即便垃圾字节会填到末尾,也不会破坏到指针了。

struct user_key_payload {
    struct rcu_head rcu;        // rcu指针可以被设置为NULL
    unsigned short  datalen;    /* length of this data */
    char        data[] __aligned(__alignof__(u64)); /* actual data */
};

key长度限制:Ubuntu 上默认会限制key的数量和长度。问题是导致溢出的对象位于 8-page,我们在漏洞对象后面也要布置一个 8-page 对象(暂时称为 victim slab),而在Ubuntu上,只有 kmalloc-2k / kmalloc-4k / kmalloc-8k 会从 order-3 取页。所以至少要使key被分配在 kmalloc-2k 上,才能使key位于 8-page 的页中。

$ sudo cat /proc/sys/kernel/keys/maxbytes 
20000
$ sudo cat /proc/sys/kernel/keys/maxkeys 
200

victim个数限制:可以用8个 kmalloc-4k 对象来填充victim slab,采用长度为 2049 的 user_key_payload 即可。这样 user_key_payload 总长度为 2049*8=16392,由于限制最多 20000 字节的key,只剩下 1 个 user_key_payload 可用 — ((20000-16392)/2049 = 1),所以最多可以布置2个 victim slab,条件非常严苛。

5-weak-page-fengshui

增大victim个数:可以每个 victim slab 放一个 user_key_payload 对象,剩下的空间填充其他对象,user_key_payload 可以在 victim slab 中任意位置,因为本漏洞可以溢出覆盖整个 victim slab。这样,我们就可以喷9个 victim slab 了,增大的泄露的成功几率。

6-strong-page-fengshui

3-2 泄露内核基址

方法:其实本可以直接在 victim slab 后面放一个包含内核指针的对象,但是作者很想尝试 post 中通过篡改 msg_msg->next 进行任意读写的技术。先通过 user_key_payload 越界读来泄露 msg_msg->next 指针,然后伪造 msg_msg->m_ts & msg_msg->nextmsg_msg->security 在Ubuntu上没用,可覆盖为0)进行任意读。

泄露msg_msg->next:堆上布局3个相邻的对象—— vul object -> user_key_payload -> msg_msg,注意 msg_msg->next 指向 kmalloc-32,并堆喷大量的 struct seq_operations 对象。触发越界写来篡改 user_key_payload->datalen,通过 user_key_payload 越界读来泄露 msg_msg->next 指针。为了增大成功几率,可以创建9对这种布局(3个相邻对象的堆布局)。

泄露内核基址:堆上布局2个相邻的对象——vul object -> msg_msg,触发越界写来篡改 msg_msg->m_ts & msg_msg->next ,通过 msg_msg 越界读来泄露 struct seq_operations 对象上的函数指针(因为之前泄露的 msg_msg->next 指向 kmalloc-32,而kmalloc-32 上已经喷射了很多 struct seq_operations 对象)。

总体步骤:(1)~(8)泄露 msg_msg->next ,(9)~(12)泄露内核基址。

(1)分配大量8-page 页来耗尽 order-3free_list,这样 order-3 就会从 order-4 取页,保证内存连续性;

(2)分配3个连续的 8-page dumy 对象(占位对象);(占位对象采用 ring_buffer

(3)释放第2个占位对象,分配1个8-page slab,其中包含1个 user_key_payload 对象和7个其他对象(这7个对象采用多个子线程调用setxattr()来堆喷);

(4)释放第3个占位对象,分配1个8-page slab,填满大小在4056~4072之间的 msg_msg,使得 msg_msgseg 位于 kmalloc-32;

(5)喷射大量的 struct seq_operations,和第4步的 msg_msgseg 位于同一cache;

(6)释放第1个占位对象,分配漏洞对象,触发越界写来修改 user_key_payload->datalen

(7)如果第(6)步成功,就能通过 user_key_payload 进行越界读;

(8)如果第(7)步成功,就能泄露出 msg_msg->next 指针;

(9)分配2个连续的 8-page dumy 对象(占位对象);

(10)释放第2个占位对象,分配1个8-page slab,填满 msg_msg

(11)释放第1个占位对象,分配漏洞对象,触发越界写来篡改 msg_msg->m_ts & msg_msg->next

(12)如果第(11)步成功,就能越界读来泄露 struct seq_operations 对象上的函数指针。

3-3 提权

任意写:还是利用 msg_msg 来进行任意写。由于普通用户需要 specific capability 才能使用userfaultfd,可以采用 CVE-2022-0185 中的介绍的FUSE方法来进行任意写。通过FUSE可以实现用户空间文件系统,然后映射我们的内存地址,只要有读写访问到该地址就可以调用我们的页错误处理函数,这样可以控制当 msg_msg->next 被篡改之后,再允许 copy_from_user() 继续访问用户空间的数据。

7-arb_write

提权:利用任意写来篡改 modprbe_path 提权。后面方法和 CVE-2022-0185 一样。将 modprbe_path 改为 /tmp/get_rooot (运行chmod u+s /bin/bash),这样提权后只要运行 /bin/bash 即可提权。

(1)分配2个连续的 8-page dumy 对象(占位对象);

(2)映射消息内容到FUSE,释放第2个占位对象,分配1个8-page slab,填满 msg_msg,线程会暂停在 copy_from_user()

(3)释放第1个占位对象,分配漏洞对象,触发越界写来篡改 msg_msg->nextmodprobe_path 地址;

(4)执行一个错误格式的binary 触发 modprobe;

(5)打开 /bin/bash 即可提权。

8-succeed

exp说明

  • 原exp中前1271行是设置环境,不重要(作者说,环境设置部分的代码是syzkaller自动生成的,非常复杂;只有利用部分,也即 loop() 函数是作者写的,我们需要重点研究该函数),需要用到几个符号:single_start / single_next / single_stop / modprobe_path,在原exploit的111行修改即可。

  • exp中 main 函数设置完环境之后,调用 clone() 创建子进程执行 loop() 函数,loop() 函数实现主要利用过程。参见 clone()分析,不同于 fork() / vfork()clone() 克隆生成的子进程继续运行时不以调用处为起点,转而去调用以参数func所指定的函数;当函数func返回或者是调用 exit()(或者 _exit())之后,克隆产生的子进程就会终止,父进程可以通过 wait() 一类函数来等待克隆子进程;调用者必须分配一块大小适中的内存空间供子进程的栈使用,同时将这块内存的指针置于参数 child_stack 中。

  #define _GNU_SOURCE
  #include <sched.h>
  int clone(int (*func)(void*),void *child_stack,int flags,void *func_arg,....
             /*pid_t *ptid,struct user_desc *tls,pid_t *ctid*/);
                                       Return process ID of child on success,or -1 on error
  • 页喷射对象:进行页风水和页占位的对象是ring_buffer,因为其size设置很灵活,适合页喷射。

CVE-2022-27666: Exploit esp6 modules in Linux kernel

exploit

[漏洞分析] CVE-2022-27666 IPV6 ESP协议页溢出内核提权


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1889/

</section


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK