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

一、漏洞概述

2020年10月,谷歌安全研究人員披露了三個Linux內核藍牙協議棧漏洞,可導致遠程代碼執行,被稱為BleedingTooth。這三個漏洞中,一個是堆溢出,編號為CVE-2020-24490;另一個是類型混淆,編號為CVE-2020-12351,最后一個是信息泄露,編號為CVE-2020-12352。近日,谷歌安全研究人員又披露了BleedingTooth中CVE-2020-12351和CVE-2020-12352組合的漏洞利用及細節,并在藍牙4.0下,實現了零點擊遠程代碼執行。

二、漏洞分析

(一)CVE-2020-12351

該漏洞出現在net/bluetooth/l2cap_core.c中。l2cap_recv_frame()是解析和處理l2cap協議數據包的函數。代碼實現如下所示:

獲取通道cid和l2cap數據包長度len。代碼實現如下所示:

根據不同的通道cid,進入不同的子過程進行處理,進入l2cap_data_channel()函數。代碼實現如下所示:

首先,通過cid找到通道chan;如果沒有找到,判斷cid是否為L2CAP_CID_A2MP;如果是,調用a2mp_channel_create()創建一個新的通道chan。a2mp_channel_create()函數實現如下所示:

調用amp_mgr_create()創建mgr,在amp_mgr_create()函數中,代碼實現如下所示:

調用a2mp_chan_open()創建通道chan,該函數將初始化一部分數據,代碼實現如下所示:

如將chan->mode初始化為L2CAP_MODE_ERTM。chan->data賦值為mgr,類型為struct amp_mgr。成功創建a2mp通道返回到l2cap_data_channel()中,代碼實現如下所示:

根據chan->mode的不同,進入不同的data處理子過程,當mode為L2CAP_MODE_ERTM和L2CAP_MODE_STREAMING時,進入l2cap_data_rcv()函數中,代碼實現如下所示:

該if條件中,會調用sk_filter()函數,此時chan->data為參數。而sk_filter()函數定義如下所示:

第一個參數類型為struct sock,而chan->data類型為struct amp_mgr,發生類型混淆。

(二)CVE-2020-12352

該漏洞是出現在a2mp協議中,漏洞代碼位于net/bluetooth/a2mp.c,多個函數使用未初始化的結構體,將數據返回到用戶層,導致信息泄露,可泄露內核棧上的內存數據。漏洞原理較為簡單,以a2mp_getinfo_req()函數為例,該函數是響應getinfo請求時調用的,代碼實現如下所示:

行304,通過req->id獲取hdev,如果不存在hdev或hdev->type不是HCI_AMP,進入if語句中,定義struct a2mp_info_rsp類型的 rsp,該結構體定義如下所示:

其只使用了rsp.id和rsp.status,其他的數據域未使用也未初始化,可以泄露16字節數據,然后調用a2mp_send()函數將響應包發送到用戶層,泄露內存數據。

(三)CVE-2020-24490

該漏洞只能在bluetooth 5.0下觸發,在bluetooth 5.0之前,HCI進行廣播的最大數據長度為0x1F,0x20-0xFF保留。如下所示:

在bluetooth 5.0中,該length最大擴展到229字節。如下所示:

該漏洞代碼位于net/bluetooth/hci_event.c中,在處理HCI_LE_Extended_Advertising_Report事件中,未判斷廣播數據長度最大值,后續拷貝廣播Data導致溢出。調用過程如下所示:

process_adv_report()函數處理廣播數據,將廣播數據拷貝到發現的設備中,代碼實現如下所示:

調用store_pending_adv_report()函數,該函數實現廣播數據拷貝,代碼實現如下所示:

其中,discovery_state結構體定義如下所示:

last_adv_data數據大小為HCI_MAX_AD_LENGTH,共31字節,當執行memcpy時發生溢出。

三、利用分析與復現

(一)控制代碼執行流程

前文分析到CVE-2020-12351類型混淆是在sk_filter()函數中發生的,sk_filter()函數調用sk_filter_trim_cap()函數,該函數代碼實現如下:

該函數第一個參數為sk,參數類型為sock結構體,這部分代碼中對sk和skb的檢查容易繞過。接下來關鍵代碼如下所示:

行113,對sk->sk_filter進行解引用,如果成功獲取filter指針,進入行115。行119,調用 bpf_prog_run_save_cb()函數,參數分別為filter->prog和skb,該函數代碼實現如下所示:

然后,行676,調用__bpf_prog_run_save_cb()函數,該函數實現代碼如下:

接著,行662,調用BPF_PROG_RUN(prog,skb),該函數定義為一個宏,實現代碼如下所示:

一路調用下來,最終會調用到紅框中的代碼,簡化一下調用過程為: sk->sk_filter->prog->bpf_func(skb, sk->sk_filter->prog->insnsi)。因此,只要控制sk->sk_filter就可以控制執行流程。

(二)堆噴占位

函數sk_filter()的第一個參數類型為struct sock,而實際傳入的參數類型為struct amp_mgr,可以采用堆噴128大小的內存塊進行占位,偽造amp_mgr 對象。這里有個問題,sk->sk_filter在sock中的偏移為0x110,而amp_mgr結構體大小為0x70,偏移已經超出了范圍。要解決這個問題,這里可以采用如下巧妙的堆噴布局:

結構體amp_mgr在kmalloc-128類型的slub中被分配,從第三個塊開始,amp_mgr結構體偏移0x10處,可以被偽造成sk_filter,便可以滿足sk對sk_filter域的解引用,并且可控。

(三)布局載荷

通過堆噴占位控制代碼執行流程后,接下來就是布局攻擊載荷。可以采用堆噴1024大小的內存塊去偽造l2cap_chan對象,因為結構體大小為792,正好落在kmalloc-1024 slub塊中,而且a2mp通道也屬于l2cap通道中,釋放a2mp通道時,l2cap通道也將被釋放,操控起來較為靈活,最終布局如下所示:

(四)泄露l2cap_chan對象地址

通過堆噴布局和創建釋放l2cap_chan通道等一系列操作后,可能存在一個指向kmalloc-1024內存塊地址的l2cap_chan對象,可以通過CVE-2020-12352漏洞泄露一個內核棧上面的內核地址,如下圖中紅框所示:

通過該內地地址減去一個0x110偏移便可以找到一個l2cap_chan對象地址,可以通過amp_mgr結構體內存地址檢查一下是否正確,因為amp_mgr結構體偏移0x18處為l2cap_chan指針,如下圖中紅框所示:

成功泄露l2cap_chan對象地址后,然后去填充amp_mgr結構體偏移0x10處的數據域。

(五)復現測試

我們在ubuntu 5.4.0-26-generic系統下復現測試漏洞利用,執行過程如下:

成功反彈root級shell,如下所示:

四、參考鏈接

1、 https://google.github.io/security-research/pocs/linux/bleedingtooth/writeup
2、 https://github.com/google/security-research/security/advisories/GHSA-ccx2-w2r4-x649
3、 https://github.com/google/security-research/security/advisories/GHSA-7mh3-gq28-gfrq
4、 https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq


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