bluetooth_stack開源藍牙協議棧源碼分析與漏洞挖掘


文章首發地址

https://xz.aliyun.com/t/9205

前言

網上閑逛的時候,發現github有個開源的藍牙協議棧項目

https://github.com/sj15712795029/bluetooth_stack

看介紹支持STM32,網上支持嵌入式芯片的開源協議棧貌似很少,這里就簡單分析一下,也能幫助助理解藍牙協議棧,順便給它找點漏洞。

代碼流程分析

這個代碼只支持HCI層以上的協議,比如L2CAP、ATT等,像HCI下層的協議比如LL則使用的 CSR8311 芯片中自帶的協議棧。程序收包的入口是hci_acl_input

void hci_acl_input(struct bt_pbuf_t *p)
{
	...............
	...............
	if(link != NULL)
    {
        if(aclhdr->len)
        {
            l2cap_acl_input(p, &(link->bdaddr));
        }
    }

函數經過簡單的處理就會進入l2cap_acl_input處理L2CAP報文,再繼續往下之前介紹一下程序中使用的存放藍牙數據包的數據結構

struct bt_pbuf_t
{
    /** 單鏈表中的下一個pbuf節點 */
    struct bt_pbuf_t *next;

    /** pbuf節點payload數據指針 */
    void *payload;

    /** pbuf單鏈表中本節點以及后續節點的數據總和 */
    uint16_t tot_len;

    /** 本pbuf節點的payload數據長度 */
    uint16_t len;

    /** pbuf類型 */
    uint8_t  type;

    /** pbuf標志 */
    uint8_t flags;

    /** pbuf引用次數 */
    uint16_t ref;
};

bt_pbuf_t結構體組成一個鏈表,其中payload表示當前pbuf的數據存放的地址,len表示payload的長度,tot_len表示整個鏈表里面所有pbuf的長度,這樣可以方便的進行數據包的重組。

l2cap_acl_input的代碼如下


void l2cap_acl_input(struct bt_pbuf_t *p, struct bd_addr_t *bdaddr)
{
    l2cap_seg_t *inseg = l2cap_reassembly_data(p,bdaddr,&can_contiue);
    if(!can_contiue)
        return;
    /* Handle packet */
    // inseg->l2caphdr = p->payload
    switch(inseg->l2caphdr->cid)
    {
    case L2CAP_NULL_CID:
        _l2cap_null_cid_process(inseg->p,bdaddr);
        break;
    case L2CAP_SIG_CID:
        _l2cap_classical_sig_cid_process(inseg->p,inseg->l2caphdr,bdaddr);
        break;
    case L2CAP_CONNLESS_CID:
        _l2cap_connless_cid_process(inseg->p,bdaddr);
        break;
    case L2CAP_ATT_CID:
        _l2cap_fixed_cid_process(L2CAP_ATT_CID,p,bdaddr);
        break;
    default:
        _l2cap_dynamic_cid_process(inseg->pcb,inseg->p,inseg->l2caphdr,bdaddr);
        break;
    }

    bt_memp_free(MEMP_L2CAP_SEG, inseg);
}

函數首先使用l2cap_reassembly_data處理L2CAP的分片,然后根據根據l2cap頭部的字段選擇相應的函數對數據包進行處理,比如如果是signaling commands的數據就會進入 _l2cap_classical_sig_cid_process 進行處理,其中入參的含義如下

inseg->p: 包含 L2CAP 數據包的 pbuf_t 結構體
inseg->l2caphdr: 指向L2CAP的頭部
bdaddr: 數據包發送者的設備地址

然后我們從l2cap_acl_input就可以開始進行漏洞挖掘了,可以重點關注涉及到變長數據結構的解析,此外我們可以根據BLE的協議規范來輔助理解代碼,接下來以一些具體的漏洞來分析一些函數的流程。

處理ATT報文時3處棧溢出漏洞

處理ATT報文的函數為_l2cap_fixed_cid_process

static err_t _l2cap_fixed_cid_process(uint16_t cid,struct bt_pbuf_t *p,struct bd_addr_t *bdaddr)
{
    bt_pbuf_header(p, -L2CAP_HDR_LEN);
    for(l2cap_pcb = l2cap_active_pcbs; l2cap_pcb != NULL; l2cap_pcb = l2cap_pcb->next)
    {
        if(l2cap_pcb->fixed_cid == cid)
        {
            bd_addr_set(&(l2cap_pcb->remote_bdaddr),bdaddr);
            L2CA_ACTION_RECV(l2cap_pcb,p,BT_ERR_OK);
            break;
        }
    }


函數首先使用bt_pbuf_header,讓p->payload 跳過 L2CAP 的頭部,即 p->payload += L2CAP_HDR_LEN,然后函數會根據cid調用之前注冊的處理函數,最終會調用到 gatt_data_recv 函數:

void gatt_data_recv(struct bd_addr_t *remote_addr,struct bt_pbuf_t *p)
{
    uint8_t opcode = ((uint8_t *)p->payload)[0];
    switch(opcode)
    {
    case ATT_REQ_MTU:
    {
		gatts_handle_mtu_req(NULL,p);
        break;
    }

函數主要就是根據 opcode 來判斷ATT數據的類型,然后調用相應的函數進行處理,存在棧溢出漏洞的函數

gatts_handle_find_info_value_type_req
gatts_handle_write_req
gatts_handle_write_cmd

這里以gatts_handle_write_req為例,另外兩個漏洞的成因類似,當opcode為ATT_REQ_WRITE時會調用gatts_handle_write_req進行處理

    case ATT_REQ_WRITE:
    {
        gatts_handle_write_req(NULL,p);
        break;
    }

gatts_handle_write_req 的關鍵代碼如下

static err_t gatts_handle_write_req(struct bd_addr_t *bdaddr, struct bt_pbuf_t *p)
{
	uint8_t req_buf_len = 0;
    uint8_t req_buf[GATT_BLE_MTU_SIZE] = {0};
	att_parse_write_req(p,&handle,req_buf,&req_buf_len);

函數入口會調用att_parse_write_req解析傳入的報文,req_buf為棧上的數組,大小為23字節

err_t att_parse_write_req(struct bt_pbuf_t *p,uint16_t *handle,uint8_t *att_value,uint8_t *value_len)
{
	uint8_t *data = p->payload;
    uint8_t data_len = p->len;

    *handle = bt_le_read_16(data,1);

	*value_len = data_len-3;
	memcpy(att_value,data+3,*value_len);
    return BT_ERR_OK;
}

att_parse_write_req函數直接將 payload+3 的內容拷貝到 att_value ,如果 value_len 大於23 就會棧溢出。

處理avrcp報文時存在堆溢出漏洞

漏洞出在avrcp_controller_parse_get_element_attr_rsp函數里面,函數調用關系如下

_l2cap_fixed_cid_process
    avctp_data_input
        avrcp_controller_data_handle
            avrcp_controller_parse_vendor_dependent
                avrcp_controller_parse_get_element_attr_rsp

關鍵代碼如下

static err_t avrcp_controller_parse_get_element_attr_rsp(struct avctp_pcb_t *avctp_pcb,uint8_t *buffer,uint16_t buffer_len)
{
    uint8_t index = 0;
    uint16_t para_len = bt_be_read_16(buffer, 8);
    uint8_t element_attr_num = buffer[10];
    uint8_t *para_palyload = buffer + 11;

    struct avrcp_pcb_t *avrcp_pcb = avrcp_get_active_pcb(&avctp_pcb->remote_bdaddr);


	memset(&avrcp_pcb->now_playing_info,0,sizeof(now_playing_info_t));
    for(index = 0; index < element_attr_num; index++)
    {
        uint32_t attr_id = bt_be_read_32(para_palyload, 0);
        uint16_t attr_length = bt_be_read_16(para_palyload+6, 0);

        switch(attr_id)
        {
        case AVRCP_MEDIA_ATTR_TITLE:
			memcpy(avrcp_pcb->now_playing_info.now_playing_title,para_palyload+8,attr_length);

buffer 中存放的是藍牙數據,函數首先調用avrcp_get_active_pcb獲取avrcp_pcb,然后調用bt_be_read_16從buffer里面取出兩個字節作為attr_length, 然后進行內存拷貝,如果attr_length過大就會導致堆溢出。

_l2cap_sig_cfg_rsp_process整數溢出導致越界讀

該函數用於處理 L2CAP_CFG_RSP 消息,其中關鍵代碼如下

_l2cap_sig_cfg_rsp_process(l2cap_pcb_t *pcb,struct bt_pbuf_t *p,l2cap_sig_hdr_t *sighdr,l2cap_sig_t *sig)
{
 	uint16_t siglen;
    siglen = sighdr->len;
    siglen -= 6;
    bt_pbuf_header(p, -6);

    switch(result)
    {
    case L2CAP_CFG_UNACCEPT:
        while(siglen > 0)
        {
            opthdr = p->payload;
            ..................
            ..................
            ..................
            bt_pbuf_header(p, -(L2CAP_CFGOPTHDR_LEN + opthdr->len));
            siglen -= L2CAP_CFGOPTHDR_LEN + opthdr->len;
        }

其中sighdrL2CAPSIGNALING 包頭,p里面存放着外部設備發送過來的藍牙數據包。

函數首先從sighdr里面取出siglen,然后 siglen-=6 ,最后根據siglen循環的去讀取數據。如果sighdr->len小於6,由於siglen的類型為uint16_t,最后siglen的值為 0xFFFF-6, 這是一個很大的數后面循環的時候就會一直讀到很后面的數據。

avrcp_controller_parse_list_app_setting_rsp越界讀

函數關鍵代碼如下

static err_t avrcp_controller_parse_list_app_setting_rsp(struct avctp_pcb_t *avctp_pcb,uint8_t *buffer,uint16_t buffer_len)
{
    uint8_t app_setting_attr_num = buffer[10];
    struct avrcp_pcb_t *avrcp_pcb = avrcp_get_active_pcb(&avctp_pcb->remote_bdaddr);

    if(app_setting_attr_num > 0)
    {
        uint8_t *setting_attr = buffer+11;
        for(index = 0; index < app_setting_attr_num; index++)
        {
            switch(setting_attr[index])
            {

首先從buffer里面取出app_setting_attr_num,然后將其作為循環條件訪問setting_attr內存,這個過程沒有校驗訪存是否超過了buffer的長度,會導致越界讀。

總結

協議棧代碼里面存在處理協議數據的典型問題,比如訪問內存時沒有檢查長度,內存拷貝的時候沒有校驗拷貝長度是否大於目的內存的大小,以及數值運算也沒有考慮整數溢出的情況,總體來說代碼質量較低。

此次分析過程的代碼思維導圖如下

bluetooth_stack.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM