BlackHat ASIA 議題解讀 | 安卓Netlink內核模塊中隱藏的“傳送門”

作者:百度安全-AIoT安全團隊 Chao Ma, Han Yan, Tim Xia

隨着安卓系統的流行,Netlink作爲Linux內核與用戶態進程之間的一種通信機制,被廣泛應用在安卓操作系統內核模塊中,但其使用的安全性卻未得到足夠的重視。針對這一現狀,百度安全研究員於北京時間4月19日下午,在BlackHat ASIA 2024上分享了《LinkDoor: A Hidden Attack Surface in the Android Netlink Kernel Modules》議題,探討了Netlink內核機制的安全邊界。議題首先介紹了Netlink的概念和編程模型,然後根據其在Linux中的內核實現機制,抽象出四類威脅模型,並分別分析了在現實世界中的典型漏洞案例,接着介紹了Netlink相關漏洞的驗證和利用方法,最後爲廠商安全使用Netlink提供了最前沿的安全建議。


BlackHat ASIA 2024議題:安卓Netlink內核模塊中的一個隱藏攻擊面


一、介紹

Netlink背景

Netlink是一個套接字家族,主要用於內核和用戶進程之間的雙向通信。與ioctl相比,它具有全雙工、異步、組播的通信特點。


Netlink全雙工和組播通信


在研究過程中,我們根據Netlink的使用方式將其分爲Classic Netlink和Generic Netlink兩大類。其中Generic Netlink的協議ID爲16,其他ID都是Classic Netlink。


Classic Netlink編程模型

Classic Netlink套接字從1999年的Linux 2.2版本開始支持。因爲它是一個套接字家族,因此其用戶態編程使用的是Linux Socket API。因其具有全雙工的特點,所以Netlink傳輸消息有Top-Down和Bottom-Up兩個方向。


Top-Down方面,內核使用netlink_kernel_create函數註冊input函數,該函數主要用於解析從用戶空間接收到的消息。


Bottom-Up方面,內核使用nlmsg_unicast或nlmsg_multicast函數向用戶空間主動發送組建好的Netlink傳輸消息。


Classic Netlink全雙工編程模型


Classic Netlink缺陷

Classic Netlink主要有兩個小瑕疵:


  • 有限的Netlink協議ID:netlink_kernel_create函數的第二個參數unit爲協議ID。如果想要自定義使用Classic Netlink,需要增加1個協議ID。但是在Linux內核中,unit一共只有32個協議ID,內核佔用了22個,只剩下10個給用戶使用。當然也可以修改內核源碼擴大該協議ID的最大值,但還有更好的方法。

  • 編程複雜:Classic Netlink由於需要用戶自己手動解析傳輸消息,因此使用起來更加複雜。


爲此,Linux內核引入了Generic Netlink。


Generic Netlink編程模型

Generic Netlink套接字從2006年的Linux 2.6.15版本開始支持。所有自定義使用Generic Netlink的用戶共享1個協議ID 16。當解析用戶空間輸入的消息時,只需要關注用戶屬性即可。


Top-Down方面,內核使用genl_register_family函數註冊ops和small_ops,二者的區別在於small_ops提供了更少的消息處理函數,相同點在於都會指定doit函數。doit函數用於解析從用戶空間輸入的屬性。


Bottom-Up方面,內核使用genlmsg_unicast、genlmsg_multicast、genlmsg_multicast_netns或者genlmsg_multicast_allns函數向用戶空間主動發送組建好的Netlink屬性。


Generic Netlink全雙工編程模型


二、攻擊面分析

Netlink架構

Netlink架構方面,應用可以直接調用或者間接通過libnl調用Linux Socket API,將Netlink消息發送到內核空間。內核接收到Netlink消息後,會將其路由給Linux內核Netlink子系統,該子系統會根據Netlink類別分爲Classic Netlink和Generic Netlink進行處理。下面我們將重點關注Netlink子系統對Netlink消息的處理機制。


Netlink架構


Classic Netlink內核機制

Classic Netlink傳輸消息可由多組Netlink消息組成。每組Netlink消息都包含了一個Netlink消息頭,用戶負載和它們的填充。Netlink消息頭和用戶負載需要4字節對齊。nlmsghdr結構體由五個字段組成:消息長度、消息類型、消息標誌、消息序號和消息pid。其中消息長度是每組Netlink消息的總長度,消息pid一般設置爲進程ID。


Classic Netlink傳輸消息格式


Classic Netlink傳輸消息通過sendto或者sendmsg函數從用戶空間發送到內核空間。skb包含了該傳輸消息,它是input函數的唯一參數,可以在input函數中通過skb->data獲取該傳輸消息。問題是Linux內核Netlink子系統會如何檢查該傳輸消息?答案是什麼都不檢查。這意味着開發者需要自己檢查該傳輸消息頭,而將檢查交給開發者本身就是一個危險的行爲。


Classic Netlink傳輸消息解析


Classic Netlink威脅模型

爲此,Classic Netlink Top-Down消息解析方面我們總結了三種可能的攻擊點:


  • 開發者沒有理解skb->len、nlh->nlmsg_len和NLMSG_HDRLEN三者之間的關係。或者開發者根本不做任何檢查。skb->len是Netlink傳輸消息的總長度,由多組Netlink消息組成。nlh->nlmsg_len是一組Netlink消息的總長度,而NLMSG_HDRLEN則是一組Netlink消息頭的長度,是一個固定值。一個更簡單的檢查是通過NLMSG_OK來進行檢查。

  • 開發者沒有檢查用戶負載的長度就直接解析負載。

  • 開發者沒有充分檢查用戶負載內容的有效性


Classic Netlink Top-Down傳輸消息解析


Bottom-Up消息組建方面,則需要結合內核其他攻擊面,如file operations、socket等。可以通過檢查nlmsg_unicast或nlmsg_multicast函數,逆推出輸入源,然後進行漏洞挖掘。


Classic Netlink Bottom-Up傳輸消息組建


Generic Netlink內核機制

Generic Netlink是基於Classic Netlink創建,從其大體的消息結構可以看出。不同點在於負載的組成,包含了一個Generic Netlink消息頭、family頭、屬性和它們的填充。每個屬性又包含了1個屬性頭、屬性負載和它們的填充。


Generic Netlink傳輸消息格式


family頭是可選的、用戶自定義的,因此我們聚焦在Generic Netlink消息頭和屬性頭。genlmsghdr結構體由命令、版本和保留三個字段組成,其中命令字段用於指導內核調用具體的doit函數。nlattr結構體由Netlink屬性長度和類型兩個字段組成,與屬性負載一起形成了TLV結構。Netlink屬性長度是屬性的總長度,包含了屬性頭;Netlink屬性類型一般是屬性數組的索引。


Generic Netlink也是通過sendto或者senmsg函數將傳輸消息從用戶空間發送到內核空間。info參數中包含了該傳輸屬性,它是doit函數的一個參數,doit函數由genl_register_family註冊,用於解析該傳輸屬性。可以通過info->attrs[nla_type]獲取該傳輸屬性。相同的問題是Linux內核Netlink子系統會如何檢查該傳輸屬性?答案是通過nla_policy檢查。


Generic Netlink傳輸屬性解析


nla_policy即Netlink屬性策略是在genl_family或genl_ops結構體中註冊,genl_ops結構體中的策略優先於genl_family中的策略。nla_policy結構體包含了屬性類型、屬性長度、聯合體等字段。這裏的屬性類型指的是屬性負載的數據類型。屬性長度根據屬性類型有不同的含義。如屬性類型是NLA_STRING,則屬性長度是屬性負載的最大值。聯合體基於有效性類型來檢查有效性。所有的屬性檢查都是在validate_nla函數中進行。


Generic Netlink威脅模型

Generic Netlink比Classic Netlink做了更多的檢查,難道就不存在安全問題嗎?當然不是。我們總結了三個開發者可能會忽視的攻擊點:


  • 開發者在屬性策略註冊階段沒有設置或者設置了一個錯誤的屬性策略

  • 開發者在屬性解析階段沒有檢查屬性的有效性

  • 開發者在屬性解析階段沒有充分檢查屬性內容的有效性


Generic Netlink Top-Down傳輸屬性解析


Bottom-Up屬性組建方面,則需要結合內核其他攻擊面,如file operations、socket等。可以通過檢查genlmsg_unicast、genlmsg_multicast、genlmsg_multicast_netns或者genlmsg_multicast_allns函數,逆推出輸入源,然後進行漏洞挖掘。


Generic Netlink Bottom-Up傳輸屬性組建


三、案例研究

漏洞統計

基於上述攻擊面分析,我們調研了4個知名廠商與Netlink相關的內核模塊,發現了38個漏洞,截止目前已經分配了19個CVE,所有的漏洞都已經被廠商修復。其中Classic Netlink類型的漏洞比Generic Netlink類型的漏洞數量更多,而且漏洞嚴重程度也更高。也就是說用戶在使用Netlink時,更推薦使用Generic Netlink。


Netlink漏洞列表


漏洞分佈方面,無論是upstream還是downstream內核都存在漏洞,但downstream內核漏洞更多。


Netlink漏洞分佈


案例1:攻擊Classic Netlink消息解析過程

漏洞CVE-2023-32880 (NETLINK_FGD OOB Read)的場景最常見也是最簡單,Netlink傳輸消息通過sendto函數發送到內核空間,input函數會解析該消息。其中input函數的實現中存在兩個越解讀漏洞:


  • 未檢查Netlink消息頭

  • 未檢查Netlink消息負載長度就開始解析


漏洞案例1數據流圖


引出的思考是所有Netlink越解讀漏洞都只是在接收緩衝區中嗎?當然不是。這裏有兩個方法可以造成越解讀到接收緩衝區外部:


  • 通過setsockopt函數設置接收緩衝區大小爲最小

  • 精心構造Netlink消息,填充滿接收緩衝區


案例2:攻擊Classic Netlink消息組建過程

漏洞CVE-2024-20833 (NETLINK_FIPS_CRYPTO Use After Free)的場景則體現了Netlink全雙工的優勢,能夠將部分不需要在內核執行的代碼轉移到用戶空間去執行,以減少內核漏洞產生概率。該漏洞挖掘過程中,我們首先定位nlmsg_unicast函數,然後繪製出請求的整個生命週期,最終在input函數中發現UAF漏洞。該漏洞需要結合ioctl去觸發,形成的根因是未保護的全局變量。


漏洞案例2數據流圖


案例3:攻擊Generic Netlink消息解析過程

漏洞CVE-2024-26811 (Linux Kernel ksmbd smb2_read_pipe OOB Read)的場景與案例2相似。但其漏洞是發生在Generic Netlink的doit函數,該函數沒有檢查屬性負載內容的合法性,最終導致了越解讀。該漏洞需要結合TCP去觸發。


漏洞案例3數據流圖


案例4:攻擊Generic Netlink消息組建過程

漏洞CVE-2023-52103 (Driver flp OOB Read)的場景是單純的Generic Netlink Bottom-Up場景。通過genlmsg_unicast可以逆推出整個消息的生命週期,進而漏洞挖掘出flp_write函數中存在越界讀,讀的數據是通過genlmsg_unicast發送到用戶空間。該漏洞需要與write去觸發,形成的根因是不嚴格的消息內容合法性檢查。


漏洞案例4數據流圖


四、驗證與利用

Classic Netlink驗證

如果想要使用Netlink觸發競爭條件,經常會遇到源端口占用問題。此時可以通過在多進程中使用進程ID作爲源端口、或者在多線程中嘗試可用的端口兩種簡單方式解決。


Generic Netlink驗證

如果想要自定義使用Generic Netlink,在用戶態首先要解決的問題就是將Family Name轉化爲Family ID,可以發送固定消息進行獲取。


通過Family Name獲取Family ID的消息格式


Netlink漏洞利用

漏洞利用方面,採用了CVE-2023-32878 (Arbitrary Read)和CVE-2023-32882 (Write-What-Where)兩個漏洞獲取了Root權限。值得注意的是Netlink漏洞可以通過在安卓中設置netlink_socket的selinux規則或者在Linux中調用netlink_capable函數檢查CAP_NET_ADMIN權限進行緩解。


五、總結

總結

  • Netlink是一個深埋在安卓系統中的隱藏攻擊面

  • 當自定義Classic Netlink時,內核並不會檢查Netlink消息的合法性

  • 當自定義Generic Netlink時,內核會根據Netlink屬性策略檢查Netlink屬性的合法性

  • Generic Netlink檢查比Classic Netlink更多,但也會帶來一些其他安全威脅


安全建議

  • 自定義使用Netlink時,儘量使用Generic Netlink替代Classic Netlink

  • 在使用Netlink前,先理解Netlink內核機制和API

相關閱讀

連中三元!百度安全多篇議題入選Blackhat Asia,以硬技術發現“芯”問題

BlackHat USA 2022議題解讀:藍牙Mesh中的安全攻擊面

Blackhat Europe 2023 | 百度安全揭祕多平臺NPU背後的安全風險


本文分享自微信公衆號 - 百度安全實驗室(BaiduX_lab)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章