作者:啟明星辰ADLab
原文鏈接:https://mp.weixin.qq.com/s/Qs_-CTZyojRe_x8E0KiXMg

一、 前言

數月前,國外安全組織ZDI研究人員披露了一個Linux內核本地權限提升漏洞,該漏洞出現在流量控制子系統包分類器的cls_route過濾器中,當舊過濾器句柄為0時,在釋放之前內核不會從哈希表中將其刪除,其漏洞編號為CVE-2022-2588,而且還提出了一種新的漏洞利用方法,命名為DirtyCred,該方法可繞過廣泛采用的內核保護和漏洞利用緩解措施,從而實現權限提升。

二、 Rtnetlink簡述與實現簡析

Rtnetlink是所有內核網絡子系統使用的網絡連接總線,包括網絡接口、路由、fdb和鄰居。一些內核網絡子系統也在通用netlink總線上提供服務。Linux內核網絡子系統使用消息類型和系列向Rtnetlink內核注冊處理程序。Rtnetlink允許讀取和更改內核的路由表。它在內核中用于在各種子系統之間進行通信,也用于與用戶空間程序進行通信。網絡路由、IP地址、鏈接參數、鄰居設置、排隊規則、流量類別和數據包分類器都可以通過NETLINK_ROUTE套接字進行控制。Rtnetlink由以下消息類型組成(除了標準的netlink消息):

  • RTM_NEWLINK、RTM_DELLINK、RTM_GETLINK創建、刪除或獲取有關特定網絡接口的信息。

  • RTM_NEWADDR、RTM_DELADDR、RTM_GETADDR添加、刪除或接收有關與接口關聯的IP地址的信息。

  • RTM_NEWROUTE、RTM_DELROUTE、RTM_GETROUTE創建、刪除或接收有關網絡路由的信息。

  • RTM_NEWNEIGH、RTM_DELNEIGH、RTM_GETNEIGH添加、刪除或接收有關鄰居表條目的信息(例如,ARP條目)。

  • RTM_NEWRULE、RTM_DELRULE、RTM_GETRULE添加、刪除或檢索路由規則。

  • RTM_NEWQDISC、RTM_DELQDISC、RTM_GETQDISC添加、刪除或獲取排隊規則。

  • RTM_NEWTCLASS、RTM_DELTCLASS、RTM_GETTCLASS添加、刪除或獲取流量類別。

  • RTM_NEWTFILTER, RTM_DELTFILTER, RTM_GETTFILTER添加、刪除或接收有關流量過濾器的信息。

(二)實現簡析

當內核啟動加載,在初始化netlink協議實現時,會調用rtnetlink_init()函數初始化路由netlink socket接口,該函數實現如下:

根據代碼可知,通過rtnl_register()函數將不同的消息類型和對應操作進行綁定,該函數簽名為void rtnl_register(int protocol, int msgtype,rtnl_doit_func doit, rtnl_dumpit_func dumpit,unsigned int flags),有的消息類型只有動作函數doit,有的消息類型只有dump函數dompit,有的消息類型兩者皆有。有的消息類型例如RTM_NEWTFILTER,添加一個流量過濾器,則是在tc_filter_init()函數中初始化,該函數實現如下:

當用戶層通過NETLINK_ROUTE套接字發送RTM_NEWTFILTER消息用于創建一個流量過濾器時,內核將調用rtnetlink_rcv_msg()函數來處理rtnetlink消息,該函數關鍵實現如下:

從消息中獲取family和type,然后調用rtnl_get_link()函數根據family和type獲取link,行5246,調用link->doit回調函數,這里的doit回調函數即為tc_new_tfilter()函數。該函數會進一步解析rtnetlink消息數據包,判斷并創建哪種類型的過濾器,具體實現如下:

獲取指定的過濾器名字,然后會獲取指定協議的過濾器tp。如下代碼所示:

如果沒有,便根據name創建一個新的tp。如下代碼所示:

判斷tca[TCA_KIND]不為空和nlmsg_flags為NLM_F_CREATE,然后調用tcf_proto_create()函數創建,該函數實現如下:

行258,分配一個tp,行262,調用tcf_proto_lookup_ops()函數根據kind獲取對應的ops,這里以route為例,將獲取到cls_route4_ops,如下代碼所示:

然后初始化tp,行274,調用route4_init()函數初始化一個route4_head,用于存放過濾器對應的哈希值,該函數實現如下:

然后tcf_proto創建完成后,將其插入chain中。如下代碼所示:

接下來調用對應的get函數,根據tcm_handle獲取過濾器。

這里將調用route4_get()函數獲取,該函數實現如下:

根據handle從route4_head鏈表中獲取對應的route4_filter。如果為空,便調用change函數進行創建。如下代碼所示:

這里將調用ruote4_change()函數進行創建,該函數具體分析見下文。創建成功后,添加一個新的route4過濾器的整個流程便完成了。

三、 漏洞原理

漏洞代碼出現在route4_change()函數中,該函數實現如下:

行483,首先進一步解析消息數據包,行488,拿出傳入的route4_filter,然后判斷是否已創建,如果創建過,再判斷handle是否一致。由于第一次創建,這里fold為空。接下來進行創建并初始化,如下代碼所示:

行493,調用kzalloc()函數分配route4_filter,該結構體大小為144字節。行497,調用tcf_exts_init()函數進行初始化,該函數實現如下:

如果內核開啟了CONFIG_NET_CLS_ACT,便調用kcalloc()函數分配exts->actions,分配大小為256字節。初始化完成返回到route4_change()函數中,行501,如果fold不為空,便將fold的數據域賦值給f。行512,然后調用route4_set_parms()函數設置其他參數。行517到行527,將新創建的route4_filter對應的哈希值放到route4_head中。如下代碼所示:

行529到行543,該段代碼是將舊過濾器的哈希值從route4_head中移除。如下代碼所示:

由于是第一次創建,fold為空,因此不進入。但是行529代碼是有問題的,這里不僅判斷fold是否為空,同時還判斷fold->handle是否為空。那如果第一次創建一個handle為0的過濾器,第二次創建新過濾器時,fold不為空,但是fold->handle為0,因此并不會將handle為0的舊過濾器的哈希值從route4_head中移除,保留了對其的索引。接下來開始釋放舊過濾器內存,如下代碼所示:

第一次創建過濾器時,fold為空,不進入。當第二次創建新過濾器時,fold不為空,行550,調用tcf_queue_work()函數對handle為0的舊過濾器進行釋放操作,回調函數為route4_delete_filter_work()函數,該函數實現如下:

行266,最后調用__route4_delete_filter()函數分別釋放f->exts和f。當內核調用route4_delete()函數進行釋放所有過濾器時,行344,會調用route4_delete_filter_work()函數進行釋放,該函數實現如下:

由于handle為0的舊過濾器對應的哈希值依然在route4_head中,因此會對兩個對象進行二次釋放,分別是route4_filer及對應的exts。該漏洞修復補丁如下代碼所示:

將判斷條件改成fold是否為空,fold不為空便將其從哈希表中刪除。

四、 利用研究

(一)DirtyCred

研究人員提出了一種新的漏洞利用方法,命名為DirtyCred,該利用方法將非特權cred與特權cred進行交換以提升權限,且不需要覆蓋內核堆棧上的任何關鍵數據字段,而是濫用堆內存重寫機制來獲得特權。該利用技術無需泄露內核地址繞過KASLR,且通用性較強,可跨不同的內核和架構,能繞過廣泛采用的內核保護和漏洞利用緩解措施。DirtyCred分為三個步驟,過程如下圖所示:

首先,DirtyCred打開一個可寫文件“/tmp/x”,它將在內核中分配一個可寫file對象。通過觸發漏洞,源指針會引用相應緩存中的file對象。然后,DirtyCred嘗試將內容寫入打開的文件“/tmp/x”中。在實際寫入內容之前,內核會檢查當前文件是否有寫權限,該位置是否可寫等。通過檢查后,DirtyCred會繼續這個實際的文件寫入操作,進入第二步。在此步驟中,DirtyCred觸發fs_context對象的釋放點以解除分配file對象,這就使file對象成為一個被釋放的內存點。第三步,DirtyCred打開了一個只讀文件“/etc/passwd”,這觸發了內核為“/etc/passwd”分配file對象。如上圖所示,新分配的file對象接管了被釋放的位置。完成此設置后,DirtyCred將釋放其暫停的寫入操作,而內核將執行實際的內容寫入。由于file對象已經被調換,擱置的內容將被重定向到只讀文件“/etc/passwd”中。假設寫入“/etc/passwd”的內容是“hacker:x:0:0:root:/:/bin/sh”,輕易地注入一個特權賬戶,從而實現權限提升。

(二)利用分析

1、file slab碎片整理

首先創建10000個“/etc/passwd”的file對象,進行file slab碎片整理,為堆噴做準備。

2、緩存跨越(cross-cache)

當為特定類型的對象創建專用的內存緩存時,該緩存中將只存在該類型的對象,從而防止不同類型的對象相鄰。但是,不同slab cache的slab page可以相鄰。當slab A與slab B相鄰時,slab A末尾的對象與slab B開頭的另一個對象相鄰。因此,攻擊者有可能放置帶有任何類型受害者對象的slab頁在易受攻擊對象的slab page之后,溢出受害者對象來執行攻擊。

當一個slab page被釋放給伙伴系統時,它會在稍后的某個時候被重用,因為內存頁面應該被內核回收。cross-cache攻擊的技術是釋放slab page中的所有內存槽,強制釋放slab page。然后再噴射另一種類型的對象分配新的slab page,回收釋放的slab page。如果攻擊成功,被釋放對象的內存將被另一種類型的對象占用。

由于file對象在專屬緩存中分配,而漏洞中route4_filter和exts在通用緩存中分配,正常情況下,兩個緩存是隔離的,無法進行常規的對象占坑。作者采用了緩存跨越攻擊解決了這個問題。前文講到route4_filter對象大小為144,在kmalloc-192中分配,exts對象大小為256,在kmalloc-256中分配。file對象大小為256(默認配置情況下),在專屬內存中分配。先通過創建basic過濾器在kmalloc-256 slab中進行內存布局。

然后創建route4過濾器,將會創建一個route4_filter漏洞對象和exts對象。最后再進行一部分basic過濾器創建,完成內存布局。關鍵對象內存分布如下圖所示:

然后第一次觸發漏洞,釋放route4_filter對象和exts對象。然后再全部釋放basic->exts對象,讓伙伴系統回收kmalloc-256 slab page。

由于內核默認開啟slab double free緩解機制,所以這里要亂序釋放多個basic->exts。

3、堆噴占坑

大量打開一個普通文件data2,進行file對象分配占坑route4->exts。緩存跨越攻擊成功后,然后再次觸發漏洞,第二次釋放route4_filter,跟著釋放route4->exts,將會把file對象非法釋放掉。

關鍵對象內存布局如下圖所示:

4、尋找file對象空洞

二次釋放后,出現了一個file對象空洞,需要找到它。所以此時再次大量打開另一個普通文件uaf,進行file對象堆噴,將會占據這個file對象空洞。然后通過kcmp()系統調用檢查 pid1 和 pid2 標識的兩個進程是否共享文件描述符定位fds[j]。

5、延長時間窗口和寫惡意數據

找到目標file對象的文件描述符fds[j]后,啟動兩個線程,slow_write線程向uaf文件中寫2G數據,用于延長時間窗口。

然后write_cmd線程通過fds[j]文件描述符寫惡意數據。

6、再次釋放并堆噴

最后進行close操作,釋放file對象,隨即大量打開“/etc/passwd”文件進行file對象堆噴。堆噴成功后,順利向“/etc/passwsd”寫數據,注入一個特權賬戶完成權限提升。

五、 漏洞復現

在內核版本5.4.124中,開啟CONFIG_NET_CLS_ACT和CONFIG_NET_SCH_SFQ,成功復現漏洞。打印調試關鍵數據,如下圖所示:

六、 參考

1、 https://grsecurity.net/how_autoslab_changes_the_memory_unsafety_game

2、 https://www.openwall.com/lists/oss-security/2022/08/09/6

3、https://www.man7.org/linux/man-pages/man7/rtnetlink.7.html

4、 https://legacy.netdevconf.info/0.1/papers/Rtnetlink-dump-filtering-in-the-kernel.pdf

5、 https://github.com/Markakd/DirtyCred

6、 https://github.com/Markakd/CVE-2022-2588

7、 https://zplin.me/papers/DirtyCred.pdf


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/2019/