From 86bb2790c769baafff9b9e115cf4262d066122bb Mon Sep 17 00:00:00 2001 From: oliver Date: Sat, 30 May 2026 21:44:42 +0800 Subject: [PATCH 1/2] feat: verify CRC on-device in the LE PHY engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The modern LE engine (le_phy.c, used by ubertooth-btle -n/-f) forwarded every received packet including bit-errored ones, so UBERTOOTH_SET_CRC_VERIFY had no effect there — only the legacy bt_le_sync() path (now just promiscuous mode) honoured le.crc_verify. Bit-flipped phantom advertisers leaked to the host. Verify the CRC after dewhitening and drop bad-CRC packets when le.crc_verify is set. Advertising-channel PDUs use the fixed CRCInit (reversed 0xAAAAAA); data-channel PDUs use the value recovered from CONNECT_IND. Disabled by default, so legacy behaviour is unchanged. --- firmware/bluetooth_rxtx/le_phy.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/firmware/bluetooth_rxtx/le_phy.c b/firmware/bluetooth_rxtx/le_phy.c index 94502c4c..f0b7050d 100644 --- a/firmware/bluetooth_rxtx/le_phy.c +++ b/firmware/bluetooth_rxtx/le_phy.c @@ -47,6 +47,10 @@ uint8_t le_dma_dest[2]; extern volatile uint8_t mode; extern volatile uint8_t requested_mode; + +// Shared LE state owned by bluetooth_rxtx.c. We only read le.crc_verify here, +// which the host toggles via UBERTOOTH_SET_CRC_VERIFY (ubertooth-btle -v1). +extern le_state_t le; extern volatile uint16_t le_adv_channel; extern volatile int cancel_follow; @@ -975,7 +979,30 @@ void le_phy_main(void) { if (queue_remove(&packet_queue, (void **)&packet)) { le_dewhiten(packet->data, packet->size, packet->channel); - if (filter_match(packet)) { + // On-device CRC validation. This modern LE engine previously + // forwarded every packet including bit-errored ones, so the + // host's UBERTOOTH_SET_CRC_VERIFY (ubertooth-btle -v1) had no + // effect on advertising/follow captures and bit-flipped + // "phantom" advertisers leaked to the host. Advertising-channel + // PDUs use the fixed CRCInit (reversed: 0xAAAAAA); data-channel + // PDUs use the value recovered from CONNECT_IND. Disabled by + // default, so the legacy behaviour is unchanged unless -v1 is set. + int crc_ok = 1; + if (le.crc_verify && packet->size > 3) { + unsigned pdu_len = packet->size - 3; + uint32_t crc_init_rev = + (btle_channel_index(packet->channel) >= 37) + ? 0xAAAAAA : conn.crc_init_reversed; + uint32_t calc_crc = + btle_crcgen_lut(crc_init_rev, packet->data, pdu_len); + uint32_t wire_crc = + (packet->data[pdu_len + 2] << 16) + | (packet->data[pdu_len + 1] << 8) + | packet->data[pdu_len + 0]; + crc_ok = (calc_crc == wire_crc); + } + + if (crc_ok && filter_match(packet)) { blink(0, 1, 0); // RX LED usb_enqueue_le(packet); packet_handler(packet); From 3b1fe5c2b61707000542bdb3a4bdf50109cc6e4c Mon Sep 17 00:00:00 2001 From: oliver Date: Sat, 30 May 2026 21:44:42 +0800 Subject: [PATCH 2/2] fix: set CRC verify before the LE capture loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ubertooth-btle handled -v (do_crc) only after the streaming loop returned, so cmd_set_crc_verify ran after capture had already ended — making -v1 a silent no-op for all sniffing modes. Move the call to just before the capture loop (after mode start, since mode entry resets crc_verify). --- host/ubertooth-tools/src/ubertooth-btle.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/host/ubertooth-tools/src/ubertooth-btle.c b/host/ubertooth-tools/src/ubertooth-btle.c index 1a41370b..dd5dc312 100644 --- a/host/ubertooth-tools/src/ubertooth-btle.c +++ b/host/ubertooth-tools/src/ubertooth-btle.c @@ -335,6 +335,15 @@ int main(int argc, char *argv[]) cmd_btle_promisc(ut->devh); } + // Set CRC verification *after* the mode has started (mode entry runs + // reset_le() which clears crc_verify) but *before* the capture loop — + // otherwise the do_crc block further below only ran after streaming + // ended, making -v1 a no-op for sniffing modes. + if (do_crc >= 0 && do_crc != 2) { + cmd_set_crc_verify(ut->devh, do_crc); + printf("CRC: %sverify\n", do_crc ? "" : "DO NOT "); + } + // running can be changed by signal handler while (running) { if (cancel_follow) { @@ -366,7 +375,8 @@ int main(int argc, char *argv[]) printf("access address set to: %08x\n", access_address); } - if (do_crc >= 0) { + // For sniffing modes this was already handled before the capture loop. + if (do_crc >= 0 && !(do_follow || do_no_follow || do_promisc)) { int r; if (do_crc == 2) { r = cmd_get_crc_verify(ut->devh);