作者: 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_E1000和CONFIG_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.c 和 net/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 申請頁。

split page:如果 free_list 中沒有空閑頁,則 lower-order free_area 從 higher-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-2 的free_list 中供下次申請。原理如下所示:

merge page:如果 free_list 中有很多空閑頁,頁分配器會整合兩個相鄰的、order相同的頁,并放入 higher-order free_area 。還是以剛才的例子來看,假設 order-3 被分成兩個 order-2 的頁,其中一個存放在 order-2 的 free_list,只要被分配的頁又被釋放回 order-2 的 free_list,頁分配器會檢查新釋放的頁在同一 free_list 中是否存在相鄰的頁(這倆就被稱為 buddy),存在的話就將這倆合并后放入 order-3。

對應源碼:以下代碼展示了頁分配器如何從 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 時受到影響。

3. 漏洞利用
3-1 泄露方法
思路一(失敗):利用 msg_msg,覆寫 msg_msg->m_ts 構造越界讀。但是測試時發現,漏洞的垃圾字節會覆寫 msg_msg->next 指針(m_ts 和 next 相鄰),導致越界讀失敗。
思路二:利用 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,條件非常嚴苛。

增大victim個數:可以每個 victim slab 放一個 user_key_payload 對象,剩下的空間填充其他對象,user_key_payload 可以在 victim slab 中任意位置,因為本漏洞可以溢出覆蓋整個 victim slab。這樣,我們就可以噴9個 victim slab 了,增大的泄露的成功幾率。

3-2 泄露內核基址
方法:其實本可以直接在 victim slab 后面放一個包含內核指針的對象,但是作者很想嘗試 post 中通過篡改 msg_msg->next 進行任意讀寫的技術。先通過 user_key_payload 越界讀來泄露 msg_msg->next 指針,然后偽造 msg_msg->m_ts & msg_msg->next (msg_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-3 的 free_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() 繼續訪問用戶空間的數據。

提權:利用任意寫來篡改 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->next 為 modprobe_path 地址;
(4)執行一個錯誤格式的binary 觸發 modprobe;
(5)打開 /bin/bash 即可提權。

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
[漏洞分析] CVE-2022-27666 IPV6 ESP協議頁溢出內核提權
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1889/
暫無評論