From a881c81ec1d7b2f9144339e2fe2fef1300f1cbd1 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:07:14 +0300 Subject: [PATCH] Add support for detecting Innovatron protocol in 'hf 14b info' --- CHANGELOG.md | 1 + armsrc/iso14443b.c | 77 ++++++++++++++++++++++++++++++++++++ client/src/cmdhf14b.c | 92 +++++++++++++++++++++++++++++++++++++++++++ include/iso14b.h | 14 ++++++- include/protocols.h | 35 ++++++++++++++++ 5 files changed, 218 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92dfde09d..c2408f279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ 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] +- Add support for Innovatron protocol detection to `hf 14b info` (@kormax) - Improved `lf cotag reader` and `lf cotag demod`: Reimplementation and enhancement of proxmark3 COTAG support - Added `hf felica sim` command (@kormax) - Added `mad read`, `mad write`, `mad verify`, `mad decode`, `mad encode` commands with typed struct MAD API (@AlxCzl) diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index 4f61912df..545c9ac45 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -1919,6 +1919,74 @@ 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, + 0x00, + 0x00 + }; + uint8_t r_repgen[PM3_CMD_DATA_SIZE] = { 0x00 }; + + AddCrc14B(apgen, sizeof(apgen) - 2); + + uint32_t start_time = 0; + uint32_t eof_time = 0; + CodeAndTransmit14443bAsReader(apgen, sizeof(apgen), &start_time, &eof_time, true); + + eof_time += DELAY_ISO14443B_PCD_TO_PICC_READER; + uint16_t retlen = 0; + if (Get14443bAnswerFromTag(r_repgen, sizeof(r_repgen), s_iso14b_timeout, &eof_time, &retlen) != PM3_SUCCESS) { + return PM3_ECARDEXCHANGE; + } + + // REPGEN carries V&T address, REPGEN command, DIV, VerLog, then CRC. + if (retlen < 9) { + return PM3_ELENGTH; + } + + if (check_crc(CRC_14443_B, r_repgen, retlen) == false) { + return PM3_ECRC; + } + + const uint16_t repgen_len = retlen - 2; + if (r_repgen[0] != apgen[0] || r_repgen[1] != ISO14443B_PRIME_CMD_REPGEN) { + return PM3_EWRONGANSWER; + } + + if (card) { + card->vt_addr = r_repgen[0]; + card->repgen_cmd = r_repgen[1]; + memcpy(card->div, r_repgen + 2, sizeof(card->div)); + card->verlog = r_repgen[6]; + + uint16_t offset = 7; + if ((card->verlog & 0x80) && repgen_len > offset) { + card->config = r_repgen[offset++]; + if ((card->config & 0x40) && 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; + } + card->atr_len = (uint8_t)MIN(atr_len, ISO14B_PRIME_ATR_MAX_LEN); + memcpy(card->atr, r_repgen + offset, card->atr_len); + } + } + } + + s_iso14b_pcb_blocknum = 0; + return PM3_SUCCESS; +} + // Xerox tag connect function: wup, anticoll, attrib, password // the original chips require all commands in this sequence @@ -3148,6 +3216,15 @@ void SendRawCommand14443B(iso14b_raw_cmd_t *p) { if (status != PM3_SUCCESS) goto out; } + if ((p->flags & ISO14B_SELECT_PRIME) == ISO14B_SELECT_PRIME) { + iso14b_prime_card_select_t *prime = (iso14b_prime_card_select_t *)buf; + memset(prime, 0, sizeof(iso14b_prime_card_select_t)); + sendlen = sizeof(iso14b_prime_card_select_t); + status = iso14443b_select_prime_card(prime); + reply_ng(CMD_HF_ISO14443B_COMMAND, status, (uint8_t *)prime, sendlen); + if (status != PM3_SUCCESS) goto out; + } + // if field is off... if ( ((p->flags & ISO14B_APDU) == ISO14B_APDU) || diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 1182d8574..e1d31ccc6 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -767,6 +767,22 @@ static void print_ct_general_info(void *vcard) { PrintAndLogEx(NORMAL, ""); } +static void print_prime_general_info(const iso14b_prime_card_select_t *card) { + 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, " 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); + } + if (card->atr_len) { + PrintAndLogEx(SUCCESS, " ATR : %s", sprint_hex(card->atr, card->atr_len)); + } + PrintAndLogEx(NORMAL, ""); +} + static void print_hdr(void) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, " block# | data |lck| ascii"); @@ -1323,6 +1339,63 @@ static bool HF14B_ST_Info(bool verbose, bool do_aid_search) { return true; } +static bool get_prime_card(iso14b_prime_card_select_t *card, bool verbose) { + + if (card == NULL) { + return false; + } + + iso14b_raw_cmd_t packet = { + .flags = (ISO14B_CONNECT | ISO14B_SELECT_PRIME | ISO14B_DISCONNECT), + .timeout = 0, + .rawlen = 0, + }; + + clearCommandBuffer(); + PacketResponseNG resp; + SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t)); + if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, TIMEOUT) == false) { + if (verbose) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + } + return false; + } + + switch (resp.status) { + case PM3_SUCCESS: { + if (resp.length < sizeof(*card)) { + if (verbose) { + PrintAndLogEx(FAILED, "ISO 14443-B' card select response too short (%u bytes)", resp.length); + } + return false; + } + + memcpy(card, resp.data.asBytes, sizeof(*card)); + if (card->repgen_cmd != ISO14443B_PRIME_CMD_REPGEN || card->atr_len > sizeof(card->atr)) { + if (verbose) { + PrintAndLogEx(FAILED, "ISO 14443-B' invalid card select response"); + } + return false; + } + return true; + } + case PM3_ELENGTH: + if (verbose) PrintAndLogEx(FAILED, "ISO 14443-B' REPGEN wrong length"); + break; + case PM3_ECRC: + if (verbose) PrintAndLogEx(FAILED, "ISO 14443-B' REPGEN CRC fail"); + break; + case PM3_EWRONGANSWER: + if (verbose) PrintAndLogEx(FAILED, "ISO 14443-B' REPGEN wrong answer"); + break; + default: + if (verbose) PrintAndLogEx(FAILED, "ISO 14443-B' APGEN failed"); + break; + } + + return false; +} + // menu command to get and print all info known about any known 14b tag static int CmdHF14Binfo(const char *Cmd) { CLIParserContext *ctx; @@ -1580,6 +1653,16 @@ static bool HF14B_picopass_reader(bool verbose) { return false; } +static bool HF14B_prime_reader(bool verbose) { + + iso14b_prime_card_select_t card = {0}; + if (get_prime_card(&card, verbose) == false) { + return false; + } + print_prime_general_info(&card); + return true; +} + // test for other 14b type tags (mimic another reader - don't have tags to identify) static bool HF14B_other_reader(bool verbose) { @@ -3169,6 +3252,10 @@ int infoHF14B(bool verbose, bool do_aid_search) { if (HF14B_ST_Info(verbose, do_aid_search)) return PM3_SUCCESS; + // try Type B' / Innovatron APGEN + if (HF14B_prime_reader(verbose)) + return PM3_SUCCESS; + // try unknown 14b read commands (to be identified later) // could be read of calypso, CEPAS, moneo, or pico pass. if (verbose) { @@ -3204,6 +3291,11 @@ int readHF14B(bool loop, bool verbose, bool read_plot) { if (found) goto plot; + // try Type B' / Innovatron APGEN + found |= HF14B_prime_reader(verbose); + if (found) + goto plot; + // try unknown 14b read commands (to be identified later) // could be read of calypso, CEPAS, moneo, or pico pass. found |= HF14B_other_reader(verbose); diff --git a/include/iso14b.h b/include/iso14b.h index de2692e58..4a0580871 100644 --- a/include/iso14b.h +++ b/include/iso14b.h @@ -34,6 +34,18 @@ typedef struct { uint8_t fc; } PACKED iso14b_cts_card_select_t; +#define ISO14B_PRIME_DIV_LEN 4 +#define ISO14B_PRIME_ATR_MAX_LEN 33 +typedef struct { + uint8_t vt_addr; + uint8_t repgen_cmd; + uint8_t div[ISO14B_PRIME_DIV_LEN]; + uint8_t verlog; + uint8_t config; + uint8_t atr_len; + uint8_t atr[ISO14B_PRIME_ATR_MAX_LEN]; +} PACKED iso14b_prime_card_select_t; + typedef enum ISO14B_COMMAND { ISO14B_CONNECT = (1 << 0), ISO14B_DISCONNECT = (1 << 1), @@ -49,6 +61,7 @@ typedef enum ISO14B_COMMAND { ISO14B_CLEARTRACE = (1 << 11), ISO14B_SELECT_XRX = (1 << 12), ISO14B_SELECT_PICOPASS = (1 << 13), + ISO14B_SELECT_PRIME = (1 << 14), } iso14b_command_t; typedef enum ISO14B_TYPE { @@ -85,4 +98,3 @@ typedef struct { #define US_TO_ETU(x) ( (float)((x) / 9.4396) ) #endif // _ISO14B_H_ - diff --git a/include/protocols.h b/include/protocols.h index 9a384d6a4..e95a9d606 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -328,6 +328,41 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define ISO14443B_PING 0xBA #define ISO14443B_PONG 0xAB +/* + * 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 1: payload type / command. + * + * 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. + * + * 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: + * + * 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 + // XEROX Commands #define ISO14443B_XEROX_PWD 0x38 #define ISO14443B_XEROX_WUP1 0x0D