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;
}
其中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的长度,会导致越界读。
总结
协议栈代码里面存在处理协议数据的典型问题,比如访问内存时没有检查长度,内存拷贝的时候没有校验拷贝长度是否大于目的内存的大小,以及数值运算也没有考虑整数溢出的情况,总体来说代码质量较低。
此次分析过程的代码思维导图如下

bluetooth_stack开源蓝牙协议栈源码分析与漏洞挖掘的更多相关文章
- 开源MyBatisGenerator组件源码分析
开源MyBatisGenerator组件源码分析 看源码前,先了解Generator能做什么? MyBatisGenerator是用来生成mybatis的Mapper接口和xml文件的工具,提供多种启 ...
- Android 网络流量监听开源项目-ConnectionClass源码分析
很多App要做到极致的话,对网络状态的监听是很有必要的,比如在网络差的时候加载质量一般的小图,缩略图,在网络好的时候,加载高清大图,脸书的android 客户端就是这么做的, 当然伟大的脸书也把这部分 ...
- Bluedroid: 蓝牙协议栈源码剖析
一. 基础知识介绍 1.缩略语 BTIF: Bluetooth Interface BTU : Bluetooth Upper Layer BTM: Bluetooth Manager BTE: Bl ...
- Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析
ActiveAndroid 项目地址在https://github.com/pardom/ActiveAndroid 关于他的详细介绍和使用步骤 可以看下面两篇文章: https://github.c ...
- MQTT开源代理Mosquitto源码分析(访问控制篇)
一.整体流程概览 从GitHub下载源码后,代理的源码在src中,同时还用到了lib库中的一些函数.对项目的工作流程有个大概理解是分析mosquitto的访问控制权限的基础,网络上已有很多中文博客在介 ...
- Android 开源项目PhotoView源码分析
https://github.com/chrisbanes/PhotoView/tree/master/library 这个就是项目地址,相信很多人都用过,我依然不去讲怎么使用.只讲他的原理和具体实现 ...
- Android之开源中国客户端源码分析(二)
1. 加载动画圈实现 <ProgressBar android:id="@+id/main_head_progress" style="@style/loading ...
- Android之开源中国客户端源码分析(一)
程序启动第一个界面类: net.oschina.app.AppStart功能描述:一张图片代码细节描述:一个透明度的动画效果,效果动画完成后自动启动新的Activity(Main) 基本BaseAct ...
- go开源项目influxdb-relay源码分析(一)
influxdb-relay项目地址: https://github.com/influxdata/influxdb-relay,主要作为负载均衡节点,写入多个influxdb节点,起到高可用效果. ...
- android 在线升级借助开源中国App源码
android 在线升级借助开源中国App源码 http://www.cnblogs.com/luomingui/p/3949429.html android 在线升级借助开源中国App源码分析如下: ...
随机推荐
- java_day2_常量,变量,数据类型,运算符
一.常量 常量:在Java程序运行过程中其值不能发生改变的量 分类: 1.字面值常量: 整数常量 表示所有的整数,包括负数 10 -8 小数常量 表示所有的小数 1.23 -3.14 布尔常量 tru ...
- ByConity与主流开源OLAP引擎(Clickhouse、Doris、Presto)性能对比分析
引言: 随着数据量和数据复杂性的不断增加,越来越多的企业开始使用OLAP(联机分析处理)引擎来处理大规模数据并提供即时分析结果.在选择OLAP引擎时,性能是一个非常重要的因素. 因此,本文将使用TPC ...
- 105份墨天轮“国产化迁移”干货文档汇总(含TiDB、openGauss、上云等)
当前国产数据库产品百花齐放,随着政策的推进.技术的迭代以及市场需求的逐步扩大,数据库国产化正在加速进行中,有越来越多的金融.通信.制造.互联网等企业机构以及政府机关单位将业务系统从Oracle.MyS ...
- 我们如何在 vue 应用我们的权限
权限可以分为用户权限和按钮权限: 用户权限,让不同的用户拥有不同的路由映射 ,具体实现方法: 1. 初始化路由实例的时候,只把静态路由规则注入 ,不要注入动态路由规则 : 2. 用户登录的时候,根据返 ...
- 为什么科技企业都在建设API开放平台?
一.API开发者门户的兴起 随着数字化转型的推进,越来越多的企业开始依赖 API 来实现内部系统的连接与外部服务的集成.然而,企业在API管理方面面临着复杂且繁琐的挑战:API的调用关系混乱.难以追踪 ...
- 使用nacos上传配置文件报错
1.使用nacos导入配置文件报错:未读取到合法数据,请检查导入的数据文件. 对比在naocs server中导出的文件,发现是少了一级目录.需要创建一个文件夹,名称为组的名称.因为在nacos上传文 ...
- jenkins + sonar 实现代码检测的配置
一.首先安装sonar scanner的客户端 我的jenkins版本为2.176.2 安装sonar需要安装客户端和服务端,这里只讲述客户端的相关配置.安装步骤省略 二.然后配置sonar scan ...
- 快速部署kafka集群和可视化界面
3台机器部署kafka集群的分布 10.1.161.111 -> kafka(1个节点)+ zookeeper(一个节点)+ kowl(一个节点) 10.1.161.112 -> kafk ...
- 什么是APP原生开发
什么是APP原生开发?原生App实际上是一种基于智能手机本地操作系统如Android.IOS并且使用原生程序编写运行的第三方移动应用程序.开发原生App软件需要针对不同智能手机的操作系统来选择不同的A ...
- CSS动画(动态导航栏)
1.项目简介 一个具有创意的导航菜单不仅能为你的大作业增色,还能展示你的技术实力.本文将分享一系列常用于期末大作业的CSS动画导航效果,这些效果不仅外观酷炫,而且易于实现.我们提供了一键复制的代码,让 ...