diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index bd9b4d2ab..d4da16fb6 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -1925,19 +1925,14 @@ int iso14443b_select_srx_card(iso14b_card_select_t *card) { return PM3_SUCCESS; } -/** - * Type B' / Innovatron APGEN. - */ static int iso14443b_select_prime_card(iso14b_prime_card_select_t *card) { uint8_t apgen[] = { ISO14443B_PRIME_VT_ADDR_DEFAULT, ISO14443B_PRIME_CMD_APGEN, - // Seems to affect time slots / response chance: - // 0x3f card responds every time - // 0x3e-0x00 reduced success rate - // 0x40-0xff card does not respond - 0x3f, - ISO14443B_PRIME_REQUEST_EXTENDED_REPGEN, + // OccuPar 0x3F is the default value used by most readers in real installations. + ISO14443B_PRIME_OCCUPAR_DEFAULT, + // APGEN Config bit 7 requests the long REPGEN form with CONFIG/ATR. + ISO14443B_PRIME_APGEN_CONFIG_REQUEST_ATR, 0x00, 0x00 }; @@ -1976,9 +1971,9 @@ static int iso14443b_select_prime_card(iso14b_prime_card_select_t *card) { card->verlog = r_repgen[6]; uint16_t offset = 7; - if ((card->verlog & 0x80) && repgen_len > offset) { + if ((card->verlog & ISO14443B_PRIME_VERLOG_LONG_REPGEN) && repgen_len > offset) { card->config = r_repgen[offset++]; - if ((card->config & 0x40) && repgen_len > offset) { + if ((card->config & ISO14443B_PRIME_CONFIG_ATR_PRESENT) && repgen_len > offset) { uint16_t atr_len = repgen_len - offset; if (atr_len >= 2 && r_repgen[repgen_len - 2] == 0x90 && r_repgen[repgen_len - 1] == 0x00) { atr_len -= 2; diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 7119029ba..ba3d8872d 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -62,7 +62,7 @@ // iso14b apdu input frame length static uint16_t apdu_frame_length = 0; static uint8_t prime_vt_addr = ISO14443B_PRIME_VT_ADDR_DEFAULT; -static uint8_t prime_com_ra_cmd = ISO14443B_PRIME_COM_RA_START; +static uint8_t prime_frame_seq = ISO14443B_PRIME_SEQUENCE_START; //static uint16_t ats_fsc[] = {16, 24, 32, 40, 48, 64, 96, 128, 256}; static bool apdu_in_framing_enable = true; @@ -70,6 +70,8 @@ static int CmdHelp(const char *Cmd); static int switch_off_field_14b(void) { SetISODEPState(ISODEP_INACTIVE); + prime_vt_addr = ISO14443B_PRIME_VT_ADDR_DEFAULT; + prime_frame_seq = ISO14443B_PRIME_SEQUENCE_START; iso14b_raw_cmd_t packet = { .flags = ISO14B_DISCONNECT, .timeout = 0, @@ -770,14 +772,22 @@ static void print_ct_general_info(void *vcard) { } static void print_prime_general_info(const iso14b_prime_card_select_t *card) { + const uint8_t vt_card_addr = (card->vt_addr & ISO14443B_PRIME_VT_CARD_ADDR_MASK) >> ISO14443B_PRIME_VT_CARD_ADDR_SHIFT; + const uint8_t vt_coupler_addr = card->vt_addr & ISO14443B_PRIME_VT_COUPLER_ADDR_MASK; + const bool long_repgen = (card->verlog & ISO14443B_PRIME_VERLOG_LONG_REPGEN) == ISO14443B_PRIME_VERLOG_LONG_REPGEN; + const uint8_t software_version = (card->verlog & ISO14443B_PRIME_VERLOG_VERSION_MASK) >> ISO14443B_PRIME_VERLOG_VERSION_SHIFT; + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Type B' / Innovatron") " ---------------------"); - PrintAndLogEx(SUCCESS, " V&T Ad : %02X", card->vt_addr); - PrintAndLogEx(SUCCESS, " Cmd : %02X (REPGEN)", card->repgen_cmd); + PrintAndLogEx(SUCCESS, " V&T Ad : %02X (card %u, coupler %u)", card->vt_addr, vt_card_addr, vt_coupler_addr); PrintAndLogEx(SUCCESS, " DIV : " _GREEN_("%s"), sprint_hex(card->div, sizeof(card->div))); - PrintAndLogEx(SUCCESS, " VerLog : %02X", card->verlog); - if (card->verlog & 0x80) { - PrintAndLogEx(SUCCESS, " Config : %02X", card->config); + PrintAndLogEx(SUCCESS, " VerLog : %02X (%s REPGEN, software version %u)", + card->verlog, long_repgen ? "long" : "short", software_version); + if (long_repgen) { + PrintAndLogEx(SUCCESS, " Config : %02X (WAIT %s, ATR %s)", + card->config, + (card->config & ISO14443B_PRIME_CONFIG_WAIT_SUPPORTED) ? "supported" : "not indicated", + (card->config & ISO14443B_PRIME_CONFIG_ATR_PRESENT) ? "present" : "absent"); } if (card->atr_len) { PrintAndLogEx(SUCCESS, " ATR : %s", sprint_hex(card->atr, card->atr_len)); @@ -1382,7 +1392,7 @@ int select_card_14443b_prime(bool disconnect, iso14b_prime_card_select_t *card, } prime_vt_addr = selected.vt_addr; - prime_com_ra_cmd = ISO14443B_PRIME_COM_RA_START; + prime_frame_seq = ISO14443B_PRIME_SEQUENCE_START; SetISODEPState(disconnect ? ISODEP_INACTIVE : ISODEP_NFCB_PRIME); if (card) { *card = selected; @@ -2607,8 +2617,8 @@ int exchange_14b_apdu(uint8_t *datain, int datainlen, bool activate_field, return PM3_SUCCESS; } -static uint8_t next_prime_com_ra_cmd(uint8_t cmd) { - return (cmd + 0x02) & 0x0F; +static uint8_t next_prime_frame_seq(uint8_t seq) { + return seq >= ISO14443B_PRIME_SEQUENCE_END ? ISO14443B_PRIME_SEQUENCE_START : seq + 1; } int exchange_14b_prime_apdu(uint8_t *datain, int datainlen, bool activate_field, @@ -2634,7 +2644,7 @@ int exchange_14b_prime_apdu(uint8_t *datain, int datainlen, bool activate_field, } frame[0] = prime_vt_addr; - frame[1] = prime_com_ra_cmd; + frame[1] = ISO14443B_PRIME_CMD_COM_R(prime_frame_seq); frame[2] = (uint8_t)datainlen + 1; if (datainlen > 0) { memcpy(frame + 3, datain, datainlen); @@ -2692,7 +2702,7 @@ int exchange_14b_prime_apdu(uint8_t *datain, int datainlen, bool activate_field, } const uint8_t rx_len = rx[2]; - if (rx[0] != prime_vt_addr || (rx[1] & 0x01) || rx_len == 0 || resp.length < (uint16_t)rx_len + 4) { + if (rx[0] != prime_vt_addr || rx[1] != ISO14443B_PRIME_CMD_REC(prime_frame_seq) || rx_len == 0 || resp.length < (uint16_t)rx_len + 4) { if (leave_signal_on == false) { switch_off_field_14b(); } @@ -2710,7 +2720,7 @@ int exchange_14b_prime_apdu(uint8_t *datain, int datainlen, bool activate_field, memcpy(dataout, rx + 3, apdu_len); *dataoutlen = apdu_len; - prime_com_ra_cmd = next_prime_com_ra_cmd(prime_com_ra_cmd); + prime_frame_seq = next_prime_frame_seq(prime_frame_seq); if (leave_signal_on == false) { switch_off_field_14b(); diff --git a/include/protocols.h b/include/protocols.h index 0f343c5d8..37378d56e 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -331,38 +331,77 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. /* * Type B' / Innovatron frame format. * - * Byte 0: V&T address. Public docs and traces label this "V&T Ad". - * 0x01 is the default address observed in APGEN, REPGEN, - * ATTRIB, COM_RA, and DISC frames. - * Observed cards respond to any value from 0x00 to 0xFF, and - * echo that same address in the response. + * Byte 0 is V&T Ad. The high nibble is the card/tag address and the low + * nibble is the coupler/validator address. APGEN uses card address 0; * - * Byte 1: payload type / command. + * APGEN: [V&T Ad, 0B, OccuPar] or [V&T Ad, 0B, OccuPar, Config] + * REPGEN: [V&T Ad, 07, DIV(4), VERLOG] or + * [V&T Ad, 07, DIV(4), VERLOG, CONFIG, ATR/application bytes...] + * ATTRIB: [V&T Ad, 0F, DIV(4)] -> RR + * DISC: [V&T Ad, 03] -> RR + * RR: [V&T Ad, 01] * - * Primary commands: - * 0x0B APGEN "Appel General"; acts as the wake-up command. - * 0x07 REPGEN response to APGEN. - * 0x0F ATTRIB attribute / activation command. - * 0x03 DISC disconnect. + * Data exchange commands: + * family | (seq << 1), with bit 0 clear and seq carried in bits 1..3. + * COM_RA C2..CE allows assigning card address and performing APDU exchange by DIV. + * COM_R 02..0E is the normal APDU exchange command, relying on card address only. + * REC 42..4E is the card response to COM_RA/COM_R. + * Public PICS rows list seq 1..7. * - * COM_RA frames: - * COM_RA uses the even-valued payload type bytes. Bit 0 is clear; bits 1..3 - * are the rolling frame counter. The resulting byte advances by 0x02 for - * each exchange and wraps in the low nibble: + * Byte 2 in data frames is LNG. LNG includes byte 2 itself, so the APDU/R-APDU + * byte count is LNG - 1. In COM_RA the trailing DIV is outside LNG. * - * 04 -> 06 -> 08 -> 0A -> 0C -> 0E -> 00 -> 02 -> 04 ... - * - * Byte 2 is the COM_RA length byte. The length includes byte 2 itself, so - * the number of bytes after byte 2 is length - 1. */ -#define ISO14443B_PRIME_VT_ADDR_DEFAULT 0x01 -#define ISO14443B_PRIME_CMD_DISC 0x03 -#define ISO14443B_PRIME_CMD_REPGEN 0x07 -#define ISO14443B_PRIME_CMD_APGEN 0x0B -#define ISO14443B_PRIME_CMD_ATTRIB 0x0F -// APGEN parameter requesting the extended REPGEN response; also called 'APGEN!' -#define ISO14443B_PRIME_REQUEST_EXTENDED_REPGEN 0x80 -#define ISO14443B_PRIME_COM_RA_START 0x02 +#define ISO14443B_PRIME_VT_ADDR_DEFAULT 0x01 +#define ISO14443B_PRIME_VT_CARD_ADDR_MASK 0xF0 +#define ISO14443B_PRIME_VT_CARD_ADDR_SHIFT 4 +#define ISO14443B_PRIME_VT_COUPLER_ADDR_MASK 0x0F +#define ISO14443B_PRIME_VT_CARD_ADDR_FIRST 1 +#define ISO14443B_PRIME_VT_ADDR(card, coupler) ((((card) << ISO14443B_PRIME_VT_CARD_ADDR_SHIFT) & ISO14443B_PRIME_VT_CARD_ADDR_MASK) | ((coupler) & ISO14443B_PRIME_VT_COUPLER_ADDR_MASK)) + +#define ISO14443B_PRIME_CMD_RR 0x01 +#define ISO14443B_PRIME_CMD_DISC 0x03 +#define ISO14443B_PRIME_CMD_REPGEN 0x07 +#define ISO14443B_PRIME_CMD_APGEN 0x0B +#define ISO14443B_PRIME_CMD_ATTRIB 0x0F + +#define ISO14443B_PRIME_OCCUPAR_VALUE_MASK 0x3F +// Public documents and sources identify bit 6 as short APGEN; +// it's not proven to work yet +#define ISO14443B_PRIME_OCCUPAR_SHORT_APGEN 0x40 +#define ISO14443B_PRIME_OCCUPAR_HIGH_BITS_MASK 0xC0 +#define ISO14443B_PRIME_OCCUPAR_BPSK_FLAG 0x80 +// OccuPar controls APGEN response chance: +// 0x3F card answers every poll attempt +// 0x3E-0x00 lower values reduce the response chance +#define ISO14443B_PRIME_OCCUPAR_DEFAULT 0x3F + +#define ISO14443B_PRIME_APGEN_CONFIG_REQUEST_ATR 0x80 +#define ISO14443B_PRIME_APGEN_CONFIG_RFU_MASK 0x7F + +#define ISO14443B_PRIME_VERLOG_LONG_REPGEN 0x80 +#define ISO14443B_PRIME_VERLOG_FIXED_BITS_MASK 0x61 +#define ISO14443B_PRIME_VERLOG_FIXED_BITS_VALUE 0x61 +#define ISO14443B_PRIME_VERLOG_VERSION_MASK 0x1E +#define ISO14443B_PRIME_VERLOG_VERSION_SHIFT 1 + +#define ISO14443B_PRIME_CONFIG_WAIT_SUPPORTED 0x80 +#define ISO14443B_PRIME_CONFIG_ATR_PRESENT 0x40 +#define ISO14443B_PRIME_CONFIG_RFU_MASK 0x3F + +#define ISO14443B_PRIME_CMD_COM_DATA_FAMILY_MASK 0xC0 +#define ISO14443B_PRIME_CMD_COM_DATA_SEQ_MASK 0x0E +#define ISO14443B_PRIME_CMD_COM_DATA_SEQ_SHIFT 1 +#define ISO14443B_PRIME_CMD_COM_DATA_FAMILY_COM_R 0x00 +#define ISO14443B_PRIME_CMD_COM_DATA_FAMILY_REC 0x40 +#define ISO14443B_PRIME_CMD_COM_DATA_FAMILY_COM_RA 0xC0 +#define ISO14443B_PRIME_SEQUENCE_START 1 +#define ISO14443B_PRIME_SEQUENCE_END 7 +#define ISO14443B_PRIME_CMD_COM_DATA_SEQ(cmd) (((cmd) & ISO14443B_PRIME_CMD_COM_DATA_SEQ_MASK) >> ISO14443B_PRIME_CMD_COM_DATA_SEQ_SHIFT) +#define ISO14443B_PRIME_CMD_COM_R(seq) (ISO14443B_PRIME_CMD_COM_DATA_FAMILY_COM_R | (((seq) << ISO14443B_PRIME_CMD_COM_DATA_SEQ_SHIFT) & ISO14443B_PRIME_CMD_COM_DATA_SEQ_MASK)) +#define ISO14443B_PRIME_CMD_REC(seq) (ISO14443B_PRIME_CMD_COM_DATA_FAMILY_REC | (((seq) << ISO14443B_PRIME_CMD_COM_DATA_SEQ_SHIFT) & ISO14443B_PRIME_CMD_COM_DATA_SEQ_MASK)) +#define ISO14443B_PRIME_CMD_COM_RA(seq) (ISO14443B_PRIME_CMD_COM_DATA_FAMILY_COM_RA | (((seq) << ISO14443B_PRIME_CMD_COM_DATA_SEQ_SHIFT) & ISO14443B_PRIME_CMD_COM_DATA_SEQ_MASK)) +#define ISO14443B_PRIME_COM_RA_START ISO14443B_PRIME_CMD_COM_RA(ISO14443B_PRIME_SEQUENCE_START) // XEROX Commands #define ISO14443B_XEROX_PWD 0x38