Files
proxmark3/armsrc/util.c
T
Travis Weathers 5c86fb47f1 fix(armsrc): poll the BWM link in data_available()
data_available() and data_available_fast() are how the device notices that
the host has asked a running command to stop. They branch on USB and on
WITH_FPC_USART_HOST, and never learned about the Proxmark5 BWM.

A PM5 with the wireless module builds PLATFORM=PM5 PLATFORM_EXTRAS=BWM,
which defines WITH_BWM_FORWARD. BTADDON errors out on PM5 and BWM errors
out on anything else (common_arm/Makefile.hal), so WITH_FPC_USART_HOST is
never defined on that build and both functions compile down to a USB-only
check.

Commands still arrive, because receive_ng() does poll the module
(armsrc/cmd.c). What was blind was the abort path: CMD_BREAK_LOOP is never
observed over BLE or WiFi, so every loop whose only exit is this check runs
until the button is pressed. That is 75 call sites across 26 files in
armsrc/, including the sniff, simulate and reader loops, so in practice a
wireless session cannot be interrupted at all. ReaderHitag() is the worst
case: with no tag on the antenna it has no other exit, so `lf search` alone
leaves the device answering nothing.

This is reachable with upstream tooling only: client/src/uart/ble_posix.c
is the native BLE transport, selected with `-p ble:<address-or-name>`, and
doc/md/PM5_Start_Here/PM5-BWM-USAGE.md documents both that and
`-p tcp:<host>:<port>` over the module's WiFi. Both share the same inbound
de-framer, so both are affected.

Folded into the existing preprocessor chain as an #elif, mirroring
receive_ng(), since the two transports are mutually exclusive by build.
USB is still tested first, so the module is only polled when USB has
nothing, exactly as the FPC branch behaves.

bwm_fwd_rxdata_available() is the same call receive_ng() already uses. When
the de-frame FIFO is empty it costs a flag test and one DMA counter read
(bwm_uart_rx_available()); it only walks bytes when bytes have actually
arrived, and those bytes are the abort. That mirrors the FPC branch, where
usart_rxdata_available() already calls usart_rx_poll() from these same two
functions. data_available_fast()'s only caller is the ReadLF_realtime()
sampling loop, which reaches it once per 64 saved samples; it is included
here so the two functions cannot drift apart again, which is how this bug
arose.

Object code is byte-identical on every platform that does not define
WITH_BWM_FORWARD. Built PLATFORM=PM3RDV4, PM3RDV4 with BTADDON, PM5 without
BWM and PM5 with BWM; only the last one changes:

  PM3RDV4           74f7f7a1...  ->  74f7f7a1...  identical
  PM3RDV4 BTADDON   f94c31de...  ->  f94c31de...  identical
  PM5               e3f0a203...  ->  e3f0a203...  identical
  PM5 BWM           e3f0a203...  ->  efbba2f0...  changed

Passes `make style` unmodified.

Verified on a Proxmark5 with the BWM fitted, over BLE, with the stock
client. Before, one command left the device deaf for good:

  [fpc|tcp] pm5 --> hw ping
  [+] Ping response received in 111 ms and content ( ok )
  [fpc|tcp] pm5 --> lf hitag read --ht2 --pwd -k 4D494B52
  [!] timeout while waiting for reply
  [fpc|tcp] pm5 --> hw ping
  [!] Ping response timeout
  [fpc|tcp] pm5 --> hw ping
  [!] Ping response timeout

After, the same sequence on the same device:

  [fpc|tcp] pm5 --> hw ping
  [+] Ping response received in 107 ms and content ( ok )
  [fpc|tcp] pm5 --> lf hitag read --ht2 --pwd -k 4D494B52
  [!] timeout while waiting for reply
  [fpc|tcp] pm5 --> hw ping
  [+] Ping response received in 102 ms and content ( ok )
  [fpc|tcp] pm5 --> hw ping
  [+] Ping response received in 85 ms and content ( ok )

A USB command recovers a device stuck this way, which is why the bug is
easy to miss on a bench with a cable attached: after the failing sequence
above, `hw ping` over USB answered in 1 ms and the wireless link resumed.
2026-09-15 10:36:09 -04:00

391 lines
10 KiB
C

//-----------------------------------------------------------------------------
// Copyright (C) Jonathan Westhues, Sept 2005
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// 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 3 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.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Utility functions used in many places, not specific to any piece of code.
//-----------------------------------------------------------------------------
#include "util.h"
#include "proxmark3_arm.h"
#include "ticks_apis.h"
#include "commonutil.h"
#include "dbprint.h"
#include "string.h"
#include "usb_cdc_apis.h"
#include "usart.h"
#ifdef WITH_BWM_FORWARD
#include "bwm_forward.h"
#endif
#include "BigBuf.h" // trace_restart_timeline
#ifdef WITH_SMARTCARD
#include "i2c.h" // sc_log_trace_reset
#endif
size_t nbytes(size_t nbits) {
return (nbits >> 3) + ((nbits % 8) > 0);
}
//convert hex digit to integer
uint8_t hex2int(char x) {
switch (x) {
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
return 0;
}
}
/*
The following methods comes from Rfidler sourcecode.
https://github.com/ApertureLabsLtd/RFIDler/blob/master/firmware/Pic32/RFIDler.X/src/
*/
// convert hex to sequence of 0/1 bit values
// returns number of bits converted
int hex2binarray(char *target, const char *source) {
return hex2binarray_n(target, source, strlen(source));
}
int hex2binarray_n(char *target, const char *source, int sourcelen) {
int count = 0;
// process 4 bits (1 hex digit) at a time
while (sourcelen--) {
char x = *(source++);
*(target++) = (x >> 7) & 1;
*(target++) = (x >> 6) & 1;
*(target++) = (x >> 5) & 1;
*(target++) = (x >> 4) & 1;
*(target++) = (x >> 3) & 1;
*(target++) = (x >> 2) & 1;
*(target++) = (x >> 1) & 1;
*(target++) = (x & 1);
count += 8;
}
return count;
}
int binarray2hex(const uint8_t *bs, int bs_len, uint8_t *hex) {
int count = 0;
int byte_index = 0;
// Clear output buffer
memset(hex, 0, bs_len >> 3);
for (int i = 0; i < bs_len; i++) {
// Set the appropriate bit in hex
if (bs[i] == 1) {
hex[byte_index] |= (1 << (7 - (count % 8)));
}
count++;
// Move to the next byte if 8 bits have been filled
if (count % 8 == 0) {
byte_index++;
}
}
return count;
}
void LEDsoff(void) {
LED_A_OFF();
LED_B_OFF();
LED_C_OFF();
LED_D_OFF();
}
//ICEMAN: LED went from 1,2,3,4 -> 1,2,4,8
void LED(int led, int ms) {
if (led & LED_A) // Proxmark3 historical mapping: LED_ORANGE
LED_A_ON();
if (led & LED_B) // Proxmark3 historical mapping: LED_GREEN
LED_B_ON();
if (led & LED_C) // Proxmark3 historical mapping: LED_RED
LED_C_ON();
if (led & LED_D) // Proxmark3 historical mapping: LED_RED2
LED_D_ON();
if (!ms)
return;
SpinDelay(ms);
if (led & LED_A)
LED_A_OFF();
if (led & LED_B)
LED_B_OFF();
if (led & LED_C)
LED_C_OFF();
if (led & LED_D)
LED_D_OFF();
}
void SpinOff(uint32_t pause) {
LED_A_OFF();
LED_B_OFF();
LED_C_OFF();
LED_D_OFF();
SpinDelay(pause);
}
// Blinks..
// A = 1, B = 2, C = 4, D = 8
void SpinErr(uint8_t led, uint32_t speed, uint8_t times) {
SpinOff(speed);
NTIME(times) {
if (led & LED_A) // Proxmark3 historical mapping: LED_ORANGE
LED_A_INV();
if (led & LED_B) // Proxmark3 historical mapping: LED_GREEN
LED_B_INV();
if (led & LED_C) // Proxmark3 historical mapping: LED_RED
LED_C_INV();
if (led & LED_D) // Proxmark3 historical mapping: LED_RED2
LED_D_INV();
SpinDelay(speed);
}
LED_A_OFF();
LED_B_OFF();
LED_C_OFF();
LED_D_OFF();
}
void SpinDown(uint32_t speed) {
SpinOff(speed);
LED_D_ON();
SpinDelay(speed);
LED_D_OFF();
LED_C_ON();
SpinDelay(speed);
LED_C_OFF();
LED_B_ON();
SpinDelay(speed);
LED_B_OFF();
LED_A_ON();
SpinDelay(speed);
LED_A_OFF();
}
void SpinUp(uint32_t speed) {
SpinOff(speed);
LED_A_ON();
SpinDelay(speed);
LED_A_OFF();
LED_B_ON();
SpinDelay(speed);
LED_B_OFF();
LED_C_ON();
SpinDelay(speed);
LED_C_OFF();
LED_D_ON();
SpinDelay(speed);
LED_D_OFF();
}
// Determine if a button is double clicked, single clicked,
// not clicked, or held down (for ms || 1sec)
// In general, don't use this function unless you expect a
// double click, otherwise it will waste 500ms -- use BUTTON_HELD instead
// Note: StartTickCount required.
int BUTTON_CLICKED(int ms) {
// If we're not even pressed, forget about it!
if (BUTTON_PRESS() == false)
return BUTTON_NO_CLICK;
ms = ms ? ms : 1000; // use ms param if valid.
int letoff = 0;
for (;;) {
// Using a very short delay period to count how long has passed is much safer than using other methods,
// as other timers may be stopped/reset
SpinDelay(1);
--ms;
// We haven't let off the button yet
if (!letoff) {
// We just let it off!
if (BUTTON_PRESS() == false) {
letoff = 1;
// reset our timer for 500ms next press waiting
ms = 500;
}
// Still haven't let it off
else
// Have we held down a full second?
if (ms <= 0)
return BUTTON_HOLD;
}
// We already let off, did we click again?
else
// Sweet, double click!
if (BUTTON_PRESS())
return BUTTON_DOUBLE_CLICK;
// Have we ran out of time to double click?
else if (ms <= 0)
// At least we did a single click
return BUTTON_SINGLE_CLICK;
WDT_HIT();
}
// We should never get here
return BUTTON_ERROR;
}
// Determine if a button is held down
// Note: StartTickCount required.
int BUTTON_HELD(int ms) {
// If we're not even pressed, forget about it!
if (BUTTON_PRESS() == false) {
return BUTTON_NO_CLICK;
}
ms = ms ? ms : 1000; // use ms param if valid.
for (;;) {
// As soon as our button let go, we didn't hold long enough
if (BUTTON_PRESS() == false) {
return BUTTON_SINGLE_CLICK;
}
// Using a very short delay period to count how long has passed is much safer than using other methods,
// as other timers may be stopped/reset
SpinDelay(1);
--ms;
// Have we waited the full second?
if (ms <= 0) {
return BUTTON_HOLD;
}
WDT_HIT();
}
// We should never get here
return BUTTON_ERROR;
}
// This function returns false if no data is available or
// the USB connection is invalid.
bool data_available(void) {
#if defined(WITH_BWM_FORWARD)
// The BWM (BLE / WiFi) link is the host connection on a Proxmark5, so it
// has to be polled here too or CMD_BREAK_LOOP is never seen over it.
return usb_poll_validate_length() || (bwm_fwd_rxdata_available() > 0);
#elif defined(WITH_FPC_USART_HOST)
return usb_poll_validate_length() || (usart_rxdata_available() > 0);
#else
return usb_poll_validate_length();
#endif
}
// This function doesn't check if the USB connection is valid.
// In most of the cases, you should use data_available() unless
// the timing is critical.
bool data_available_fast(void) {
#if defined(WITH_BWM_FORWARD)
return usb_available_length() || (bwm_fwd_rxdata_available() > 0);
#elif defined(WITH_FPC_USART_HOST)
return usb_available_length() || (usart_rxdata_available() > 0);
#else
return usb_available_length();
#endif
}
// Combined function to convert an unsigned int to an array of hex values corresponding to the last three bits of k1
void convertToHexArray(uint32_t num, uint8_t *partialkey) {
char binaryStr[25]; // 24 bits for binary representation + 1 for null terminator
binaryStr[24] = '\0'; // Null-terminate the string
// Convert the number to binary string
for (int i = 23; i >= 0; i--) {
binaryStr[i] = (num % 2) ? '1' : '0';
num /= 2;
}
// Split the binary string into groups of 3 and convert to hex
for (int i = 0; i < 8 ; i++) {
char group[4] = {'0', '0', '0', '\0'}; // Ensure group is initialized correctly
memcpy(group, binaryStr + i * 3, 3); // Use memcpy to copy exactly 3 characters
group[3] = '\0'; // Null-terminate the group string
partialkey[i] = (uint8_t)strtoul(group, NULL, 2);
}
}
void switch_clock_to_ticks(void) {
StopTicks();
StartTicks();
#ifdef WITH_SMARTCARD
sc_log_trace_reset();
#endif
trace_restart_timeline();
}
void switch_clock_to_countsspclk(void) {
StopTicks();
StartCountSspClk();
trace_restart_timeline();
}