BlueBorne 之 CVE-2017-1000251 原理分析与 DoS Exp 构造

CVE-2017-1000251 是 Linux BlueZ 蓝牙协议栈中 L2CAP (Logical Link Control and Adaption Protocol) 模块曝出的 stack overflow 漏洞。由于 BlueZ 把 L2CAP 实现在了 Linux 内核中,所以触发该漏洞极易导致 Linux 系统崩溃。当然该漏洞也可以实现内核级的 RCE。

本文的分析环境

存在漏洞的操作系统使用镜像 ubuntu-16.04.1-desktop-amd64.iso 安装。该系统使用的内核版本如下:

$ uname -rv
4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016

执行如下命令可以得到该内核的源码:

wget https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/linux/4.4.0-31.50/linux_4.4.0.orig.tar.gz https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/linux/4.4.0-31.50/linux_4.4.0-31.50.diff.gz https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/linux/4.4.0-31.50/linux_4.4.0-31.50.dsc
tar -xvf linux_4.4.0.orig.tar.gz
gzip -dk linux_4.4.0-31.50.diff.gz
cd linux-4.4
patch -p1 < ../linux_4.4.0-31.50.diff

背景知识

L2CAP 简介

L2CAP 在蓝牙世界中的地位相当于因特网中的 TCP/UDP 协议。它可以把逻辑链路复用为多个逻辑信道,同时在这些信道上为高层提供面向连接或无连接的数据传输以及数据分段重组服务。

L2CAP 连接的建立过程

L2CAP 连接的建立过程分 3 个阶段:

  1. 信息交换

    该阶段涉及的数据包为 L2CAP_INFORMATION_REQL2CAP_INFORMATION_RSP。两个蓝牙设备在建立 L2CAP 连接时会先交换各自的信息。这些信息的类型可能如下:

  2. 创建连接

    该阶段涉及的数据包为 L2CAP_CONNECTION_REQL2CAP_CONNECTION_RSP。其中前者使用 PSM (Protocol/Service Multiplexer) 向远端说明了当前连接服务的上层协议,并说明本地为连接分配的 CID,即 SCID 字段;若远端设备同意连接,则后者也会说明远端设备为当前连接分配的 CID,即 DCID 字段。SCID-DCID pair 标识了一个 L2CAP 连接。

  3. 配置连接

    • BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part A page 1122, 7.1 CONFIGURATION PROCESS
    • BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part A page 1138, 7.10 SUPPORTING EXTENDED FLOW SPECIFICATION FOR BR/EDR AND BR/EDR/LE CONTROLLERS
    • BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part A page 1095, 5.6 EXTENDED FLOW SPECIFICATION OPTION

    连接创建后,蓝牙设备会立即对它做进一步的配置。该阶段涉及的数据包为 L2CAP_CONFIGURATION_REQL2CAP_CONFIGURATION_RSP本文分析的漏洞就存在于这个阶段。

    蓝牙设备可能会多次协商配置连接的参数。响应 L2CAP_CONFIGURATION_REQL2CAP_CONFIGURATION_RSP 也可能携带配置选项(Config 字段)。此时表示原先请求配置的数据可能不被设备接受。同时响应中携带的配置选项就是设备给出的可接受配置,即“你原先提供的配置我不支持,你看我提出的配置是否可以?”

    另外配置连接的流程有两种可能,一种是 Standard process 另一种是 Lockstep process。当两个蓝牙设备都支持 EFS (Extended Flow Specification) 时,将使用 Lockstep process 而非 Standard process。

漏洞分析

当本地设备接收到响应配置请求的 L2CAP_CONFIGURATION_RSP 时,内核将调用 l2cap_config_rsp() 处理它:

// net/bluetooth/l2cap_core.c#4129
static inline int l2cap_config_rsp(
    struct l2cap_conn *conn,
    struct l2cap_cmd_hdr *cmd, u16 cmd_len,
    u8 *data) {
    ... ...
}

该函数的 conncmd 参数很好理解。前者指向的结构体描述了当前处理的 L2CAP_CONFIGURATION_RSP packet 所属的 L2CAP 连接;后者指向的结构体存储了 L2CAP_CONFIGURATION_RSP command header:

然后先说明 data 参数再说明 cmd_len 参数。data 参数指向的 buffer 存储了 L2CAP_CONFIGURATION_RSP packet 的 data 字段,即下图的红框部分:

cmd_len 参数则表示上面红框中字段的总长度,等同于 command header 中的 Length 字段。l2cap_config_rsp() 仅使用该参数确定红框中可变长字段 Config 的长度 len

// net/bluetooth/l2cap_core.c#4133
struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data;
... ...
// net/bluetooth/l2cap_core.c#4136
int len = cmd_len - sizeof(*rsp);
... ...
// net/bluetooth/l2cap_core.c#4139
if (cmd_len < sizeof(*rsp))
    return -EPROTO;

之后 l2cap_config_rsp() 会根据 L2CAP_CONFIGURATION_RSP packet 包含的 Result 字段,分情况对它做进一步处理。Result 字段有如下几种情况:

本文所分析的 Linux 内核 (4.4.0-31.50) 仅对 0x0000 L2CAP_CONF_SUCCESS、0x0004 L2CAP_CONF_PENDING 以及 0x0001 L2CAP_CONF_UNACCEPT 做了特殊的处理。对于其他 Result,内核将直接发送 L2CAP_DISCONNECTION_REQ 断开 L2CAP 连接:

// net/bluetooth/l2cap_core.c#4210
default:
    l2cap_chan_set_err(chan, ECONNRESET);

    __set_chan_timer(chan, L2CAP_DISC_REJ_TIMEOUT);
    l2cap_send_disconn_req(chan, ECONNRESET);
    goto done;

在处理 Result 为 L2CAP_CONF_PENDINGL2CAP_CONF_UNACCEPT 的包时,l2cap_config_rsp() 都可能创建一个 64 字节 buffer,并把它传入存在漏洞的函数 l2cap_parse_conf_rsp()

// net/bluetooth/l2cap_core.c#4159
case L2CAP_CONF_PENDING:
    set_bit(CONF_REM_CONF_PEND, &chan->conf_state);

    if (test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) {
        char buf[64];

        len = l2cap_parse_conf_rsp(chan, rsp->data, len,
                        buf, &result);
        ... ...
    }
    goto done;

case L2CAP_CONF_UNACCEPT:
    if (chan->num_conf_rsp <= L2CAP_CONF_MAX_CONF_RSP) {
        char req[64];

        if (len > sizeof(req) - sizeof(struct l2cap_conf_req)) {
            l2cap_send_disconn_req(chan, ECONNRESET);
            goto done;
        }

        /* throw out any old stored conf requests */
        result = L2CAP_CONF_SUCCESS;
        len = l2cap_parse_conf_rsp(chan, rsp->data, len,
                        req, &result);
        ... ...
    }

漏洞函数 l2cap_parse_conf_rsp() 的原型如下:

// net/bluetooth/l2cap_core.c#3508
static int l2cap_parse_conf_rsp(
    struct l2cap_chan *chan, void *rsp, int len,
    void *data, u16 *result) {
    ... ...
}

上面的 chan 参数比较好理解,它用于描述当前 L2CAP 连接使用的 channel。

rsp 参数指向的 buffer 存储了 L2CAP_CONFIGURATION_RSP packet 的 Config 字段。len 参数则与 rsp 参数对应,说明了可变长 Config 字段的大小。

根据 l2cap_config_rsp() 两处调用 l2cap_parse_conf_rsp() 传入的参数可知,data 参数指向了大小为 64 字节的 buffer (char buf[64]char req[64])。但是 l2cap_parse_conf_rsp() 本身并不知道 data 指向的 buffer 有 64 字节的限制,因为该 buffer 的长度没有作为参数传入 l2cap_parse_conf_rsp(),这也是导致漏洞的地方。

由于收到的 L2CAP_CONFIGURATION_RSP 中有新的配置数据,这说明对端设备可能不同意本地设备先前提供的配置,同时新的配置数据就是对端设备期望继续协商的内容。于是本地设备需要继续发送新的 request 来协商配置。这也是在 l2cap_parse_conf_rsp() 的开头,data buffer 直接被转换为了 l2cap_conf_req 结构体指针的原因:

// net/bluetooth/l2cap_core.c#3511
struct l2cap_conf_req *req = data;
void *ptr = req->data;

然后漏洞函数会逐个解析 L2CAP_CONFIGURATION_RSP 携带的配置选项,并根据此构造自己一方新的协商配置,然后存储到 data buffer 中。这些新的协商配置将放在后续的配置请求中发给对端设备。

比如当 L2CAP_CONFIGURATION_RSP 携带 MTU (Maximum Transmission Unit) 配置选项时,如果其值不满足本地 incoming MTU 的要求,l2cap_parse_conf_rsp() 会首先重设 Result 字段(result 参数)为 unacceptable,并给一个协议默认的最小值作为新的 incoming MTU。同时不论值是否满足本地要求,漏洞函数都会构造一个新的 MTU 配置选项存储在 data buffer (下面的 ptr) 中,供后续的配置请求使用:

BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part A page 1081, 5.1 MAXIMUM TRANSMISSION UNIT (MTU)

// net/bluetooth/l2cap_core.c#3524
case L2CAP_CONF_MTU:
    if (val < L2CAP_DEFAULT_MIN_MTU) {
        *result = L2CAP_CONF_UNACCEPT;
        chan->imtu = L2CAP_DEFAULT_MIN_MTU;
    } else
        chan->imtu = val;
    l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->imtu);
    break;

如果接收的 L2CAP_CONFIGURATION_RSP packet 恶意包含了过多的 MTU 配置选项,那么漏洞函数将新构造同样多的 MTU 选项并把它们存储 data buffer 中。于是 64 字节的 buffer 就会溢出,最终导致栈溢出漏洞。

构造 DoS Exp

BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part A page 1054, Flags

  1. 构造 L2CAP_CONNECTION_REQ

    首先发送连接请求与目标设备建立连接。这里 PSM 选用 0x0001,因为它对应的上层应用为 SDP (Service Discovery Protocol),几乎所有 BR/EDR 设备都支持 SDP。这能保证连接建立成功;SCID 选一个动态分配的 CID 即可,比如 0x0050。

    对于远端响应的 L2CAP_CONNECTION_RSP,我们需要保存其中的 DCID,供后续的包使用。

  2. 构造 L2CAP_CONFIGURATION_REQ

    连接成功后,我们发起配置连接的请求,目的是让目标系统进入 pending state。因为根据内核中的如下代码,要调用到漏洞函数 l2cap_parse_conf_rsp() 需信道的配置状态为 CONF_LOC_CONF_PEND

    // // net/bluetooth/l2cap_core.c#4162
    if (test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) {
        char buf[64];
    
        len = l2cap_parse_conf_rsp(chan, rsp->data, len,
                       buf, &result);
        ... ...
    }
    

    其中信道的配置状态 chan->conf_state 只可能在内核解析 L2CAP_CONFIGURATION_REQ 时被置 CONF_LOC_CONF_PEND。具体位置在 l2cap_parse_conf_req() 中:

    // net/bluetooth/l2cap_core.c#3414
    if (remote_efs) {
        if (chan->local_stype != L2CAP_SERV_NOTRAFIC &&
            efs.stype != L2CAP_SERV_NOTRAFIC &&
            efs.stype != chan->local_stype) {
    
            result = L2CAP_CONF_UNACCEPT;
    
            if (chan->num_conf_req >= 1)
                return -ECONNREFUSED;
    
            l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS,
                        sizeof(efs),
                        (unsigned long) &efs);
        } else {
            /* Send PENDING Conf Rsp */
            result = L2CAP_CONF_PENDING;
            set_bit(CONF_LOC_CONF_PEND, &chan->conf_state);
        }
    }
    

    根据上面的代码可知,执行到 set_bit() 需满足两个条件,即对端设备的 L2CAP_CONFIGURATION_REQ 要携带 EFS option,且该选项的 Service Type (efs.stype) 为 No Traffic (L2CAP_SERV_NOTRAFIC)。

    DCID 字段设置为上一步我们从 L2CAP_CONNECTION_RSP 中提取的 DCID 即可。

    根据蓝牙协议,flags 字段的合法值目前只有 0x0000 和 0x0001。这里需要把它设置为 0x0000,表示我们走 Lockstep 配置流程。如果设置成 0x0001,会导致配置流程走 Standard process 而不是 Lockstep process。如果不走 Lockstep process,l2cap_config_req() 将直接跳过 l2cap_parse_conf_req(),于是就算我们携带了精心构造的 EFS option,目标内核也不会处理它,CONF_LOC_CONF_PEND 也不会被设置:

    // net/bluetooth/l2cap_core.c#4063
    if (flags & L2CAP_CONF_FLAG_CONTINUATION) {
        /* Incomplete config. Send empty response. */
        l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
                    l2cap_build_conf_rsp(chan, rsp,
                    L2CAP_CONF_SUCCESS, flags), rsp);
        goto unlock;
    }
    
    /* Complete config. */
    len = l2cap_parse_conf_req(chan, rsp);
    
  3. 构造 L2CAP_CONFIGURATION_RSP

    该包用于响应目标系统发送的 L2CAP_CONFIGURATION_REQ,同时将恶意 payload 注入其内核。

    SCID 字段设置为远端设备为当前信道分配的 CID 即可,即先前 L2CAP_CONNECTION_RSP 中的 DCID。

    根据协议 Flags 字段目前只有两个合法值 0x0000 或 0x0001。这里将其设置为 0x0000,表示我们支持 Extended Flow Specification,走 Lockstep process。其实设置成 0x0001 也可以,因为在处理 L2CAP_CONNECTION_RSP 时,内核处理完 result 字段(漏洞函数所在的位置)后才会处理 flags 字段,因此这里 flags 不影响漏洞的触发:

    // net/bluetooth/l2cap_core.c#4153
    switch (result) {
        ... ...
    }
    
    if (flags & L2CAP_CONF_FLAG_CONTINUATION)
        goto done;
    

    Result 字段设为 pending (0x0004)。因为这是让处理响应包的 l2cap_config_rsp() 调用漏洞函数 l2cap_parse_conf_rsp() 的前提条件:

    // net/bluetooth/l2cap_core.c#4159
    case L2CAP_CONF_PENDING:
        set_bit(CONF_REM_CONF_PEND, &chan->conf_state);
    
        if (test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) {
            char buf[64];
    
        len = l2cap_parse_conf_rsp(chan, rsp->data, len,
                       buf, &result);
        ... ...
    }
    goto done;
    

    Configuration options 字段就用于填写恶意的 payload。比如我们填写 100 个 MTU option。每个 MTU option 占用 4 bytes,总共 400 bytes 远超存在溢出点 buffer 的 64 bytes 大小:

漏洞系统对 DoS Exp 的反应

漏洞系统的蓝牙地址为 12:43:24:14:23:34,处于 page scan 状态,即可连接状态:

此时攻击者执行脚本,打出 DoS Exp:

之后目标虚拟机将直接崩溃:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354