Add support for polling loop annotations

This commit is contained in:
kormax 2025-04-07 20:00:27 +03:00
parent cd5fb7add7
commit 56336d9d82
4 changed files with 144 additions and 59 deletions

View file

@ -36,9 +36,9 @@
#include "protocols.h"
#include "generator.h"
#include "desfire_crypto.h" // UL-C authentication helpers
#include "mifare.h" // for iso14a_polling_frame_t structure
#define MAX_ISO14A_TIMEOUT 524288
// this timeout is in MS
static uint32_t iso14a_timeout;
@ -141,7 +141,7 @@ Default HF 14a config is set to:
forcecl3 = 0 (auto)
forcerats = 0 (auto)
*/
static hf14a_config hf14aconfig = { 0, 0, 0, 0, 0 } ;
static hf14a_config hf14aconfig = { 0, 0, 0, 0, 0, {{0}, 0, 0, 0} };
// Polling frames and configurations
@ -166,31 +166,37 @@ struct Crypto1State crypto1_state = {0, 0};
void printHf14aConfig(void) {
DbpString(_CYAN_("HF 14a config"));
Dbprintf(" [a] Anticol override.... %s%s%s",
Dbprintf(" [a] Anticol override.............. %s%s%s",
(hf14aconfig.forceanticol == 0) ? _GREEN_("std") " ( follow standard )" : "",
(hf14aconfig.forceanticol == 1) ? _RED_("force") " ( always do anticol )" : "",
(hf14aconfig.forceanticol == 2) ? _RED_("skip") " ( always skip anticol )" : ""
);
Dbprintf(" [b] BCC override........ %s%s%s",
Dbprintf(" [b] BCC override.................. %s%s%s",
(hf14aconfig.forcebcc == 0) ? _GREEN_("std") " ( follow standard )" : "",
(hf14aconfig.forcebcc == 1) ? _RED_("fix") " ( fix bad BCC )" : "",
(hf14aconfig.forcebcc == 2) ? _RED_("ignore") " ( ignore bad BCC, always use card BCC )" : ""
);
Dbprintf(" [2] CL2 override........ %s%s%s",
Dbprintf(" [2] CL2 override.................. %s%s%s",
(hf14aconfig.forcecl2 == 0) ? _GREEN_("std") " ( follow standard )" : "",
(hf14aconfig.forcecl2 == 1) ? _RED_("force") " ( always do CL2 )" : "",
(hf14aconfig.forcecl2 == 2) ? _RED_("skip") " ( always skip CL2 )" : ""
);
Dbprintf(" [3] CL3 override........ %s%s%s",
Dbprintf(" [3] CL3 override.................. %s%s%s",
(hf14aconfig.forcecl3 == 0) ? _GREEN_("std") " ( follow standard )" : "",
(hf14aconfig.forcecl3 == 1) ? _RED_("force") " ( always do CL3 )" : "",
(hf14aconfig.forcecl3 == 2) ? _RED_("skip") " ( always skip CL3 )" : ""
);
Dbprintf(" [r] RATS override....... %s%s%s",
Dbprintf(" [r] RATS override................. %s%s%s",
(hf14aconfig.forcerats == 0) ? _GREEN_("std") " ( follow standard )" : "",
(hf14aconfig.forcerats == 1) ? _RED_("force") " ( always do RATS )" : "",
(hf14aconfig.forcerats == 2) ? _RED_("skip") " ( always skip RATS )" : ""
);
Dbprintf(" [p] Polling loop annotation....... %s %*D",
(hf14aconfig.polling_loop_annotation.frame_length <= 0) ? _YELLOW_("disabled") : _GREEN_("enabled"),
hf14aconfig.polling_loop_annotation.frame_length,
hf14aconfig.polling_loop_annotation.frame,
""
);
}
/**
@ -213,6 +219,13 @@ void setHf14aConfig(const hf14a_config *hc) {
hf14aconfig.forcecl3 = hc->forcecl3;
if ((hc->forcerats >= 0) && (hc->forcerats <= 2))
hf14aconfig.forcerats = hc->forcerats;
if (hc->polling_loop_annotation.frame_length > 0) {
memcpy(&hf14aconfig.polling_loop_annotation, &hc->polling_loop_annotation, sizeof(iso14a_polling_frame_t));
} else if (hc->polling_loop_annotation.frame_length < 0) {
// Reset if set to empty
hf14aconfig.polling_loop_annotation.frame_length = 0;
}
}
hf14a_config *getHf14aConfig(void) {
@ -448,7 +461,6 @@ RAMFUNC bool MillerDecoding(uint8_t bit, uint32_t non_real_time) {
Uart.parityBits |= ((Uart.shiftReg >> 8) & 0x01); // store parity bit
Uart.bitCount = 0;
Uart.shiftReg = 0;
// Every 8 data bytes, store 8 parity bits into a parity byte
if ((Uart.len & 0x0007) == 0) { // every 8 data bytes
Uart.parity[Uart.parityLen++] = Uart.parityBits; // store 8 parity bits
@ -592,7 +604,7 @@ RAMFUNC int ManchesterDecoding(uint8_t bit, uint16_t offset, uint32_t non_real_t
if (Demod.bitCount > 0) { // there are some remaining data bits
Demod.shiftReg >>= (9 - Demod.bitCount); // right align the decoded bits
Demod.output[Demod.len++] = Demod.shiftReg & 0xff; // and add them to the output
Demod.output[Demod.len++] = (Demod.shiftReg & 0xff); // and add them to the output
Demod.parityBits <<= 1; // add a (void) parity bit
Demod.parityBits <<= (8 - (Demod.len & 0x0007)); // left align remaining parity bits
Demod.parity[Demod.parityLen++] = Demod.parityBits; // and store them
@ -1421,7 +1433,6 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
// "precompiled" responses.
// These exist for speed reasons. There are no time in the anti collision phase to calculate responses.
// There are 12 predefined responses with a total of 84 bytes data to transmit.
//
// Coded responses need one byte per bit to transfer (data, parity, start, stop, correction)
// 85 * 8 data bits, 85 * 1 parity bits, 12 start bits, 12 stop bits, 12 correction bits
// 85 * 8 + 85 + 12 + 12 + 12 == 801
@ -2702,16 +2713,37 @@ static int GetATQA(uint8_t *resp, uint16_t resp_len, uint8_t *resp_par, iso14a_p
uint32_t save_iso14a_timeout = iso14a_get_timeout();
iso14a_set_timeout(1236 / 128 + 1); // response to WUPA is expected at exactly 1236/fc. No need to wait longer.
// Create temporary polling parameters structure that might include both standard and custom frames
iso14a_polling_parameters_t temp_params;
memcpy(&temp_params, polling_parameters, sizeof(iso14a_polling_parameters_t));
// If we have a custom polling frame annotation, add it to the temporary structure
if (hf14aconfig.polling_loop_annotation.frame_length > 0) {
// Only add if we have space in the frames array
if (temp_params.frame_count < ARRAYLEN(temp_params.frames)) {
// Add the custom frame at the end of the frames array
memcpy(&temp_params.frames[temp_params.frame_count],
&hf14aconfig.polling_loop_annotation,
sizeof(iso14a_polling_frame_t));
temp_params.frame_count++;
}
// Increase timeout if polling loop annotation is provided, as target may respond slower
if (temp_params.extra_timeout == 0) {
temp_params.extra_timeout = 250;
}
}
bool first_try = true;
uint32_t retry_timeout = WUPA_RETRY_TIMEOUT * polling_parameters->frame_count + polling_parameters->extra_timeout;
uint32_t start_time = 0;
int len;
uint32_t retry_timeout = WUPA_RETRY_TIMEOUT * temp_params.frame_count + temp_params.extra_timeout;
uint32_t start_time = 0;
uint8_t current_frame = 0;
// Use the temporary polling parameters
do {
iso14a_polling_frame_t *frame_parameters = &polling_parameters->frames[current_frame];
iso14a_polling_frame_t *frame_parameters = &temp_params.frames[current_frame];
if (frame_parameters->last_byte_bits == 8) {
ReaderTransmit(frame_parameters->frame, frame_parameters->frame_length, NULL);
@ -2729,12 +2761,11 @@ static int GetATQA(uint8_t *resp, uint16_t resp_len, uint8_t *resp_par, iso14a_p
// We set the start_time here otherwise in some cases we miss the window and only ever try once
if (first_try) {
start_time = GetTickCount();
first_try = false;
}
first_try = false;
// Go over frame configurations, loop back when we reach the end
current_frame = current_frame < (polling_parameters->frame_count - 1) ? current_frame + 1 : 0;
current_frame = current_frame < (temp_params.frame_count - 1) ? current_frame + 1 : 0;
} while (len == 0 && GetTickCountDelta(start_time) <= retry_timeout);
iso14a_set_timeout(save_iso14a_timeout);

View file

@ -43,6 +43,8 @@
#include "mifare/desfirecore.h" // desfire context
#include "mifare/mifaredefault.h"
#include "preferences.h" // get/set device debug level
#include "pm3_cmd.h"
static bool g_apdu_in_framing_enable = true;
bool Get_apdu_in_framing(void) {
@ -333,6 +335,15 @@ static int hf_14a_config_example(void) {
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --atqa force --bcc ignore --cl2 force --cl3 skip -rats skip"));
PrintAndLogEx(NORMAL, _YELLOW_(" hf mfu setuid --uid 04112233445566"));
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --std"));
PrintAndLogEx(NORMAL, "\nExamples of polling loop annotations used to enable anticollision on mobile targets:");
PrintAndLogEx(NORMAL, _CYAN_(" ECP Express Transit EMV")":");
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a02c801000300027900000000"));
PrintAndLogEx(NORMAL, _CYAN_(" ECP VAS Only")":");
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a01000002"));
PrintAndLogEx(NORMAL, _CYAN_(" ECP Access Wildcard")":");
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a02c3020002ffff"));
return PM3_SUCCESS;
}
static int CmdHf14AConfig(const char *Cmd) {
@ -341,25 +352,26 @@ static int CmdHf14AConfig(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf 14a config",
"Configure 14a settings (use with caution)\n"
" `-v` also prints examples for reviving Gen2 cards",
"hf 14a config -> Print current configuration\n"
"hf 14a config --std -> Reset default configuration (follow standard)\n"
"hf 14a config --atqa std -> Follow standard\n"
"hf 14a config --atqa force -> Force execution of anticollision\n"
"hf 14a config --atqa skip -> Skip anticollision\n"
"hf 14a config --bcc std -> Follow standard\n"
"hf 14a config --bcc fix -> Fix bad BCC in anticollision\n"
"hf 14a config --bcc ignore -> Ignore bad BCC and use it as such\n"
"hf 14a config --cl2 std -> Follow standard\n"
"hf 14a config --cl2 force -> Execute CL2\n"
"hf 14a config --cl2 skip -> Skip CL2\n"
"hf 14a config --cl3 std -> Follow standard\n"
"hf 14a config --cl3 force -> Execute CL3\n"
"hf 14a config --cl3 skip -> Skip CL3\n"
"hf 14a config --rats std -> Follow standard\n"
"hf 14a config --rats force -> Execute RATS\n"
"hf 14a config --rats skip -> Skip RATS");
" `-v` also prints examples for reviving Gen2 cards & configuring polling loop annotations",
"hf 14a config -> Print current configuration\n"
"hf 14a config --std -> Reset default configuration (follow standard)\n"
"hf 14a config --atqa std -> Follow standard\n"
"hf 14a config --atqa force -> Force execution of anticollision\n"
"hf 14a config --atqa skip -> Skip anticollision\n"
"hf 14a config --bcc std -> Follow standard\n"
"hf 14a config --bcc fix -> Fix bad BCC in anticollision\n"
"hf 14a config --bcc ignore -> Ignore bad BCC and use it as such\n"
"hf 14a config --cl2 std -> Follow standard\n"
"hf 14a config --cl2 force -> Execute CL2\n"
"hf 14a config --cl2 skip -> Skip CL2\n"
"hf 14a config --cl3 std -> Follow standard\n"
"hf 14a config --cl3 force -> Execute CL3\n"
"hf 14a config --cl3 skip -> Skip CL3\n"
"hf 14a config --rats std -> Follow standard\n"
"hf 14a config --rats force -> Execute RATS\n"
"hf 14a config --rats skip -> Skip RATS\n"
"hf 14a config --pla <hex> -> Set polling loop annotation (max 22 bytes)\n"
"hf 14a config --pla skip -> Disable polling loop annotation\n");
void *argtable[] = {
arg_param_begin,
arg_str0(NULL, "atqa", "<std|force|skip>", "Configure ATQA<>anticollision behavior"),
@ -367,14 +379,17 @@ static int CmdHf14AConfig(const char *Cmd) {
arg_str0(NULL, "cl2", "<std|force|skip>", "Configure SAK<>CL2 behavior"),
arg_str0(NULL, "cl3", "<std|force|skip>", "Configure SAK<>CL3 behavior"),
arg_str0(NULL, "rats", "<std|force|skip>", "Configure RATS behavior"),
arg_str0(NULL, "pla", "<hex|skip>", "Configure polling loop annotation"),
arg_lit0(NULL, "std", "Reset default configuration: follow all standard"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool defaults = arg_get_lit(ctx, 6);
bool defaults = arg_get_lit(ctx, 7);
bool verbose = arg_get_lit(ctx, 8);
int vlen = 0;
char value[10];
char value[64];
int atqa = defaults ? 0 : -1;
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)value, sizeof(value), &vlen);
if (vlen > 0) {
@ -436,11 +451,43 @@ static int CmdHf14AConfig(const char *Cmd) {
}
}
bool verbose = arg_get_lit(ctx, 7);
// Handle polling loop annotation parameter
iso14a_polling_frame_t pla = {
// -1 signals that PLA has to be disabled, 0 signals that no change has to be made
.frame_length = defaults ? -1 : 0,
.last_byte_bits = 8,
.extra_delay = 5
};
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)value, sizeof(value), &vlen);
if (vlen > 0 && (strncmp((char *)value, "skip", 4) || strncmp((char *)value, "std", 3)) == 0) {
pla.frame_length = -1;
} else if (vlen > 0) {
// Convert hex string to bytes
int length = 0;
if (param_gethex_to_eol((char *)value, 0, pla.frame, sizeof(pla.frame), &length) != 0) {
PrintAndLogEx(ERR, "Error parsing polling loop annotation bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
pla.frame_length = length;
// Validate length before adding CRC
if (pla.frame_length < 1 || pla.frame_length > 22) {
PrintAndLogEx(ERR, "Polling loop annotation length invalid: min %d; max %d", 1, 22);
CLIParserFree(ctx);
return PM3_EINVARG;
}
uint8_t first, second;
compute_crc(CRC_14443_A, pla.frame, pla.frame_length, &first, &second);
pla.frame[pla.frame_length++] = first;
pla.frame[pla.frame_length++] = second;
PrintAndLogEx(INFO, "Added CRC16A to polling loop annotation: %s", sprint_hex(pla.frame, pla.frame_length));
}
CLIParserFree(ctx);
// validations
// Handle empty command
if (strlen(Cmd) == 0) {
return hf14a_setconfig(NULL, verbose);
}
@ -449,12 +496,14 @@ static int CmdHf14AConfig(const char *Cmd) {
hf_14a_config_example();
}
// Initialize config with all parameters
hf14a_config config = {
.forceanticol = atqa,
.forcebcc = bcc,
.forcecl2 = cl2,
.forcecl3 = cl3,
.forcerats = rats
.forcerats = rats,
.polling_loop_annotation = pla
};
return hf14a_setconfig(&config, verbose);

View file

@ -108,23 +108,6 @@ typedef enum ISO14A_COMMAND {
ISO14A_CRYPTO1MODE = (1 << 14)
} iso14a_command_t;
// Defines a frame that will be used in a polling sequence
// ECP Frames are up to (7 + 16) bytes long, 24 bytes should cover future and other cases
typedef struct {
uint8_t frame[24];
uint8_t frame_length;
uint8_t last_byte_bits;
uint16_t extra_delay;
} PACKED iso14a_polling_frame_t;
// Defines polling sequence configuration
// 6 would be enough for 4 magsafe, 1 wupa, 1 ecp,
typedef struct {
iso14a_polling_frame_t frames[6];
uint8_t frame_count;
uint16_t extra_timeout;
} PACKED iso14a_polling_parameters_t;
typedef struct {
uint8_t *response;
uint8_t *modulation;

View file

@ -131,6 +131,27 @@ typedef struct {
bool verbose;
} PACKED sample_config;
// Defines a frame that will be used in a polling sequence
// Polling loop annotations are up to (7 + 16) bytes long, 24 bytes should cover future and other cases
typedef struct {
uint8_t frame[24];
// negative values can be used to carry special info
int8_t frame_length;
uint8_t last_byte_bits;
uint16_t extra_delay;
} PACKED iso14a_polling_frame_t;
// Defines polling sequence configuration
// 6 would be enough for 4 magsafe, 1 wupa, 1 pla,
typedef struct {
iso14a_polling_frame_t frames[6];
int8_t frame_count;
uint16_t extra_timeout;
} PACKED iso14a_polling_parameters_t;
// A struct used to send hf14a-configs over USB
typedef struct {
int8_t forceanticol; // 0:auto 1:force executing anticol 2:force skipping anticol
@ -138,6 +159,7 @@ typedef struct {
int8_t forcecl2; // 0:auto 1:force executing CL2 2:force skipping CL2
int8_t forcecl3; // 0:auto 1:force executing CL3 2:force skipping CL3
int8_t forcerats; // 0:auto 1:force executing RATS 2:force skipping RATS
iso14a_polling_frame_t polling_loop_annotation; // Polling loop annotation
} PACKED hf14a_config;
// Tracelog Header struct