mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-23 19:33:09 +00:00
Merge pull request #3624 from nieldk/esp32-c2
PM5 add option to connect by BLE name
This commit is contained in:
6 files changed
+261
-13
No files matched your search
@@ -968,6 +968,27 @@ endif (NOT SKIPPYTHON EQUAL 1)
|
||||
target_link_directories(proxmark3 PRIVATE ${ADDITIONAL_LNKDIRS})
|
||||
|
||||
install(TARGETS proxmark3 DESTINATION "bin")
|
||||
|
||||
# Grant the capabilities the BLE connect-by-name scan needs (raw HCI: CAP_NET_RAW
|
||||
# + CAP_NET_ADMIN). Linux + Bluez only, and non-fatal: if setcap is absent, the
|
||||
# target filesystem lacks xattr support, or this is a staged/packaged install,
|
||||
# it just warns. Connect-by-address (-p ble:<MAC>) never needs this.
|
||||
if (BLUEZ_FOUND AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
find_program(SETCAP_EXECUTABLE setcap)
|
||||
if (SETCAP_EXECUTABLE)
|
||||
install(CODE "
|
||||
set(_pm3_bin \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/proxmark3\")
|
||||
execute_process(
|
||||
COMMAND \"${SETCAP_EXECUTABLE}\" cap_net_raw,cap_net_admin+eip \"\${_pm3_bin}\"
|
||||
RESULT_VARIABLE _pm3_setcap_rc)
|
||||
if (NOT _pm3_setcap_rc EQUAL 0)
|
||||
message(WARNING \"setcap failed - BLE connect-by-name (-p ble:<name>) will need sudo; connect-by-MAC is unaffected\")
|
||||
endif ()
|
||||
")
|
||||
else ()
|
||||
message(STATUS "setcap not found - BLE connect-by-name will need sudo at runtime")
|
||||
endif ()
|
||||
endif ()
|
||||
install(DIRECTORY cmdscripts lualibs luascripts pyscripts resources dictionaries DESTINATION "share/proxmark3")
|
||||
|
||||
add_custom_command(OUTPUT lualibs/pm3_cmd.lua
|
||||
|
||||
@@ -1068,6 +1068,9 @@ install: all
|
||||
ifneq (,$(INSTALLBIN))
|
||||
$(Q)$(INSTALLSUDO) $(MKDIR) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLBINRELPATH)
|
||||
$(Q)$(INSTALLSUDO) $(CP) $(INSTALLBIN) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLBINRELPATH)
|
||||
ifeq ($(platform),Linux)
|
||||
$(Q)$(INSTALLSUDO) setcap 'cap_net_raw,cap_net_admin+eip' $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLBINRELPATH)$(PATHSEP)$(INSTALLBIN) || echo "[!] setcap failed - BLE connect-by-name (-p ble:<name>) will need sudo; connect-by-MAC is unaffected"
|
||||
endif
|
||||
endif
|
||||
ifneq (,$(INSTALLSHARE))
|
||||
$(Q)$(INSTALLSUDO) $(MKDIR) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH)
|
||||
|
||||
+187
-6
@@ -17,13 +17,17 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h> // strcasecmp
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/hci_lib.h>
|
||||
|
||||
#include "ui.h" // PrintAndLogEx
|
||||
#include "pm3_cmd.h" // PM3_* (only for messaging parity; returns are 0/neg here)
|
||||
@@ -183,14 +187,168 @@ static int att_subscribe(int fd, uint16_t cccd_handle) {
|
||||
return (n >= 1) ? 0 : -1;
|
||||
}
|
||||
|
||||
int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) {
|
||||
memset(conn, 0, sizeof(*conn));
|
||||
conn->fd = -1;
|
||||
// ---- name -> address resolution (active LE scan over raw HCI) ----
|
||||
|
||||
// AD types carrying the device name (Core Spec, Supplement, Part A).
|
||||
#define BLE_ADV_NAME_SHORT 0x08
|
||||
#define BLE_ADV_NAME_FULL 0x09
|
||||
|
||||
static long ble_now_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
|
||||
}
|
||||
|
||||
// Walk the AD structures of one advertising/scan-response report, looking for a
|
||||
// Local Name (complete or shortened) that equals `want` (case-insensitive).
|
||||
// Copies whatever name it finds into `found` for logging. Returns 1 on match.
|
||||
static int ble_ad_name_matches(const char *want, const uint8_t *ad, size_t adlen,
|
||||
char *found, size_t foundsz) {
|
||||
size_t i = 0;
|
||||
while (i < adlen) {
|
||||
uint8_t flen = ad[i]; // length byte counts the type + value
|
||||
if (flen == 0) break; // padding -> end of meaningful data
|
||||
if (i + 1 + flen > adlen) break; // truncated field -> stop, don't overrun
|
||||
uint8_t type = ad[i + 1];
|
||||
if (type == BLE_ADV_NAME_FULL || type == BLE_ADV_NAME_SHORT) {
|
||||
size_t nlen = flen - 1;
|
||||
if (nlen >= foundsz) nlen = foundsz - 1;
|
||||
memcpy(found, &ad[i + 2], nlen);
|
||||
found[nlen] = 0;
|
||||
if (strcasecmp(found, want) == 0) return 1;
|
||||
}
|
||||
i += flen + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Print an exact, copy-pasteable command to grant the capabilities the LE scan
|
||||
// needs, using the running binary's own path so it survives rebuilds/renames.
|
||||
// Also points at the no-privilege alternative (connect by address).
|
||||
static void ble_print_caps_hint(void) {
|
||||
char exe[4096];
|
||||
ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
|
||||
if (n > 0) {
|
||||
exe[n] = 0;
|
||||
PrintAndLogEx(INFO, "BLE: scanning by name needs extra privileges. Grant them once with:");
|
||||
PrintAndLogEx(INFO, " " _YELLOW_("sudo setcap 'cap_net_raw,cap_net_admin+eip' %s"), exe);
|
||||
} else {
|
||||
PrintAndLogEx(INFO, "BLE: scanning by name needs " _YELLOW_("CAP_NET_RAW+CAP_NET_ADMIN")
|
||||
" on the client binary (or run with sudo)");
|
||||
}
|
||||
PrintAndLogEx(INFO, " or connect by address instead: " _YELLOW_("-p ble:<MAC>") " (no extra privileges)");
|
||||
}
|
||||
|
||||
int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms) {
|
||||
if (name == NULL || out_mac == NULL || out_mac_sz < 18) return -1;
|
||||
out_mac[0] = 0;
|
||||
|
||||
int dev_id = hci_get_route(NULL);
|
||||
if (dev_id < 0) {
|
||||
PrintAndLogEx(ERR, "BLE: no local Bluetooth adapter found (%s)", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
int dd = hci_open_dev(dev_id);
|
||||
if (dd < 0) {
|
||||
PrintAndLogEx(ERR, "BLE: cannot open hci%d (%s)", dev_id, strerror(errno));
|
||||
if (errno == EPERM || errno == EACCES)
|
||||
ble_print_caps_hint();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// BlueZ's hci_le_set_scan_* take the open descriptor (dd), not dev_id,
|
||||
// despite the arg name - passing dev_id silently fails.
|
||||
// Active scan (0x01) so SCAN_RSP (where the BWM puts its name) is seen.
|
||||
if (hci_le_set_scan_parameters(dd, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0x00, 1000) < 0)
|
||||
PrintAndLogEx(INFO, "BLE: set scan parameters failed (%s), continuing", strerror(errno));
|
||||
|
||||
// A scan already left enabled makes the enable below return EIO; clear it first.
|
||||
hci_le_set_scan_enable(dd, 0x00, 0x00, 1000);
|
||||
if (hci_le_set_scan_enable(dd, 0x01, 0x00, 1000) < 0) { // filter_dup off: names may only be in SCAN_RSP
|
||||
PrintAndLogEx(ERR, "BLE: cannot start LE scan (%s)", strerror(errno));
|
||||
if (errno == EPERM || errno == EACCES)
|
||||
ble_print_caps_hint();
|
||||
hci_close_dev(dd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Receive only HCI event packets carrying LE meta events; save/restore the
|
||||
// socket's previous filter so we leave the handle as we found it.
|
||||
struct hci_filter of, nf;
|
||||
socklen_t olen = sizeof(of);
|
||||
int have_of = (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) == 0);
|
||||
hci_filter_clear(&nf);
|
||||
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
|
||||
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
|
||||
setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));
|
||||
|
||||
PrintAndLogEx(INFO, "BLE: scanning up to " _YELLOW_("%d") " ms for " _YELLOW_("%s") " ...", timeout_ms, name);
|
||||
|
||||
int rc = -1;
|
||||
char nm[64];
|
||||
long deadline = ble_now_ms() + timeout_ms;
|
||||
uint8_t buf[HCI_MAX_EVENT_SIZE];
|
||||
|
||||
while (ble_now_ms() < deadline) {
|
||||
long rem = deadline - ble_now_ms();
|
||||
if (rem <= 0) break;
|
||||
struct timeval tv = { .tv_sec = rem / 1000, .tv_usec = (rem % 1000) * 1000 };
|
||||
fd_set rs;
|
||||
FD_ZERO(&rs);
|
||||
FD_SET(dd, &rs);
|
||||
int s = select(dd + 1, &rs, NULL, NULL, &tv);
|
||||
if (s < 0) { if (errno == EINTR) break; break; } // Ctrl-C or error -> give up cleanly
|
||||
if (s == 0) break; // timeout
|
||||
|
||||
int n = read(dd, buf, sizeof(buf));
|
||||
if (n < (int)(1 + HCI_EVENT_HDR_SIZE + 1)) continue;
|
||||
uint8_t *pkt_end = buf + n;
|
||||
|
||||
// layout: [0]=HCI_EVENT_PKT, [1..2]=hci_event_hdr, then evt_le_meta_event
|
||||
evt_le_meta_event *meta = (evt_le_meta_event *)(buf + 1 + HCI_EVENT_HDR_SIZE);
|
||||
if ((uint8_t *)meta->data > pkt_end) continue;
|
||||
if (meta->subevent != EVT_LE_ADVERTISING_REPORT) continue;
|
||||
|
||||
uint8_t *p = meta->data;
|
||||
uint8_t num = *p++; // number of reports in this event
|
||||
for (uint8_t r = 0; r < num; r++) {
|
||||
if (p + LE_ADVERTISING_INFO_SIZE > pkt_end) break;
|
||||
le_advertising_info *info = (le_advertising_info *)p;
|
||||
uint8_t *next = p + LE_ADVERTISING_INFO_SIZE + info->length + 1; // +1 trailing RSSI
|
||||
if (next > pkt_end) break; // malformed / truncated report
|
||||
if (ble_ad_name_matches(name, info->data, info->length, nm, sizeof(nm))) {
|
||||
ba2str(&info->bdaddr, out_mac);
|
||||
PrintAndLogEx(SUCCESS, "BLE: found " _GREEN_("%s") " at " _GREEN_("%s"), nm, out_mac);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
p = next;
|
||||
}
|
||||
if (rc == 0) break;
|
||||
}
|
||||
|
||||
hci_le_set_scan_enable(dd, 0x00, 0x00, 1000);
|
||||
if (have_of) setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
|
||||
hci_close_dev(dd);
|
||||
|
||||
if (rc != 0)
|
||||
PrintAndLogEx(ERR, "BLE: no advertising device named " _YELLOW_("%s") " seen within %d ms", name, timeout_ms);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Retry the L2CAP connect briefly: a create-connection issued as an LE scan
|
||||
// tears down can be refused, and the peer may be between advertising events.
|
||||
#define BLE_CONNECT_TRIES 6
|
||||
#define BLE_CONNECT_BACKOFF_MS 150
|
||||
|
||||
// Open an L2CAP/ATT socket and connect to `mac`. Returns the fd on success,
|
||||
// -1 on a retryable connect failure (errno set to the connect error), or
|
||||
// -2 on a hard setup failure (socket/bind), which is already logged.
|
||||
static int ble_l2cap_connect(const char *mac) {
|
||||
int fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
||||
if (fd < 0) {
|
||||
PrintAndLogEx(ERR, "BLE: cannot create L2CAP socket (%s)", strerror(errno));
|
||||
return -1;
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Enlarge the kernel RX buffer so bursts of notifications queue instead of
|
||||
@@ -206,7 +364,7 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) {
|
||||
if (bind(fd, (struct sockaddr *)&src, sizeof(src)) < 0) {
|
||||
PrintAndLogEx(ERR, "BLE: bind failed (%s)", strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
return -2;
|
||||
}
|
||||
|
||||
struct sockaddr_l2 dst = {0};
|
||||
@@ -215,8 +373,31 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) {
|
||||
str2ba(mac, &dst.l2_bdaddr);
|
||||
dst.l2_cid = htobs(ATT_CID);
|
||||
if (connect(fd, (struct sockaddr *)&dst, sizeof(dst)) < 0) {
|
||||
PrintAndLogEx(ERR, "BLE: cannot connect to " _YELLOW_("%s") " (%s)", mac, strerror(errno));
|
||||
int e = errno; // close() may clobber errno; preserve it for the caller
|
||||
close(fd);
|
||||
errno = e;
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) {
|
||||
memset(conn, 0, sizeof(*conn));
|
||||
conn->fd = -1;
|
||||
|
||||
int fd = -1;
|
||||
int last_errno = 0;
|
||||
for (int attempt = 1; attempt <= BLE_CONNECT_TRIES; attempt++) {
|
||||
fd = ble_l2cap_connect(mac);
|
||||
if (fd >= 0) break;
|
||||
if (fd == -2) return -1; // hard setup failure, already logged
|
||||
last_errno = errno; // retryable connect failure
|
||||
if (attempt < BLE_CONNECT_TRIES) {
|
||||
usleep(BLE_CONNECT_BACKOFF_MS * 1000);
|
||||
}
|
||||
}
|
||||
if (fd < 0) {
|
||||
PrintAndLogEx(ERR, "BLE: cannot connect to " _YELLOW_("%s") " (%s)", mac, strerror(last_errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,18 +39,30 @@ typedef struct {
|
||||
size_t leftover_len;
|
||||
} ble_conn_t;
|
||||
|
||||
// Default time budget for a name -> address scan.
|
||||
#define BLE_SCAN_TIMEOUT_MS 8000
|
||||
|
||||
// Resolve an advertised device name to its LE address via an active HCI scan.
|
||||
// Matches the Complete (0x09) or Shortened (0x08) Local Name, case-insensitively.
|
||||
// On success writes "XX:XX:XX:XX:XX:XX" into out_mac (needs >= 18 bytes) and
|
||||
// returns PM3_SUCCESS; returns PM3_EINVARG on bad arguments, PM3_EIO on a
|
||||
// local adapter/scan failure, or PM3_ETIMEOUT if no such device is seen in time.
|
||||
// Needs CAP_NET_RAW+CAP_NET_ADMIN (raw HCI), i.e. more privilege than connect.
|
||||
int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms);
|
||||
|
||||
// Connect to `mac` (LE public), discover the SPP char, subscribe to notifications.
|
||||
// `chr_uuid16` selects the data characteristic (pass BLE_SPP_CHR_UUID16).
|
||||
// Returns 0 on success (conn filled), negative on error. On error conn->fd == -1.
|
||||
// Returns PM3_SUCCESS (conn filled) or PM3_EIO on error. On error conn->fd == -1.
|
||||
int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn);
|
||||
|
||||
// Send len bytes to the device, chunked to (mtu-3) as ATT Write Commands.
|
||||
// Returns 0 on success, negative on error.
|
||||
// Returns PM3_SUCCESS, PM3_ENOTTY if not connected, or PM3_EIO on write error.
|
||||
int ble_send(ble_conn_t *conn, const uint8_t *data, size_t len);
|
||||
|
||||
// Receive up to maxlen payload bytes (drains leftover first, then waits up to
|
||||
// timeout_ms for notifications). *out_len set to bytes copied. Returns 0 on
|
||||
// success (including 0 bytes on timeout), negative on error/disconnect.
|
||||
// timeout_ms for notifications). *out_len set to bytes copied. Returns
|
||||
// PM3_SUCCESS on success (including 0 bytes on timeout), PM3_ENOTTY if not
|
||||
// connected, or PM3_EIO on error/disconnect.
|
||||
int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int timeout_ms);
|
||||
|
||||
void ble_close(ble_conn_t *conn);
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef HAVE_BLUEZ
|
||||
#include <ctype.h> // isxdigit (ble: name vs address check)
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/rfcomm.h>
|
||||
#include "ble_posix.h"
|
||||
@@ -115,6 +116,22 @@ uint32_t uart_get_timeouts(void) {
|
||||
return newtimeout_value;
|
||||
}
|
||||
|
||||
#ifdef HAVE_BLUEZ
|
||||
// True if `s` is a plain 6-octet BD address "XX:XX:XX:XX:XX:XX" (hex, colon-sep).
|
||||
// Used to tell a literal address from a device name after the "ble:" prefix.
|
||||
static bool uart_is_bdaddr(const char *s) {
|
||||
if (s == NULL || strlen(s) != 17) return false;
|
||||
for (int i = 0; i < 17; i++) {
|
||||
if ((i % 3) == 2) {
|
||||
if (s[i] != ':') return false;
|
||||
} else if (isxdigit((unsigned char)s[i]) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // HAVE_BLUEZ
|
||||
|
||||
serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
|
||||
serial_port_unix_t_t *sp = calloc(sizeof(serial_port_unix_t_t), sizeof(uint8_t));
|
||||
|
||||
@@ -416,8 +433,22 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
|
||||
free(sp);
|
||||
return INVALID_SERIAL_PORT;
|
||||
}
|
||||
// pcPortName is "ble:<MAC>"
|
||||
if (ble_connect(pcPortName + 4, BLE_SPP_CHR_UUID16, bc) != 0) {
|
||||
|
||||
// pcPortName is "ble:<address-or-name>". If the part after the prefix is
|
||||
// a literal BD address use it as-is; otherwise treat it as an advertised
|
||||
// device name and resolve it to an address via an active LE scan.
|
||||
const char *ble_arg = pcPortName + 4;
|
||||
char resolved[18];
|
||||
if (uart_is_bdaddr(ble_arg) == false) {
|
||||
if (ble_resolve_name(ble_arg, resolved, sizeof(resolved), BLE_SCAN_TIMEOUT_MS) != 0) {
|
||||
free(bc);
|
||||
free(sp);
|
||||
return INVALID_SERIAL_PORT;
|
||||
}
|
||||
ble_arg = resolved;
|
||||
}
|
||||
|
||||
if (ble_connect(ble_arg, BLE_SPP_CHR_UUID16, bc) != 0) {
|
||||
free(bc);
|
||||
free(sp);
|
||||
return INVALID_SERIAL_PORT;
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@
|
||||
// Over the BWM/FPC link the forward buffers (AT32 DMA ring, ESP UART RX) are
|
||||
// small, so a full PM3_CMD_DATA_SIZE frame overruns them. Cap the payload the
|
||||
// device advertises and sends when replying via FPC. USB is unaffected.
|
||||
#define PM3_FPC_MAX_DATA 2048
|
||||
#define PM3_FPC_MAX_DATA 4096
|
||||
|
||||
typedef struct {
|
||||
uint64_t cmd;
|
||||
|
||||
Reference in new issue
Block a user