Merge pull request #3389 from kormax/hf-14b-info-innovatron

Add support for detecting Innovatron protocol in 'hf 14b info'
This commit is contained in:
Iceman
2026-06-19 01:04:25 +07:00
committed by GitHub
5 changed files with 218 additions and 1 deletions
+1
View File
@@ -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)
+77
View File
@@ -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) ||
+92
View File
@@ -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);
+13 -1
View File
@@ -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_
+35
View File
@@ -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