文章首發地址
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;
}
其中sighdr
為L2CAP
的SIGNALING
包頭,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
的長度,會導致越界讀。
總結
協議棧代碼里面存在處理協議數據的典型問題,比如訪問內存時沒有檢查長度,內存拷貝的時候沒有校驗拷貝長度是否大於目的內存的大小,以及數值運算也沒有考慮整數溢出的情況,總體來說代碼質量較低。
此次分析過程的代碼思維導圖如下