BleedingTOOTH:Linux蓝牙零点击远程代码执行

2021-04-08 00:36:25

BleedingTooth是Linux蓝牙子系统中的一组零点球漏洞,可以允许未经身份验证的远程攻击者在短距离中执行任意代码,并在易受攻击的设备上执行内核权限。

我注意到网络子系统已经被Syzkaller广泛欺骗,但是蓝牙这样的子系统覆盖不太好。通常,对蓝牙宿主攻击表面的研究似乎非常有限 - 在蓝牙中的大多数公共漏洞仅影响固件或规范本身,并且只允许攻击者窃听和/或操纵信息。

但是,如果攻击者可以完全控制设备?表现出这种情况的最突出的例子是Blueborne和Bluefrag。我为自己设置了研究Linux蓝牙堆栈的目标,以扩展蓝鸟的调查结果,并扩展Syzkaller的能力来模糊/ dev / vhci设备。

此Blogpost描述了我潜入代码的过程,揭示了高度严重性漏洞,并最终将它们链接到一个完全成熟的RCE漏洞利用目标X86-64 Ubuntu 20.04.1(视频)。

谷歌直接到达Bluez和Linux蓝牙子系统维护者(英特尔),而不是Linux内核安全团队,以协调这一系列漏洞的多方响应。英特尔将安全咨询Intel-SA-00435发布,但这些不包含在披露时的任何发布的内核版本中。应该通知Linux内核安全团队以便于协调,并且还将向他们报告任何未来这种类型的漏洞。通信的时间表位于此帖子的底部。各自漏洞的补丁是:

Badchoice(CVE-2020-12352)和Badkarma(CVE-2020-12351)在2020-Sep-25上固定在蓝牙 - 下一步:提交1,2,3,4

单独,这些漏洞的严重程度因中等而异,但结合它们代表严重的安全风险。这篇文章过于这些风险。

让我们简要描述蓝牙堆栈。蓝牙芯片使用HCI(主机控制器接口)协议与主机(操作系统)通信。常见数据包是:

数据包 - 通常携带L2CAP(逻辑链路控制和适配协议)数据包,该数据包实现传输层。

诸如2MP(AMP Manager协议)或SMP(安全管理协议)之类的更高级别协议是基于L2CAP的顶部构建的。在Linux实现中,所有这些协议都在没有身份验证的情况下暴露,并且漏洞存在至关重要的,因为这些协议中的一些即使在内核内存。

我通过手动查看HCI事件包解析器发现了第一个漏洞(在Linux内核4.19中引入)。通过蓝牙芯片制作并发送了HCI事件报文,通常不能由攻击者控制(除非它们也可以控制蓝牙固件)。但是,有两个非常相似的方法,hci_ple_adv_report_evt()和hci_le_ext_adv_report_evt(),其目的是解析来自远程蓝牙设备的广告报告。这些报告的大小是可变的。

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/thci_event.c static void hci_le_adv_report_evt(struct hci_dev * hdev,struct sk_buff * skb ){U8 num_reports = skb - >数据[0]; void * ptr =& skb - >数据[1]; hci_dev_lock(hdev); while(num_reports - ){struct hci_ev_le_advertising_info * ev = ptr; S8 RSSI; if(ev - > length< = hci_max_ad_length){rssi = ev - >数据[EV - >长度 ]; process_adv_report(hdev,ev - > evt_type,& ev - > bdaddr,ev - > bdaddr_type,null,0,rssi,ev - >数据,ev - >长度); } else {bt_dev_err(hdev,"丢弃无效的广告数据"); Ptr + =尺寸(* EV)+ EV - >长度+ 1; hci_dev_unlock(hdev); } ...静态void hci_le_ext_adv_report_evt(struct hci_dev * hdev,struct sk_buff * skb){u8 num_reports = skb - >数据[0]; void * ptr =& skb - >数据[1]; hci_dev_lock(hdev); while(num_reports - ){struct hci_ev_le_ext_adv_report * ev = ptr; U8 Legacy_VT_TYPE; U16 EVT_TYPE; evt_type = __016_to_cpu(ev - > evt_type); Legacy_evt_type = ext_evt_type_to_legacy(hdev,evt_type); if(learacy_evt_type!= le_adv_invalid){process_adv_report(hdev,legacy_evt_type,& ev - > bdaddr,ev - > bdaddr_type,null,0,ev - > rssi,ev - >数据,ev - >长度) ; Ptr + =尺寸(* EV)+ EV - >长度 ; hci_dev_unlock(hdev); }

请注意如何调用process_adv_report(),但后者方法没有检查EV->长度看它是否较小或等于HCI_MAX_AD_LENCHTENG = 31。然后函数corpord_adv_report()然后使用事件数据和长度调用Store_Pending_Adv_Report():

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/thci_event.c static void process_adv_report(struct hci_dev * hdev,U8类型,bdaddr_t * bdaddr,u8 bdaddr_type,bdaddr_t * direct_addr,u8 direct_addr_type,s8 rssi,u8 * data,u8 len){...(!has_pending_add_report(hdev)){...(type == le_adv_ind || type == LE_ADV_SCAN_IND){Store_pending_adv_report(HDEV,BDADDR,BDADDR_TYPE,RSSI,标志,数据,LEN);返回 ; } ...} ...}

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/thci_event.c static void store_pending_adv_repd(struct_adv_pdev,bdaddr_t * bdaddr, U8 BDADDR_TYPE,S8 RSSI,U32标志,U8 * DATA,U8 LEN){struct discovery_state * d =& hdev - >发现; ... memcpy(d - > last_adv_data,data,len); d - > last_add_data_len = len; }

查看struct hci_dev,我们可以看到缓冲区last_add_data与hci_max_ad_length具有与hci_max_ad_length的大小相同,这是不足以持有扩展的广告数据。解析器可以理论上可以接收并将数据包路由到此方法最多255个字节。如果可能,我们可以溢出Last_Adv_Data和损坏的成员偏移0xBAF。

// pahole -e -c hci_dev --hex bluetooth.ko struct hci_dev {... struct discovery_state {... / * typedef u8 - > __u8 * / unsigned char last_add_data [31]; / * 0xab0 0x1f * / ...}发现; / * 0xa68 0x88 * / ... struct list_head {struct list_head * next; / * 0xb18 0x8 * / struct list_head * prev; / * 0xb20 0x8 * /} mgmt_pending; / * 0xB18 0x10 * / ... / *尺寸:4264,Cachelines:67,会员:192 * / / *总成员:4216,孔:17,Sum孔:48 * // * Paddings:10,Sum Paddings: 43 * // *强制对齐:1 * // *最后一次Cacheline:40字节* /} __Attribute__((__aligned__(8)));

然而,是hci_le_ext_adv_report_evt()甚至能够接收这么大的报告?这可能是更大的广告预期,因为它似乎故意,扩展广告解析器明确地删除了31个字节的检查。此外,由于它在代码中靠近HCI_LE_ADV_REPORT_EVT(),因此检查可能不会错误地忘记。事实上,看了看规范,我们可以看到从31个字节扩展到255字节是蓝牙5的主要功能之一:

在蓝牙4.0中召回,广告有效载荷最多为31个八位字节。在蓝牙5中,我们通过添加其他广告频道和新的广告PDU,我们将有效载荷增加到255个八位字节。来源:https://www.bluetooth.com/blog/explorion-bluettooth5-whats-new-in-advertising/

因此,如果受害者的机器具有蓝牙5芯片(相对“的新”技术,并且仅在较新的笔记本电脑上可用),并且受害者正在积极扫描广告数据(即打开蓝牙设置并打开蓝牙设置并搜索并搜索并搜索,则仅可用周围的设备)。

使用两个蓝牙5功能的设备,我们可以轻松确认漏洞并观察类似于:的恐慌

[118.490999]普通保护故障:0000 [#1] SMP PTI [118.491006] CPU:6 PID:205 Comm:kWorker / U17:0不受污染5.4.0-37-通用#41-Ubuntu [118.491008]硬件名称:戴尔Inc. XPS 15 7590 / 0CF6RR,BIOS 1.7.0 05/11/2020 [118.491034]工作用:HCI0 HCI_RX_WORK [蓝牙] [118.491056] RIP:0010:HCI_BDADDR_LIST_LOOKUP + 0x1E / 0x40 [蓝牙] [118.491060]代码:FF FF E9 26 ff ff 0f 0f 1f 44 00 00 0f 1f 44 00 00 55 48 8b 07 48 89 E5 48 39 C7 75 0a Eb 24 48 8b 00 48 39 F8 74 1c 44 8b 06 <44。 39 40 10 75 EF 44 0F B7 4E 04 66 44 39 48 14 75 E3 38 50 16 75 [118.491062] RSP:0018:FFFFBC6A40493C70 EFLAGS:00010286 [118.491066]罗克斯:4141414141414141 RBX:0000000000000000 [118.491068] RDX:000000000000000000 RSI:ffff9903e76c100f RDI:ffff9904289d4b28 [118.491070] RBP:ffffbc6a40493c70 R08:0000000093570362 R09:0000000000000000 [118.491072] R10:0000000000000000 R11:ffff9904344eae38 R12:ffff9904289d4000 [118.491074] R13:0000000000000000 R14:00000000ffffffa3 R15:ffff9903e76c100f [118.491077] FS:0000000000000000(0000 )GS:ffff990434580000(0000)knlGS:0000000000000000 [118.491079] CS:0010 DS:0000 ES:0000 CR0:0000000080050033 [118.491081] CR2:00007feed125a000 CR3:00000001b860a003 CR4:00000000003606e0 [118.491083]呼叫跟踪:[118.491108] process_adv_report + 0x12e / 0x560 [蓝牙] [118.491128] HCI_LE_META_EVT + 0x7B2 / 0xBA0 [蓝牙] [118.491134]? __wake_up_sync_key + 0x1e / 0x30 [118.491140]? sock_def_readable + 0x40 / 0x70 [118.491143]? __sock_queue_rcv_skb + 0x142 / 0x1f0 [118.491162] hci_event_packet + 0x1c29 / 0x2a90 [蓝牙] [118.491186]? hci_send_to_monitor + 0xae / 0x120 [蓝牙] [118.491190]? skb_release_all + 0x26 / 0x30 [118.491207] hci_rx_work + 0x19b / 0x360 [蓝牙] [118.491211]? __Schedule + 0x2EB / 0x740 [118.491217] Process_one_Work + 0x1EB / 0x3B0 [118.491221] Worker_Thread + 0x4D / 0x400 [118.491225] Kthread + 0x104 / 0x140 [118.491229]? process_one_work + 0x3b0 / 0x3b0 [118.491232]? KThread_Park + 0x90 / 0x90 [118.491236] RET_FROM_FORK + 0x35 / 0x40

恐慌表明,我们可以完全控制struct hci_dev中的成员。一个有趣的指针损坏是mgmt_pending-&gt;下一步,因为它是包含函数指针cmd_complete()的type struct mgmt_pending_cmd:

// pahole -e -c mgmt_pending_cmd --hex bluetooth.ko struct mgmt_pending_cmd {... int(* cmd_complete)(struct mgmt_pending_cmd *,u8); / * 0x38 0x8 * // *尺寸:64,Cachelines:1,会员:8 * // *总和:62,孔:1,SUM孔:2 * /};

例如,此处理程序可以通过中止HCI连接来触发。但是,为了成功重定向mgmt_pending-&gt;下一个指针,我们需要额外的信息泄漏漏洞,因为我们将在下一节中学到。

Badvibes漏洞不足以变成任意R / W基元,并且似乎无法使用它来泄漏受害者的内存布局。原因是,唯一可以损坏的有趣成员是指向循环列表的指针。顾名思义,这些数据结构是循环的,因此我们无法改变它们而不会确保他们最终返回到它们启动的位置。当受害者的内存布局随机化时,这一要求很难实现。虽然在静态地址分配的内核中存在一些资源,但它们的内容很可能是最有可能无法控制的。因此,我们需要首先想到存储器布局,以便利用Badvibes。要更具体地,我们需要泄露受害者的一些内存地址,其内容我们可以控制或至少预测。

通常,通过利用界限访问,利用未初始化的变量来实现信息泄漏,或者最近通过执行侧通道/定时攻击来实现。随着变速器可能具有抖动,后者可能难以拔下。相反,让我们专注于前两个错误类,并通过所有向攻击者发送一些信息的子例程,并查看其中的任何一个都可以透露出界限的数据或未初始化的内存。

我通过通过所有A2MP_SEND()调用在A2MP协议的命令A2MP_GETINFO_REQ中发现了第二种漏洞。由于Linux内核3.6以来,该漏洞已存在,并且如果默认情况下用于启用的Config_Bt_HS = Y可到达。

让我们来看看由A2MP_GetInfo_Req命令调用的子程序A2MP_GetInfo_req():

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.ctatic int a2mp_getinfo_req(struct amp_mgr * mgr,struct sk_buff * skb ,struct a2mp_cmd * hdr){struct a2mp_info_req * req =(void *)skb - &gt;数据 ; ... hdev = hci_dev_get(req - &gt; ID); if(!hdev || hdev - &gt; dev_type!= hci_amp){struct a2mp_info_rsp rsp; RSP。 ID = req - &gt; ID ; RSP。 status = a2mp_status_invalid_ctrl_id; A2MP_SEND(MGR,A2MP_GETINFO_RSP,HDR - &gt; ident,sizeof(rsp),&amp; rsp);转到了; } ...}

子程序旨在使用HCI设备ID请求有关AMP控制器的信息。但是,如果它无效或不具有HCI_AMP类型,则会拍摄错误路径,这意味着受害者将我们返回我们的状态A2MP_Status_Invalid_ctrl_ID。不幸的是,STRACH A2MP_INFO_RSP由更多成员组成,而不是仅仅是ID和状态,而且我们可以看到,响应结构未完全初始化。因此,可以向攻击者公开16个字节的内核堆栈,攻击者可能包含受害者的敏感数据:

// pahole -e -c a2mp_info_rsp --hex bluetooth.ko struct a2mp_info_rsp {/ * typedef __u8 * / unsigned char id; / * 0 0x1 * // * typedef __u8 * / unsigned char状态; / * 0x1 0x1 * / / * typedef __032 - &gt; __u32 * / unsigned int total_bw; / * 0x2 0x4 * / / * typedef __032 - &gt; __u32 * / unsigned int max_bw; / * 0x6 0x4 * / / * typedef __032 - &gt; __u32 * / unsigned int min_latency; / * 0xA 0x4 * / / * typedef __016 - &gt; __u16 * / short unsigned int pal_cap; / * 0xe 0x2 * / / * typedef __016 - &gt; __u16 * / short unsigned int asc_size; / * 0x10 0x2 * // *尺寸:18,cachelines:1,会员:7 * // *最后一次Cacherele:18字节* /} __Attribute__((__packed__));

可以通过发送有趣的命令来利用这种漏洞来在发送A2MP_GetInfo_req之前填充堆栈帧。这里,有趣的命令是那些在同一堆栈帧中引出的指针,即a2mp_getinfo_req()重复使用。通过这样做,未初始化的变量最终可能最终包含先前被推到堆栈上的指针。

请注意,使用config_init_stack_all_pattern = y编译的内核不应容易受到此类攻击的影响。例如,在ChromeoS上,Badchoice只返回0xAA。但是,此选项似乎在流行的Linux Distrs上默认情况下似乎无法启用。

我发现第三次漏洞,同时尝试触发糟糕码头并确认其利用率。即,受害者的机器意外地崩溃了以下呼叫跟踪:

[445.440736]普通保护故障:0000 [#1] SMP PTI [445.440740] CPU:4 PID:483 Comm:kWorker / U17:1未受到污染5.4.0-40-通用#44-Ubuntu [445.440741]硬件名称:戴尔Inc.XPS 15 7590 / 0CF6RR,BIOS 1.7.0 05/11/2020 [445.440764]工作用:HCI0 HCI_RX_WORK [蓝牙] [445.440771] RIP:0010:SK_FILTER_TRIM_CAP + 0x6D / 0x220 [445.44077]代码:E8 18 EAF FF 41 89 C5 85 75 C0 62 48 83 8B 10 01 00 00 48 85 C0 74 56 49 8B 24 4C 18 49 89 5C 24 18 4C 78 8B 18 48 89 4D B0&LT; 41&GT; F6 47 02 08 0F 85 41 01 00 00 0F 1F 44 00 00 49 8B 47 30 49 8D [445.440776] RSP:0018:FFFFA86B403ABCA0 EFLAGS:00010286 [445.440778] rax:ffffffffc071cc50 RBx:ffff8e95af6d7000 rcx:0000000000000000 [445.440780] RDX:000000000000000000 RSI:ffff8e95ac533800 RDI:ffff8e95af6d7000 [445.440781] RBP:ffffa86b403abd00 R08:ffff8e95b452f0e0 R09:ffff8e95b34072c0 [445.440782] R10:ffff8e95acd57818 R11:ffff8e95b456ae38 R12:ffff8e95ac533800 [445.440784] R13:0000000000000000 R14:0000000000000001 R15:30478b4800000208 [445.440786] FS:0000000000000000(0000 gs:ffff8e95b450000000(0000)knlgs:0000000000000000 [445.440788] CS:00000 DS:0000 ES:0000 ES:0000 ES:0000 _ [445.440789] CR2:000055F371A94A8CR3:000000022DC0A005CCR4:00000000003606C [445.440791]呼叫跟踪:[445.44071]? __l2cap_chan_add +均为0x88 / 0x1c0 [蓝牙] [445.440838] l2cap_data_rcv + 0x351 /量0x510 [蓝牙] [445.440857] l2cap_data_channel + 0x29f / 0x470 [蓝牙] [445.440875] l2cap_recv_frame +为0xE5 /是0x300 [蓝牙] [445.440878]? skb_release_all + 0×26 /的0x30 [445.440896] l2cap_recv_acldata + 0x2d2 / 0x2e0 [蓝牙] [445.440914] hci_rx_work + 0x186 / 0x360的[蓝牙] [445.440919] process_one_work + 0x1eb / 0x3b0 [445.440921] worker_thread +送出0x4d / 0x400的[445.440924] KTHREAD +量0x104 / 0x140 [445.440927]? process_one_work + 0x3b0 / 0x3b0 [445.440929]? KThread_Park + 0x90 / 0x90 [445.440932] RET_FROM_FORK + 0x35 / 0x40

看看L2CAP_DATA_RCV(),我们可以看到在使用ERTM(增强的重传模式)或流模式(类似于TCP)时调用SK_FILTER():

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/l2cap_core.c static int l2cap_data_rcv(struct l2cap_chan * chan,struct sk_buff * skb ){...如果(陈 - &gt; mode == l2cap_mode_ertm || chan - &gt; mode == l2cap_mode_streaming)&amp; sk_filter(chan - &gt; data,skb))goto drop; ...}

这确实是A2MP通道的情况(可以将通道与网络端口进行比较):

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c static struct l2cap_chan * a2mp_chan_open(struct l2cap_conn * conn,bool锁定){struct l2cap_chan * chan;呃; chan = l2cap_chan_create();如果(!chan)返回null; ...陈 - &gt; mode = l2cap_mode_ertm; ......回归陈; } ...静态结构amp_mgr * amp_mgr_create(struct l2cap_conn * conn,bool锁定){struct amp_mgr * mgr; struct l2cap_chan * chan; mgr = kzalloc(sizeof(* mgr),gfp_kernel);如果(!mgr)返回null; ... chan = a2mp_chan_open(conn,锁定); if(!chan){kfree(mgr);返回null; } mgr - &gt; a2mp_chan = chan;陈 - &gt;数据= mgr; ......返回mgr; }

看着amp_mgr_create(),很清楚错误的位置。即,chan-&gt;数据是struct amp_mgr类型,而sk_filter()采用类型结构袜子的参数,这意味着我们通过设计具有远程类型混淆。在Linux内核4.8中引入了这种混乱,从那时起,仍保持不变。

Badchoice漏洞可以用Badvibes以及Badkarma锁定,实现RCE。在这个Blogpost中,由于以下原因,我们将仅关注使用Badkarma的方法:

另一方面,Badvibes攻击仅是广播,因此只能成功地利用一台机器,而侦听相同消息的所有其他机器将只是崩溃。 具有讽刺意味的是,为了利用Badkarma,我们必须先摆脱Badkarma。 回想一下,通过设计存在一个类型的混淆错误,只要A2MP通道被配置为ERTM /流模式,我们就无法通过L2CAP_DATA_RCV()到达A2MP子程序而不触发SK_FILTER()中的恐慌。 查看L2CAP_DATA_CHANNEL(),我们可以看到拍摄不同路由的唯一可能的方法是将频道模式重新配置为L2CAP_MODE_BASIC。 这将“基本上”允许我们直接调用A2MP接收处理程序: // https://git.ke. ......