From 248f3b28393f201802f3bb0ea1980b0e0ecebf6c Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 8 Jun 2024 03:00:18 +0300 Subject: [PATCH 01/60] Add support for Ultralight emulation. This version only emulates MF0ICU1 properly. --- firmware/application/Makefile | 2 +- firmware/application/src/app_cmd.c | 20 + firmware/application/src/app_main.c | 8 +- firmware/application/src/data_cmd.h | 2 + .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 492 ++++++++++++++++++ .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 50 ++ .../application/src/rfid/nfctag/hf/nfc_ntag.c | 301 ----------- .../application/src/rfid/nfctag/hf/nfc_ntag.h | 39 -- .../src/rfid/nfctag/tag_base_type.h | 10 +- .../src/rfid/nfctag/tag_emulation.c | 14 +- firmware/application/src/rfid_main.h | 2 +- software/script/chameleon_cli_unit.py | 25 +- software/script/chameleon_cmd.py | 11 + software/script/chameleon_enum.py | 16 +- 14 files changed, 639 insertions(+), 353 deletions(-) create mode 100644 firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c create mode 100644 firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h delete mode 100644 firmware/application/src/rfid/nfctag/hf/nfc_ntag.c delete mode 100644 firmware/application/src/rfid/nfctag/hf/nfc_ntag.h diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 632b3e28..b6596afd 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -28,7 +28,7 @@ SRC_FILES += \ $(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \ $(PROJ_DIR)/rfid/nfctag/hf/nfc_14a.c \ $(PROJ_DIR)/rfid/nfctag/hf/nfc_mf1.c \ - $(PROJ_DIR)/rfid/nfctag/hf/nfc_ntag.c \ + $(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \ $(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \ $(PROJ_DIR)/utils/dataframe.c \ $(PROJ_DIR)/utils/delayed_reset.c \ diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 8a7e9ab1..d4fc9e89 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -1026,6 +1026,24 @@ static data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint1 // fct will be defined after m_data_cmd_map because we need to know its size data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); +static data_frame_tx_t *cmd_processor_mf0_ntag_get_uid_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + int rc = nfc_tag_mf0_ntag_get_uid_mode(); + + if (rc < 0) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + else { + uint8_t res = rc; + return data_frame_make(cmd, STATUS_SUCCESS, 1, &res); + } +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_uid_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || !nfc_tag_mf0_ntag_set_uid_mode(data[0] != 0)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + /** * (cmd -> processor) function map, the map struct is: * cmd code before process cmd processor after process @@ -1109,6 +1127,8 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL }, { DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL }, { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_get_uid_mode, NULL }, + { DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL }, { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 011f4ba7..5a6d3225 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -653,8 +653,12 @@ static void btn_fn_copy_ic_uid(void) { case TAG_TYPE_NTAG_213: case TAG_TYPE_NTAG_215: - case TAG_TYPE_NTAG_216: { - nfc_tag_ntag_information_t *p_info = (nfc_tag_ntag_information_t *)buffer->buffer; + case TAG_TYPE_NTAG_216: + case TAG_TYPE_MF0ICU1: + case TAG_TYPE_MF0ICU2: + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: { + nfc_tag_mf0_ntag_information_t *p_info = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; antres = &(p_info->res_coll); break; } diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 10313b16..7ede2550 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -106,6 +106,8 @@ #define DATA_CMD_MF1_GET_WRITE_MODE (4016) #define DATA_CMD_MF1_SET_WRITE_MODE (4017) #define DATA_CMD_HF14A_GET_ANTI_COLL_DATA (4018) +#define DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE (4019) +#define DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE (4020) // // ****************************************************************** diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c new file mode 100644 index 00000000..665fa27f --- /dev/null +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -0,0 +1,492 @@ +#include + +#include "nfc_mf0_ntag.h" +#include "nfc_14a.h" +#include "fds_util.h" +#include "tag_persistence.h" + +#define NRF_LOG_MODULE_NAME tag_mf0_ntag +#include "nrf_log.h" +#include "nrf_log_ctrl.h" +#include "nrf_log_default_backends.h" +NRF_LOG_MODULE_REGISTER(); + + +#define VERSION_FIXED_HEADER 0x00 +#define VERSION_VENDOR_ID 0x04 +#define MF0ULx1_VERSION_PRODUCT_TYPE 0x03 +#define NTAG_VERSION_PRODUCT_TYPE 0x04 +#define VERSION_PRODUCT_SUBTYPE_17pF 0x01 +#define VERSION_PRODUCT_SUBTYPE_50pF 0x02 +#define VERSION_MAJOR_PRODUCT 0x01 +#define VERSION_MINOR_PRODUCT 0x00 +#define MF0UL11_VERSION_STORAGE_SIZE 0x0B +#define MF0UL21_VERSION_STORAGE_SIZE 0x0E +#define NTAG213_VERSION_STORAGE_SIZE 0x0F +#define NTAG215_VERSION_STORAGE_SIZE 0x11 +#define NTAG216_VERSION_STORAGE_SIZE 0x13 +#define VERSION_PROTOCOL_TYPE 0x03 + +// MF0 and NTAG COMMANDS +#define CMD_GET_VERSION 0x60 +#define CMD_READ 0x30 +#define CMD_FAST_READ 0x3A +#define CMD_WRITE 0xA2 +#define CMD_COMPAT_WRITE 0xA0 +#define CMD_READ_CNT 0x39 +#define CMD_INCR_CNT 0x3A +#define CMD_PWD_AUTH 0x1B +#define CMD_READ_SIG 0x3C +#define CMD_CHECK_TEARING_EVENT 0x3E +#define CMD_VCSL 0x4B + +// MEMORY LAYOUT STUFF, addresses and sizes in bytes +// UID stuff +#define UID_CL1_ADDRESS 0x00 +#define UID_CL1_SIZE 3 +#define UID_BCC1_ADDRESS 0x03 +#define UID_CL2_ADDRESS 0x04 +#define UID_CL2_SIZE 4 +#define UID_BCC2_ADDRESS 0x08 +// LockBytes stuff +#define STATIC_LOCKBYTE_0_ADDRESS 0x0A +#define STATIC_LOCKBYTE_1_ADDRESS 0x0B +// CONFIG stuff +#define MF0UL11_FIRST_CFG_PAGE 0x10 +#define MF0UL21_FIRST_CFG_PAGE 0x25 +#define NTAG213_FIRST_CFG_PAGE 0x29 +#define NTAG215_FIRST_CFG_PAGE 0x83 +#define NTAG216_FIRST_CFG_PAGE 0xE3 +#define CONFIG_AREA_SIZE 8 + +// CONFIG offsets, relative to config start address +#define CONF_AUTH0_BYTE 0x03 +#define CONF_ACCESS_OFFSET 0x04 +#define CONF_PWD_PAGE_OFFSET 2 +#define CONF_PACK_PAGE_OFFSET 3 + +// WRITE STUFF +#define BYTES_PER_WRITE 4 +#define PAGE_WRITE_MIN 0x02 + +// CONFIG masks to check individual needed bits +#define CONF_ACCESS_PROT 0x80 + +#define VERSION_INFO_LENGTH 8 //8 bytes info length + crc + +#define BYTES_PER_READ 16 + +// SIGNATURE Length +#define SIGNATURE_LENGTH 32 + +// NTAG215_Version[7] mean: +// 0x0F ntag213 +// 0x11 ntag215 +// 0x13 ntag216 +const uint8_t ntagVersion[8] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x11, 0x03}; +/* pwd auth for amiibo */ +uint8_t ntagPwdOK[2] = {0x80, 0x80}; + +// Data structure pointer to the label information +static nfc_tag_mf0_ntag_information_t *m_tag_information = NULL; +// Define and use shadow anti -collision resources +static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res; +//Define and use MF0/NTAG special communication buffer +static nfc_tag_mf0_ntag_tx_buffer_t m_tag_tx_buffer; +// Save the specific type of MF0/NTAG currently being simulated +static tag_specific_type_t m_tag_type; +static bool m_tag_authenticated = false; + +static uint8_t get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { + uint8_t nr_pages; + + switch (tag_type) { + case TAG_TYPE_MF0ICU1: + nr_pages = MF0ICU1_PAGES; + break; + case TAG_TYPE_MF0ICU2: + nr_pages = MF0ICU2_PAGES; + break; + case TAG_TYPE_MF0UL11: + nr_pages = MF0UL11_PAGES; + break; + case TAG_TYPE_MF0UL21: + nr_pages = MF0UL21_PAGES; + break; + case TAG_TYPE_NTAG_213: + nr_pages = NTAG213_PAGES; + break; + case TAG_TYPE_NTAG_215: + nr_pages = NTAG215_PAGES; + break; + case TAG_TYPE_NTAG_216: + nr_pages = NTAG216_PAGES; + break; + default: + nr_pages = 0; + break; + } + + return nr_pages; +} + +static uint8_t get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { + uint8_t page; + + switch (tag_type) { + case TAG_TYPE_MF0UL11: + page = MF0UL11_FIRST_CFG_PAGE; + break; + case TAG_TYPE_MF0UL21: + page = MF0UL21_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_213: + page = NTAG213_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_215: + page = NTAG215_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_216: + page = NTAG216_FIRST_CFG_PAGE; + break; + default: + page = 0; + break; + } + + return page; +} + +static uint8_t get_block_max_by_tag_type(tag_specific_type_t tag_type) { + uint8_t max_pages = get_nr_pages_by_tag_type(tag_type); + uint8_t first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); + + if (first_cfg_page == 0 || m_tag_authenticated || m_tag_information->config.mode_uid_magic) return max_pages; + + uint8_t auth0 = m_tag_information->memory[first_cfg_page][CONF_AUTH0_BYTE]; + return (max_pages > auth0) ? auth0 : max_pages; +} + +static bool is_ntag() { + switch (m_tag_type) { + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + return true; + default: + return false; + } +} + +static void handle_get_version_command() { + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + m_tag_tx_buffer.tx_buffer[6] = MF0UL11_VERSION_STORAGE_SIZE; + m_tag_tx_buffer.tx_buffer[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_MF0UL21: + m_tag_tx_buffer.tx_buffer[6] = MF0UL21_VERSION_STORAGE_SIZE; + m_tag_tx_buffer.tx_buffer[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_213: + m_tag_tx_buffer.tx_buffer[6] = NTAG213_VERSION_STORAGE_SIZE; + m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_215: + m_tag_tx_buffer.tx_buffer[6] = NTAG215_VERSION_STORAGE_SIZE; + m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_216: + m_tag_tx_buffer.tx_buffer[6] = NTAG216_VERSION_STORAGE_SIZE; + m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + default: + // MF0ICU1 and MF0ICU2 do not support GET_VERSION + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + m_tag_tx_buffer.tx_buffer[0] = VERSION_FIXED_HEADER; + m_tag_tx_buffer.tx_buffer[1] = VERSION_VENDOR_ID; + m_tag_tx_buffer.tx_buffer[3] = VERSION_PRODUCT_SUBTYPE_50pF; // TODO: make configurable for MF0ULx1 + m_tag_tx_buffer.tx_buffer[4] = VERSION_MAJOR_PRODUCT; + m_tag_tx_buffer.tx_buffer[5] = VERSION_MINOR_PRODUCT; + m_tag_tx_buffer.tx_buffer[7] = VERSION_PROTOCOL_TYPE; + + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); +} + +static void handle_read_command(uint8_t block_num) { + int block_max = get_block_max_by_tag_type(m_tag_type); + + if (block_num >= block_max) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + uint8_t pwd_page = get_first_cfg_page_by_tag_type(m_tag_type); + if (pwd_page != 0) pwd_page += CONF_PWD_PAGE_OFFSET; + + for (int block = 0; block < 4; block++) { + // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. + if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || ((block - pwd_page) >= 2)) { + memcpy(m_tag_tx_buffer.tx_buffer + block * 4, m_tag_information->memory[(block_num + block) % block_max], NFC_TAG_MF0_NTAG_DATA_SIZE); + } else { + memset(m_tag_tx_buffer.tx_buffer + block * 4, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + } + } + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, BYTES_PER_READ, true); +} + +static bool check_ro_lock_on_page(int block_num) { + if (block_num < 3) return true; + else if (block_num == 3) return (m_tag_information->memory[2][2] & 1) == 1; + else if (block_num <= MF0ICU1_PAGES) { + bool locked = false; + + // check block locking bits + if (block_num <= 9) locked |= (m_tag_information->memory[2][2] & 2) == 2; + else locked |= (m_tag_information->memory[2][2] & 4) == 4; + + locked |= (((*(uint16_t *)&m_tag_information->memory[2][2]) >> block_num) & 1) == 1; + + return locked; + } else { + // too large block number + return true; + } +} + +static int handle_write_command(uint8_t block_num, uint8_t *p_data) { + int block_max = get_block_max_by_tag_type(m_tag_type); + + if (block_num >= block_max) { + return NAK_INVALID_OPERATION_TBIV; + } + + if (m_tag_information->config.mode_uid_magic) { + // anything can be written in this mode + memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE); + return ACK_VALUE; + } + + switch (block_num) { + case 0: + case 1: + return NAK_INVALID_OPERATION_TBIV; + case 2: + // Page 2 contains lock bytes for pages 3-15. These are OR'ed when not in the UID + // magic mode. First two bytes are ignored. + m_tag_information->memory[2][2] |= p_data[2]; + m_tag_information->memory[2][3] |= p_data[3]; + break; + case 3: + // Page 3 contains what's called OTP bits for Ultralight tags and CC bits for NTAG + // cards, these work in the same way. + if (!check_ro_lock_on_page(block_num)) { + // lock bit for OTP page is not set + for (int i = 0; i < NFC_TAG_MF0_NTAG_DATA_SIZE; i++) { + m_tag_information->memory[3][i] |= p_data[i]; + } + } + break; + default: + if (!check_ro_lock_on_page(block_num)) { + memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE); + } + break; + } + + return ACK_VALUE; +} + +static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { + uint8_t command = p_data[0]; + uint8_t block_num = p_data[1]; + + switch (command) { + case CMD_GET_VERSION: + handle_get_version_command(); + break; + case CMD_READ: { + handle_read_command(block_num); + break; + } + case CMD_FAST_READ: { + uint8_t end_block_num = p_data[2]; + // TODO: support ultralight + if (!is_ntag() || (block_num > end_block_num) || (block_num >= get_block_max_by_tag_type(m_tag_type)) || (end_block_num >= get_block_max_by_tag_type(m_tag_type))) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + break; + } + for (int block = block_num; block <= end_block_num; block++) { + memcpy(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, m_tag_information->memory[block], NFC_TAG_MF0_NTAG_DATA_SIZE); + } + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, (end_block_num - block_num + 1) * NFC_TAG_MF0_NTAG_DATA_SIZE, true); + break; + } + case CMD_WRITE: + case CMD_COMPAT_WRITE: { + int resp = handle_write_command(block_num, &p_data[2]); + nfc_tag_14a_tx_nbit(resp, 4); + break; + } + /*case CMD_PWD_AUTH: { + // TODO: IMPLEMENT COUNTER AUTHLIM + uint8_t Password[4]; + memcpy(Password, m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 4); + if (Password[0] != p_data[1] || Password[1] != p_data[2] || Password[2] != p_data[3] || Password[3] != p_data[4]) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; + } + // Authenticate the user + //RESET AUTHLIM COUNTER, CURRENTLY NOT IMPLEMENTED + // TODO + // Send the PACK value back + if (m_tag_information->config.mode_uid_magic) { + nfc_tag_14a_tx_bytes(ntagPwdOK, 2, true); + } else { + nfc_tag_14a_tx_bytes(m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 2, true); + } + break; + }*/ + case CMD_READ_SIG: + memset(m_tag_tx_buffer.tx_buffer, 0xCA, SIGNATURE_LENGTH); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, SIGNATURE_LENGTH, true); + break; + } + return; +} + +static nfc_tag_14a_coll_res_reference_t *get_coll_res() { + // Use a separate anti -conflict information instead of using the information in the sector + m_shadow_coll_res.sak = m_tag_information->res_coll.sak; + m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa; + m_shadow_coll_res.uid = m_tag_information->res_coll.uid; + m_shadow_coll_res.size = &(m_tag_information->res_coll.size); + m_shadow_coll_res.ats = &(m_tag_information->res_coll.ats); + // Finally, a shadow data structure pointer with only reference, no physical shadow, + return &m_shadow_coll_res; +} + +static void nfc_tag_mf0_ntag_reset_handler() { + +} + +static int get_information_size_by_tag_type(tag_specific_type_t type) { + return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_mf0_ntag_configure_t) + (get_nr_pages_by_tag_type(type) * NFC_TAG_MF0_NTAG_DATA_SIZE); +} + +/** @brief MF0/NTAG callback before saving data + * @param type detailed label type + * @param buffer data buffer + * @return to be saved, the length of the data that needs to be saved, it means not saved when 0 + */ +int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { + if (m_tag_type != TAG_TYPE_UNDEFINED && m_tag_information != NULL) { + // Save the corresponding size data according to the current label type + return get_information_size_by_tag_type(type); + } else { + return 0; + } +} + +int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) { + int info_size = get_information_size_by_tag_type(type); + if (buffer->length >= info_size) { + // Convert the data buffer to MF0/NTAG structure type + m_tag_information = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; + // The specific type of MF0/NTAG tag that is simulated by the cache + m_tag_type = type; + // Register 14A communication management interface + nfc_tag_14a_handler_t handler_for_14a = { + .get_coll_res = get_coll_res, + .cb_state = nfc_tag_mf0_ntag_state_handler, + .cb_reset = nfc_tag_mf0_ntag_reset_handler, + }; + nfc_tag_14a_set_handler(&handler_for_14a); + NRF_LOG_INFO("HF ntag data load finish."); + } else { + NRF_LOG_ERROR("nfc_tag_mf0_ntag_information_t too big."); + } + return info_size; +} + +// Initialized NTAG factory data +bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { + // default ntag data + uint8_t default_p0[] = { 0x04, 0x68, 0x95, 0x71 }; + uint8_t default_p1[] = { 0xFA, 0x5C, 0x64, 0x80 }; + uint8_t default_p2[] = { 0x42, 0x48, 0x0F, 0xE0 }; + + if (!is_ntag()) { + default_p2[2] = 0; + default_p2[3] = 0; + } + + // default ntag info + nfc_tag_mf0_ntag_information_t ntag_tmp_information; + nfc_tag_mf0_ntag_information_t *p_ntag_information; + p_ntag_information = &ntag_tmp_information; + int block_max = get_nr_pages_by_tag_type(tag_type); + for (int block = 0; block < block_max; block++) { + switch (block) { + case 0: + memcpy(p_ntag_information->memory[block], default_p0, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + case 1: + memcpy(p_ntag_information->memory[block], default_p1, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + case 2: + memcpy(p_ntag_information->memory[block], default_p2, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + default: + memset(p_ntag_information->memory[block], 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + } + } + + // default ntag auto ant-collision res + p_ntag_information->res_coll.atqa[0] = 0x44; + p_ntag_information->res_coll.atqa[1] = 0x00; + p_ntag_information->res_coll.sak[0] = 0x00; + p_ntag_information->res_coll.uid[0] = 0x04; + p_ntag_information->res_coll.uid[1] = 0x68; + p_ntag_information->res_coll.uid[2] = 0x95; + p_ntag_information->res_coll.uid[3] = 0x71; + p_ntag_information->res_coll.uid[4] = 0xFA; + p_ntag_information->res_coll.uid[5] = 0x5C; + p_ntag_information->res_coll.uid[6] = 0x64; + p_ntag_information->res_coll.size = NFC_TAG_14A_UID_DOUBLE_SIZE; + p_ntag_information->res_coll.ats.length = 0; + + // default ntag config + p_ntag_information->config.mode_uid_magic = false; + + // save data to flash + tag_sense_type_t sense_type = get_sense_type_from_tag_type(tag_type); + fds_slot_record_map_t map_info; + get_fds_map_by_slot_sense_type_for_dump(slot, sense_type, &map_info); + int info_size = get_information_size_by_tag_type(tag_type); + NRF_LOG_INFO("MF0/NTAG info size: %d", info_size); + bool ret = fds_write_sync(map_info.id, map_info.key, info_size, p_ntag_information); + if (ret) { + NRF_LOG_INFO("Factory slot data success."); + } else { + NRF_LOG_ERROR("Factory slot data error."); + } + return ret; +} + +int nfc_tag_mf0_ntag_get_uid_mode() { + if (m_tag_type == TAG_TYPE_UNDEFINED || m_tag_information == NULL) return -1; + + return (int)m_tag_information->config.mode_uid_magic; +} + +bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled) { + if (m_tag_type == TAG_TYPE_UNDEFINED || m_tag_information == NULL) return false; + + m_tag_information->config.mode_uid_magic = enabled; + return true; +} \ No newline at end of file diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h new file mode 100644 index 00000000..cc971852 --- /dev/null +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -0,0 +1,50 @@ +#ifndef NFC_NTAG_H +#define NFC_NTAG_H + +#include "nfc_14a.h" + +#define NFC_TAG_MF0_NTAG_DATA_SIZE 4 + +#define NFC_TAG_NTAG_FRAME_SIZE 64 +#define NFC_TAG_NTAG_BLOCK_MAX 231 + +#define NFC_TAG_MF0_FRAME_SIZE (16 + NFC_TAG_14A_CRC_LENGTH) +#define NFC_TAG_MF0_BLOCK_MAX 41 + +#define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 +#define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 +#define NTAG216_PAGES 231 //231 pages total for ntag216, from 0 to 230 + +#define MF0ICU1_PAGES 16 //16 pages total for MF0ICU1 (the original UL), from 0 to 15 +#define MF0ICU2_PAGES 36 //16 pages total for MF0ICU2 (UL C), from 0 to 35 +#define MF0UL11_PAGES 20 //20 pages total for MF0UL11 (UL EV1), from 0 to 19 +#define MF0UL21_PAGES 41 //231 pages total for MF0UL21 (UL EV1), from 0 to 40 + + +typedef struct { + uint8_t mode_uid_magic: 1; + // reserve + uint8_t reserved1: 7; + uint8_t reserved2; + uint8_t reserved3; +} nfc_tag_mf0_ntag_configure_t; + +typedef struct __attribute__((aligned(4))) { + nfc_tag_14a_coll_res_entity_t res_coll; + nfc_tag_mf0_ntag_configure_t config; + uint8_t memory[][NFC_TAG_MF0_NTAG_DATA_SIZE]; +} +nfc_tag_mf0_ntag_information_t; + +typedef struct { + uint8_t tx_buffer[NFC_TAG_NTAG_FRAME_SIZE]; +} nfc_tag_mf0_ntag_tx_buffer_t; + +int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); +int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); +bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); + +int nfc_tag_mf0_ntag_get_uid_mode(void); +bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled); + +#endif diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c deleted file mode 100644 index a90f3c7b..00000000 --- a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c +++ /dev/null @@ -1,301 +0,0 @@ -#include - -#include "nfc_ntag.h" -#include "nfc_14a.h" -#include "fds_util.h" -#include "tag_persistence.h" - -#define NRF_LOG_MODULE_NAME tag_ntag -#include "nrf_log.h" -#include "nrf_log_ctrl.h" -#include "nrf_log_default_backends.h" -NRF_LOG_MODULE_REGISTER(); - -#define NTAG213_VERSION 0x0F -#define NTAG215_VERSION 0x11 -#define NTAG216_VERSION 0x13 - -// NTAG COMMANDS -#define CMD_GET_VERSION 0x60 -#define CMD_READ 0x30 -#define CMD_FAST_READ 0x3A -#define CMD_WRITE 0xA2 -#define CMD_COMPAT_WRITE 0xA0 -#define CMD_READ_CNT 0x39 -#define CMD_PWD_AUTH 0x1B -#define CMD_READ_SIG 0x3C - -// MEMORY LAYOUT STUFF, addresses and sizes in bytes -// UID stuff -#define UID_CL1_ADDRESS 0x00 -#define UID_CL1_SIZE 3 -#define UID_BCC1_ADDRESS 0x03 -#define UID_CL2_ADDRESS 0x04 -#define UID_CL2_SIZE 4 -#define UID_BCC2_ADDRESS 0x08 -// LockBytes stuff -#define STATIC_LOCKBYTE_0_ADDRESS 0x0A -#define STATIC_LOCKBYTE_1_ADDRESS 0x0B -// CONFIG stuff -#define NTAG213_CONFIG_AREA_START_ADDRESS 0xA4 // 4 * 0x29 -#define NTAG215_CONFIG_AREA_START_ADDRESS 0x20C // 4 * 0x83 -#define NTAG216_CONFIG_AREA_START_ADDRESS 0x38C // 4 * 0xE3 -#define CONFIG_AREA_SIZE 8 -// CONFIG offsets, relative to config start address -#define CONF_AUTH0_OFFSET 0x03 -#define CONF_ACCESS_OFFSET 0x04 -#define CONF_PASSWORD_OFFSET 0x08 -#define CONF_PACK_OFFSET 0x0C - -// WRITE STUFF -#define BYTES_PER_WRITE 4 -#define PAGE_WRITE_MIN 0x02 - -// CONFIG masks to check individual needed bits -#define CONF_ACCESS_PROT 0x80 - -#define VERSION_INFO_LENGTH 8 //8 bytes info length + crc - -#define BYTES_PER_READ 16 - -// SIGNATURE Length -#define SIGNATURE_LENGTH 32 - -// NTAG215_Version[7] mean: -// 0x0F ntag213 -// 0x11 ntag215 -// 0x13 ntag216 -const uint8_t ntagVersion[8] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x11, 0x03}; -/* pwd auth for amiibo */ -uint8_t ntagPwdOK[2] = {0x80, 0x80}; - -// Data structure pointer to the label information -static nfc_tag_ntag_information_t *m_tag_information = NULL; -// Define and use shadow anti -collision resources -static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res; -//Define and use NTAG special communication buffer -static nfc_tag_ntag_tx_buffer_t m_tag_tx_buffer; -// Save the specific type of NTAG currently being simulated -static tag_specific_type_t m_tag_type; - -static int get_block_max_by_tag_type(tag_specific_type_t tag_type) { - int block_max; - switch (tag_type) { - case TAG_TYPE_NTAG_213: - block_max = NTAG213_PAGES; - break; - default: - case TAG_TYPE_NTAG_215: - block_max = NTAG215_PAGES; - break; - case TAG_TYPE_NTAG_216: - block_max = NTAG216_PAGES; - break; - } - return block_max; -} - -static int get_block_cfg_by_tag_type(tag_specific_type_t tag_type) { - int block_max; - switch (tag_type) { - case TAG_TYPE_NTAG_213: - block_max = NTAG213_CONFIG_AREA_START_ADDRESS; - break; - default: - case TAG_TYPE_NTAG_215: - block_max = NTAG215_CONFIG_AREA_START_ADDRESS; - break; - case TAG_TYPE_NTAG_216: - block_max = NTAG216_CONFIG_AREA_START_ADDRESS; - break; - } - return block_max; -} - -void nfc_tag_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { - uint8_t command = p_data[0]; - uint8_t block_num = p_data[1]; - - switch (command) { - case CMD_GET_VERSION: - memcpy(m_tag_tx_buffer.tx_buffer, ntagVersion, 8); - switch (m_tag_type) { - case TAG_TYPE_NTAG_213: - m_tag_tx_buffer.tx_buffer[6] = NTAG213_VERSION; - break; - default: - case TAG_TYPE_NTAG_215: - m_tag_tx_buffer.tx_buffer[6] = NTAG215_VERSION; - break; - case TAG_TYPE_NTAG_216: - m_tag_tx_buffer.tx_buffer[6] = NTAG216_VERSION; - break; - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); - break; - case CMD_READ: - if (block_num < get_block_max_by_tag_type(m_tag_type)) { - for (int block = 0; block < 4; block++) { - memcpy(m_tag_tx_buffer.tx_buffer + block * 4, m_tag_information->memory[block_num + block], NFC_TAG_NTAG_DATA_SIZE); - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, BYTES_PER_READ, true); - } else { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - } - break; - case CMD_FAST_READ: { - uint8_t end_block_num = p_data[2]; - if ((block_num > end_block_num) || (block_num >= get_block_max_by_tag_type(m_tag_type)) || (end_block_num >= get_block_max_by_tag_type(m_tag_type))) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); - break; - } - for (int block = block_num; block <= end_block_num; block++) { - memcpy(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, m_tag_information->memory[block], NFC_TAG_NTAG_DATA_SIZE); - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, (end_block_num - block_num + 1) * NFC_TAG_NTAG_DATA_SIZE, true); - break; - } - case CMD_WRITE: - // TODO - nfc_tag_14a_tx_nbit(ACK_VALUE, 4); - break; - case CMD_COMPAT_WRITE: - // TODO - break; - case CMD_PWD_AUTH: { - /* TODO: IMPLEMENT COUNTER AUTHLIM */ - uint8_t Password[4]; - memcpy(Password, m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 4); - if (Password[0] != p_data[1] || Password[1] != p_data[2] || Password[2] != p_data[3] || Password[3] != p_data[4]) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - break; - } - /* Authenticate the user */ - //RESET AUTHLIM COUNTER, CURRENTLY NOT IMPLEMENTED - // TODO - /* Send the PACK value back */ - if (m_tag_information->config.mode_uid_magic) { - nfc_tag_14a_tx_bytes(ntagPwdOK, 2, true); - } else { - nfc_tag_14a_tx_bytes(m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 2, true); - } - break; - } - case CMD_READ_SIG: - memset(m_tag_tx_buffer.tx_buffer, 0xCA, SIGNATURE_LENGTH); - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, SIGNATURE_LENGTH, true); - break; - } - return; -} - -nfc_tag_14a_coll_res_reference_t *get_ntag_coll_res() { - // Use a separate anti -conflict information instead of using the information in the sector - m_shadow_coll_res.sak = m_tag_information->res_coll.sak; - m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa; - m_shadow_coll_res.uid = m_tag_information->res_coll.uid; - m_shadow_coll_res.size = &(m_tag_information->res_coll.size); - m_shadow_coll_res.ats = &(m_tag_information->res_coll.ats); - // Finally, a shadow data structure pointer with only reference, no physical shadow, - return &m_shadow_coll_res; -} - -void nfc_tag_ntag_reset_handler() { - // TODO -} - -static int get_information_size_by_tag_type(tag_specific_type_t type) { - return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_ntag_configure_t) + (get_block_max_by_tag_type(type) * NFC_TAG_NTAG_DATA_SIZE); -} - -/** @brief ntag's callback before saving data - * @param type detailed label type - * @param buffer data buffer - * @return to be saved, the length of the data that needs to be saved, it means not saved when 0 - */ -int nfc_tag_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - if (m_tag_type != TAG_TYPE_UNDEFINED) { - // Save the corresponding size data according to the current label type - return get_information_size_by_tag_type(type); - } else { - return 0; - } -} - -int nfc_tag_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - int info_size = get_information_size_by_tag_type(type); - if (buffer->length >= info_size) { - // Convert the data buffer to NTAG structure type - m_tag_information = (nfc_tag_ntag_information_t *)buffer->buffer; - // The specific type of NTAG that is simulated by the cache - m_tag_type = type; - // Register 14A communication management interface - nfc_tag_14a_handler_t handler_for_14a = { - .get_coll_res = get_ntag_coll_res, - .cb_state = nfc_tag_ntag_state_handler, - .cb_reset = nfc_tag_ntag_reset_handler, - }; - nfc_tag_14a_set_handler(&handler_for_14a); - NRF_LOG_INFO("HF ntag data load finish."); - } else { - NRF_LOG_ERROR("nfc_tag_ntag_information_t too big."); - } - return info_size; -} - -// Initialized NTAG factory data -bool nfc_tag_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { - // default ntag data - uint8_t default_p0[] = { 0x04, 0x68, 0x95, 0x71 }; - uint8_t default_p1[] = { 0xFA, 0x5C, 0x64, 0x80 }; - uint8_t default_p2[] = { 0x42, 0x48, 0x0F, 0xE0 }; - - // default ntag info - nfc_tag_ntag_information_t ntag_tmp_information; - nfc_tag_ntag_information_t *p_ntag_information; - p_ntag_information = &ntag_tmp_information; - int block_max = get_block_max_by_tag_type(tag_type); - for (int block = 0; block < block_max; block++) { - if (block == 0) { - memcpy(p_ntag_information->memory[block], default_p0, NFC_TAG_NTAG_DATA_SIZE); - } - if (block == 1) { - memcpy(p_ntag_information->memory[block], default_p1, NFC_TAG_NTAG_DATA_SIZE); - } - if (block == 2) { - memcpy(p_ntag_information->memory[block], default_p2, NFC_TAG_NTAG_DATA_SIZE); - } - } - - // default ntag auto ant-collision res - p_ntag_information->res_coll.atqa[0] = 0x44; - p_ntag_information->res_coll.atqa[1] = 0x00; - p_ntag_information->res_coll.sak[0] = 0x00; - p_ntag_information->res_coll.uid[0] = 0x04; - p_ntag_information->res_coll.uid[1] = 0x68; - p_ntag_information->res_coll.uid[2] = 0x95; - p_ntag_information->res_coll.uid[3] = 0x71; - p_ntag_information->res_coll.uid[4] = 0xFA; - p_ntag_information->res_coll.uid[5] = 0x5C; - p_ntag_information->res_coll.uid[6] = 0x64; - p_ntag_information->res_coll.size = NFC_TAG_14A_UID_DOUBLE_SIZE; - p_ntag_information->res_coll.ats.length = 0; - - // default ntag config - p_ntag_information->config.mode_uid_magic = true; - p_ntag_information->config.detection_enable = false; - - // save data to flash - tag_sense_type_t sense_type = get_sense_type_from_tag_type(tag_type); - fds_slot_record_map_t map_info; - get_fds_map_by_slot_sense_type_for_dump(slot, sense_type, &map_info); - int info_size = get_information_size_by_tag_type(tag_type); - NRF_LOG_INFO("NTAG info size: %d", info_size); - bool ret = fds_write_sync(map_info.id, map_info.key, info_size, p_ntag_information); - if (ret) { - NRF_LOG_INFO("Factory slot data success."); - } else { - NRF_LOG_ERROR("Factory slot data error."); - } - return ret; -} diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.h deleted file mode 100644 index fcbd4306..00000000 --- a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef NFC_NTAG_H -#define NFC_NTAG_H - -#include "nfc_14a.h" - -#define NFC_TAG_NTAG_DATA_SIZE 4 -#define NFC_TAG_NTAG_FRAME_SIZE 64 -#define NFC_TAG_NTAG_BLOCK_MAX 231 - -#define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 -#define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 -#define NTAG216_PAGES 231 //231 pages total for ntag216, from 0 to 230 - - -typedef struct { - uint8_t mode_uid_magic: 1; - uint8_t detection_enable: 1; - // reserve - uint8_t reserved1: 5; - uint8_t reserved2; - uint8_t reserved3; -} nfc_tag_ntag_configure_t; - -typedef struct __attribute__((aligned(4))) { - nfc_tag_14a_coll_res_entity_t res_coll; - nfc_tag_ntag_configure_t config; - uint8_t memory[NFC_TAG_NTAG_BLOCK_MAX][NFC_TAG_NTAG_DATA_SIZE]; -} -nfc_tag_ntag_information_t; - -typedef struct { - uint8_t tx_buffer[NFC_TAG_NTAG_FRAME_SIZE]; -} nfc_tag_ntag_tx_buffer_t; - -int nfc_tag_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); -int nfc_tag_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); -bool nfc_tag_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); - -#endif diff --git a/firmware/application/src/rfid/nfctag/tag_base_type.h b/firmware/application/src/rfid/nfctag/tag_base_type.h index 9f6b2027..c4d3dac7 100644 --- a/firmware/application/src/rfid/nfctag/tag_base_type.h +++ b/firmware/application/src/rfid/nfctag/tag_base_type.h @@ -75,6 +75,10 @@ typedef enum { TAG_TYPE_NTAG_213 = 1100, TAG_TYPE_NTAG_215, TAG_TYPE_NTAG_216, + TAG_TYPE_MF0ICU1, + TAG_TYPE_MF0ICU2, + TAG_TYPE_MF0UL11, + TAG_TYPE_MF0UL21, // MIFARE Plus series 1200 // DESFire series 1300 @@ -106,7 +110,11 @@ typedef enum { TAG_TYPE_MIFARE_4096,\ TAG_TYPE_NTAG_213,\ TAG_TYPE_NTAG_215,\ - TAG_TYPE_NTAG_216 + TAG_TYPE_NTAG_216,\ + TAG_TYPE_MF0ICU1,\ + TAG_TYPE_MF0ICU2,\ + TAG_TYPE_MF0UL11,\ + TAG_TYPE_MF0UL21 typedef struct { tag_specific_type_t tag_hf; diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index 65a05e45..85d77be9 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -2,7 +2,7 @@ #include "nfc_14a.h" #include "lf_tag_em.h" #include "nfc_mf1.h" -#include "nfc_ntag.h" +#include "nfc_mf0_ntag.h" #include "fds_ids.h" #include "fds_util.h" #include "tag_emulation.h" @@ -71,7 +71,7 @@ static tag_slot_config_t slotConfig ALIGN_U32 = { // See tag_emulation_factory_init for actual tag content .slots = { { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 - { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 + { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MF0ICU1, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 { .enabled_hf = false, .enabled_lf = true, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_EM410X, }, // 3 { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 4 { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 5 @@ -100,9 +100,11 @@ static tag_base_handler_map_t tag_base_map[] = { { TAG_SENSE_HF, TAG_TYPE_MIFARE_2048, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf }, { TAG_SENSE_HF, TAG_TYPE_MIFARE_4096, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf }, // NTAG tag simulation - { TAG_SENSE_HF, TAG_TYPE_NTAG_213, nfc_tag_ntag_data_loadcb, nfc_tag_ntag_data_savecb, nfc_tag_ntag_data_factory, &m_tag_data_hf }, - { TAG_SENSE_HF, TAG_TYPE_NTAG_215, nfc_tag_ntag_data_loadcb, nfc_tag_ntag_data_savecb, nfc_tag_ntag_data_factory, &m_tag_data_hf }, - { TAG_SENSE_HF, TAG_TYPE_NTAG_216, nfc_tag_ntag_data_loadcb, nfc_tag_ntag_data_savecb, nfc_tag_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_213, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_215, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_216, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + // MF0 tag simulation + { TAG_SENSE_HF, TAG_TYPE_MF0ICU1, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, }; @@ -701,7 +703,7 @@ void tag_emulation_factory_init(void) { } } - if (slotConfig.slots[1].enabled_hf && slotConfig.slots[1].tag_hf == TAG_TYPE_MIFARE_1024) { + if (slotConfig.slots[1].enabled_hf && slotConfig.slots[1].tag_hf == TAG_TYPE_MF0ICU1) { // Initialize a high -frequency M1 card in the card slot 2, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(1, TAG_SENSE_HF, &map_info); if (!fds_is_exists(map_info.id, map_info.key)) { diff --git a/firmware/application/src/rfid_main.h b/firmware/application/src/rfid_main.h index fd6279b3..dadf6a34 100644 --- a/firmware/application/src/rfid_main.h +++ b/firmware/application/src/rfid_main.h @@ -8,7 +8,7 @@ #include "hw_connect.h" #include "nfc_14a.h" #include "nfc_mf1.h" -#include "nfc_ntag.h" +#include "nfc_mf0_ntag.h" #include "lf_tag_em.h" #include "tag_emulation.h" diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index bc369942..0b1bf012 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1744,9 +1744,12 @@ def on_exec(self, args: argparse.Namespace): class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() - parser.description = 'Settings of Mifare Classic emulator' + parser.description = 'Settings of Mifare Ultralight / NTAG emulator' self.add_slot_args(parser) self.add_hf14a_anticoll_args(parser) + uid_magic_group = parser.add_mutually_exclusive_group() + uid_magic_group.add_argument('--enable-uid-magic', action='store_true', help="Enable UID magic mode") + uid_magic_group.add_argument('--disable-uid-magic', action='store_true', help="Disable UID magic mode") return parser def on_exec(self, args: argparse.Namespace): @@ -1763,6 +1766,10 @@ def on_exec(self, args: argparse.Namespace): fwslot = SlotNumber.to_fw(self.slot_num) hf_tag_type = TagSpecificType(slotinfo[fwslot]['hf']) if hf_tag_type not in [ + TagSpecificType.MF0ICU1, + TagSpecificType.MF0ICU2, + TagSpecificType.MF0UL11, + TagSpecificType.MF0UL21, TagSpecificType.NTAG_213, TagSpecificType.NTAG_215, TagSpecificType.NTAG_216, @@ -1770,6 +1777,18 @@ def on_exec(self, args: argparse.Namespace): print(f"{CR}Slot {self.slot_num} not configured as MIFARE Ultralight / NTAG{C0}") return change_requested, change_done, uid, atqa, sak, ats = self.update_hf14a_anticoll(args, uid, atqa, sak, ats) + + if args.enable_uid_magic: + change_requested = True + self.cmd.mf0_ntag_set_uid_magic_mode(True) + magic_mode = True + elif args.disable_uid_magic: + change_requested = True + self.cmd.mf0_ntag_set_uid_magic_mode(False) + magic_mode = False + else: + magic_mode = self.cmd.mf0_ntag_get_uid_magic_mode() + if change_done: print(' - MFU/NTAG Emulator settings updated') if not change_requested: @@ -1780,6 +1799,10 @@ def on_exec(self, args: argparse.Namespace): print(f'- {"SAK:":40}{CY}{sak.hex().upper()}{C0}') if len(ats) > 0: print(f'- {"ATS:":40}{CY}{ats.hex().upper()}{C0}') + if magic_mode: + print(f'- {"UID Magic:":40}{CY}enabled{C0}') + else: + print(f'- {"UID Magic:":40}{CY}disabled{C0}') @lf_em_410x.command('read') diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 2ef363dd..5ed16a66 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -919,6 +919,17 @@ def hf14a_get_anti_coll_data(self): resp.parsed = {'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats} return resp + @expect_response(Status.SUCCESS) + def mf0_ntag_get_uid_magic_mode(self): + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_UID_MAGIC_MODE) + if resp.status == Status.SUCCESS: + resp.parsed, = struct.unpack('!?', resp.data) + return resp + + @expect_response(Status.SUCCESS) + def mf0_ntag_set_uid_magic_mode(self, enabled: bool): + return self.device.send_cmd_sync(Command.MF0_NTAG_SET_UID_MAGIC_MODE, struct.pack('?', enabled)) + @expect_response(Status.SUCCESS) def get_ble_pairing_enable(self): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index 2c13b8fe..2bd4d074 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -95,6 +95,8 @@ class Command(enum.IntEnum): MF1_GET_WRITE_MODE = 4016 MF1_SET_WRITE_MODE = 4017 HF14A_GET_ANTI_COLL_DATA = 4018 + MF0_NTAG_GET_UID_MAGIC_MODE = 4019 + MF0_NTAG_SET_UID_MAGIC_MODE = 4020 EM410X_SET_EMU_ID = 5000 EM410X_GET_EMU_ID = 5001 @@ -260,6 +262,10 @@ class TagSpecificType(enum.IntEnum): NTAG_213 = 1100 NTAG_215 = 1101 NTAG_216 = 1102 + MF0ICU1 = 1103 + MF0ICU2 = 1104 + MF0UL11 = 1105 + MF0UL21 = 1106 # MIFARE Plus series 1200 # DESFire series 1300 @@ -303,6 +309,14 @@ def __str__(self): return "NTAG 215" elif self == TagSpecificType.NTAG_216: return "NTAG 216" + elif self == TagSpecificType.MF0ICU1: + return "Mifare Ultralight" + elif self == TagSpecificType.MF0ICU2: + return "Mifare Ultralight C" + elif self == TagSpecificType.MF0UL11: + return "Mifare Ultralight EV1 (640 bit)" + elif self == TagSpecificType.MF0UL21: + return "Mifare Ultralight EV1 (1312 bit)" elif self < TagSpecificType.OLD_TAG_TYPES_END: return "Old tag type, must be migrated! Upgrade fw!" return "Invalid" @@ -438,4 +452,4 @@ def __str__(self): class MfcValueBlockOperator(enum.IntEnum): DECREMENT = 0xC0 INCREMENT = 0xC1 - RESTORE = 0xC2 \ No newline at end of file + RESTORE = 0xC2 From 34eabd7db3e528a82f7a20658e83007534f493bf Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 8 Jun 2024 03:35:38 +0300 Subject: [PATCH 02/60] Fix stack overflow on slot init. --- .../application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 665fa27f..3dfce9f0 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -412,6 +412,13 @@ int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *bu return info_size; } +typedef struct __attribute__((aligned(4))) { + nfc_tag_14a_coll_res_entity_t res_coll; + nfc_tag_mf0_ntag_configure_t config; + uint8_t memory[NFC_TAG_NTAG_BLOCK_MAX][NFC_TAG_MF0_NTAG_DATA_SIZE]; +} +nfc_tag_mf0_ntag_information_max_t; + // Initialized NTAG factory data bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { // default ntag data @@ -425,9 +432,9 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { } // default ntag info - nfc_tag_mf0_ntag_information_t ntag_tmp_information; + nfc_tag_mf0_ntag_information_max_t ntag_tmp_information; nfc_tag_mf0_ntag_information_t *p_ntag_information; - p_ntag_information = &ntag_tmp_information; + p_ntag_information = (nfc_tag_mf0_ntag_information_t *)&ntag_tmp_information; int block_max = get_nr_pages_by_tag_type(tag_type); for (int block = 0; block < block_max; block++) { switch (block) { From f7460cef42de15ca35121a66541a7447342c5da1 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 8 Jun 2024 03:35:58 +0300 Subject: [PATCH 03/60] Add the remaining tags to the tag map. --- firmware/application/src/rfid/nfctag/tag_emulation.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index 85d77be9..a273d840 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -105,6 +105,9 @@ static tag_base_handler_map_t tag_base_map[] = { { TAG_SENSE_HF, TAG_TYPE_NTAG_216, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, // MF0 tag simulation { TAG_SENSE_HF, TAG_TYPE_MF0ICU1, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_MF0ICU2, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_MF0UL11, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_MF0UL21, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, }; From 472857359540c0396b87818187f2bd94921f4fe0 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 8 Jun 2024 13:38:55 +0300 Subject: [PATCH 04/60] Fix `CMD_INCR_CNT` value. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 3dfce9f0..4cb653eb 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -34,7 +34,7 @@ NRF_LOG_MODULE_REGISTER(); #define CMD_WRITE 0xA2 #define CMD_COMPAT_WRITE 0xA0 #define CMD_READ_CNT 0x39 -#define CMD_INCR_CNT 0x3A +#define CMD_INCR_CNT 0xA5 #define CMD_PWD_AUTH 0x1B #define CMD_READ_SIG 0x3C #define CMD_CHECK_TEARING_EVENT 0x3E From 89dc073a8b1901cd2459e40d26c96675f9acc265 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 8 Jun 2024 14:24:55 +0300 Subject: [PATCH 05/60] Support reading and incrementing counters. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 131 ++++++++++++++++-- .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 7 + 2 files changed, 130 insertions(+), 8 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 4cb653eb..6a85a4b4 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -97,8 +97,8 @@ static nfc_tag_mf0_ntag_tx_buffer_t m_tag_tx_buffer; static tag_specific_type_t m_tag_type; static bool m_tag_authenticated = false; -static uint8_t get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { - uint8_t nr_pages; +static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages; switch (tag_type) { case TAG_TYPE_MF0ICU1: @@ -130,8 +130,41 @@ static uint8_t get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { return nr_pages; } -static uint8_t get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { - uint8_t page; +static int get_nr_mem_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages; + + switch (tag_type) { + case TAG_TYPE_MF0ICU1: + nr_pages = MF0ICU1_PAGES; + break; + case TAG_TYPE_MF0ICU2: + nr_pages = MF0ICU2_PAGES; + break; + case TAG_TYPE_MF0UL11: + nr_pages = MF0UL11_PAGES_WITH_CTRS; + break; + case TAG_TYPE_MF0UL21: + nr_pages = MF0UL21_PAGES_WITH_CTRS; + break; + case TAG_TYPE_NTAG_213: + nr_pages = NTAG213_PAGES_WITH_CTR; + break; + case TAG_TYPE_NTAG_215: + nr_pages = NTAG215_PAGES_WITH_CTR; + break; + case TAG_TYPE_NTAG_216: + nr_pages = NTAG216_PAGES_WITH_CTR; + break; + default: + nr_pages = 0; + break; + } + + return nr_pages; +} + +static int get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { + int page; switch (tag_type) { case TAG_TYPE_MF0UL11: @@ -157,9 +190,9 @@ static uint8_t get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { return page; } -static uint8_t get_block_max_by_tag_type(tag_specific_type_t tag_type) { - uint8_t max_pages = get_nr_pages_by_tag_type(tag_type); - uint8_t first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); +static int get_block_max_by_tag_type(tag_specific_type_t tag_type) { + int max_pages = get_nr_pages_by_tag_type(tag_type); + int first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); if (first_cfg_page == 0 || m_tag_authenticated || m_tag_information->config.mode_uid_magic) return max_pages; @@ -300,6 +333,82 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { return ACK_VALUE; } +static void handle_read_cnt_command(uint8_t block_num) { + uint8_t ctr_page_off; + uint8_t ctr_page_end; + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + ctr_page_off = MF0UL11_PAGES; + ctr_page_end = MF0UL11_PAGES_WITH_CTRS; + break; + case TAG_TYPE_MF0UL21: + ctr_page_off = MF0UL21_PAGES; + ctr_page_end = MF0UL21_PAGES_WITH_CTRS; + break; + case TAG_TYPE_NTAG_213: + ctr_page_off = NTAG213_PAGES; + ctr_page_end = NTAG213_PAGES_WITH_CTR; + break; + case TAG_TYPE_NTAG_215: + ctr_page_off = NTAG215_PAGES; + ctr_page_end = NTAG215_PAGES_WITH_CTR; + break; + case TAG_TYPE_NTAG_216: + ctr_page_off = NTAG216_PAGES; + ctr_page_end = NTAG216_PAGES_WITH_CTR; + break; + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + // check that counter index is in bounds + if (block_num >= (ctr_page_end - ctr_page_off)) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + memcpy(m_tag_tx_buffer.tx_buffer, m_tag_information->memory[ctr_page_off + block_num], 3); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 3, true); +} + +static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { + uint8_t ctr_page_off; + uint8_t ctr_page_end; + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + ctr_page_off = MF0UL11_PAGES; + ctr_page_end = MF0UL11_PAGES_WITH_CTRS; + break; + case TAG_TYPE_MF0UL21: + ctr_page_off = MF0UL21_PAGES; + ctr_page_end = MF0UL21_PAGES_WITH_CTRS; + break; + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + // check that counter index is in bounds + if (block_num >= (ctr_page_end - ctr_page_off)) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + uint8_t *cnt_data = m_tag_information->memory[ctr_page_off + block_num]; + uint32_t incr_value = ((uint32_t)p_data[0]) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2] << 16); + uint32_t cnt = ((uint32_t)cnt_data[0]) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2] << 16); + + if ((0xFFFFFF - cnt) < incr_value) cnt = 0xFFFFFF; + else cnt += incr_value; + + cnt_data[0] = (uint8_t)(cnt & 0xff); + cnt_data[1] = (uint8_t)(cnt >> 8); + cnt_data[2] = (uint8_t)(cnt >> 16); + + nfc_tag_14a_tx_nbit(ACK_VALUE, 4); +} + static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { uint8_t command = p_data[0]; uint8_t block_num = p_data[1]; @@ -354,6 +463,12 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) memset(m_tag_tx_buffer.tx_buffer, 0xCA, SIGNATURE_LENGTH); nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, SIGNATURE_LENGTH, true); break; + case CMD_READ_CNT: + handle_read_cnt_command(block_num); + break; + case CMD_INCR_CNT: + handle_incr_cnt_command(block_num, &p_data[2]); + break; } return; } @@ -374,7 +489,7 @@ static void nfc_tag_mf0_ntag_reset_handler() { } static int get_information_size_by_tag_type(tag_specific_type_t type) { - return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_mf0_ntag_configure_t) + (get_nr_pages_by_tag_type(type) * NFC_TAG_MF0_NTAG_DATA_SIZE); + return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_mf0_ntag_configure_t) + (get_nr_mem_pages_by_tag_type(type) * NFC_TAG_MF0_NTAG_DATA_SIZE); } /** @brief MF0/NTAG callback before saving data diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index cc971852..6b49ead4 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -11,14 +11,21 @@ #define NFC_TAG_MF0_FRAME_SIZE (16 + NFC_TAG_14A_CRC_LENGTH) #define NFC_TAG_MF0_BLOCK_MAX 41 +#define MF0ULx1_NUM_CTRS 3 // number of Ultralight EV1 one-way counters + #define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 +#define NTAG213_PAGES_WITH_CTR (NTAG213_PAGES + 1) // 1 more page for the counter #define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 +#define NTAG215_PAGES_WITH_CTR (NTAG215_PAGES + 1) // 1 more page for the counter #define NTAG216_PAGES 231 //231 pages total for ntag216, from 0 to 230 +#define NTAG216_PAGES_WITH_CTR (NTAG216_PAGES + 1) // 1 more page for the counter #define MF0ICU1_PAGES 16 //16 pages total for MF0ICU1 (the original UL), from 0 to 15 #define MF0ICU2_PAGES 36 //16 pages total for MF0ICU2 (UL C), from 0 to 35 #define MF0UL11_PAGES 20 //20 pages total for MF0UL11 (UL EV1), from 0 to 19 +#define MF0UL11_PAGES_WITH_CTRS (MF0UL11_PAGES + MF0ULx1_NUM_CTRS) // 3 more pages for 3 one way counters #define MF0UL21_PAGES 41 //231 pages total for MF0UL21 (UL EV1), from 0 to 40 +#define MF0UL21_PAGES_WITH_CTRS (MF0UL21_PAGES + MF0ULx1_NUM_CTRS) // 3 more pages for 3 one way counters typedef struct { From eee3666790103ac8ded81a2280dd6f8d0e16fd95 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 8 Jun 2024 14:28:07 +0300 Subject: [PATCH 06/60] Fix a bug that made Ultralight EV1 and NTAG unreadable. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 6a85a4b4..a6b1625a 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -260,10 +260,11 @@ static void handle_read_command(uint8_t block_num) { uint8_t pwd_page = get_first_cfg_page_by_tag_type(m_tag_type); if (pwd_page != 0) pwd_page += CONF_PWD_PAGE_OFFSET; - for (int block = 0; block < 4; block++) { - // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. - if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || ((block - pwd_page) >= 2)) { - memcpy(m_tag_tx_buffer.tx_buffer + block * 4, m_tag_information->memory[(block_num + block) % block_max], NFC_TAG_MF0_NTAG_DATA_SIZE); + for (uint8_t block = 0; block < 4; block++) { + // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. + uint8_t block_to_read = (block_num + block) % block_max; + if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || (block_to_read < pwd_page) || (block_to_read > (pwd_page + 1))) { + memcpy(m_tag_tx_buffer.tx_buffer + block * 4, m_tag_information->memory[block_to_read], NFC_TAG_MF0_NTAG_DATA_SIZE); } else { memset(m_tag_tx_buffer.tx_buffer + block * 4, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); } From af5670fa44afd627cc9fa635b649851c6fe8dcc9 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 9 Jun 2024 23:17:26 +0300 Subject: [PATCH 07/60] Respect PROT bit in the ACCESS byte. --- .../application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index a6b1625a..fce3117d 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -190,14 +190,16 @@ static int get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { return page; } -static int get_block_max_by_tag_type(tag_specific_type_t tag_type) { +static int get_block_max_by_tag_type(tag_specific_type_t tag_type, bool read) { int max_pages = get_nr_pages_by_tag_type(tag_type); int first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); if (first_cfg_page == 0 || m_tag_authenticated || m_tag_information->config.mode_uid_magic) return max_pages; uint8_t auth0 = m_tag_information->memory[first_cfg_page][CONF_AUTH0_BYTE]; - return (max_pages > auth0) ? auth0 : max_pages; + uint8_t access = m_tag_information->memory[first_cfg_page + 1][0]; + if (!read || ((access & CONF_ACCESS_PROT) != 0)) return (max_pages > auth0) ? auth0 : max_pages; + else return max_pages; } static bool is_ntag() { @@ -250,7 +252,7 @@ static void handle_get_version_command() { } static void handle_read_command(uint8_t block_num) { - int block_max = get_block_max_by_tag_type(m_tag_type); + int block_max = get_block_max_by_tag_type(m_tag_type, true); if (block_num >= block_max) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); @@ -292,7 +294,7 @@ static bool check_ro_lock_on_page(int block_num) { } static int handle_write_command(uint8_t block_num, uint8_t *p_data) { - int block_max = get_block_max_by_tag_type(m_tag_type); + int block_max = get_block_max_by_tag_type(m_tag_type, false); if (block_num >= block_max) { return NAK_INVALID_OPERATION_TBIV; @@ -423,9 +425,10 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) break; } case CMD_FAST_READ: { + uint8_t block_max = get_block_max_by_tag_type(m_tag_type, true); uint8_t end_block_num = p_data[2]; // TODO: support ultralight - if (!is_ntag() || (block_num > end_block_num) || (block_num >= get_block_max_by_tag_type(m_tag_type)) || (end_block_num >= get_block_max_by_tag_type(m_tag_type))) { + if (!is_ntag() || (block_num > end_block_num) || (block_num >= block_max) || (end_block_num >= block_max)) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); break; } From 56c4647afb2907ec0e6ecaa4359eb191c7ff2418 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 11 Jun 2024 16:23:40 +0300 Subject: [PATCH 08/60] Make MF0/NTAG tx buffer size appropriate for large NTAG fast reads. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index 6b49ead4..2005cecf 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -44,7 +44,9 @@ typedef struct __attribute__((aligned(4))) { nfc_tag_mf0_ntag_information_t; typedef struct { - uint8_t tx_buffer[NFC_TAG_NTAG_FRAME_SIZE]; + // TX buffer must fit the largest possible frame size. + // TODO: This size should be decreased as the maximum allowed frame size is 257 (see 6.14.13.36 in datasheet). + uint8_t tx_buffer[NFC_TAG_NTAG_BLOCK_MAX * NFC_TAG_MF0_NTAG_DATA_SIZE]; } nfc_tag_mf0_ntag_tx_buffer_t; int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); From 1e18bd6c600b483afe1bb2aa086f0d99e50c765c Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 11 Jun 2024 16:34:59 +0300 Subject: [PATCH 09/60] Properly implement fast reads. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index fce3117d..c97f1ad1 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -274,6 +274,44 @@ static void handle_read_command(uint8_t block_num) { nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, BYTES_PER_READ, true); } +static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { + switch (m_tag_type) + { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + // command is supported + break; + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + int block_max = get_block_max_by_tag_type(m_tag_type, true); + + if (block_num >= end_block_num || end_block_num >= block_max) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + uint8_t pwd_page = get_first_cfg_page_by_tag_type(m_tag_type); + if (pwd_page != 0) pwd_page += CONF_PWD_PAGE_OFFSET; + + for (uint8_t block = block_num; block < end_block_num; block++) { + // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. + if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || (block < pwd_page) || (block > (pwd_page + 1))) { + memcpy(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, m_tag_information->memory[block], NFC_TAG_MF0_NTAG_DATA_SIZE); + } else { + memset(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + } + } + + size_t send_size = (end_block_num - block_num) * NFC_TAG_MF0_NTAG_DATA_SIZE; + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, send_size, true); +} + static bool check_ro_lock_on_page(int block_num) { if (block_num < 3) return true; else if (block_num == 3) return (m_tag_information->memory[2][2] & 1) == 1; @@ -425,17 +463,9 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) break; } case CMD_FAST_READ: { - uint8_t block_max = get_block_max_by_tag_type(m_tag_type, true); uint8_t end_block_num = p_data[2]; // TODO: support ultralight - if (!is_ntag() || (block_num > end_block_num) || (block_num >= block_max) || (end_block_num >= block_max)) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); - break; - } - for (int block = block_num; block <= end_block_num; block++) { - memcpy(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, m_tag_information->memory[block], NFC_TAG_MF0_NTAG_DATA_SIZE); - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, (end_block_num - block_num + 1) * NFC_TAG_MF0_NTAG_DATA_SIZE, true); + handle_fast_read_command(block_num, end_block_num); break; } case CMD_WRITE: From 95d69b142b890eabd65487987ef9daeb36a7381d Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 11 Jun 2024 17:27:00 +0300 Subject: [PATCH 10/60] Proper password authentication. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index c97f1ad1..ad069784 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -61,7 +61,7 @@ NRF_LOG_MODULE_REGISTER(); // CONFIG offsets, relative to config start address #define CONF_AUTH0_BYTE 0x03 -#define CONF_ACCESS_OFFSET 0x04 +#define CONF_ACCESS_AUTHLIM_MASK 0x07 #define CONF_PWD_PAGE_OFFSET 2 #define CONF_PACK_PAGE_OFFSET 3 @@ -79,6 +79,11 @@ NRF_LOG_MODULE_REGISTER(); // SIGNATURE Length #define SIGNATURE_LENGTH 32 +// Since all counters are 24-bit and each currently supported tag that supports counters +// has password authentication we store the auth attempts counter in the last bit of the +// first counter. +#define AUTHLIM_OFF_IN_CTR 3 + // NTAG215_Version[7] mean: // 0x0F ntag213 // 0x11 ntag215 @@ -374,7 +379,7 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { return ACK_VALUE; } -static void handle_read_cnt_command(uint8_t block_num) { +static uint8_t *get_counter_data_by_index(uint8_t index) { uint8_t ctr_page_off; uint8_t ctr_page_end; switch (m_tag_type) { @@ -400,16 +405,23 @@ static void handle_read_cnt_command(uint8_t block_num) { break; default: nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - return; + return NULL; } // check that counter index is in bounds - if (block_num >= (ctr_page_end - ctr_page_off)) { + if (index >= (ctr_page_end - ctr_page_off)) return NULL; + + return m_tag_information->memory[ctr_page_off + index]; +} + +static void handle_read_cnt_command(uint8_t index) { + uint8_t *cnt_data = get_counter_data_by_index(index); + if (cnt_data == NULL) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } - memcpy(m_tag_tx_buffer.tx_buffer, m_tag_information->memory[ctr_page_off + block_num], 3); + memcpy(m_tag_tx_buffer.tx_buffer, cnt_data, 3); nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 3, true); } @@ -450,6 +462,42 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { nfc_tag_14a_tx_nbit(ACK_VALUE, 4); } +static void handle_pwd_auth_command(uint8_t *p_data) { + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + uint8_t *cnt_data = get_counter_data_by_index(0); + if (first_cfg_page == 0 || cnt_data == NULL) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + // check AUTHLIM counter + uint8_t auth_cnt = cnt_data[AUTHLIM_OFF_IN_CTR]; + uint8_t auth_lim = m_tag_information->memory[first_cfg_page + 1][0] & CONF_ACCESS_AUTHLIM_MASK; + if ((auth_lim > 0) && (auth_lim <= auth_cnt)) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET]; + uint32_t supplied_pwd = *(uint32_t *)&p_data[1]; + if (pwd != supplied_pwd) { + cnt_data[AUTHLIM_OFF_IN_CTR] = auth_cnt + 1; + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + // reset authentication attempts counter and authenticate user + cnt_data[AUTHLIM_OFF_IN_CTR] = 0; + m_tag_authenticated = true; // TODO: this should be possible to reset somehow + + // Send the PACK value back + if (m_tag_information->config.mode_uid_magic) { + nfc_tag_14a_tx_bytes(ntagPwdOK, 2, true); + } else { + nfc_tag_14a_tx_bytes(m_tag_information->memory[first_cfg_page + CONF_PACK_PAGE_OFFSET], 2, true); + } +} + static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { uint8_t command = p_data[0]; uint8_t block_num = p_data[1]; @@ -474,25 +522,10 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) nfc_tag_14a_tx_nbit(resp, 4); break; } - /*case CMD_PWD_AUTH: { - // TODO: IMPLEMENT COUNTER AUTHLIM - uint8_t Password[4]; - memcpy(Password, m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 4); - if (Password[0] != p_data[1] || Password[1] != p_data[2] || Password[2] != p_data[3] || Password[3] != p_data[4]) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - break; - } - // Authenticate the user - //RESET AUTHLIM COUNTER, CURRENTLY NOT IMPLEMENTED - // TODO - // Send the PACK value back - if (m_tag_information->config.mode_uid_magic) { - nfc_tag_14a_tx_bytes(ntagPwdOK, 2, true); - } else { - nfc_tag_14a_tx_bytes(m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 2, true); - } + case CMD_PWD_AUTH: { + handle_pwd_auth_command(p_data); break; - }*/ + } case CMD_READ_SIG: memset(m_tag_tx_buffer.tx_buffer, 0xCA, SIGNATURE_LENGTH); nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, SIGNATURE_LENGTH, true); From 8bfad8b9ab2672a53e4839a946651e85b5ad02b6 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 21:52:12 +0300 Subject: [PATCH 11/60] Small improvements to the tx buffer handling. --- firmware/application/src/rfid/nfctag/hf/nfc_14a.c | 1 + firmware/application/src/rfid/nfctag/hf/nfc_14a.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c index 352efc8a..d1cd700f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c @@ -295,6 +295,7 @@ uint8_t nfc_tag_14a_unwrap_frame(const uint8_t *pbtFrame, const size_t szFrameBi * @param[in] appendCrc Whether to send the byte flow, automatically send the CRC16 verification automatically */ void nfc_tag_14a_tx_bytes(uint8_t *data, uint32_t bytes, bool appendCrc) { + ASSERT(bytes <= MAX_NFC_TX_BUFFER_SIZE); NFC_14A_TX_BYTE_CORE(data, bytes, appendCrc, NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID); } diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.h b/firmware/application/src/rfid/nfctag/hf/nfc_14a.h index f70fc1fd..c103c02f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.h @@ -3,7 +3,7 @@ #include "tag_emulation.h" -#define MAX_NFC_RX_BUFFER_SIZE 64 +#define MAX_NFC_RX_BUFFER_SIZE 257 #define MAX_NFC_TX_BUFFER_SIZE 64 #define NFC_TAG_14A_CRC_LENGTH 2 From 80a14a13921c9059af6539b2a91823a83adcd1c1 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 21:55:13 +0300 Subject: [PATCH 12/60] Fix factory initialization for MF0/NTAG. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index ad069784..55c19b8c 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -617,6 +617,9 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { nfc_tag_mf0_ntag_information_max_t ntag_tmp_information; nfc_tag_mf0_ntag_information_t *p_ntag_information; p_ntag_information = (nfc_tag_mf0_ntag_information_t *)&ntag_tmp_information; + + memset(p_ntag_information, 0, sizeof(nfc_tag_mf0_ntag_information_max_t)); + int block_max = get_nr_pages_by_tag_type(tag_type); for (int block = 0; block < block_max; block++) { switch (block) { @@ -635,6 +638,27 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { } } + int first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); + if (first_cfg_page != 0) { + p_ntag_information->memory[first_cfg_page][CONF_AUTH0_BYTE] = 0xFF; // set AUTH to 0xFF + *(uint32_t *)p_ntag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET] = 0xFFFFFFFF; // set PWD to FFFFFFFF + + switch (tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + p_ntag_information->memory[first_cfg_page + 1][1] = 0x05; // set VCTID to 0x05 + break; + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + p_ntag_information->memory[first_cfg_page][0] = 0x04; // set MIRROR to 0x04 (STRG_MOD_EN to 1) + break; + default: + ASSERT(false); + break; + } + } + // default ntag auto ant-collision res p_ntag_information->res_coll.atqa[0] = 0x44; p_ntag_information->res_coll.atqa[1] = 0x00; From 59f2611fea7e249de1c4da75eeef613e833c50b4 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 21:56:42 +0300 Subject: [PATCH 13/60] Insert some assertions and locking. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 55c19b8c..d9d03b22 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -128,7 +128,7 @@ static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { nr_pages = NTAG216_PAGES; break; default: - nr_pages = 0; + ASSERT(false); break; } @@ -219,6 +219,8 @@ static bool is_ntag() { } static void handle_get_version_command() { + NRF_LOG_DEBUG("handling GET_VERSION"); + switch (m_tag_type) { case TAG_TYPE_MF0UL11: m_tag_tx_buffer.tx_buffer[6] = MF0UL11_VERSION_STORAGE_SIZE; @@ -241,6 +243,7 @@ static void handle_get_version_command() { m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; break; default: + NRF_LOG_WARNING("current card type does not support GET_VERSION"); // MF0ICU1 and MF0ICU2 do not support GET_VERSION nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; @@ -253,13 +256,23 @@ static void handle_get_version_command() { m_tag_tx_buffer.tx_buffer[5] = VERSION_MINOR_PRODUCT; m_tag_tx_buffer.tx_buffer[7] = VERSION_PROTOCOL_TYPE; + NRF_LOG_INFO( + "replying with %08x%08x", + U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[0]), + U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[0]) + ); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); } static void handle_read_command(uint8_t block_num) { int block_max = get_block_max_by_tag_type(m_tag_type, true); + NRF_LOG_DEBUG("handling READ %02x %02x", block_num, block_max); + if (block_num >= block_max) { + NRF_LOG_WARNING("too large block num %02x >= %02x", block_num, block_max); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } @@ -276,6 +289,9 @@ static void handle_read_command(uint8_t block_num) { memset(m_tag_tx_buffer.tx_buffer + block * 4, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); } } + + NRF_LOG_DEBUG("READ handled %02x %02x", block_num); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, BYTES_PER_READ, true); } @@ -314,6 +330,8 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { } size_t send_size = (end_block_num - block_num) * NFC_TAG_MF0_NTAG_DATA_SIZE; + + ASSERT(send_size <= MAX_NFC_TX_BUFFER_SIZE); nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, send_size, true); } @@ -502,6 +520,10 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) uint8_t command = p_data[0]; uint8_t block_num = p_data[1]; + if (szDataBits < 16) return; + + NRF_LOG_INFO("received mfu command %x of size %u bits", command, szDataBits); + switch (command) { case CMD_GET_VERSION: handle_get_version_command(); @@ -569,6 +591,7 @@ int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *bu // Save the corresponding size data according to the current label type return get_information_size_by_tag_type(type); } else { + ASSERT(false); return 0; } } @@ -589,6 +612,7 @@ int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *bu nfc_tag_14a_set_handler(&handler_for_14a); NRF_LOG_INFO("HF ntag data load finish."); } else { + ASSERT(buffer->length == info_size); NRF_LOG_ERROR("nfc_tag_mf0_ntag_information_t too big."); } return info_size; From 8ddf2eaaeea5792af4d9e97614271b3e55788e59 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 21:58:10 +0300 Subject: [PATCH 14/60] Respect access rules when reading/writing MF0/NTAG. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 90 +++++++++++++++++-- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index d9d03b22..f8d630af 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -52,14 +52,23 @@ NRF_LOG_MODULE_REGISTER(); #define STATIC_LOCKBYTE_0_ADDRESS 0x0A #define STATIC_LOCKBYTE_1_ADDRESS 0x0B // CONFIG stuff +#define MF0ICU2_USER_MEMORY_END 0x28 +#define MF0ICU2_CNT_PAGE 0x29 +#define MF0ICU2_FIRST_KEY_PAGE 0x2C #define MF0UL11_FIRST_CFG_PAGE 0x10 +#define MF0UL11_USER_MEMORY_END (MF0UL11_FIRST_CFG_PAGE) #define MF0UL21_FIRST_CFG_PAGE 0x25 +#define MF0UL21_USER_MEMORY_END 0x24 #define NTAG213_FIRST_CFG_PAGE 0x29 +#define NTAG213_USER_MEMORY_END 0x28 #define NTAG215_FIRST_CFG_PAGE 0x83 +#define NTAG215_USER_MEMORY_END 0x82 #define NTAG216_FIRST_CFG_PAGE 0xE3 +#define NTAG216_USER_MEMORY_END 0xE2 #define CONFIG_AREA_SIZE 8 // CONFIG offsets, relative to config start address +#define CONF_MIRROR_BYTE 0 #define CONF_AUTH0_BYTE 0x03 #define CONF_ACCESS_AUTHLIM_MASK 0x07 #define CONF_PWD_PAGE_OFFSET 2 @@ -70,6 +79,7 @@ NRF_LOG_MODULE_REGISTER(); #define PAGE_WRITE_MIN 0x02 // CONFIG masks to check individual needed bits +#define CONF_CFGLCK_PROT 0x40 #define CONF_ACCESS_PROT 0x80 #define VERSION_INFO_LENGTH 8 //8 bytes info length + crc @@ -103,7 +113,7 @@ static tag_specific_type_t m_tag_type; static bool m_tag_authenticated = false; static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { - int nr_pages; + int nr_pages = 0; switch (tag_type) { case TAG_TYPE_MF0ICU1: @@ -136,7 +146,7 @@ static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { } static int get_nr_mem_pages_by_tag_type(tag_specific_type_t tag_type) { - int nr_pages; + int nr_pages = 0; switch (tag_type) { case TAG_TYPE_MF0ICU1: @@ -161,7 +171,7 @@ static int get_nr_mem_pages_by_tag_type(tag_specific_type_t tag_type) { nr_pages = NTAG216_PAGES_WITH_CTR; break; default: - nr_pages = 0; + ASSERT(false); break; } @@ -321,11 +331,12 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { if (pwd_page != 0) pwd_page += CONF_PWD_PAGE_OFFSET; for (uint8_t block = block_num; block < end_block_num; block++) { + int tx_buf_offset = (block - block_num) * 4; // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || (block < pwd_page) || (block > (pwd_page + 1))) { - memcpy(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, m_tag_information->memory[block], NFC_TAG_MF0_NTAG_DATA_SIZE); + memcpy(m_tag_tx_buffer.tx_buffer + tx_buf_offset, m_tag_information->memory[block], NFC_TAG_MF0_NTAG_DATA_SIZE); } else { - memset(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + memset(m_tag_tx_buffer.tx_buffer + tx_buf_offset, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); } } @@ -349,8 +360,75 @@ static bool check_ro_lock_on_page(int block_num) { return locked; } else { - // too large block number + uint8_t *p_lock_bytes = NULL; + int user_memory_end = 0; + int dyn_lock_bit_page_cnt = 0; + int index = block_num - MF0ICU1_PAGES; + + switch (m_tag_type) { + // These two do only have 16 pages so a single pair of lock bytes. + case TAG_TYPE_MF0ICU1: + case TAG_TYPE_MF0UL11: + default: return true; + case TAG_TYPE_MF0ICU2: { + p_lock_bytes = m_tag_information->memory[MF0ICU2_USER_MEMORY_END]; + + if (block_num < MF0ICU2_USER_MEMORY_END) { + uint8_t byte2 = p_lock_bytes[0]; + + // Account for block locking bits first. + bool locked = (byte2 & (0x10 * (block_num >= 28))) != 0; + locked |= (byte2 >> (1 + (index / 4) + (block_num >= 28))); + return locked; + } else if (block_num == MF0ICU2_USER_MEMORY_END) { + return false; + } else if (block_num < MF0ICU2_FIRST_KEY_PAGE) { + uint8_t byte3 = p_lock_bytes[1]; + return ((byte3 >> (block_num - MF0ICU2_CNT_PAGE)) & 1) != 0; + } else { + uint8_t byte3 = p_lock_bytes[1]; + return (byte3 & 0x80) != 0; + } + } + case TAG_TYPE_MF0UL21: { + p_lock_bytes = m_tag_information->memory[MF0UL21_USER_MEMORY_END]; + uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; + bool locked = ((lock_word >> (index / 2)) & 1) != 0; + locked |= ((p_lock_bytes[2] >> (index / 4)) & 1) != 0; + return locked; + } + case TAG_TYPE_NTAG_213: + user_memory_end = NTAG213_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 2; + break; + case TAG_TYPE_NTAG_215: + user_memory_end = NTAG215_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 16; + break; + case TAG_TYPE_NTAG_216: + user_memory_end = NTAG216_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 16; + break; + } + + if (block_num < user_memory_end) { + p_lock_bytes = m_tag_information->memory[user_memory_end]; + uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; + + bool locked_small_range = ((lock_word >> (index / dyn_lock_bit_page_cnt)) & 1) != 0; + bool locked_large_range = ((p_lock_bytes[2] >> (index / dyn_lock_bit_page_cnt / 2)) & 1) != 0; + + return locked_small_range | locked_large_range; + } else { + // check CFGLCK bit + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + uint8_t mirror = m_tag_information->memory[first_cfg_page][CONF_MIRROR_BYTE]; + if ((mirror & CONF_CFGLCK_PROT) != 0) + return (block_num >= first_cfg_page) && ((block_num - first_cfg_page) <= 1); + else + return false; + } } } From 2acd42acd256eaf9431becdd7ff03ce113479f87 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 21:58:44 +0300 Subject: [PATCH 15/60] Fix maximum block size. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index 2005cecf..c365716f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -6,7 +6,7 @@ #define NFC_TAG_MF0_NTAG_DATA_SIZE 4 #define NFC_TAG_NTAG_FRAME_SIZE 64 -#define NFC_TAG_NTAG_BLOCK_MAX 231 +#define NFC_TAG_NTAG_BLOCK_MAX 231 + 1 #define NFC_TAG_MF0_FRAME_SIZE (16 + NFC_TAG_14A_CRC_LENGTH) #define NFC_TAG_MF0_BLOCK_MAX 41 From 2447c79963b002757dedbe72f757193155cae6a8 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 22:50:45 +0300 Subject: [PATCH 16/60] Fix handling of R/O pages and CFGLCK. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index f8d630af..957bda0b 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -69,6 +69,8 @@ NRF_LOG_MODULE_REGISTER(); // CONFIG offsets, relative to config start address #define CONF_MIRROR_BYTE 0 +#define CONF_ACCESS_PAGE_OFFSET 1 +#define CONF_ACCESS_BYTE 0 #define CONF_AUTH0_BYTE 0x03 #define CONF_ACCESS_AUTHLIM_MASK 0x07 #define CONF_PWD_PAGE_OFFSET 2 @@ -213,6 +215,9 @@ static int get_block_max_by_tag_type(tag_specific_type_t tag_type, bool read) { uint8_t auth0 = m_tag_information->memory[first_cfg_page][CONF_AUTH0_BYTE]; uint8_t access = m_tag_information->memory[first_cfg_page + 1][0]; + + NRF_LOG_INFO("auth0 %02x access %02x max_pages %02x first_cfg_page %02x", auth0, access, max_pages, first_cfg_page); + if (!read || ((access & CONF_ACCESS_PROT) != 0)) return (max_pages > auth0) ? auth0 : max_pages; else return max_pages; } @@ -366,10 +371,7 @@ static bool check_ro_lock_on_page(int block_num) { int index = block_num - MF0ICU1_PAGES; switch (m_tag_type) { - // These two do only have 16 pages so a single pair of lock bytes. case TAG_TYPE_MF0ICU1: - case TAG_TYPE_MF0UL11: - default: return true; case TAG_TYPE_MF0ICU2: { p_lock_bytes = m_tag_information->memory[MF0ICU2_USER_MEMORY_END]; @@ -391,12 +393,21 @@ static bool check_ro_lock_on_page(int block_num) { return (byte3 & 0x80) != 0; } } + // for the next two we reuse the check for CFGLCK bit used for NTAG + case TAG_TYPE_MF0UL11: + ASSERT(block_num >= MF0UL11_USER_MEMORY_END); + user_memory_end = MF0UL11_USER_MEMORY_END; + break; case TAG_TYPE_MF0UL21: { + user_memory_end = MF0UL11_USER_MEMORY_END; + if (block_num < user_memory_end) { p_lock_bytes = m_tag_information->memory[MF0UL21_USER_MEMORY_END]; uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; bool locked = ((lock_word >> (index / 2)) & 1) != 0; locked |= ((p_lock_bytes[2] >> (index / 4)) & 1) != 0; return locked; + } + break; } case TAG_TYPE_NTAG_213: user_memory_end = NTAG213_USER_MEMORY_END; @@ -410,6 +421,9 @@ static bool check_ro_lock_on_page(int block_num) { user_memory_end = NTAG216_USER_MEMORY_END; dyn_lock_bit_page_cnt = 16; break; + default: + ASSERT(false); + break; } if (block_num < user_memory_end) { @@ -423,8 +437,8 @@ static bool check_ro_lock_on_page(int block_num) { } else { // check CFGLCK bit int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); - uint8_t mirror = m_tag_information->memory[first_cfg_page][CONF_MIRROR_BYTE]; - if ((mirror & CONF_CFGLCK_PROT) != 0) + uint8_t access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; + if ((access & CONF_CFGLCK_PROT) != 0) return (block_num >= first_cfg_page) && ((block_num - first_cfg_page) <= 1); else return false; @@ -463,12 +477,12 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { for (int i = 0; i < NFC_TAG_MF0_NTAG_DATA_SIZE; i++) { m_tag_information->memory[3][i] |= p_data[i]; } - } + } else return NAK_INVALID_OPERATION_TBIV; break; default: if (!check_ro_lock_on_page(block_num)) { memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE); - } + } else return NAK_INVALID_OPERATION_TBIV; break; } @@ -636,6 +650,9 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) case CMD_INCR_CNT: handle_incr_cnt_command(block_num, &p_data[2]); break; + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; } return; } From 41e6a70d7c8fd42660f17b7edce03fc33ba7c0de Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 19 Jun 2024 23:08:55 +0300 Subject: [PATCH 17/60] Changelog entry. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 135277e7..398b0b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added support for timestamped comments in CLI via `rem`, `;`, `%` or `#` (@doegox) - Fixed watchdog trigger during `hw factory_reset` (@doegox) - Added PyInstaller support for CLI client (@augustozanellato) + - Added proper Mifare Ultralight (original, C, EV1) / NTAG (213, 215, 216) emulation (@turbocooler). ## [v2.0.0][2023-09-26] - Added `hw slot nick delete` and DELETE_SLOT_TAG_NICK (@doegox) From 1f1b4fd8800e8d1cdb5e55a6052bcd650540d6e9 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sat, 29 Jun 2024 21:33:15 +0300 Subject: [PATCH 18/60] Fix logging for GET_VERSION command. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 957bda0b..4d1b8f83 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -274,7 +274,7 @@ static void handle_get_version_command() { NRF_LOG_INFO( "replying with %08x%08x", U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[0]), - U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[0]) + U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[1]) ); nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); From 3af3488b603aa80610db46937df3d0d465207099 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 30 Jun 2024 20:58:19 +0300 Subject: [PATCH 19/60] Add mirroring support for NTAG. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 271 +++++++++++++----- 1 file changed, 206 insertions(+), 65 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 4d1b8f83..54fe7d20 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -69,6 +69,7 @@ NRF_LOG_MODULE_REGISTER(); // CONFIG offsets, relative to config start address #define CONF_MIRROR_BYTE 0 +#define CONF_MIRROR_PAGE_BYTE 2 #define CONF_ACCESS_PAGE_OFFSET 1 #define CONF_ACCESS_BYTE 0 #define CONF_AUTH0_BYTE 0x03 @@ -76,6 +77,11 @@ NRF_LOG_MODULE_REGISTER(); #define CONF_PWD_PAGE_OFFSET 2 #define CONF_PACK_PAGE_OFFSET 3 +#define MIRROR_BYTE_BYTE_MASK 0x30 +#define MIRROR_BYTE_BYTE_SHIFT 4 +#define MIRROR_BYTE_CONF_MASK 0xC0 +#define MIRROR_BYTE_CONF_SHIFT 6 + // WRITE STUFF #define BYTES_PER_WRITE 4 #define PAGE_WRITE_MIN 0x02 @@ -96,6 +102,16 @@ NRF_LOG_MODULE_REGISTER(); // first counter. #define AUTHLIM_OFF_IN_CTR 3 +// Values for MIRROR_CONF +#define MIRROR_CONF_DISABLED 0 +#define MIRROR_CONF_UID 1 +#define MIRROR_CONF_CNT 2 +#define MIRROR_CONF_UID_CNT 3 + +#define MIRROR_UID_SIZE 14 +#define MIRROR_CNT_SIZE 6 +#define MIRROR_UID_CNT_SIZE 21 + // NTAG215_Version[7] mean: // 0x0F ntag213 // 0x11 ntag215 @@ -280,34 +296,205 @@ static void handle_get_version_command() { nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); } -static void handle_read_command(uint8_t block_num) { - int block_max = get_block_max_by_tag_type(m_tag_type, true); +static int mirror_size_for_mode(uint8_t mirror_mode) { + switch (mirror_mode) { + case MIRROR_CONF_UID: + return MIRROR_UID_SIZE; + case MIRROR_CONF_CNT: + return MIRROR_CNT_SIZE; + case MIRROR_CONF_UID_CNT: + return MIRROR_UID_CNT_SIZE; + default: + ASSERT(false); + return 0; + } +} - NRF_LOG_DEBUG("handling READ %02x %02x", block_num, block_max); +static int get_user_data_end_by_tag_type(tag_specific_type_t type) { + int nr_pages = 0; - if (block_num >= block_max) { - NRF_LOG_WARNING("too large block num %02x >= %02x", block_num, block_max); + switch (type) { + case TAG_TYPE_MF0ICU1: + nr_pages = MF0ICU1_PAGES; + break; + case TAG_TYPE_MF0ICU2: + nr_pages = MF0ICU2_USER_MEMORY_END; + break; + case TAG_TYPE_MF0UL11: + nr_pages = MF0UL11_USER_MEMORY_END; + break; + case TAG_TYPE_MF0UL21: + nr_pages = MF0UL21_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_213: + nr_pages = NTAG213_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_215: + nr_pages = NTAG215_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_216: + nr_pages = NTAG216_USER_MEMORY_END; + break; + default: + ASSERT(false); + break; + } + return nr_pages; +} + +static uint8_t *get_counter_data_by_index(uint8_t index) { + uint8_t ctr_page_off; + uint8_t ctr_page_end; + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + ctr_page_off = MF0UL11_PAGES; + ctr_page_end = MF0UL11_PAGES_WITH_CTRS; + break; + case TAG_TYPE_MF0UL21: + ctr_page_off = MF0UL21_PAGES; + ctr_page_end = MF0UL21_PAGES_WITH_CTRS; + break; + case TAG_TYPE_NTAG_213: + ctr_page_off = NTAG213_PAGES; + ctr_page_end = NTAG213_PAGES_WITH_CTR; + break; + case TAG_TYPE_NTAG_215: + ctr_page_off = NTAG215_PAGES; + ctr_page_end = NTAG215_PAGES_WITH_CTR; + break; + case TAG_TYPE_NTAG_216: + ctr_page_off = NTAG216_PAGES; + ctr_page_end = NTAG216_PAGES_WITH_CTR; + break; + default: nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - return; + return NULL; } - uint8_t pwd_page = get_first_cfg_page_by_tag_type(m_tag_type); - if (pwd_page != 0) pwd_page += CONF_PWD_PAGE_OFFSET; + // check that counter index is in bounds + if (index >= (ctr_page_end - ctr_page_off)) return NULL; - for (uint8_t block = 0; block < 4; block++) { - // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. + return m_tag_information->memory[ctr_page_off + index]; +} + +static char hex_digit(int n) { + if (n < 10) return '0' + n; + else return 'A' + n; +} + +static void bytes2hex(const uint8_t *bytes, char *hex, size_t len) { + for (size_t i = 0; i < len; i++) { + *hex++ = hex_digit(bytes[i] >> 8); + *hex++ = hex_digit(bytes[i] & 0x0F); + } +} + +static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_max) { + ASSERT(block_cnt <= block_max); + ASSERT((block_max - block_cnt) >= block_num); + + uint8_t first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + + // password pages are present on all tags that have config pages + uint8_t pwd_page = 0; + if (first_cfg_page != 0) pwd_page = first_cfg_page + CONF_PWD_PAGE_OFFSET; + + // extract mirroring config + int mirror_page_off = 0; + int mirror_page_end = 0; + int mirror_byte_off = 0; + int mirror_mode = 0; + int mirror_size = 0; + uint8_t mirror_buf[MIRROR_UID_CNT_SIZE]; + if (is_ntag()) { + uint8_t mirror = m_tag_information->memory[first_cfg_page][CONF_MIRROR_BYTE]; + mirror_page_off = m_tag_information->memory[first_cfg_page][CONF_MIRROR_PAGE_BYTE]; + mirror_mode = (mirror & MIRROR_BYTE_CONF_MASK) >> MIRROR_BYTE_CONF_SHIFT; + mirror_byte_off = (mirror & MIRROR_BYTE_BYTE_MASK) >> MIRROR_BYTE_BYTE_SHIFT; + + if ((mirror_page_off > 3) && (mirror_mode != MIRROR_CONF_DISABLED)) { + mirror_size = mirror_size_for_mode(mirror_mode); + int user_data_end = get_user_data_end_by_tag_type(m_tag_type); + int pages_needed = + (mirror_byte_off + mirror_size + (NFC_TAG_MF0_NTAG_DATA_SIZE - 1)) / NFC_TAG_MF0_NTAG_DATA_SIZE; + + if ((pages_needed >= user_data_end) || ((user_data_end - pages_needed) < mirror_page_off)) { + NRF_LOG_ERROR("invalid mirror config %02x %02x %02x", mirror_page_off, mirror_byte_off, mirror_mode); + mirror_page_off = 0; + } else { + mirror_page_end = mirror_page_off + pages_needed; + + switch (mirror_mode) { + case MIRROR_CONF_UID: + bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); + break; + case MIRROR_CONF_CNT: + bytes2hex(get_counter_data_by_index(0), (char *)mirror_buf, 3); + break; + case MIRROR_CONF_UID_CNT: + bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); + mirror_buf[7] = 'x'; + bytes2hex(get_counter_data_by_index(0), (char *)&mirror_buf[8], 3); + break; + } + } + } + } + + for (uint8_t block = 0; block < block_cnt; block++) { uint8_t block_to_read = (block_num + block) % block_max; + uint8_t *tx_buf_ptr = m_tag_tx_buffer.tx_buffer + block * NFC_TAG_MF0_NTAG_DATA_SIZE; + + // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || (block_to_read < pwd_page) || (block_to_read > (pwd_page + 1))) { - memcpy(m_tag_tx_buffer.tx_buffer + block * 4, m_tag_information->memory[block_to_read], NFC_TAG_MF0_NTAG_DATA_SIZE); + memcpy(tx_buf_ptr, m_tag_information->memory[block_to_read], NFC_TAG_MF0_NTAG_DATA_SIZE); } else { - memset(m_tag_tx_buffer.tx_buffer + block * 4, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + memset(tx_buf_ptr, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + } + + // apply mirroring if needed + if ((mirror_page_off > 0) && (mirror_size > 0) && (block_to_read >= mirror_page_off) && (block_to_read < mirror_page_end)) { + // When accessing the first page that includes mirrored data the offset into the mirror buffer is + // definitely zero. Later pages need to account for the offset in the first page. Offset in the + // destination page chunk will be zero however. + int mirror_buf_off = (block_to_read - mirror_page_off) * NFC_TAG_MF0_NTAG_DATA_SIZE; + int offset_in_cur_block = mirror_byte_off; + if (mirror_buf_off != 0) { + mirror_buf_off -= mirror_byte_off; + offset_in_cur_block = 0; + } + + int mirror_copy_size = mirror_size - mirror_buf_off; + if (mirror_copy_size > NFC_TAG_MF0_NTAG_DATA_SIZE) mirror_copy_size = NFC_TAG_MF0_NTAG_DATA_SIZE; + + // Ensure we don't corrupt memory here. + ASSERT(offset_in_cur_block < NFC_TAG_MF0_NTAG_DATA_SIZE); + ASSERT(mirror_buf_off <= sizeof(mirror_buf)); + ASSERT(mirror_copy_size <= (sizeof(mirror_buf) - mirror_buf_off)); + + memcpy(&tx_buf_ptr[offset_in_cur_block], &mirror_buf[mirror_buf_off], mirror_copy_size); } } - NRF_LOG_DEBUG("READ handled %02x %02x", block_num); + NRF_LOG_DEBUG("READ handled %02x %02x %02x", block_num, block_cnt, block_max); - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, BYTES_PER_READ, true); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, ((int)block_cnt) * NFC_TAG_MF0_NTAG_DATA_SIZE, true); +} + +static void handle_read_command(uint8_t block_num) { + int block_max = get_block_max_by_tag_type(m_tag_type, true); + + NRF_LOG_DEBUG("handling READ %02x %02x", block_num, block_max); + + if (block_num >= block_max) { + NRF_LOG_WARNING("too large block num %02x >= %02x", block_num, block_max); + + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + handle_any_read(block_num, 4, block_max); } static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { @@ -332,23 +519,9 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { return; } - uint8_t pwd_page = get_first_cfg_page_by_tag_type(m_tag_type); - if (pwd_page != 0) pwd_page += CONF_PWD_PAGE_OFFSET; - - for (uint8_t block = block_num; block < end_block_num; block++) { - int tx_buf_offset = (block - block_num) * 4; - // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. - if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || (block < pwd_page) || (block > (pwd_page + 1))) { - memcpy(m_tag_tx_buffer.tx_buffer + tx_buf_offset, m_tag_information->memory[block], NFC_TAG_MF0_NTAG_DATA_SIZE); - } else { - memset(m_tag_tx_buffer.tx_buffer + tx_buf_offset, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); - } - } + NRF_LOG_INFO("HANDLING FAST READ %02x %02x", block_num, end_block_num); - size_t send_size = (end_block_num - block_num) * NFC_TAG_MF0_NTAG_DATA_SIZE; - - ASSERT(send_size <= MAX_NFC_TX_BUFFER_SIZE); - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, send_size, true); + handle_any_read(block_num, end_block_num - block_num, block_max); } static bool check_ro_lock_on_page(int block_num) { @@ -462,6 +635,9 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { switch (block_num) { case 0: case 1: + if (!memcmp(p_data, m_tag_information->memory[block_num], NFC_TAG_MF0_NTAG_DATA_SIZE)) + return ACK_VALUE; + else return NAK_INVALID_OPERATION_TBIV; case 2: // Page 2 contains lock bytes for pages 3-15. These are OR'ed when not in the UID @@ -489,41 +665,6 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { return ACK_VALUE; } -static uint8_t *get_counter_data_by_index(uint8_t index) { - uint8_t ctr_page_off; - uint8_t ctr_page_end; - switch (m_tag_type) { - case TAG_TYPE_MF0UL11: - ctr_page_off = MF0UL11_PAGES; - ctr_page_end = MF0UL11_PAGES_WITH_CTRS; - break; - case TAG_TYPE_MF0UL21: - ctr_page_off = MF0UL21_PAGES; - ctr_page_end = MF0UL21_PAGES_WITH_CTRS; - break; - case TAG_TYPE_NTAG_213: - ctr_page_off = NTAG213_PAGES; - ctr_page_end = NTAG213_PAGES_WITH_CTR; - break; - case TAG_TYPE_NTAG_215: - ctr_page_off = NTAG215_PAGES; - ctr_page_end = NTAG215_PAGES_WITH_CTR; - break; - case TAG_TYPE_NTAG_216: - ctr_page_off = NTAG216_PAGES; - ctr_page_end = NTAG216_PAGES_WITH_CTR; - break; - default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - return NULL; - } - - // check that counter index is in bounds - if (index >= (ctr_page_end - ctr_page_off)) return NULL; - - return m_tag_information->memory[ctr_page_off + index]; -} - static void handle_read_cnt_command(uint8_t index) { uint8_t *cnt_data = get_counter_data_by_index(index); if (cnt_data == NULL) { From 9e25b0913fb9ba21e28bb8fd5ea1231a8a205074 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 3 Jul 2024 01:02:54 +0300 Subject: [PATCH 20/60] Fix mirroring bug. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 54fe7d20..a02a4dac 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -368,7 +368,7 @@ static uint8_t *get_counter_data_by_index(uint8_t index) { ctr_page_end = NTAG216_PAGES_WITH_CTR; break; default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return NULL; } @@ -380,12 +380,12 @@ static uint8_t *get_counter_data_by_index(uint8_t index) { static char hex_digit(int n) { if (n < 10) return '0' + n; - else return 'A' + n; + else return 'A' + n - 10; } static void bytes2hex(const uint8_t *bytes, char *hex, size_t len) { for (size_t i = 0; i < len; i++) { - *hex++ = hex_digit(bytes[i] >> 8); + *hex++ = hex_digit(bytes[i] >> 4); *hex++ = hex_digit(bytes[i] & 0x0F); } } @@ -545,7 +545,7 @@ static bool check_ro_lock_on_page(int block_num) { switch (m_tag_type) { case TAG_TYPE_MF0ICU1: - return true; + return true; case TAG_TYPE_MF0ICU2: { p_lock_bytes = m_tag_information->memory[MF0ICU2_USER_MEMORY_END]; @@ -574,11 +574,11 @@ static bool check_ro_lock_on_page(int block_num) { case TAG_TYPE_MF0UL21: { user_memory_end = MF0UL11_USER_MEMORY_END; if (block_num < user_memory_end) { - p_lock_bytes = m_tag_information->memory[MF0UL21_USER_MEMORY_END]; - uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; - bool locked = ((lock_word >> (index / 2)) & 1) != 0; - locked |= ((p_lock_bytes[2] >> (index / 4)) & 1) != 0; - return locked; + p_lock_bytes = m_tag_information->memory[MF0UL21_USER_MEMORY_END]; + uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; + bool locked = ((lock_word >> (index / 2)) & 1) != 0; + locked |= ((p_lock_bytes[2] >> (index / 4)) & 1) != 0; + return locked; } break; } @@ -638,7 +638,7 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { if (!memcmp(p_data, m_tag_information->memory[block_num], NFC_TAG_MF0_NTAG_DATA_SIZE)) return ACK_VALUE; else - return NAK_INVALID_OPERATION_TBIV; + return NAK_INVALID_OPERATION_TBIV; case 2: // Page 2 contains lock bytes for pages 3-15. These are OR'ed when not in the UID // magic mode. First two bytes are ignored. @@ -915,7 +915,7 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { break; default: ASSERT(false); - break; + break; } } From 1c14fc00b2426bdf6a50cebf03724c50365065d6 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 3 Jul 2024 02:22:56 +0300 Subject: [PATCH 21/60] NTAG counter and access fixes. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 142 +++++++++++------- 1 file changed, 88 insertions(+), 54 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index a02a4dac..fab6f959 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -42,75 +42,77 @@ NRF_LOG_MODULE_REGISTER(); // MEMORY LAYOUT STUFF, addresses and sizes in bytes // UID stuff -#define UID_CL1_ADDRESS 0x00 -#define UID_CL1_SIZE 3 -#define UID_BCC1_ADDRESS 0x03 -#define UID_CL2_ADDRESS 0x04 -#define UID_CL2_SIZE 4 -#define UID_BCC2_ADDRESS 0x08 +#define UID_CL1_ADDRESS 0x00 +#define UID_CL1_SIZE 3 +#define UID_BCC1_ADDRESS 0x03 +#define UID_CL2_ADDRESS 0x04 +#define UID_CL2_SIZE 4 +#define UID_BCC2_ADDRESS 0x08 // LockBytes stuff -#define STATIC_LOCKBYTE_0_ADDRESS 0x0A -#define STATIC_LOCKBYTE_1_ADDRESS 0x0B +#define STATIC_LOCKBYTE_0_ADDRESS 0x0A +#define STATIC_LOCKBYTE_1_ADDRESS 0x0B // CONFIG stuff -#define MF0ICU2_USER_MEMORY_END 0x28 -#define MF0ICU2_CNT_PAGE 0x29 -#define MF0ICU2_FIRST_KEY_PAGE 0x2C -#define MF0UL11_FIRST_CFG_PAGE 0x10 -#define MF0UL11_USER_MEMORY_END (MF0UL11_FIRST_CFG_PAGE) -#define MF0UL21_FIRST_CFG_PAGE 0x25 -#define MF0UL21_USER_MEMORY_END 0x24 -#define NTAG213_FIRST_CFG_PAGE 0x29 -#define NTAG213_USER_MEMORY_END 0x28 -#define NTAG215_FIRST_CFG_PAGE 0x83 -#define NTAG215_USER_MEMORY_END 0x82 -#define NTAG216_FIRST_CFG_PAGE 0xE3 -#define NTAG216_USER_MEMORY_END 0xE2 -#define CONFIG_AREA_SIZE 8 +#define MF0ICU2_USER_MEMORY_END 0x28 +#define MF0ICU2_CNT_PAGE 0x29 +#define MF0ICU2_FIRST_KEY_PAGE 0x2C +#define MF0UL11_FIRST_CFG_PAGE 0x10 +#define MF0UL11_USER_MEMORY_END (MF0UL11_FIRST_CFG_PAGE) +#define MF0UL21_FIRST_CFG_PAGE 0x25 +#define MF0UL21_USER_MEMORY_END 0x24 +#define NTAG213_FIRST_CFG_PAGE 0x29 +#define NTAG213_USER_MEMORY_END 0x28 +#define NTAG215_FIRST_CFG_PAGE 0x83 +#define NTAG215_USER_MEMORY_END 0x82 +#define NTAG216_FIRST_CFG_PAGE 0xE3 +#define NTAG216_USER_MEMORY_END 0xE2 +#define CONFIG_AREA_SIZE 8 // CONFIG offsets, relative to config start address -#define CONF_MIRROR_BYTE 0 -#define CONF_MIRROR_PAGE_BYTE 2 -#define CONF_ACCESS_PAGE_OFFSET 1 -#define CONF_ACCESS_BYTE 0 -#define CONF_AUTH0_BYTE 0x03 -#define CONF_ACCESS_AUTHLIM_MASK 0x07 -#define CONF_PWD_PAGE_OFFSET 2 -#define CONF_PACK_PAGE_OFFSET 3 - -#define MIRROR_BYTE_BYTE_MASK 0x30 -#define MIRROR_BYTE_BYTE_SHIFT 4 -#define MIRROR_BYTE_CONF_MASK 0xC0 -#define MIRROR_BYTE_CONF_SHIFT 6 +#define CONF_MIRROR_BYTE 0 +#define CONF_MIRROR_PAGE_BYTE 2 +#define CONF_ACCESS_PAGE_OFFSET 1 +#define CONF_ACCESS_BYTE 0 +#define CONF_AUTH0_BYTE 0x03 +#define CONF_PWD_PAGE_OFFSET 2 +#define CONF_PACK_PAGE_OFFSET 3 + +#define MIRROR_BYTE_BYTE_MASK 0x30 +#define MIRROR_BYTE_BYTE_SHIFT 4 +#define MIRROR_BYTE_CONF_MASK 0xC0 +#define MIRROR_BYTE_CONF_SHIFT 6 // WRITE STUFF -#define BYTES_PER_WRITE 4 -#define PAGE_WRITE_MIN 0x02 +#define BYTES_PER_WRITE 4 +#define PAGE_WRITE_MIN 0x02 // CONFIG masks to check individual needed bits -#define CONF_CFGLCK_PROT 0x40 -#define CONF_ACCESS_PROT 0x80 +#define CONF_ACCESS_AUTHLIM_MASK 0x07 +#define CONF_ACCESS_NFC_CNT_EN 0x10 +#define CONF_ACCESS_NFC_CNT_PWD_PROT 0x04 +#define CONF_ACCESS_CFGLCK 0x40 +#define CONF_ACCESS_PROT 0x80 -#define VERSION_INFO_LENGTH 8 //8 bytes info length + crc +#define VERSION_INFO_LENGTH 8 //8 bytes info length + crc -#define BYTES_PER_READ 16 +#define BYTES_PER_READ 16 // SIGNATURE Length -#define SIGNATURE_LENGTH 32 +#define SIGNATURE_LENGTH 32 // Since all counters are 24-bit and each currently supported tag that supports counters // has password authentication we store the auth attempts counter in the last bit of the // first counter. -#define AUTHLIM_OFF_IN_CTR 3 +#define AUTHLIM_OFF_IN_CTR 3 // Values for MIRROR_CONF -#define MIRROR_CONF_DISABLED 0 -#define MIRROR_CONF_UID 1 -#define MIRROR_CONF_CNT 2 -#define MIRROR_CONF_UID_CNT 3 +#define MIRROR_CONF_DISABLED 0 +#define MIRROR_CONF_UID 1 +#define MIRROR_CONF_CNT 2 +#define MIRROR_CONF_UID_CNT 3 -#define MIRROR_UID_SIZE 14 -#define MIRROR_CNT_SIZE 6 -#define MIRROR_UID_CNT_SIZE 21 +#define MIRROR_UID_SIZE 14 +#define MIRROR_CNT_SIZE 6 +#define MIRROR_UID_CNT_SIZE 21 // NTAG215_Version[7] mean: // 0x0F ntag213 @@ -129,6 +131,7 @@ static nfc_tag_mf0_ntag_tx_buffer_t m_tag_tx_buffer; // Save the specific type of MF0/NTAG currently being simulated static tag_specific_type_t m_tag_type; static bool m_tag_authenticated = false; +static bool m_did_first_read = false; static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { int nr_pages = 0; @@ -232,7 +235,7 @@ static int get_block_max_by_tag_type(tag_specific_type_t tag_type, bool read) { uint8_t auth0 = m_tag_information->memory[first_cfg_page][CONF_AUTH0_BYTE]; uint8_t access = m_tag_information->memory[first_cfg_page + 1][0]; - NRF_LOG_INFO("auth0 %02x access %02x max_pages %02x first_cfg_page %02x", auth0, access, max_pages, first_cfg_page); + NRF_LOG_INFO("auth0 %02x access %02x max_pages %02x first_cfg_page %02x authenticated %i", auth0, access, max_pages, first_cfg_page, m_tag_authenticated); if (!read || ((access & CONF_ACCESS_PROT) != 0)) return (max_pages > auth0) ? auth0 : max_pages; else return max_pages; @@ -479,6 +482,23 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ NRF_LOG_DEBUG("READ handled %02x %02x %02x", block_num, block_cnt, block_max); + // update counter for NTAG cards if needed + if (is_ntag() && !m_did_first_read) { + m_did_first_read = true; + + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; + + if ((access & CONF_ACCESS_NFC_CNT_EN) != 0) { + uint8_t *ctr = get_counter_data_by_index(0); + uint32_t counter = (((uint32_t)ctr[0]) << 16) | (((uint32_t)ctr[1]) << 8) | ((uint32_t)ctr[2]); + if (counter < 0xFFFFFF) counter += 1; + ctr[0] = (uint8_t)(counter >> 16); + ctr[1] = (uint8_t)(counter >> 8); + ctr[2] = (uint8_t)(counter); + } + } + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, ((int)block_cnt) * NFC_TAG_MF0_NTAG_DATA_SIZE, true); } @@ -611,7 +631,7 @@ static bool check_ro_lock_on_page(int block_num) { // check CFGLCK bit int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); uint8_t access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; - if ((access & CONF_CFGLCK_PROT) != 0) + if ((access & CONF_ACCESS_CFGLCK) != 0) return (block_num >= first_cfg_page) && ((block_num - first_cfg_page) <= 1); else return false; @@ -623,6 +643,8 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { int block_max = get_block_max_by_tag_type(m_tag_type, false); if (block_num >= block_max) { + NRF_LOG_ERROR("Write failed: block_num %08x >= block_max %08x", block_num, block_max); + return NAK_INVALID_OPERATION_TBIV; } @@ -666,6 +688,17 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { } static void handle_read_cnt_command(uint8_t index) { + // deny counter reading when counter password protection is enabled and reader is not authenticated + if (is_ntag() && !m_tag_information->config.mode_uid_magic) { + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; + + if ((access & CONF_ACCESS_NFC_CNT_PWD_PROT) != 0 && !m_tag_authenticated) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + } + uint8_t *cnt_data = get_counter_data_by_index(index); if (cnt_data == NULL) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); @@ -732,7 +765,7 @@ static void handle_pwd_auth_command(uint8_t *p_data) { uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET]; uint32_t supplied_pwd = *(uint32_t *)&p_data[1]; if (pwd != supplied_pwd) { - cnt_data[AUTHLIM_OFF_IN_CTR] = auth_cnt + 1; + if (auth_lim) cnt_data[AUTHLIM_OFF_IN_CTR] = auth_cnt + 1; nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } @@ -810,7 +843,8 @@ static nfc_tag_14a_coll_res_reference_t *get_coll_res() { } static void nfc_tag_mf0_ntag_reset_handler() { - + m_tag_authenticated = false; + m_did_first_read = false; } static int get_information_size_by_tag_type(tag_specific_type_t type) { From a283795f074d915c6a5ccff7582e418b406971e4 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 3 Jul 2024 02:45:45 +0300 Subject: [PATCH 22/60] Fix NTAG INCR_CNT command byte order. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index fab6f959..d59de1a1 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -733,15 +733,15 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { } uint8_t *cnt_data = m_tag_information->memory[ctr_page_off + block_num]; - uint32_t incr_value = ((uint32_t)p_data[0]) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2] << 16); - uint32_t cnt = ((uint32_t)cnt_data[0]) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2] << 16); + uint32_t incr_value = ((uint32_t)p_data[0] << 16) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2]); + uint32_t cnt = ((uint32_t)cnt_data[0] << 16) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2]); if ((0xFFFFFF - cnt) < incr_value) cnt = 0xFFFFFF; else cnt += incr_value; - cnt_data[0] = (uint8_t)(cnt & 0xff); + cnt_data[0] = (uint8_t)(cnt >> 16); cnt_data[1] = (uint8_t)(cnt >> 8); - cnt_data[2] = (uint8_t)(cnt >> 16); + cnt_data[2] = (uint8_t)(cnt & 0xff); nfc_tag_14a_tx_nbit(ACK_VALUE, 4); } From 2da6d35784497742eb29db93d185524db098eaf1 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Wed, 3 Jul 2024 03:04:25 +0300 Subject: [PATCH 23/60] Improvements to MFU / NTAG cli commands. Added `wrbl` and `rcnt` commands for writing blocks and reading counters. Added `-P` parameter to all commands that may need it to allow prior auth with a 4-byte password. `dump` command now dumps all pages until it fails rather than just the first 16 pages when no `-q` parameter is provided. Added `version` command to allow requesting version bytes. --- software/script/chameleon_cli_unit.py | 223 +++++++++++++++++++++++--- 1 file changed, 205 insertions(+), 18 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 0b1bf012..c0a1ead7 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -913,21 +913,12 @@ def on_exec(self, args: argparse.Namespace): # read keys from key format file if args.import_key is not None: - buf = args.import_key.read() - if len(buf) % 6 != 0: - print(f' - {CR}Failed to parse keys from {args.import_key.name} (as .key format){C0}') + if not load_key_file(args.import_key, keys): return - for i in range(0, len(buf), 6): - keys.add(bytes(buf[i:i+6])) if args.import_dic is not None: - text = re.sub(r'#.*$', '', args.import_dic.read(), flags=re.MULTILINE) - buf = bytearray.fromhex(text) - if len(buf) % 6 != 0: - print(f' - {CR}Failed to parse keys from {args.import_dic.name} (as .dic format){C0}') + if not load_dic_file(args.import_dic, keys): return - for i in range(0, len(buf), 6): - keys.add(bytes(buf[i:i+6])) if len(keys) == 0: print(f' - {CR}No keys{C0}') @@ -1657,15 +1648,26 @@ def on_exec(self, args: argparse.Namespace): class HFMFURDPG(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: parser = super().args_parser() - parser.description = 'MIFARE Ultralight read one page' + parser.description = 'MIFARE Ultralight / NTAG read one page' parser.add_argument('-p', '--page', type=int, required=True, metavar="", help="The page where the key will be used against") + parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", + help="Ultralight EV1 / NTAG password, as a 4 byte (8 character) hex string.") return parser def get_param(self, args): + if args.pwd is not None: + pwd = bytes.fromhex(args.pwd) + if len(pwd) != 4: + raise LengthError("Invalid MFU EV 1 / NTAG password data length.") + else: + pwd = None + class Param: def __init__(self): self.page = args.page + self.pwd = pwd + return Param() def on_exec(self, args: argparse.Namespace): @@ -1679,11 +1681,126 @@ def on_exec(self, args: argparse.Namespace): 'keep_rf_field': 0, 'check_response_crc': 1, } - # TODO: auth first if a key is given + + if param.pwd is not None: + options['keep_rf_field'] = 1 + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + options['keep_rf_field'] = 0 + options['auto_select'] = 0 + print(f" - PACK: {resp[:2].hex()}") + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page)) print(f" - Data: {resp[:4].hex()}") +@hf_mfu.command('wrpg') +class HFMFUWRPG(MFUAuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = super().args_parser() + parser.description = 'MIFARE Ultralight / NTAG write one page' + parser.add_argument('-p', '--page', type=int, required=True, metavar="", + help="The index of the page to write to.") + parser.add_argument('-d', '--data', type=str, required=True, metavar="", + help="Your page data, as a 4 byte (8 character) hex string.") + parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", + help="Ultralight EV1 / NTAG password, as a 4 byte (8 character) hex string.") + return parser + + def get_param(self, args): + data = bytes.fromhex(args.data) + if len(data) != 4: + raise LengthError("Invalid MF0 / NTAG page data length.") + + if args.pwd is not None: + pwd = bytes.fromhex(args.pwd) + if len(pwd) != 4: + raise LengthError("Invalid MFU EV 1 / NTAG password data length.") + else: + pwd = None + + class Param: + def __init__(self): + self.page = args.page + self.data = data + self.pwd = pwd + + return Param() + + def on_exec(self, args: argparse.Namespace): + try: + param = self.get_param(args) + except: + print(f"{CR}Page data and password should be 4 byte (8 character) hex strings.{C0}") + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + if param.pwd is not None: + options['keep_rf_field'] = 1 + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + options['keep_rf_field'] = 0 + options['auto_select'] = 0 + print(f" - PACK: {resp[:2].hex()}") + + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, param.page)+param.data) + print(f" - Ok") + + +@hf_mfu.command('rcnt') +class HFMFURCNT(MFUAuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = super().args_parser() + parser.description = 'MIFARE Ultralight / NTAG read counter' + parser.add_argument('-c', '--counter', type=int, required=True, metavar="", + help="Index of the counter to read (always 0 for NTAG, 0-2 for Ultralight EV1).") + parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", + help="NTAG password, as a 4 byte (8 character) hex string.") + return parser + + def get_param(self, args): + if args.pwd is not None: + pwd = bytes.fromhex(args.pwd) + if len(pwd) != 4: + raise LengthError("Invalid MFU EV 1 / NTAG password data length.") + else: + pwd = None + + class Param: + def __init__(self): + self.counter = args.counter + self.pwd = pwd + + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + if param.pwd is not None: + options['keep_rf_field'] = 1 + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + options['keep_rf_field'] = 0 + options['auto_select'] = 0 + print(f" - PACK: {resp[:2].hex()}") + + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, param.counter)) + print(f" - Data: {resp[:3].hex()}") + + @hf_mfu.command('dump') class HFMFUDUMP(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: @@ -1691,18 +1808,34 @@ def args_parser(self) -> ArgumentParserNoExit: parser.description = 'MIFARE Ultralight dump pages' parser.add_argument('-p', '--page', type=int, required=False, metavar="", default=0, help="Manually set number of pages to dump") - parser.add_argument('-q', '--qty', type=int, required=False, metavar="", default=16, + parser.add_argument('-q', '--qty', type=int, required=False, metavar="", help="Manually set number of pages to dump") parser.add_argument('-f', '--file', type=str, required=False, default="", help="Specify a filename for dump file") + parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", + help="Ultralight EV1 / NTAG password, as a 4 byte (8 character) hex string.") return parser def get_param(self, args): + if args.pwd is not None: + pwd = bytes.fromhex(args.pwd) + if len(pwd) != 4: + raise LengthError("Invalid MFU EV 1 / NTAG password data length.") + else: + pwd = None + class Param: def __init__(self): self.start_page = args.page - self.stop_page = args.page + args.qty + + if args.qty is None: + self.stop_page = 256 + else: + self.stop_page = min(args.page + args.qty, 256) + self.output_file = args.file + self.pwd = pwd + return Param() def on_exec(self, args: argparse.Namespace): @@ -1715,17 +1848,46 @@ def on_exec(self, args: argparse.Namespace): save_as_eml = True else: fd = open(param.output_file, 'wb+') - # TODO: auth first if a key is given + options = { 'activate_rf_field': 0, 'wait_response': 1, 'append_crc': 1, 'auto_select': 1, - 'keep_rf_field': 0, + 'keep_rf_field': 1, 'check_response_crc': 1, } + + pack = None + needs_stop = False for i in range(param.start_page, param.stop_page): - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + # this could be done once in theory but the command would need to be optimized properly + if param.pwd is not None: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + options['auto_select'] = 0 # prevent resets + pack = resp[:2].hex() + + # disable the rf field after the last command + if i == (param.stop_page - 1) or needs_stop: + options['keep_rf_field'] = 0 + + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + except: + # probably lost tag, but we still need to disable rf field + resp = None + + if needs_stop: + # break if this command was sent just to disable RF field + break + elif resp is None or len(resp) == 0: + # we need to disable RF field if we reached the last valid page so send one more read command + needs_stop = True + continue + + # after the read we are sure we no longer need to select again + options['auto_select'] = 0 + # TODO: can be optimized as we get 4 pages at once but beware of wrapping # in case of end of memory or LOCK on ULC and no key provided data = resp[:4] @@ -1735,11 +1897,36 @@ def on_exec(self, args: argparse.Namespace): fd.write(data.hex()+'\n') else: fd.write(data) + + if pack is not None: + print(f" - PACK: {pack}") + if fd is not None: print(f" - {CG}Dump written in {param.output_file}.{C0}") fd.close() +@hf_mfu.command('version') +class HFMFUVERSION(MFUAuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = super().args_parser() + parser.description = 'Request MIFARE Ultralight / NTAG version data.' + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x60)) + print(f" - Data: {resp[:8].hex()}") + + @hf_mfu.command('econfig') class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: From 76b36dd1befefd84ca19d5b952422b38343ea2ac Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 01:52:40 +0300 Subject: [PATCH 24/60] Document MF0/NTAG UID magic mode commands. --- docs/protocol.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/protocol.md b/docs/protocol.md index 2eef2a63..413e0b9a 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -388,6 +388,14 @@ Notes: * Command: no data * Response: no data or N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. * CLI: cf `hw slot list`/`hf mf econfig`/`hf mfu econfig` +### 4019: MF0_NTAG_GET_UID_MAGIC_MODE +* Command: no data +* Response: 1 byte where a non-zero value indicates that UID magic mode is enabled for the current slot. +* CLI: cf `hf mfu econfig` +### 4020: MF0_NTAG_SET_UID_MAGIC_MODE +* Command: 1 byte where a non-zero value indicates that UID magic mode should be enabled for the current slot, otherwise disabled. +* Response: no data +* CLI: cf `hf mfu econfig --enable-uid-magic`/`hf mfu econfig --disable-uid-magic` ### 5000: EM410X_SET_EMU_ID * Command: 5 bytes. `id[5]`. ID as 5 bytes. * Response: no data From 9488127531082d495896a0222c062d33555c8877 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 02:57:54 +0300 Subject: [PATCH 25/60] Add commands for reading and writing to/from MF0/NTAG emulator memory. --- docs/protocol.md | 8 +++ firmware/application/src/app_cmd.c | 61 +++++++++++++++++++ firmware/application/src/app_status.h | 2 + firmware/application/src/data_cmd.h | 2 + .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 12 +++- .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 1 + software/script/chameleon_cmd.py | 22 +++++++ software/script/chameleon_enum.py | 8 +++ 8 files changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 413e0b9a..0b41523d 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -396,6 +396,14 @@ Notes: * Command: 1 byte where a non-zero value indicates that UID magic mode should be enabled for the current slot, otherwise disabled. * Response: no data * CLI: cf `hf mfu econfig --enable-uid-magic`/`hf mfu econfig --disable-uid-magic` +### 4021: MF0_NTAG_READ_EMU_PAGE_DATA +* Command: 2 bytes: one for first page index, one for count of pages to be read. +* Response: `4 * n` bytes where `n` is the number if pages to be read +* CLI: cf `hf mfu eview` +### 4022: MF0_NTAG_WRITE_EMU_PAGE_DATA +* Command: 2 + `n * 4` bytes: one for first page index, one for count of pages to be read, `n * 4` for `n` pages data. +* Response: no data +* CLI: unused ### 5000: EM410X_SET_EMU_ID * Command: 5 bytes. `id[5]`. ID as 5 bytes. * Response: no data diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index d4fc9e89..e6bdae5c 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -790,6 +790,65 @@ static data_frame_tx_t *cmd_processor_mf1_read_emu_block_data(uint16_t cmd, uint return data_frame_make(cmd, STATUS_SUCCESS, result_length, result_buffer); } +static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t active_slot = tag_emulation_get_slot(); + + tag_slot_specific_type_t active_slot_tag_types; + tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); + + uint8_t nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + // This means wrong slot type. + if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); + + if (length < 2) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + + int page_index = data[0]; + int pages_count = data[1]; + int byte_length = (int)nr_pages * NFC_TAG_MF0_NTAG_DATA_SIZE; + + if (pages_count == 0) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); + else if ( + (page_index >= ((int)nr_pages)) + || (pages_count > (((int)nr_pages) - page_index)) + || (((int)length - 2) < byte_length) + ) { + return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + } + + tag_data_buffer_t *buffer = get_buffer_by_tag_type(active_slot_tag_types.tag_hf); + nfc_tag_mf0_ntag_information_t *info = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; + + memcpy(&info->memory[page_index][0], &data[2], byte_length); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_read_emu_page_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t active_slot = tag_emulation_get_slot(); + + tag_slot_specific_type_t active_slot_tag_types; + tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); + + uint8_t nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + // This means wrong slot type. + if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); + + if (length < 2) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + + int page_index = data[0]; + int pages_count = data[1]; + + if (pages_count == 0) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); + else if ((page_index >= ((int)nr_pages)) || (pages_count > (((int)nr_pages) - page_index))) { + return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + } + + tag_data_buffer_t *buffer = get_buffer_by_tag_type(active_slot_tag_types.tag_hf); + nfc_tag_mf0_ntag_information_t *info = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; + + return data_frame_make(cmd, STATUS_SUCCESS, pages_count * NFC_TAG_MF0_NTAG_DATA_SIZE, &info->memory[page_index][0]); +} + static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct @@ -1129,6 +1188,8 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, { DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_get_uid_mode, NULL }, { DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL }, + { DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_read_emu_page_data, NULL }, + { DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_write_emu_page_data, NULL }, { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, diff --git a/firmware/application/src/app_status.h b/firmware/application/src/app_status.h index b0c05f64..1c720027 100644 --- a/firmware/application/src/app_status.h +++ b/firmware/application/src/app_status.h @@ -32,4 +32,6 @@ #define STATUS_NOT_IMPLEMENTED (0x69) // Calling some unrealized operations, which belongs to the missed error of the developer #define STATUS_FLASH_WRITE_FAIL (0x70) // Flash writing failed #define STATUS_FLASH_READ_FAIL (0x71) // Flash read failed +#define STATUS_INVALID_SLOT_TYPE (0x72) // Invalid slot type +#define STATUS_INVALID_PARAMS (0x73) // Invalid command parameters #endif diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 7ede2550..2f5b7c93 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -108,6 +108,8 @@ #define DATA_CMD_HF14A_GET_ANTI_COLL_DATA (4018) #define DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE (4019) #define DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE (4020) +#define DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA (4021) +#define DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA (4022) // // ****************************************************************** diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index d59de1a1..06504052 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -133,8 +133,8 @@ static tag_specific_type_t m_tag_type; static bool m_tag_authenticated = false; static bool m_did_first_read = false; -static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { - int nr_pages = 0; +int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages = -1; switch (tag_type) { case TAG_TYPE_MF0ICU1: @@ -159,13 +159,19 @@ static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { nr_pages = NTAG216_PAGES; break; default: - ASSERT(false); + nr_pages = -1; break; } return nr_pages; } +static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_type); + ASSERT(nr_pages > 0); + return nr_pages; +} + static int get_nr_mem_pages_by_tag_type(tag_specific_type_t tag_type) { int nr_pages = 0; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index c365716f..e23bb470 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -52,6 +52,7 @@ typedef struct { int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); +int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type); int nfc_tag_mf0_ntag_get_uid_mode(void); bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled); diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 5ed16a66..ac46ee69 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -573,6 +573,28 @@ def mf1_read_emu_block_data(self, block_start: int, block_count: int): resp.parsed = resp.data return resp + @expect_response(Status.INVALID_PARAMS) + def mfu_get_emu_pages_count(self): + """ + Gets the number of pages available in the current MF0 / NTAG slot + """ + data = struct.pack('!BB', 255, 255) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_READ_EMU_PAGE_DATA, data) + if len(resp.data) > 0: + resp.parsed = resp.data[0] + print(resp.data) + return resp + + @expect_response(Status.SUCCESS) + def mfu_read_emu_page_data(self, page_start: int, page_count: int): + """ + Gets data for selected block range + """ + data = struct.pack('!BB', page_start, page_count) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_READ_EMU_PAGE_DATA, data) + resp.parsed = resp.data + return resp + @expect_response(Status.SUCCESS) def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index 2bd4d074..221ca8af 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -97,6 +97,8 @@ class Command(enum.IntEnum): HF14A_GET_ANTI_COLL_DATA = 4018 MF0_NTAG_GET_UID_MAGIC_MODE = 4019 MF0_NTAG_SET_UID_MAGIC_MODE = 4020 + MF0_NTAG_READ_EMU_PAGE_DATA = 4021 + MF0_NTAG_WRITE_EMU_PAGE_DATA = 4023 EM410X_SET_EMU_ID = 5000 EM410X_GET_EMU_ID = 5001 @@ -129,6 +131,8 @@ class Status(enum.IntEnum): NOT_IMPLEMENTED = 0x69 FLASH_WRITE_FAIL = 0x70 FLASH_READ_FAIL = 0x71 + INVALID_SLOT_TYPE = 0x72 + INVALID_PARAMS = 0x73 def __str__(self): if self == Status.HF_TAG_OK: @@ -167,6 +171,10 @@ def __str__(self): return "Flash write failed" elif self == Status.FLASH_READ_FAIL: return "Flash read failed" + elif self == Status.INVALID_SLOT_TYPE: + return "Invalid card type in slot" + elif self == Status.INVALID_PARAMS: + return "Invalid command parameters" return "Invalid status" From eafa14e4e573aba050963ba3e4afc008a334c42b Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 03:00:02 +0300 Subject: [PATCH 26/60] Add `hf mfu eview` command. --- software/script/chameleon_cli_unit.py | 27 +++++++++++++++++++++++++++ software/script/chameleon_cmd.py | 1 - 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index c0a1ead7..f0c1085b 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1752,6 +1752,33 @@ def on_exec(self, args: argparse.Namespace): print(f" - Ok") +@hf_mfu.command('eview') +class HFMFUEVIEW(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Ultralight / NTAG view emulator data' + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + nr_pages = self.cmd.mfu_get_emu_pages_count() + page = 0 + while page < nr_pages: + count = min(nr_pages - page, 16) + data = self.cmd.mfu_read_emu_page_data(page, count) + for i in range(0, len(data), 4): + print(f"#{page+(i>>2):02x}: {data[i:i+4].hex()}") + page += count + + @hf_mfu.command('rcnt') class HFMFURCNT(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index ac46ee69..c38208e3 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -582,7 +582,6 @@ def mfu_get_emu_pages_count(self): resp = self.device.send_cmd_sync(Command.MF0_NTAG_READ_EMU_PAGE_DATA, data) if len(resp.data) > 0: resp.parsed = resp.data[0] - print(resp.data) return resp @expect_response(Status.SUCCESS) From efeaf1d95c757b44a874ff44e65bd373bf85ae18 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 03:43:08 +0300 Subject: [PATCH 27/60] Add support for VCSL command. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 06504052..db4f68a8 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -75,6 +75,8 @@ NRF_LOG_MODULE_REGISTER(); #define CONF_AUTH0_BYTE 0x03 #define CONF_PWD_PAGE_OFFSET 2 #define CONF_PACK_PAGE_OFFSET 3 +#define CONF_VCTID_PAGE_OFFSET 1 +#define CONF_VCTID_PAGE_BYTE 1 #define MIRROR_BYTE_BYTE_MASK 0x30 #define MIRROR_BYTE_BYTE_SHIFT 4 @@ -830,6 +832,25 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) case CMD_INCR_CNT: handle_incr_cnt_command(block_num, &p_data[2]); break; + case CMD_VCSL: { + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + if (szDataBits < 168) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; + } + break; + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; + } + + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + m_tag_tx_buffer.tx_buffer[0] = m_tag_information->memory[first_cfg_page + CONF_VCTID_PAGE_OFFSET][CONF_VCTID_PAGE_BYTE]; + + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); + } default: nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); break; From 18d5da09dd765b95129eed917e90cf4b60bc94f8 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 16:04:16 +0300 Subject: [PATCH 28/60] Add `hf mfu signature` command. --- software/script/chameleon_cli_unit.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index f0c1085b..f5593a41 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1954,6 +1954,27 @@ def on_exec(self, args: argparse.Namespace): print(f" - Data: {resp[:8].hex()}") +@hf_mfu.command('signature') +class HFMFUSIGNATURE(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Request MIFARE Ultralight / NTAG ECC signature data.' + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x3C)) + print(f" - Data: {resp[:32].hex()}") + + @hf_mfu.command('econfig') class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: From 297cb061cbf76038d9367833f8be888c7dee9b49 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 16:30:38 +0300 Subject: [PATCH 29/60] Add support for custom version and signature data for MF0 / NTAG emulator. --- docs/protocol.md | 16 ++ firmware/application/src/app_cmd.c | 38 ++++ firmware/application/src/data_cmd.h | 4 + .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 178 +++++++++++++----- .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 25 ++- software/script/chameleon_cli_unit.py | 50 ++++- software/script/chameleon_cmd.py | 24 +++ software/script/chameleon_enum.py | 6 +- 8 files changed, 284 insertions(+), 57 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 0b41523d..4c5e05ca 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -404,6 +404,22 @@ Notes: * Command: 2 + `n * 4` bytes: one for first page index, one for count of pages to be read, `n * 4` for `n` pages data. * Response: no data * CLI: unused +### 4023: MF0_NTAG_GET_VERSION_DATA +* Command: no data +* Response: 8 version data bytes. +* CLI: cf `hf mfu econfig` +### 4024: MF0_NTAG_SET_VERSION_DATA +* Command: 8 version data bytes. +* Response: no data +* CLI: cf `hf mfu econfig --set-version ` +### 4025: MF0_NTAG_GET_SIGNATURE_DATA +* Command: no data +* Response: 32 signature data bytes. +* CLI: cf `hf mfu econfig` +### 4026: MF0_NTAG_SET_SIGNATURE_DATA +* Command: 32 signature data bytes. +* Response: no data +* CLI: cf `hf mfu econfig --set-signature ` ### 5000: EM410X_SET_EMU_ID * Command: 5 bytes. `id[5]`. ID as 5 bytes. * Response: no data diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index e6bdae5c..93d74df9 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -849,6 +849,40 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_read_emu_page_data(uint16_t cmd, return data_frame_make(cmd, STATUS_SUCCESS, pages_count * NFC_TAG_MF0_NTAG_DATA_SIZE, &info->memory[page_index][0]); } +static data_frame_tx_t *cmd_processor_mf0_ntag_get_version_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t *version_data = nfc_tag_mf0_ntag_get_version_data(); + if (version_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + return data_frame_make(cmd, STATUS_SUCCESS, NFC_TAG_MF0_NTAG_VER_SIZE, version_data); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_version_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 8) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 0, NULL); + + uint8_t *version_data = nfc_tag_mf0_ntag_get_version_data(); + if (version_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + memcpy(version_data, data, NFC_TAG_MF0_NTAG_VER_SIZE); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_get_signature_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t *signature_data = nfc_tag_mf0_ntag_get_signature_data(); + if (signature_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + return data_frame_make(cmd, STATUS_SUCCESS, NFC_TAG_MF0_NTAG_SIG_SIZE, signature_data); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_signature_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != NFC_TAG_MF0_NTAG_SIG_SIZE) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 0, NULL); + + uint8_t *signature_data = nfc_tag_mf0_ntag_get_signature_data(); + if (signature_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + memcpy(signature_data, data, NFC_TAG_MF0_NTAG_SIG_SIZE); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct @@ -1190,6 +1224,10 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL }, { DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_read_emu_page_data, NULL }, { DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_write_emu_page_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_get_version_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_set_version_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_get_signature_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_set_signature_data, NULL }, { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 2f5b7c93..fae143ac 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -110,6 +110,10 @@ #define DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE (4020) #define DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA (4021) #define DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA (4022) +#define DATA_CMD_MF0_NTAG_GET_VERSION_DATA (4023) +#define DATA_CMD_MF0_NTAG_SET_VERSION_DATA (4024) +#define DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA (4025) +#define DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA (4026) // // ****************************************************************** diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index db4f68a8..84115527 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -100,6 +100,7 @@ NRF_LOG_MODULE_REGISTER(); // SIGNATURE Length #define SIGNATURE_LENGTH 32 +#define PAGES_PER_VERSION (NFC_TAG_MF0_NTAG_VER_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) // Since all counters are 24-bit and each currently supported tag that supports counters // has password authentication we store the auth attempts counter in the last bit of the @@ -174,7 +175,7 @@ static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { return nr_pages; } -static int get_nr_mem_pages_by_tag_type(tag_specific_type_t tag_type) { +static int get_total_pages_by_tag_type(tag_specific_type_t tag_type) { int nr_pages = 0; switch (tag_type) { @@ -185,19 +186,19 @@ static int get_nr_mem_pages_by_tag_type(tag_specific_type_t tag_type) { nr_pages = MF0ICU2_PAGES; break; case TAG_TYPE_MF0UL11: - nr_pages = MF0UL11_PAGES_WITH_CTRS; + nr_pages = MF0UL11_TOTAL_PAGES; break; case TAG_TYPE_MF0UL21: - nr_pages = MF0UL21_PAGES_WITH_CTRS; + nr_pages = MF0UL21_TOTAL_PAGES; break; case TAG_TYPE_NTAG_213: - nr_pages = NTAG213_PAGES_WITH_CTR; + nr_pages = NTAG213_TOTAL_PAGES; break; case TAG_TYPE_NTAG_215: - nr_pages = NTAG215_PAGES_WITH_CTR; + nr_pages = NTAG215_TOTAL_PAGES; break; case TAG_TYPE_NTAG_216: - nr_pages = NTAG216_PAGES_WITH_CTR; + nr_pages = NTAG216_TOTAL_PAGES; break; default: ASSERT(false); @@ -260,51 +261,98 @@ static bool is_ntag() { } } -static void handle_get_version_command() { - NRF_LOG_DEBUG("handling GET_VERSION"); +int get_version_page_by_tag_type(tag_specific_type_t tag_type) { + int version_page_off; + + switch (tag_type) { + case TAG_TYPE_MF0UL11: + version_page_off = MF0UL11_PAGES + MF0ULx1_NUM_CTRS; + break; + case TAG_TYPE_MF0UL21: + version_page_off = MF0UL21_PAGES + MF0ULx1_NUM_CTRS; + break; + case TAG_TYPE_NTAG_213: + version_page_off = NTAG213_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_215: + version_page_off = NTAG215_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_216: + version_page_off = NTAG216_PAGES + NTAG_NUM_CTRS; + break; + default: + version_page_off = -1; + break; + } + + return version_page_off; +} + +int get_signature_page_by_tag_type(tag_specific_type_t tag_type) { + int version_page_off; switch (m_tag_type) { case TAG_TYPE_MF0UL11: - m_tag_tx_buffer.tx_buffer[6] = MF0UL11_VERSION_STORAGE_SIZE; - m_tag_tx_buffer.tx_buffer[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + version_page_off = MF0UL11_PAGES + MF0ULx1_NUM_CTRS + PAGES_PER_VERSION; break; case TAG_TYPE_MF0UL21: - m_tag_tx_buffer.tx_buffer[6] = MF0UL21_VERSION_STORAGE_SIZE; - m_tag_tx_buffer.tx_buffer[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + version_page_off = MF0UL21_PAGES + MF0ULx1_NUM_CTRS + PAGES_PER_VERSION; break; case TAG_TYPE_NTAG_213: - m_tag_tx_buffer.tx_buffer[6] = NTAG213_VERSION_STORAGE_SIZE; - m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; + version_page_off = NTAG213_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; break; case TAG_TYPE_NTAG_215: - m_tag_tx_buffer.tx_buffer[6] = NTAG215_VERSION_STORAGE_SIZE; - m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; + version_page_off = NTAG215_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; break; case TAG_TYPE_NTAG_216: - m_tag_tx_buffer.tx_buffer[6] = NTAG216_VERSION_STORAGE_SIZE; - m_tag_tx_buffer.tx_buffer[2] = NTAG_VERSION_PRODUCT_TYPE; + version_page_off = NTAG216_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; break; default: - NRF_LOG_WARNING("current card type does not support GET_VERSION"); - // MF0ICU1 and MF0ICU2 do not support GET_VERSION - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - return; + version_page_off = -1; + break; } - m_tag_tx_buffer.tx_buffer[0] = VERSION_FIXED_HEADER; - m_tag_tx_buffer.tx_buffer[1] = VERSION_VENDOR_ID; - m_tag_tx_buffer.tx_buffer[3] = VERSION_PRODUCT_SUBTYPE_50pF; // TODO: make configurable for MF0ULx1 - m_tag_tx_buffer.tx_buffer[4] = VERSION_MAJOR_PRODUCT; - m_tag_tx_buffer.tx_buffer[5] = VERSION_MINOR_PRODUCT; - m_tag_tx_buffer.tx_buffer[7] = VERSION_PROTOCOL_TYPE; + return version_page_off; +} + +uint8_t *nfc_tag_mf0_ntag_get_version_data() { + int version_page = get_version_page_by_tag_type(m_tag_type); + + if (version_page > 0) return &m_tag_information->memory[version_page][0]; + else return NULL; +} + +uint8_t *nfc_tag_mf0_ntag_get_signature_data() { + int signature_page = get_signature_page_by_tag_type(m_tag_type); + + if (signature_page > 0) return &m_tag_information->memory[signature_page][0]; + else return NULL; +} + +static void handle_get_version_command() { + int version_page = get_version_page_by_tag_type(m_tag_type); + + if (version_page > 0) { + memcpy(m_tag_tx_buffer.tx_buffer, &m_tag_information->memory[version_page][0], NFC_TAG_MF0_NTAG_VER_SIZE); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, NFC_TAG_MF0_NTAG_VER_SIZE, true); + } else { + NRF_LOG_WARNING("current card type does not support GET_VERSION"); + // MF0ICU1 and MF0ICU2 do not support GET_VERSION + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + } +} - NRF_LOG_INFO( - "replying with %08x%08x", - U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[0]), - U32HTONL(*(uint32_t *)&m_tag_tx_buffer.tx_buffer[1]) - ); +static void handle_read_sig_command() { + int signature_page = get_signature_page_by_tag_type(m_tag_type); - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); + if (signature_page > 0) { + memcpy(m_tag_tx_buffer.tx_buffer, &m_tag_information->memory[signature_page][0], NFC_TAG_MF0_NTAG_SIG_SIZE); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, NFC_TAG_MF0_NTAG_SIG_SIZE, true); + } else { + NRF_LOG_WARNING("current card type does not support READ_SIG"); + // MF0ICU1 and MF0ICU2 do not support READ_SIG + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + } } static int mirror_size_for_mode(uint8_t mirror_mode) { @@ -360,23 +408,23 @@ static uint8_t *get_counter_data_by_index(uint8_t index) { switch (m_tag_type) { case TAG_TYPE_MF0UL11: ctr_page_off = MF0UL11_PAGES; - ctr_page_end = MF0UL11_PAGES_WITH_CTRS; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; break; case TAG_TYPE_MF0UL21: ctr_page_off = MF0UL21_PAGES; - ctr_page_end = MF0UL21_PAGES_WITH_CTRS; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; break; case TAG_TYPE_NTAG_213: ctr_page_off = NTAG213_PAGES; - ctr_page_end = NTAG213_PAGES_WITH_CTR; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; break; case TAG_TYPE_NTAG_215: ctr_page_off = NTAG215_PAGES; - ctr_page_end = NTAG215_PAGES_WITH_CTR; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; break; case TAG_TYPE_NTAG_216: ctr_page_off = NTAG216_PAGES; - ctr_page_end = NTAG216_PAGES_WITH_CTR; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; break; default: nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); @@ -723,11 +771,11 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { switch (m_tag_type) { case TAG_TYPE_MF0UL11: ctr_page_off = MF0UL11_PAGES; - ctr_page_end = MF0UL11_PAGES_WITH_CTRS; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; break; case TAG_TYPE_MF0UL21: ctr_page_off = MF0UL21_PAGES; - ctr_page_end = MF0UL21_PAGES_WITH_CTRS; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; break; default: nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); @@ -823,8 +871,7 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) break; } case CMD_READ_SIG: - memset(m_tag_tx_buffer.tx_buffer, 0xCA, SIGNATURE_LENGTH); - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, SIGNATURE_LENGTH, true); + handle_read_sig_command(); break; case CMD_READ_CNT: handle_read_cnt_command(block_num); @@ -875,7 +922,7 @@ static void nfc_tag_mf0_ntag_reset_handler() { } static int get_information_size_by_tag_type(tag_specific_type_t type) { - return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_mf0_ntag_configure_t) + (get_nr_mem_pages_by_tag_type(type) * NFC_TAG_MF0_NTAG_DATA_SIZE); + return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_mf0_ntag_configure_t) + (get_total_pages_by_tag_type(type) * NFC_TAG_MF0_NTAG_DATA_SIZE); } /** @brief MF0/NTAG callback before saving data @@ -980,6 +1027,49 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { } } + int version_page = get_version_page_by_tag_type(tag_type); + if (version_page > 0) { + uint8_t *version_data = &p_ntag_information->memory[version_page][0]; + + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + version_data[6] = MF0UL11_VERSION_STORAGE_SIZE; + version_data[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_MF0UL21: + version_data[6] = MF0UL21_VERSION_STORAGE_SIZE; + version_data[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_213: + version_data[6] = NTAG213_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_215: + version_data[6] = NTAG215_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_216: + version_data[6] = NTAG216_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + default: + ASSERT(false); + break; + } + + version_data[0] = VERSION_FIXED_HEADER; + version_data[1] = VERSION_VENDOR_ID; + version_data[3] = VERSION_PRODUCT_SUBTYPE_50pF; // TODO: make configurable for MF0ULx1 + version_data[4] = VERSION_MAJOR_PRODUCT; + version_data[5] = VERSION_MINOR_PRODUCT; + version_data[7] = VERSION_PROTOCOL_TYPE; + } + + int signature_page = get_signature_page_by_tag_type(tag_type); + if (signature_page > 0) { + memset(&p_ntag_information->memory[signature_page][0], 0, NFC_TAG_MF0_NTAG_SIG_SIZE); + } + // default ntag auto ant-collision res p_ntag_information->res_coll.atqa[0] = 0x44; p_ntag_information->res_coll.atqa[1] = 0x00; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index e23bb470..b8518024 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -4,29 +4,36 @@ #include "nfc_14a.h" #define NFC_TAG_MF0_NTAG_DATA_SIZE 4 - -#define NFC_TAG_NTAG_FRAME_SIZE 64 -#define NFC_TAG_NTAG_BLOCK_MAX 231 + 1 +#define NFC_TAG_MF0_NTAG_SIG_SIZE 32 +#define NFC_TAG_MF0_NTAG_VER_SIZE 8 +#define NFC_TAG_MF0_NTAG_SIG_PAGES (NFC_TAG_MF0_NTAG_SIG_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) +#define NFC_TAG_MF0_NTAG_VER_PAGES (NFC_TAG_MF0_NTAG_VER_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) #define NFC_TAG_MF0_FRAME_SIZE (16 + NFC_TAG_14A_CRC_LENGTH) #define NFC_TAG_MF0_BLOCK_MAX 41 #define MF0ULx1_NUM_CTRS 3 // number of Ultralight EV1 one-way counters +#define NTAG_NUM_CTRS 1 // number of NTAG one-way counters + +#define MF0ULx1_EXTRA_PAGES (MF0ULx1_NUM_CTRS + NFC_TAG_MF0_NTAG_VER_PAGES + NFC_TAG_MF0_NTAG_SIG_PAGES) +#define NTAG_EXTRA_PAGES (NTAG_NUM_CTRS + NFC_TAG_MF0_NTAG_VER_PAGES + NFC_TAG_MF0_NTAG_SIG_PAGES) #define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 -#define NTAG213_PAGES_WITH_CTR (NTAG213_PAGES + 1) // 1 more page for the counter +#define NTAG213_TOTAL_PAGES (NTAG213_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter #define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 -#define NTAG215_PAGES_WITH_CTR (NTAG215_PAGES + 1) // 1 more page for the counter +#define NTAG215_TOTAL_PAGES (NTAG215_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter #define NTAG216_PAGES 231 //231 pages total for ntag216, from 0 to 230 -#define NTAG216_PAGES_WITH_CTR (NTAG216_PAGES + 1) // 1 more page for the counter +#define NTAG216_TOTAL_PAGES (NTAG216_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter #define MF0ICU1_PAGES 16 //16 pages total for MF0ICU1 (the original UL), from 0 to 15 #define MF0ICU2_PAGES 36 //16 pages total for MF0ICU2 (UL C), from 0 to 35 #define MF0UL11_PAGES 20 //20 pages total for MF0UL11 (UL EV1), from 0 to 19 -#define MF0UL11_PAGES_WITH_CTRS (MF0UL11_PAGES + MF0ULx1_NUM_CTRS) // 3 more pages for 3 one way counters +#define MF0UL11_TOTAL_PAGES (MF0UL11_PAGES + MF0ULx1_EXTRA_PAGES) // 3 more pages for 3 one way counters #define MF0UL21_PAGES 41 //231 pages total for MF0UL21 (UL EV1), from 0 to 40 -#define MF0UL21_PAGES_WITH_CTRS (MF0UL21_PAGES + MF0ULx1_NUM_CTRS) // 3 more pages for 3 one way counters +#define MF0UL21_TOTAL_PAGES (MF0UL21_PAGES + MF0ULx1_EXTRA_PAGES) // 3 more pages for 3 one way counters +#define NFC_TAG_NTAG_FRAME_SIZE 64 +#define NFC_TAG_NTAG_BLOCK_MAX NTAG216_TOTAL_PAGES typedef struct { uint8_t mode_uid_magic: 1; @@ -53,6 +60,8 @@ int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *bu int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type); +uint8_t *nfc_tag_mf0_ntag_get_version_data(void); +uint8_t *nfc_tag_mf0_ntag_get_signature_data(void); int nfc_tag_mf0_ntag_get_uid_mode(void); bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index f5593a41..8fb7f6f1 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1934,9 +1934,9 @@ def on_exec(self, args: argparse.Namespace): @hf_mfu.command('version') -class HFMFUVERSION(MFUAuthArgsUnit): +class HFMFUVERSION(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: - parser = super().args_parser() + parser = ArgumentParserNoExit() parser.description = 'Request MIFARE Ultralight / NTAG version data.' return parser @@ -1985,9 +1985,39 @@ def args_parser(self) -> ArgumentParserNoExit: uid_magic_group = parser.add_mutually_exclusive_group() uid_magic_group.add_argument('--enable-uid-magic', action='store_true', help="Enable UID magic mode") uid_magic_group.add_argument('--disable-uid-magic', action='store_true', help="Disable UID magic mode") + parser.add_argument('--set-version', type=bytes.fromhex, help="Set data to be returned by the GET_VERSION command.") + parser.add_argument('--set-signature', type=bytes.fromhex, help="Set data to be returned by the READ_SIG command.") return parser def on_exec(self, args: argparse.Namespace): + aux_data_changed = False + + if args.set_version is not None: + aux_data_changed = True + + if len(args.set_version) != 8: + print(f"{CR}Version data should be 8 bytes long.{C0}") + return + + try: + self.cmd.mf0_ntag_set_version_data(args.set_version) + except: + print(f"{CR}Tag type does not support GET_VERSION command.{C0}") + return + + if args.set_signature is not None: + aux_data_changed = True + + if len(args.set_signature) != 32: + print(f"{CR}Signature data should be 32 bytes long.{C0}") + return + + try: + self.cmd.mf0_ntag_set_signature_data(args.set_signature) + except: + print(f"{CR}Tag type does not support READ_SIG command.{C0}") + return + # collect current settings anti_coll_data = self.cmd.hf14a_get_anti_coll_data() if len(anti_coll_data) == 0: @@ -2024,9 +2054,9 @@ def on_exec(self, args: argparse.Namespace): else: magic_mode = self.cmd.mf0_ntag_get_uid_magic_mode() - if change_done: + if change_done or aux_data_changed: print(' - MFU/NTAG Emulator settings updated') - if not change_requested: + if not (change_requested or aux_data_changed): print(f'- {"Type:":40}{CY}{hf_tag_type}{C0}') print(f'- {"UID:":40}{CY}{uid.hex().upper()}{C0}') print(f'- {"ATQA:":40}{CY}{atqa.hex().upper()} ' @@ -2038,6 +2068,18 @@ def on_exec(self, args: argparse.Namespace): print(f'- {"UID Magic:":40}{CY}enabled{C0}') else: print(f'- {"UID Magic:":40}{CY}disabled{C0}') + + try: + version = self.cmd.mf0_ntag_get_version_data() + print(f'- {"Version:":40}{CY}{version.hex().upper()}{C0}') + except: + pass + + try: + signature = self.cmd.mf0_ntag_get_signature_data() + print(f'- {"Signature:":40}{CY}{signature.hex().upper()}{C0}') + except: + pass @lf_em_410x.command('read') diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index c38208e3..af5d21d1 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -951,6 +951,30 @@ def mf0_ntag_get_uid_magic_mode(self): def mf0_ntag_set_uid_magic_mode(self, enabled: bool): return self.device.send_cmd_sync(Command.MF0_NTAG_SET_UID_MAGIC_MODE, struct.pack('?', enabled)) + @expect_response(Status.SUCCESS) + def mf0_ntag_get_version_data(self): + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_VERSION_DATA) + if resp.status == Status.SUCCESS: + resp.parsed = resp.data[:8] + return resp + + @expect_response(Status.SUCCESS) + def mf0_ntag_set_version_data(self, data: bytes): + assert len(data) == 8 + return self.device.send_cmd_sync(Command.MF0_NTAG_SET_VERSION_DATA, data) + + @expect_response(Status.SUCCESS) + def mf0_ntag_get_signature_data(self): + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_SIGNATURE_DATA) + if resp.status == Status.SUCCESS: + resp.parsed = resp.data[:32] + return resp + + @expect_response(Status.SUCCESS) + def mf0_ntag_set_signature_data(self, data: bytes): + assert len(data) == 32 + return self.device.send_cmd_sync(Command.MF0_NTAG_SET_SIGNATURE_DATA, data) + @expect_response(Status.SUCCESS) def get_ble_pairing_enable(self): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index 221ca8af..c88bd54b 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -98,7 +98,11 @@ class Command(enum.IntEnum): MF0_NTAG_GET_UID_MAGIC_MODE = 4019 MF0_NTAG_SET_UID_MAGIC_MODE = 4020 MF0_NTAG_READ_EMU_PAGE_DATA = 4021 - MF0_NTAG_WRITE_EMU_PAGE_DATA = 4023 + MF0_NTAG_WRITE_EMU_PAGE_DATA = 4022 + MF0_NTAG_GET_VERSION_DATA = 4023 + MF0_NTAG_SET_VERSION_DATA = 4024 + MF0_NTAG_GET_SIGNATURE_DATA = 4025 + MF0_NTAG_SET_SIGNATURE_DATA = 4026 EM410X_SET_EMU_ID = 5000 EM410X_GET_EMU_ID = 5001 From 780e594fe2a0f5ec449d9fc84ebe0c0c6feb9afa Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 16:31:18 +0300 Subject: [PATCH 30/60] Handle VCSL command in a separate function. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 84115527..59911abf 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -838,6 +838,26 @@ static void handle_pwd_auth_command(uint8_t *p_data) { } } +static void handle_vcsl_command(uint16_t szDataBits) { + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + if (szDataBits < 168) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; + } + break; + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; + } + + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + m_tag_tx_buffer.tx_buffer[0] = m_tag_information->memory[first_cfg_page + CONF_VCTID_PAGE_OFFSET][CONF_VCTID_PAGE_BYTE]; + + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); +} + static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { uint8_t command = p_data[0]; uint8_t block_num = p_data[1]; @@ -880,23 +900,8 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) handle_incr_cnt_command(block_num, &p_data[2]); break; case CMD_VCSL: { - switch (m_tag_type) { - case TAG_TYPE_MF0UL11: - case TAG_TYPE_MF0UL21: - if (szDataBits < 168) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - break; - } - break; - default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - break; - } - - int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); - m_tag_tx_buffer.tx_buffer[0] = m_tag_information->memory[first_cfg_page + CONF_VCTID_PAGE_OFFSET][CONF_VCTID_PAGE_BYTE]; - - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); + handle_vcsl_command(szDataBits); + break; } default: nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); From 94474ee8aea944aeb2e0d7e484ee664c8c63945d Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Fri, 5 Jul 2024 16:32:40 +0300 Subject: [PATCH 31/60] Remove the MF0ICU1 test entry in factory data initialization. --- firmware/application/src/rfid/nfctag/tag_emulation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index a273d840..b7cd281d 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -71,7 +71,7 @@ static tag_slot_config_t slotConfig ALIGN_U32 = { // See tag_emulation_factory_init for actual tag content .slots = { { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 - { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MF0ICU1, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 + { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 { .enabled_hf = false, .enabled_lf = true, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_EM410X, }, // 3 { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 4 { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 5 From ddcffba26fc45b3e2ab7a807bf0fddd0ee02131f Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 7 Jul 2024 01:14:43 +0300 Subject: [PATCH 32/60] Fix a bug in `hf mfu signature` command. --- software/script/chameleon_cli_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 8fb7f6f1..5de73e4e 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1971,7 +1971,7 @@ def on_exec(self, args: argparse.Namespace): 'check_response_crc': 1, } - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x3C)) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x3C, 0x00)) print(f" - Data: {resp[:32].hex()}") From b5d6ad8858d3a255aa29b2f60352968571b46e6e Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 7 Jul 2024 01:39:53 +0300 Subject: [PATCH 33/60] Prevent sending NACKs when retrieving counter data for unsupported tags. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 1 - 1 file changed, 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 59911abf..f4892038 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -427,7 +427,6 @@ static uint8_t *get_counter_data_by_index(uint8_t index) { ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; break; default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return NULL; } From cb41662ea43b4c4d8a8fff0b796dbcc772f42046 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 7 Jul 2024 02:03:58 +0300 Subject: [PATCH 34/60] Add support for CHECK_TEARING_EVENT command. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index f4892038..f50f2bc6 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -104,8 +104,11 @@ NRF_LOG_MODULE_REGISTER(); // Since all counters are 24-bit and each currently supported tag that supports counters // has password authentication we store the auth attempts counter in the last bit of the -// first counter. +// first counter. AUTHLIM is only 3 bits though so we reserve 4 bits just to be sure and +// use the top bit for tearing event flag. #define AUTHLIM_OFF_IN_CTR 3 +#define AUTHLIM_MASK_IN_CTR 0xF +#define TEARING_MASK_IN_AUTHLIM 0x80 // Values for MIRROR_CONF #define MIRROR_CONF_DISABLED 0 @@ -791,14 +794,20 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { uint32_t incr_value = ((uint32_t)p_data[0] << 16) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2]); uint32_t cnt = ((uint32_t)cnt_data[0] << 16) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2]); - if ((0xFFFFFF - cnt) < incr_value) cnt = 0xFFFFFF; - else cnt += incr_value; + if ((0xFFFFFF - cnt) < incr_value) { + // set tearing event flag + cnt_data[AUTHLIM_OFF_IN_CTR] |= TEARING_MASK_IN_AUTHLIM; + + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + } else { + cnt += incr_value; - cnt_data[0] = (uint8_t)(cnt >> 16); - cnt_data[1] = (uint8_t)(cnt >> 8); - cnt_data[2] = (uint8_t)(cnt & 0xff); + cnt_data[0] = (uint8_t)(cnt >> 16); + cnt_data[1] = (uint8_t)(cnt >> 8); + cnt_data[2] = (uint8_t)(cnt & 0xff); - nfc_tag_14a_tx_nbit(ACK_VALUE, 4); + nfc_tag_14a_tx_nbit(ACK_VALUE, 4); + } } static void handle_pwd_auth_command(uint8_t *p_data) { @@ -810,7 +819,7 @@ static void handle_pwd_auth_command(uint8_t *p_data) { } // check AUTHLIM counter - uint8_t auth_cnt = cnt_data[AUTHLIM_OFF_IN_CTR]; + uint8_t auth_cnt = cnt_data[AUTHLIM_OFF_IN_CTR] & AUTHLIM_MASK_IN_CTR; uint8_t auth_lim = m_tag_information->memory[first_cfg_page + 1][0] & CONF_ACCESS_AUTHLIM_MASK; if ((auth_lim > 0) && (auth_lim <= auth_cnt)) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); @@ -820,7 +829,7 @@ static void handle_pwd_auth_command(uint8_t *p_data) { uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET]; uint32_t supplied_pwd = *(uint32_t *)&p_data[1]; if (pwd != supplied_pwd) { - if (auth_lim) cnt_data[AUTHLIM_OFF_IN_CTR] = auth_cnt + 1; + if (auth_lim) cnt_data[AUTHLIM_OFF_IN_CTR] |= (auth_cnt + 1) & AUTHLIM_MASK_IN_CTR; nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } @@ -837,6 +846,27 @@ static void handle_pwd_auth_command(uint8_t *p_data) { } } +static void handle_check_tearing_event(int index) { + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: { + uint8_t *ctr_data = get_counter_data_by_index(index); + + if (ctr_data) { + m_tag_tx_buffer.tx_buffer[0] = (ctr_data[AUTHLIM_OFF_IN_CTR] & TEARING_MASK_IN_AUTHLIM) == 0 ? 0xBD : 0x00; + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); + } else { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + } + + break; + } + default: + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + break; + } +} + static void handle_vcsl_command(uint16_t szDataBits) { switch (m_tag_type) { case TAG_TYPE_MF0UL11: @@ -898,6 +928,9 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) case CMD_INCR_CNT: handle_incr_cnt_command(block_num, &p_data[2]); break; + case CMD_CHECK_TEARING_EVENT: + handle_check_tearing_event(block_num); + break; case CMD_VCSL: { handle_vcsl_command(szDataBits); break; From 3ff1c785a51b8e89a79328ea1c8bca4802ca3226 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 7 Jul 2024 02:09:48 +0300 Subject: [PATCH 35/60] Fix integer conversion bugs related to `nfc_tag_mf0_ntag_get_nr_pages_by_tag_type`. --- firmware/application/src/app_cmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 93d74df9..f5847462 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -796,7 +796,7 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, tag_slot_specific_type_t active_slot_tag_types; tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); - uint8_t nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); // This means wrong slot type. if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); @@ -829,7 +829,7 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_read_emu_page_data(uint16_t cmd, tag_slot_specific_type_t active_slot_tag_types; tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); - uint8_t nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); // This means wrong slot type. if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); From 1bf23cdf083a3ee2384526efb593d798bbc630d2 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Sun, 7 Jul 2024 20:09:25 +0300 Subject: [PATCH 36/60] Make `MFUAuthArgsUnit` parse key and swap arguments automatically. --- software/script/chameleon_cli_unit.py | 166 +++++++++----------------- 1 file changed, 59 insertions(+), 107 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 5de73e4e..eae77dcc 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -328,16 +328,42 @@ def update_hf14a_anticoll(self, args, uid, atqa, sak, ats): class MFUAuthArgsUnit(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() - # TODO: - # -k, --key Authentication key (UL-C 16 bytes, EV1/NTAG 4 bytes) - # -l Swap entered key's endianness + + def key_parser(key: str) -> bytes: + try: + key = bytes.fromhex(key) + except: + raise ValueError("Key should be a hex string") + + if len(key) not in [4, 16]: + raise ValueError("Key should either be 4 or 16 bytes long") + elif len(key) == 16: + raise ValueError("Ultralight-C authentication isn't supported yet") + + return key + + parser.add_argument( + '-k', '--key', type=key_parser, metavar="", help="Authentication key (EV1/NTAG 4 bytes)." + ) + parser.add_argument('-l', action='store_true', dest='swap_endian', help="Swap endianness of the key.") + return parser def get_param(self, args): + key = args.key + + if key is not None and args.swap_endian: + key = bytearray(key) + for i in range(len(key)): + tmp = key[i] + key[i] = key[len(key) - 1 - i] + key = bytes(key) + class Param: - def __init__(self): - pass - return Param() + def __init__(self, key): + self.key = key + + return Param(key) def on_exec(self, args: argparse.Namespace): raise NotImplementedError("Please implement this") @@ -1651,25 +1677,8 @@ def args_parser(self) -> ArgumentParserNoExit: parser.description = 'MIFARE Ultralight / NTAG read one page' parser.add_argument('-p', '--page', type=int, required=True, metavar="", help="The page where the key will be used against") - parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", - help="Ultralight EV1 / NTAG password, as a 4 byte (8 character) hex string.") return parser - def get_param(self, args): - if args.pwd is not None: - pwd = bytes.fromhex(args.pwd) - if len(pwd) != 4: - raise LengthError("Invalid MFU EV 1 / NTAG password data length.") - else: - pwd = None - - class Param: - def __init__(self): - self.page = args.page - self.pwd = pwd - - return Param() - def on_exec(self, args: argparse.Namespace): param = self.get_param(args) @@ -1682,14 +1691,14 @@ def on_exec(self, args: argparse.Namespace): 'check_response_crc': 1, } - if param.pwd is not None: + if param.key is not None: options['keep_rf_field'] = 1 - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) options['keep_rf_field'] = 0 options['auto_select'] = 0 print(f" - PACK: {resp[:2].hex()}") - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page)) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) print(f" - Data: {resp[:4].hex()}") @@ -1700,37 +1709,16 @@ def args_parser(self) -> ArgumentParserNoExit: parser.description = 'MIFARE Ultralight / NTAG write one page' parser.add_argument('-p', '--page', type=int, required=True, metavar="", help="The index of the page to write to.") - parser.add_argument('-d', '--data', type=str, required=True, metavar="", + parser.add_argument('-d', '--data', type=bytes.fromhex, required=True, metavar="", help="Your page data, as a 4 byte (8 character) hex string.") - parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", - help="Ultralight EV1 / NTAG password, as a 4 byte (8 character) hex string.") return parser - def get_param(self, args): - data = bytes.fromhex(args.data) - if len(data) != 4: - raise LengthError("Invalid MF0 / NTAG page data length.") - - if args.pwd is not None: - pwd = bytes.fromhex(args.pwd) - if len(pwd) != 4: - raise LengthError("Invalid MFU EV 1 / NTAG password data length.") - else: - pwd = None - - class Param: - def __init__(self): - self.page = args.page - self.data = data - self.pwd = pwd - - return Param() - def on_exec(self, args: argparse.Namespace): - try: - param = self.get_param(args) - except: - print(f"{CR}Page data and password should be 4 byte (8 character) hex strings.{C0}") + param = self.get_param(args) + + data = args.data + if len(data) != 4: + print(f"{CR}Page data should be a 4 byte (8 character) hex string{C0}") options = { 'activate_rf_field': 0, @@ -1741,14 +1729,14 @@ def on_exec(self, args: argparse.Namespace): 'check_response_crc': 1, } - if param.pwd is not None: + if param.key is not None: options['keep_rf_field'] = 1 - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) options['keep_rf_field'] = 0 options['auto_select'] = 0 print(f" - PACK: {resp[:2].hex()}") - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, param.page)+param.data) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, args.page)+data) print(f" - Ok") @@ -1786,25 +1774,8 @@ def args_parser(self) -> ArgumentParserNoExit: parser.description = 'MIFARE Ultralight / NTAG read counter' parser.add_argument('-c', '--counter', type=int, required=True, metavar="", help="Index of the counter to read (always 0 for NTAG, 0-2 for Ultralight EV1).") - parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", - help="NTAG password, as a 4 byte (8 character) hex string.") return parser - def get_param(self, args): - if args.pwd is not None: - pwd = bytes.fromhex(args.pwd) - if len(pwd) != 4: - raise LengthError("Invalid MFU EV 1 / NTAG password data length.") - else: - pwd = None - - class Param: - def __init__(self): - self.counter = args.counter - self.pwd = pwd - - return Param() - def on_exec(self, args: argparse.Namespace): param = self.get_param(args) @@ -1817,9 +1788,9 @@ def on_exec(self, args: argparse.Namespace): 'check_response_crc': 1, } - if param.pwd is not None: + if param.key is not None: options['keep_rf_field'] = 1 - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) options['keep_rf_field'] = 0 options['auto_select'] = 0 print(f" - PACK: {resp[:2].hex()}") @@ -1839,42 +1810,23 @@ def args_parser(self) -> ArgumentParserNoExit: help="Manually set number of pages to dump") parser.add_argument('-f', '--file', type=str, required=False, default="", help="Specify a filename for dump file") - parser.add_argument('-P', '--pwd', type=str, required=False, metavar="", - help="Ultralight EV1 / NTAG password, as a 4 byte (8 character) hex string.") return parser - def get_param(self, args): - if args.pwd is not None: - pwd = bytes.fromhex(args.pwd) - if len(pwd) != 4: - raise LengthError("Invalid MFU EV 1 / NTAG password data length.") - else: - pwd = None - - class Param: - def __init__(self): - self.start_page = args.page - - if args.qty is None: - self.stop_page = 256 - else: - self.stop_page = min(args.page + args.qty, 256) - - self.output_file = args.file - self.pwd = pwd - - return Param() - def on_exec(self, args: argparse.Namespace): param = self.get_param(args) fd = None save_as_eml = False - if param.output_file != "": - if param.output_file.endswith('.eml'): - fd = open(param.output_file, 'w+') + if args.file != "": + if args.file.endswith('.eml'): + fd = open(args.file, 'w+') save_as_eml = True else: - fd = open(param.output_file, 'wb+') + fd = open(args.file, 'wb+') + + if args.qty is not None: + stop_page = min(args.page + args.qty, 256) + else: + stop_page = 256 options = { 'activate_rf_field': 0, @@ -1887,15 +1839,15 @@ def on_exec(self, args: argparse.Namespace): pack = None needs_stop = False - for i in range(param.start_page, param.stop_page): + for i in range(args.page, stop_page): # this could be done once in theory but the command would need to be optimized properly - if param.pwd is not None: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.pwd) + if param.key is not None and not needs_stop: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) options['auto_select'] = 0 # prevent resets pack = resp[:2].hex() # disable the rf field after the last command - if i == (param.stop_page - 1) or needs_stop: + if i == (stop_page - 1) or needs_stop: options['keep_rf_field'] = 0 try: From 163b41b95b4bbe41712f8ef9a754948b2a449c1f Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 8 Jul 2024 00:31:20 +0300 Subject: [PATCH 37/60] Fix commands for r/w into UL / NTAG emulator memory. --- firmware/application/src/app_cmd.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index f5847462..947b165f 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -791,6 +791,7 @@ static data_frame_tx_t *cmd_processor_mf1_read_emu_block_data(uint16_t cmd, uint } static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t byte; uint8_t active_slot = tag_emulation_get_slot(); tag_slot_specific_type_t active_slot_tag_types; @@ -800,11 +801,14 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, // This means wrong slot type. if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); - if (length < 2) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + if (length < 2) { + byte = nr_pages; + return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &byte); + } int page_index = data[0]; int pages_count = data[1]; - int byte_length = (int)nr_pages * NFC_TAG_MF0_NTAG_DATA_SIZE; + int byte_length = (int)pages_count * NFC_TAG_MF0_NTAG_DATA_SIZE; if (pages_count == 0) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); else if ( @@ -812,7 +816,8 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, || (pages_count > (((int)nr_pages) - page_index)) || (((int)length - 2) < byte_length) ) { - return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + byte = nr_pages; + return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &byte); } tag_data_buffer_t *buffer = get_buffer_by_tag_type(active_slot_tag_types.tag_hf); @@ -824,6 +829,7 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, } static data_frame_tx_t *cmd_processor_mf0_ntag_read_emu_page_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t byte; uint8_t active_slot = tag_emulation_get_slot(); tag_slot_specific_type_t active_slot_tag_types; @@ -833,14 +839,18 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_read_emu_page_data(uint16_t cmd, // This means wrong slot type. if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); - if (length < 2) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + if (length < 2) { + byte = nr_pages; + return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &byte); + } int page_index = data[0]; int pages_count = data[1]; if (pages_count == 0) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); else if ((page_index >= ((int)nr_pages)) || (pages_count > (((int)nr_pages) - page_index))) { - return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &nr_pages); + byte = nr_pages; + return data_frame_make(cmd, STATUS_INVALID_PARAMS, 1, &byte); } tag_data_buffer_t *buffer = get_buffer_by_tag_type(active_slot_tag_types.tag_hf); From 37f2b0783c8932faa2d476b9481535a990e0def9 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 8 Jul 2024 00:33:33 +0300 Subject: [PATCH 38/60] Add `hf mfu eload` command. --- software/script/chameleon_cli_unit.py | 58 +++++++++++++++++++++++++++ software/script/chameleon_cmd.py | 14 +++++++ 2 files changed, 72 insertions(+) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index eae77dcc..321a1fe7 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1767,6 +1767,64 @@ def on_exec(self, args: argparse.Namespace): page += count +@hf_mfu.command('eload') +class HFMFUELOAD(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Ultralight / NTAG load emulator data' + parser.add_argument( + '-f', '--file', required=True, type=str, help="File to load data from." + ) + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + if args.file.endswith('.eml'): + with open(args.file, 'r') as f: + data = f.read() + data = re.sub('#.*$', '', data, flags=re.MULTILINE) + data = bytes.fromhex(data) + else: + with open(args.file, 'rb') as f: + data = f.read() + + # this will throw an exception on incorrect slot type + nr_pages = self.cmd.mfu_get_emu_pages_count() + size = nr_pages * 4 + if len(data) > size: + print(f"{CR}Dump file is too large for the current slot (expected {size} bytes).{C0}") + return + elif (len(data) % 4) > 0: + print(f"{CR}Dump file's length is not a multiple of 4 bytes.{C0}") + return + elif len(data) < size: + print(f"{CY}Dump file is smaller than the current slot's memory ({len(data)} < {size}).{C0}") + + nr_pages = len(data) >> 2 + page = 0 + while page < nr_pages: + offset = page * 4 + cur_count = min(16, nr_pages - page) + + if offset >= len(data): + page_data = bytes.fromhex("00000000") * cur_count + else: + page_data = data[offset:offset + 4 * cur_count] + + self.cmd.mfu_write_emu_page_data(page, page_data) + page += cur_count + + print(f" - Ok") + + @hf_mfu.command('rcnt') class HFMFURCNT(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index af5d21d1..7ff952f7 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -594,6 +594,20 @@ def mfu_read_emu_page_data(self, page_start: int, page_count: int): resp.parsed = resp.data return resp + @expect_response(Status.SUCCESS) + def mfu_write_emu_page_data(self, page_start: int, data: bytes): + """ + Gets data for selected block range + """ + count = len(data) >> 2 + + assert (len(data) % 4) == 0 + assert (page_start >= 0) and (count + page_start) <= 256 + + data = struct.pack('!BB', page_start, count) + data + resp = self.device.send_cmd_sync(Command.MF0_NTAG_WRITE_EMU_PAGE_DATA, data) + return resp + @expect_response(Status.SUCCESS) def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ From bdcf5d5851b3d054d38914667dfc38c8f7dca674 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 8 Jul 2024 00:34:08 +0300 Subject: [PATCH 39/60] Fix `hf mfu rdpg` not exiting when data is not properly aligned. --- software/script/chameleon_cli_unit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 321a1fe7..f9327d2f 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1719,6 +1719,7 @@ def on_exec(self, args: argparse.Namespace): data = args.data if len(data) != 4: print(f"{CR}Page data should be a 4 byte (8 character) hex string{C0}") + return options = { 'activate_rf_field': 0, From 4aa6b3c0fc4cb8aa0d80639b409f6a52341b2964 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 8 Jul 2024 00:52:39 +0300 Subject: [PATCH 40/60] Fix file output in `hf mfu dump` command. --- software/script/chameleon_cli_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index f9327d2f..3f941bf0 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1940,7 +1940,7 @@ def on_exec(self, args: argparse.Namespace): print(f" - PACK: {pack}") if fd is not None: - print(f" - {CG}Dump written in {param.output_file}.{C0}") + print(f" - {CG}Dump written in {args.file}.{C0}") fd.close() From 103d51ce64a2ad2289dfb6982fdd73ce5a01c9fb Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 8 Jul 2024 01:04:23 +0300 Subject: [PATCH 41/60] Properly detect auth failures in `hf mfu` subcommands. --- software/script/chameleon_cli_unit.py | 109 +++++++++++++++++++++----- 1 file changed, 91 insertions(+), 18 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 3f941bf0..d451dffd 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1693,13 +1693,31 @@ def on_exec(self, args: argparse.Namespace): if param.key is not None: options['keep_rf_field'] = 1 - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + failed_auth = len(resp) < 2 + if not failed_auth: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + failed_auth = True + options['keep_rf_field'] = 0 options['auto_select'] = 0 - print(f" - PACK: {resp[:2].hex()}") + else: + failed_auth = False - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) - print(f" - Data: {resp[:4].hex()}") + if not failed_auth: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) + print(f" - Data: {resp[:4].hex()}") + else: + try: + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) + except: + # we may lose the tag again here + pass + print(f" {CR}- Auth failed{C0}") @hf_mfu.command('wrpg') @@ -1732,13 +1750,32 @@ def on_exec(self, args: argparse.Namespace): if param.key is not None: options['keep_rf_field'] = 1 - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + failed_auth = len(resp) < 2 + if not failed_auth: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + failed_auth = True + options['keep_rf_field'] = 0 options['auto_select'] = 0 - print(f" - PACK: {resp[:2].hex()}") + else: + failed_auth = False - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, args.page)+data) - print(f" - Ok") + if not failed_auth: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, args.page)+data) + print(f" - Ok") + else: + # send a command just to disable the field. use read to avoid corrupting the data + try: + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) + except: + # we may lose the tag again here + pass + print(f" {CR}- Auth failed{C0}") @hf_mfu.command('eview') @@ -1846,16 +1883,34 @@ def on_exec(self, args: argparse.Namespace): 'keep_rf_field': 0, 'check_response_crc': 1, } - + if param.key is not None: options['keep_rf_field'] = 1 - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + failed_auth = len(resp) < 2 + if not failed_auth: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + failed_auth = True + options['keep_rf_field'] = 0 options['auto_select'] = 0 - print(f" - PACK: {resp[:2].hex()}") - - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, param.counter)) - print(f" - Data: {resp[:3].hex()}") + else: + failed_auth = False + + if not failed_auth: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, param.counter)) + print(f" - Data: {resp[:3].hex()}") + else: + try: + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, param.counter)) + except: + # we may lose the tag again here + pass + print(f" {CR}- Auth failed{C0}") @hf_mfu.command('dump') @@ -1896,8 +1951,29 @@ def on_exec(self, args: argparse.Namespace): 'check_response_crc': 1, } - pack = None needs_stop = False + + if param.key is not None: + options['keep_rf_field'] = 1 + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + needs_stop = len(resp) < 2 + if not needs_stop: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + needs_stop = True + + options['auto_select'] = 0 + + # this handles auth failure + if needs_stop: + print(f" {CR}- Auth failed{C0}") + if fd is not None: + fd.close() + fd = None + for i in range(args.page, stop_page): # this could be done once in theory but the command would need to be optimized properly if param.key is not None and not needs_stop: @@ -1936,9 +2012,6 @@ def on_exec(self, args: argparse.Namespace): else: fd.write(data) - if pack is not None: - print(f" - PACK: {pack}") - if fd is not None: print(f" - {CG}Dump written in {args.file}.{C0}") fd.close() From b7a6a3fc07b06277c9e4b27be33d486d4e4eae21 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 8 Jul 2024 01:58:50 +0300 Subject: [PATCH 42/60] Remove amiibo-specific code. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index f50f2bc6..0c37d374 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -125,8 +125,6 @@ NRF_LOG_MODULE_REGISTER(); // 0x11 ntag215 // 0x13 ntag216 const uint8_t ntagVersion[8] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x11, 0x03}; -/* pwd auth for amiibo */ -uint8_t ntagPwdOK[2] = {0x80, 0x80}; // Data structure pointer to the label information static nfc_tag_mf0_ntag_information_t *m_tag_information = NULL; @@ -839,11 +837,7 @@ static void handle_pwd_auth_command(uint8_t *p_data) { m_tag_authenticated = true; // TODO: this should be possible to reset somehow // Send the PACK value back - if (m_tag_information->config.mode_uid_magic) { - nfc_tag_14a_tx_bytes(ntagPwdOK, 2, true); - } else { - nfc_tag_14a_tx_bytes(m_tag_information->memory[first_cfg_page + CONF_PACK_PAGE_OFFSET], 2, true); - } + nfc_tag_14a_tx_bytes(m_tag_information->memory[first_cfg_page + CONF_PACK_PAGE_OFFSET], 2, true); } static void handle_check_tearing_event(int index) { From b5259c313ad803e22e5719510cd6452a424b0390 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 00:57:43 +0300 Subject: [PATCH 43/60] Add `--type` argument to `eload` and `dump` commands. --- software/script/chameleon_cli_unit.py | 174 ++++++++++++++------------ 1 file changed, 97 insertions(+), 77 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index d451dffd..d2eaebd7 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1813,6 +1813,9 @@ def args_parser(self) -> ArgumentParserNoExit: parser.add_argument( '-f', '--file', required=True, type=str, help="File to load data from." ) + parser.add_argument( + '-t', '--type', type=str, required=False, help="Force writing as either raw binary or hex.", choices=['bin', 'hex'] + ) return parser def get_param(self, args): @@ -1823,9 +1826,14 @@ def __init__(self): return Param() def on_exec(self, args: argparse.Namespace): - param = self.get_param(args) - - if args.file.endswith('.eml'): + file_type = args.type + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' + + if file_type == 'hex': with open(args.file, 'r') as f: data = f.read() data = re.sub('#.*$', '', data, flags=re.MULTILINE) @@ -1924,97 +1932,109 @@ def args_parser(self) -> ArgumentParserNoExit: help="Manually set number of pages to dump") parser.add_argument('-f', '--file', type=str, required=False, default="", help="Specify a filename for dump file") + parser.add_argument('-t', '--type', type=str, required=False, choices=['bin', 'hex'], + help="Force writing as either raw binary or hex.") return parser def on_exec(self, args: argparse.Namespace): param = self.get_param(args) + + file_type = args.type fd = None save_as_eml = False - if args.file != "": - if args.file.endswith('.eml'): - fd = open(args.file, 'w+') - save_as_eml = True - else: - fd = open(args.file, 'wb+') - - if args.qty is not None: - stop_page = min(args.page + args.qty, 256) - else: - stop_page = 256 - - options = { - 'activate_rf_field': 0, - 'wait_response': 1, - 'append_crc': 1, - 'auto_select': 1, - 'keep_rf_field': 1, - 'check_response_crc': 1, - } - - needs_stop = False - if param.key is not None: - options['keep_rf_field'] = 1 - try: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' - needs_stop = len(resp) < 2 - if not needs_stop: - print(f" - PACK: {resp[:2].hex()}") - except Exception as e: - # failed auth may cause tags to be lost - needs_stop = True + if file_type == 'hex': + fd = open(args.file, 'w+') + save_as_eml = True + else: + fd = open(args.file, 'wb+') - options['auto_select'] = 0 - - # this handles auth failure - if needs_stop: - print(f" {CR}- Auth failed{C0}") - if fd is not None: - fd.close() - fd = None + with fd: + fd.truncate(0) - for i in range(args.page, stop_page): - # this could be done once in theory but the command would need to be optimized properly - if param.key is not None and not needs_stop: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) - options['auto_select'] = 0 # prevent resets - pack = resp[:2].hex() + if args.qty is not None: + stop_page = min(args.page + args.qty, 256) + else: + stop_page = 256 - # disable the rf field after the last command - if i == (stop_page - 1) or needs_stop: - options['keep_rf_field'] = 0 + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 1, + 'check_response_crc': 1, + } + + needs_stop = False - try: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) - except: - # probably lost tag, but we still need to disable rf field - resp = None + if param.key is not None: + options['keep_rf_field'] = 1 + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) - if needs_stop: - # break if this command was sent just to disable RF field - break - elif resp is None or len(resp) == 0: - # we need to disable RF field if we reached the last valid page so send one more read command - needs_stop = True - continue + needs_stop = len(resp) < 2 + if not needs_stop: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + needs_stop = True - # after the read we are sure we no longer need to select again - options['auto_select'] = 0 + options['auto_select'] = 0 + + # this handles auth failure + if needs_stop: + print(f" {CR}- Auth failed{C0}") + if fd is not None: + fd.close() + fd = None + + for i in range(args.page, stop_page): + # this could be done once in theory but the command would need to be optimized properly + if param.key is not None and not needs_stop: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + options['auto_select'] = 0 # prevent resets + pack = resp[:2].hex() + + # disable the rf field after the last command + if i == (stop_page - 1) or needs_stop: + options['keep_rf_field'] = 0 - # TODO: can be optimized as we get 4 pages at once but beware of wrapping - # in case of end of memory or LOCK on ULC and no key provided - data = resp[:4] - print(f" - Page {i:2}: {data.hex()}") - if fd is not None: - if save_as_eml: - fd.write(data.hex()+'\n') - else: - fd.write(data) + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + except: + # probably lost tag, but we still need to disable rf field + resp = None + + if needs_stop: + # break if this command was sent just to disable RF field + break + elif resp is None or len(resp) == 0: + # we need to disable RF field if we reached the last valid page so send one more read command + needs_stop = True + continue + + # after the read we are sure we no longer need to select again + options['auto_select'] = 0 + + # TODO: can be optimized as we get 4 pages at once but beware of wrapping + # in case of end of memory or LOCK on ULC and no key provided + data = resp[:4] + print(f" - Page {i:2}: {data.hex()}") + if fd is not None: + if save_as_eml: + fd.write(data.hex()+'\n') + else: + fd.write(data) - if fd is not None: + if args.file is not None: print(f" - {CG}Dump written in {args.file}.{C0}") - fd.close() @hf_mfu.command('version') From ff58d97dd925d7782469fe9a65f639185f44263d Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 00:58:07 +0300 Subject: [PATCH 44/60] Add `hf mfu esave` command. --- software/script/chameleon_cli_unit.py | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index d2eaebd7..e6524c07 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1871,6 +1871,82 @@ def on_exec(self, args: argparse.Namespace): print(f" - Ok") +@hf_mfu.command('esave') +class HFMFUESAVE(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Ultralight / NTAG save emulator data' + parser.add_argument( + '-f', '--file', required=True, type=str, help='File to save data to.' + ) + parser.add_argument( + '-t', '--type', type=str, required=False, help="Force writing as either raw binary or hex.", choices=['bin', 'hex'] + ) + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + + return Param() + + def on_exec(self, args: argparse.Namespace): + file_type = args.type + fd = None + save_as_eml = False + + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' + + if file_type == 'hex': + fd = open(args.file, 'w+') + save_as_eml = True + else: + fd = open(args.file, 'wb+') + + with fd: + # this will throw an exception on incorrect slot type + nr_pages = self.cmd.mfu_get_emu_pages_count() + + fd.truncate(0) + + # write version and signature as comments if saving as .eml + if save_as_eml: + try: + version = self.cmd.mf0_ntag_get_version_data() + + fd.write(f"# Version: {version.hex()}\n") + except: + pass # slot does not have version data + + try: + signature = self.cmd.mf0_ntag_get_signature_data() + + if signature != b"\x00" * 32: + fd.write(f"# Signature: {signature.hex()}\n") + except: + pass # slot does not have signature data + + page = 0 + while page < nr_pages: + cur_count = min(32, nr_pages - page) + + data = self.cmd.mfu_read_emu_page_data(page, cur_count) + if save_as_eml: + for i in range(0, len(data), 4): + fd.write(data[i:i+4].hex() + "\n") + else: + fd.write(data) + + page += cur_count + + print(f" - Ok") + + @hf_mfu.command('rcnt') class HFMFURCNT(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: From 0ce920cfcced5c7e4842bdb57c64bc18d21f56a7 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 01:55:06 +0300 Subject: [PATCH 45/60] Add `hf mfu ercnt/ewcnt` commands for reading and writing emulator's counters. --- docs/protocol.md | 8 ++++ firmware/application/src/app_cmd.c | 36 ++++++++++++++++++ firmware/application/src/data_cmd.h | 2 + .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 32 ++++++---------- .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 9 +++++ software/script/chameleon_cli_unit.py | 37 +++++++++++++++++++ software/script/chameleon_cmd.py | 20 ++++++++++ software/script/chameleon_enum.py | 2 + 8 files changed, 126 insertions(+), 20 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 4c5e05ca..3427d7fd 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -420,6 +420,14 @@ Notes: * Command: 32 signature data bytes. * Response: no data * CLI: cf `hf mfu econfig --set-signature ` +### 4027: MF0_NTAG_GET_COUNTER_DATA +* Command: 1 byte for the counter index +* Response: 3 bytes for the counter value (big-endian) + 1 byte for tearing where `0xBD` means tearing flag is not set. +* CLI: cf `hf mfu ercnt` +### 4028: MF0_NTAG_SET_COUNTER_DATA +* Command: 1 byte where the lower 7 bits are the counter index and the top bit indicates whether tearing event flag should be reset + 3 bytes of the counter value (big-endian). +* Response: no data +* CLI: cf `hf mfu ewcnt` ### 5000: EM410X_SET_EMU_ID * Command: 5 bytes. `id[5]`. ID as 5 bytes. * Response: no data diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 947b165f..6820f2e7 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -893,6 +893,40 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_set_signature_data(uint16_t cmd, return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); } +static data_frame_tx_t *cmd_processor_mf0_ntag_get_counter_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 0, NULL); + + uint8_t index = data[0] & 0x7F; + uint8_t *counter_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); + if (counter_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + bool tearing = (counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_AUTHLIM_MASK_IN_CTR) != 0; + + uint8_t response[4]; + memcpy(response, counter_data, 3); + response[3] = tearing ? 0x00 : 0xBD; + + return data_frame_make(cmd, STATUS_SUCCESS, 4, response); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_counter_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 4) return data_frame_make(cmd, STATUS_INVALID_PARAMS, 0, NULL); + + uint8_t index = data[0] & 0x7F; + uint8_t *counter_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); + if (counter_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + // clear tearing event flag + if ((data[0] & 0x80) == 0x80) { + counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_TEARING_MASK_IN_AUTHLIM; + } + + // copy the actual counter value + memcpy(counter_data, &data[1], 3); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct @@ -1238,6 +1272,8 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF0_NTAG_SET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_set_version_data, NULL }, { DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_get_signature_data, NULL }, { DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_set_signature_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_get_counter_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_set_counter_data, NULL }, { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index fae143ac..2000d476 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -114,6 +114,8 @@ #define DATA_CMD_MF0_NTAG_SET_VERSION_DATA (4024) #define DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA (4025) #define DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA (4026) +#define DATA_CMD_MF0_NTAG_GET_COUNTER_DATA (4027) +#define DATA_CMD_MF0_NTAG_SET_COUNTER_DATA (4028) // // ****************************************************************** diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 0c37d374..8adff231 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -102,14 +102,6 @@ NRF_LOG_MODULE_REGISTER(); #define SIGNATURE_LENGTH 32 #define PAGES_PER_VERSION (NFC_TAG_MF0_NTAG_VER_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) -// Since all counters are 24-bit and each currently supported tag that supports counters -// has password authentication we store the auth attempts counter in the last bit of the -// first counter. AUTHLIM is only 3 bits though so we reserve 4 bits just to be sure and -// use the top bit for tearing event flag. -#define AUTHLIM_OFF_IN_CTR 3 -#define AUTHLIM_MASK_IN_CTR 0xF -#define TEARING_MASK_IN_AUTHLIM 0x80 - // Values for MIRROR_CONF #define MIRROR_CONF_DISABLED 0 #define MIRROR_CONF_UID 1 @@ -403,7 +395,7 @@ static int get_user_data_end_by_tag_type(tag_specific_type_t type) { return nr_pages; } -static uint8_t *get_counter_data_by_index(uint8_t index) { +uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { uint8_t ctr_page_off; uint8_t ctr_page_end; switch (m_tag_type) { @@ -489,12 +481,12 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); break; case MIRROR_CONF_CNT: - bytes2hex(get_counter_data_by_index(0), (char *)mirror_buf, 3); + bytes2hex(nfc_tag_mf0_ntag_get_counter_data_by_index(0), (char *)mirror_buf, 3); break; case MIRROR_CONF_UID_CNT: bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); mirror_buf[7] = 'x'; - bytes2hex(get_counter_data_by_index(0), (char *)&mirror_buf[8], 3); + bytes2hex(nfc_tag_mf0_ntag_get_counter_data_by_index(0), (char *)&mirror_buf[8], 3); break; } } @@ -546,7 +538,7 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; if ((access & CONF_ACCESS_NFC_CNT_EN) != 0) { - uint8_t *ctr = get_counter_data_by_index(0); + uint8_t *ctr = nfc_tag_mf0_ntag_get_counter_data_by_index(0); uint32_t counter = (((uint32_t)ctr[0]) << 16) | (((uint32_t)ctr[1]) << 8) | ((uint32_t)ctr[2]); if (counter < 0xFFFFFF) counter += 1; ctr[0] = (uint8_t)(counter >> 16); @@ -755,7 +747,7 @@ static void handle_read_cnt_command(uint8_t index) { } } - uint8_t *cnt_data = get_counter_data_by_index(index); + uint8_t *cnt_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); if (cnt_data == NULL) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; @@ -794,7 +786,7 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { if ((0xFFFFFF - cnt) < incr_value) { // set tearing event flag - cnt_data[AUTHLIM_OFF_IN_CTR] |= TEARING_MASK_IN_AUTHLIM; + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] |= MF0_NTAG_TEARING_MASK_IN_AUTHLIM; nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); } else { @@ -810,14 +802,14 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { static void handle_pwd_auth_command(uint8_t *p_data) { int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); - uint8_t *cnt_data = get_counter_data_by_index(0); + uint8_t *cnt_data = nfc_tag_mf0_ntag_get_counter_data_by_index(0); if (first_cfg_page == 0 || cnt_data == NULL) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } // check AUTHLIM counter - uint8_t auth_cnt = cnt_data[AUTHLIM_OFF_IN_CTR] & AUTHLIM_MASK_IN_CTR; + uint8_t auth_cnt = cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_AUTHLIM_MASK_IN_CTR; uint8_t auth_lim = m_tag_information->memory[first_cfg_page + 1][0] & CONF_ACCESS_AUTHLIM_MASK; if ((auth_lim > 0) && (auth_lim <= auth_cnt)) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); @@ -827,13 +819,13 @@ static void handle_pwd_auth_command(uint8_t *p_data) { uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET]; uint32_t supplied_pwd = *(uint32_t *)&p_data[1]; if (pwd != supplied_pwd) { - if (auth_lim) cnt_data[AUTHLIM_OFF_IN_CTR] |= (auth_cnt + 1) & AUTHLIM_MASK_IN_CTR; + if (auth_lim) cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] |= (auth_cnt + 1) & MF0_NTAG_AUTHLIM_MASK_IN_CTR; nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } // reset authentication attempts counter and authenticate user - cnt_data[AUTHLIM_OFF_IN_CTR] = 0; + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] = 0; m_tag_authenticated = true; // TODO: this should be possible to reset somehow // Send the PACK value back @@ -844,10 +836,10 @@ static void handle_check_tearing_event(int index) { switch (m_tag_type) { case TAG_TYPE_MF0UL11: case TAG_TYPE_MF0UL21: { - uint8_t *ctr_data = get_counter_data_by_index(index); + uint8_t *ctr_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); if (ctr_data) { - m_tag_tx_buffer.tx_buffer[0] = (ctr_data[AUTHLIM_OFF_IN_CTR] & TEARING_MASK_IN_AUTHLIM) == 0 ? 0xBD : 0x00; + m_tag_tx_buffer.tx_buffer[0] = (ctr_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_TEARING_MASK_IN_AUTHLIM) == 0 ? 0xBD : 0x00; nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); } else { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index b8518024..5a833561 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -35,6 +35,14 @@ #define NFC_TAG_NTAG_FRAME_SIZE 64 #define NFC_TAG_NTAG_BLOCK_MAX NTAG216_TOTAL_PAGES +// Since all counters are 24-bit and each currently supported tag that supports counters +// has password authentication we store the auth attempts counter in the last bit of the +// first counter. AUTHLIM is only 3 bits though so we reserve 4 bits just to be sure and +// use the top bit for tearing event flag. +#define MF0_NTAG_AUTHLIM_OFF_IN_CTR 3 +#define MF0_NTAG_AUTHLIM_MASK_IN_CTR 0xF +#define MF0_NTAG_TEARING_MASK_IN_AUTHLIM 0x80 + typedef struct { uint8_t mode_uid_magic: 1; // reserve @@ -60,6 +68,7 @@ int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *bu int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type); +uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index); uint8_t *nfc_tag_mf0_ntag_get_version_data(void); uint8_t *nfc_tag_mf0_ntag_get_signature_data(void); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index e6524c07..10980daa 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1670,6 +1670,43 @@ def on_exec(self, args: argparse.Namespace): f'- {"Log (mfkey32) mode:":40}{f"{CG}enabled{C0}" if detection else f"{CR}disabled{C0}"}') +@hf_mfu.command('ercnt') +class HFMFUVERSION(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Read MIFARE Ultralight / NTAG counter value.' + parser.add_argument('-c', '--counter', type=int, required=True, help="Counter index.") + return parser + + def on_exec(self, args: argparse.Namespace): + value, no_tearing = self.cmd.mfu_read_emu_counter_data(args.counter) + print(f" - Value: {value:06x}") + if no_tearing: + print(f" - Tearing: {CG}not set{C0}") + else: + print(f" - Tearing: {CR}set{C0}") + + +@hf_mfu.command('ewcnt') +class HFMFUVERSION(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Read MIFARE Ultralight / NTAG counter value.' + parser.add_argument('-c', '--counter', type=int, required=True, help="Counter index.") + parser.add_argument('-v', '--value', type=int, required=True, help="Counter value (24-bit).") + parser.add_argument('-t', '--reset-tearing', action='store_true', help="Reset tearing event flag.") + return parser + + def on_exec(self, args: argparse.Namespace): + if args.value > 0xFFFFFF: + print(f"{CR}Counter value {args.value:#x} is too large.{C0}") + return + + self.cmd.mfu_write_emu_counter_data(args.counter, args.value, args.reset_tearing) + + print('- Ok') + + @hf_mfu.command('rdpg') class HFMFURDPG(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 7ff952f7..43bbd575 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -608,6 +608,26 @@ def mfu_write_emu_page_data(self, page_start: int, data: bytes): resp = self.device.send_cmd_sync(Command.MF0_NTAG_WRITE_EMU_PAGE_DATA, data) return resp + @expect_response(Status.SUCCESS) + def mfu_read_emu_counter_data(self, index: int) -> (int, bool): + """ + Gets data for selected counter + """ + data = struct.pack('!B', index) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_COUNTER_DATA, data) + if resp.status == Status.SUCCESS: + resp.parsed = (((resp.data[0] << 16) | (resp.data[1] << 8) | resp.data[2]), resp.data[3] == 0xBD) + return resp + + @expect_response(Status.SUCCESS) + def mfu_write_emu_counter_data(self, index: int, value: int, reset_tearing: bool): + """ + Sets data for selected counter + """ + data = struct.pack('!BBBB', index | (int(reset_tearing) << 7), (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_SET_COUNTER_DATA, data) + return resp + @expect_response(Status.SUCCESS) def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index c88bd54b..c250afdc 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -103,6 +103,8 @@ class Command(enum.IntEnum): MF0_NTAG_SET_VERSION_DATA = 4024 MF0_NTAG_GET_SIGNATURE_DATA = 4025 MF0_NTAG_SET_SIGNATURE_DATA = 4026 + MF0_NTAG_GET_COUNTER_DATA = 4027 + MF0_NTAG_SET_COUNTER_DATA = 4028 EM410X_SET_EMU_ID = 5000 EM410X_GET_EMU_ID = 5001 From 09310dcacbde810aef292bd266395d58a8d0b899 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 18:35:45 +0300 Subject: [PATCH 46/60] Account for the second locking bit when checking OTP lock status. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 8adff231..b86562d9 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -594,7 +594,7 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { static bool check_ro_lock_on_page(int block_num) { if (block_num < 3) return true; - else if (block_num == 3) return (m_tag_information->memory[2][2] & 1) == 1; + else if (block_num == 3) return (m_tag_information->memory[2][2] & 9) != 0; // bits 0 and 3 else if (block_num <= MF0ICU1_PAGES) { bool locked = false; From a52bb989e9d49f87e4d991931ecf26021383f796 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:03:57 +0300 Subject: [PATCH 47/60] Fix `hf mfu dump` command not running without file. --- software/script/chameleon_cli_unit.py | 184 +++++++++++++------------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 10980daa..736c2359 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -2048,107 +2048,113 @@ def args_parser(self) -> ArgumentParserNoExit: parser.add_argument('-t', '--type', type=str, required=False, choices=['bin', 'hex'], help="Force writing as either raw binary or hex.") return parser + + def do_dump(self, args: argparse.Namespace, param, fd, save_as_eml): + if args.qty is not None: + stop_page = min(args.page + args.qty, 256) + else: + stop_page = 256 + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 1, + 'check_response_crc': 1, + } + + needs_stop = False - def on_exec(self, args: argparse.Namespace): - param = self.get_param(args) - - file_type = args.type - fd = None - save_as_eml = False - - if file_type is None: - if args.file.endswith('.eml') or args.file.endswith('.txt'): - file_type = 'hex' - else: - file_type = 'bin' + if param.key is not None: + options['keep_rf_field'] = 1 + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) - if file_type == 'hex': - fd = open(args.file, 'w+') - save_as_eml = True - else: - fd = open(args.file, 'wb+') + needs_stop = len(resp) < 2 + if not needs_stop: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + needs_stop = True - with fd: - fd.truncate(0) + options['auto_select'] = 0 + + # this handles auth failure + if needs_stop: + print(f" {CR}- Auth failed{C0}") + if fd is not None: + fd.close() + fd = None - if args.qty is not None: - stop_page = min(args.page + args.qty, 256) - else: - stop_page = 256 - - options = { - 'activate_rf_field': 0, - 'wait_response': 1, - 'append_crc': 1, - 'auto_select': 1, - 'keep_rf_field': 1, - 'check_response_crc': 1, - } + for i in range(args.page, stop_page): + # this could be done once in theory but the command would need to be optimized properly + if param.key is not None and not needs_stop: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + options['auto_select'] = 0 # prevent resets + pack = resp[:2].hex() - needs_stop = False - - if param.key is not None: - options['keep_rf_field'] = 1 - try: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + # disable the rf field after the last command + if i == (stop_page - 1) or needs_stop: + options['keep_rf_field'] = 0 - needs_stop = len(resp) < 2 - if not needs_stop: - print(f" - PACK: {resp[:2].hex()}") - except Exception as e: - # failed auth may cause tags to be lost - needs_stop = True + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + except: + # probably lost tag, but we still need to disable rf field + resp = None - options['auto_select'] = 0 - - # this handles auth failure if needs_stop: - print(f" {CR}- Auth failed{C0}") - if fd is not None: - fd.close() - fd = None - - for i in range(args.page, stop_page): - # this could be done once in theory but the command would need to be optimized properly - if param.key is not None and not needs_stop: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) - options['auto_select'] = 0 # prevent resets - pack = resp[:2].hex() - - # disable the rf field after the last command - if i == (stop_page - 1) or needs_stop: - options['keep_rf_field'] = 0 + # break if this command was sent just to disable RF field + break + elif resp is None or len(resp) == 0: + # we need to disable RF field if we reached the last valid page so send one more read command + needs_stop = True + continue - try: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) - except: - # probably lost tag, but we still need to disable rf field - resp = None - - if needs_stop: - # break if this command was sent just to disable RF field - break - elif resp is None or len(resp) == 0: - # we need to disable RF field if we reached the last valid page so send one more read command - needs_stop = True - continue - - # after the read we are sure we no longer need to select again - options['auto_select'] = 0 - - # TODO: can be optimized as we get 4 pages at once but beware of wrapping - # in case of end of memory or LOCK on ULC and no key provided - data = resp[:4] - print(f" - Page {i:2}: {data.hex()}") - if fd is not None: - if save_as_eml: - fd.write(data.hex()+'\n') - else: - fd.write(data) + # after the read we are sure we no longer need to select again + options['auto_select'] = 0 + + # TODO: can be optimized as we get 4 pages at once but beware of wrapping + # in case of end of memory or LOCK on ULC and no key provided + data = resp[:4] + print(f" - Page {i:2}: {data.hex()}") + if fd is not None: + if save_as_eml: + fd.write(data.hex()+'\n') + else: + fd.write(data) - if args.file is not None: + if args.file != '': print(f" - {CG}Dump written in {args.file}.{C0}") + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + file_type = args.type + fd = None + save_as_eml = False + + if args.file != '': + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' + + if file_type == 'hex': + fd = open(args.file, 'w+') + save_as_eml = True + else: + fd = open(args.file, 'wb+') + + if fd is not None: + with fd: + fd.truncate(0) + self.do_dump(args, param, fd, save_as_eml) + else: + self.do_dump(args, param, fd, save_as_eml) + @hf_mfu.command('version') class HFMFUVERSION(ReaderRequiredUnit): From dd42e7e7dda04c90bf6140880867855e99faeea2 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:05:11 +0300 Subject: [PATCH 48/60] NTAG tags refer to their only counter by index 2. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index b86562d9..774111d6 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -398,6 +398,8 @@ static int get_user_data_end_by_tag_type(tag_specific_type_t type) { uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { uint8_t ctr_page_off; uint8_t ctr_page_end; + uint8_t first_index = 0; // NTAG cards have one counter that is at address 2 so we have to adjust logic + switch (m_tag_type) { case TAG_TYPE_MF0UL11: ctr_page_off = MF0UL11_PAGES; @@ -410,21 +412,24 @@ uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { case TAG_TYPE_NTAG_213: ctr_page_off = NTAG213_PAGES; ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + first_index = 2; break; case TAG_TYPE_NTAG_215: ctr_page_off = NTAG215_PAGES; ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + first_index = 2; break; case TAG_TYPE_NTAG_216: ctr_page_off = NTAG216_PAGES; ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + first_index = 2; break; default: return NULL; } // check that counter index is in bounds - if (index >= (ctr_page_end - ctr_page_off)) return NULL; + if ((index < first_index) || ((index - first_index) >= (ctr_page_end - ctr_page_off))) return NULL; return m_tag_information->memory[ctr_page_off + index]; } From cf109f94b2deac76518a85876ef409e54dfc43e0 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:09:16 +0300 Subject: [PATCH 49/60] Detect NAKs in `hf mfu wrpg` command. --- software/script/chameleon_cli_unit.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 736c2359..944ef1f7 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1782,11 +1782,12 @@ def on_exec(self, args: argparse.Namespace): 'append_crc': 1, 'auto_select': 1, 'keep_rf_field': 0, - 'check_response_crc': 1, + 'check_response_crc': 0, } if param.key is not None: options['keep_rf_field'] = 1 + options['check_response_crc'] = 1 try: resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) @@ -1799,12 +1800,17 @@ def on_exec(self, args: argparse.Namespace): options['keep_rf_field'] = 0 options['auto_select'] = 0 + options['check_response_crc'] = 0 else: failed_auth = False if not failed_auth: resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, args.page)+data) - print(f" - Ok") + + if resp[0] == 0x0A: + print(f" - Ok") + else: + print(f"{CR}Write failed ({resp[0]:#04x}).{C0}") else: # send a command just to disable the field. use read to avoid corrupting the data try: From 202f5d38842a0d4091e6bbdd7aab33175fbbb94f Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:28:56 +0300 Subject: [PATCH 50/60] Fix internal MF0 / NTAG counter indexing. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 774111d6..297e1ca4 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -395,7 +395,7 @@ static int get_user_data_end_by_tag_type(tag_specific_type_t type) { return nr_pages; } -uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { +static uint8_t *get_counter_data_by_index(uint8_t index, bool external) { uint8_t ctr_page_off; uint8_t ctr_page_end; uint8_t first_index = 0; // NTAG cards have one counter that is at address 2 so we have to adjust logic @@ -428,12 +428,19 @@ uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { return NULL; } + if (!external) first_index = 0; + // check that counter index is in bounds if ((index < first_index) || ((index - first_index) >= (ctr_page_end - ctr_page_off))) return NULL; return m_tag_information->memory[ctr_page_off + index]; } +uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { + // from the point of chameleon this is an internal access since only NFC accesses are considered external accesses + return get_counter_data_by_index(index, false); +} + static char hex_digit(int n) { if (n < 10) return '0' + n; else return 'A' + n - 10; @@ -486,12 +493,12 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); break; case MIRROR_CONF_CNT: - bytes2hex(nfc_tag_mf0_ntag_get_counter_data_by_index(0), (char *)mirror_buf, 3); + bytes2hex(get_counter_data_by_index(0, false), (char *)mirror_buf, 3); break; case MIRROR_CONF_UID_CNT: bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); mirror_buf[7] = 'x'; - bytes2hex(nfc_tag_mf0_ntag_get_counter_data_by_index(0), (char *)&mirror_buf[8], 3); + bytes2hex(get_counter_data_by_index(0, false), (char *)&mirror_buf[8], 3); break; } } @@ -543,7 +550,7 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; if ((access & CONF_ACCESS_NFC_CNT_EN) != 0) { - uint8_t *ctr = nfc_tag_mf0_ntag_get_counter_data_by_index(0); + uint8_t *ctr = get_counter_data_by_index(0, false); uint32_t counter = (((uint32_t)ctr[0]) << 16) | (((uint32_t)ctr[1]) << 8) | ((uint32_t)ctr[2]); if (counter < 0xFFFFFF) counter += 1; ctr[0] = (uint8_t)(counter >> 16); @@ -752,7 +759,7 @@ static void handle_read_cnt_command(uint8_t index) { } } - uint8_t *cnt_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); + uint8_t *cnt_data = get_counter_data_by_index(index, true); if (cnt_data == NULL) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; @@ -807,7 +814,7 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { static void handle_pwd_auth_command(uint8_t *p_data) { int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); - uint8_t *cnt_data = nfc_tag_mf0_ntag_get_counter_data_by_index(0); + uint8_t *cnt_data = get_counter_data_by_index(0, false); if (first_cfg_page == 0 || cnt_data == NULL) { nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; @@ -841,7 +848,7 @@ static void handle_check_tearing_event(int index) { switch (m_tag_type) { case TAG_TYPE_MF0UL11: case TAG_TYPE_MF0UL21: { - uint8_t *ctr_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); + uint8_t *ctr_data = get_counter_data_by_index(index, true); if (ctr_data) { m_tag_tx_buffer.tx_buffer[0] = (ctr_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_TEARING_MASK_IN_AUTHLIM) == 0 ? 0xBD : 0x00; From 607df41bca1fc0bf3ac696c68bb8753b34b1666e Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:30:27 +0300 Subject: [PATCH 51/60] Add a command to reset MF0 / NTAG unsuccessful auth counter. --- docs/protocol.md | 4 ++++ firmware/application/src/app_cmd.c | 14 +++++++++++++- firmware/application/src/data_cmd.h | 1 + software/script/chameleon_cli_unit.py | 13 ++++++++++++- software/script/chameleon_cmd.py | 10 ++++++++++ software/script/chameleon_enum.py | 1 + 6 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 3427d7fd..4afa1c42 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -428,6 +428,10 @@ Notes: * Command: 1 byte where the lower 7 bits are the counter index and the top bit indicates whether tearing event flag should be reset + 3 bytes of the counter value (big-endian). * Response: no data * CLI: cf `hf mfu ewcnt` +### 4029: MF0_NTAG_RESET_AUTH_CNT +* Command: no data +* Response: 1 byte for the old value of the unsuccessful auth counter. +* CLI: cf `hf mfu econfig --reset-auth-cnt` ### 5000: EM410X_SET_EMU_ID * Command: 5 bytes. `id[5]`. ID as 5 bytes. * Response: no data diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 6820f2e7..85462cac 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -927,6 +927,17 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_set_counter_data(uint16_t cmd, ui return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); } +static data_frame_tx_t *cmd_processor_mf0_ntag_reset_auth_cnt(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // all tags with counters support auth + uint8_t *counter_data = nfc_tag_mf0_ntag_get_counter_data_by_index(0); + if (counter_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + uint8_t old_value = counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_AUTHLIM_MASK_IN_CTR; + counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR; + + return data_frame_make(cmd, STATUS_SUCCESS, 1, &old_value); +} + static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct @@ -1273,7 +1284,8 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_get_signature_data, NULL }, { DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_set_signature_data, NULL }, { DATA_CMD_MF0_NTAG_GET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_get_counter_data, NULL }, - { DATA_CMD_MF0_NTAG_SET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_set_counter_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_set_counter_data, NULL }, + { DATA_CMD_MF0_NTAG_RESET_AUTH_CNT, NULL, cmd_processor_mf0_ntag_reset_auth_cnt, NULL }, { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 2000d476..16fd864d 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -116,6 +116,7 @@ #define DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA (4026) #define DATA_CMD_MF0_NTAG_GET_COUNTER_DATA (4027) #define DATA_CMD_MF0_NTAG_SET_COUNTER_DATA (4028) +#define DATA_CMD_MF0_NTAG_RESET_AUTH_CNT (4029) // // ****************************************************************** diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 944ef1f7..4a8043f5 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -2216,12 +2216,15 @@ def args_parser(self) -> ArgumentParserNoExit: uid_magic_group.add_argument('--disable-uid-magic', action='store_true', help="Disable UID magic mode") parser.add_argument('--set-version', type=bytes.fromhex, help="Set data to be returned by the GET_VERSION command.") parser.add_argument('--set-signature', type=bytes.fromhex, help="Set data to be returned by the READ_SIG command.") + parser.add_argument('--reset-auth-cnt', action='store_true', help="Resets the counter of unsuccessful authentication attempts.") return parser def on_exec(self, args: argparse.Namespace): aux_data_changed = False + aux_data_change_requested = False if args.set_version is not None: + aux_data_change_requested = True aux_data_changed = True if len(args.set_version) != 8: @@ -2235,6 +2238,7 @@ def on_exec(self, args: argparse.Namespace): return if args.set_signature is not None: + aux_data_change_requested = True aux_data_changed = True if len(args.set_signature) != 32: @@ -2246,6 +2250,13 @@ def on_exec(self, args: argparse.Namespace): except: print(f"{CR}Tag type does not support READ_SIG command.{C0}") return + + if args.reset_auth_cnt: + aux_data_change_requested = True + old_value = self.cmd.mfu_reset_auth_cnt() + if old_value != 0: + aux_data_changed = True + print(f"- Unsuccessful auth counter has been reset from {old_value} to 0.") # collect current settings anti_coll_data = self.cmd.hf14a_get_anti_coll_data() @@ -2285,7 +2296,7 @@ def on_exec(self, args: argparse.Namespace): if change_done or aux_data_changed: print(' - MFU/NTAG Emulator settings updated') - if not (change_requested or aux_data_changed): + if not (change_requested or aux_data_change_requested): print(f'- {"Type:":40}{CY}{hf_tag_type}{C0}') print(f'- {"UID:":40}{CY}{uid.hex().upper()}{C0}') print(f'- {"ATQA:":40}{CY}{atqa.hex().upper()} ' diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 43bbd575..55e7205a 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -628,6 +628,16 @@ def mfu_write_emu_counter_data(self, index: int, value: int, reset_tearing: bool resp = self.device.send_cmd_sync(Command.MF0_NTAG_SET_COUNTER_DATA, data) return resp + @expect_response(Status.SUCCESS) + def mfu_reset_auth_cnt(self): + """ + Resets authentication counter + """ + resp = self.device.send_cmd_sync(Command.MF0_NTAG_RESET_AUTH_CNT, bytes()) + if resp.status == Status.SUCCESS: + resp.parsed = resp.data[0] + return resp + @expect_response(Status.SUCCESS) def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index c250afdc..4592d8bc 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -105,6 +105,7 @@ class Command(enum.IntEnum): MF0_NTAG_SET_SIGNATURE_DATA = 4026 MF0_NTAG_GET_COUNTER_DATA = 4027 MF0_NTAG_SET_COUNTER_DATA = 4028 + MF0_NTAG_RESET_AUTH_CNT = 4029 EM410X_SET_EMU_ID = 5000 EM410X_GET_EMU_ID = 5001 From 3fe0a5f79d694137cd30f7137af8c780886f093c Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:30:52 +0300 Subject: [PATCH 52/60] Fix `hf mfu rcnt` command. --- software/script/chameleon_cli_unit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 4a8043f5..6e9da954 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -2029,11 +2029,11 @@ def on_exec(self, args: argparse.Namespace): failed_auth = False if not failed_auth: - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, param.counter)) + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, args.counter)) print(f" - Data: {resp[:3].hex()}") else: try: - self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, param.counter)) + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, args.counter)) except: # we may lose the tag again here pass From a8c2fc144110bee36cab4fab5356bf4627ec132d Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 21:54:14 +0300 Subject: [PATCH 53/60] Make answers to commands on errors similar to real cards. NAKs returned by both Ultralight and NTAG cards were 0 in my tests and Ultralights didn't respond to invalid commands at all. --- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 297e1ca4..89d29e7e 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -331,7 +331,7 @@ static void handle_get_version_command() { } else { NRF_LOG_WARNING("current card type does not support GET_VERSION"); // MF0ICU1 and MF0ICU2 do not support GET_VERSION - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); } } @@ -344,7 +344,7 @@ static void handle_read_sig_command() { } else { NRF_LOG_WARNING("current card type does not support READ_SIG"); // MF0ICU1 and MF0ICU2 do not support READ_SIG - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); } } @@ -570,7 +570,7 @@ static void handle_read_command(uint8_t block_num) { if (block_num >= block_max) { NRF_LOG_WARNING("too large block num %02x >= %02x", block_num, block_max); - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } @@ -588,14 +588,14 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { // command is supported break; default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } int block_max = get_block_max_by_tag_type(m_tag_type, true); if (block_num >= end_block_num || end_block_num >= block_max) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } @@ -705,7 +705,7 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { if (block_num >= block_max) { NRF_LOG_ERROR("Write failed: block_num %08x >= block_max %08x", block_num, block_max); - return NAK_INVALID_OPERATION_TBIV; + return NAK_INVALID_OPERATION_TBV; } if (m_tag_information->config.mode_uid_magic) { @@ -717,10 +717,7 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { switch (block_num) { case 0: case 1: - if (!memcmp(p_data, m_tag_information->memory[block_num], NFC_TAG_MF0_NTAG_DATA_SIZE)) - return ACK_VALUE; - else - return NAK_INVALID_OPERATION_TBIV; + return NAK_INVALID_OPERATION_TBV; case 2: // Page 2 contains lock bytes for pages 3-15. These are OR'ed when not in the UID // magic mode. First two bytes are ignored. @@ -735,12 +732,12 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { for (int i = 0; i < NFC_TAG_MF0_NTAG_DATA_SIZE; i++) { m_tag_information->memory[3][i] |= p_data[i]; } - } else return NAK_INVALID_OPERATION_TBIV; + } else return NAK_INVALID_OPERATION_TBV; break; default: if (!check_ro_lock_on_page(block_num)) { memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE); - } else return NAK_INVALID_OPERATION_TBIV; + } else return NAK_INVALID_OPERATION_TBV; break; } @@ -754,14 +751,14 @@ static void handle_read_cnt_command(uint8_t index) { int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; if ((access & CONF_ACCESS_NFC_CNT_PWD_PROT) != 0 && !m_tag_authenticated) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } } uint8_t *cnt_data = get_counter_data_by_index(index, true); if (cnt_data == NULL) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } @@ -782,13 +779,13 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; break; default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } // check that counter index is in bounds if (block_num >= (ctr_page_end - ctr_page_off)) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } @@ -816,7 +813,7 @@ static void handle_pwd_auth_command(uint8_t *p_data) { int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); uint8_t *cnt_data = get_counter_data_by_index(0, false); if (first_cfg_page == 0 || cnt_data == NULL) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); return; } @@ -854,13 +851,13 @@ static void handle_check_tearing_event(int index) { m_tag_tx_buffer.tx_buffer[0] = (ctr_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_TEARING_MASK_IN_AUTHLIM) == 0 ? 0xBD : 0x00; nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); } else { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); } break; } default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); break; } } @@ -870,12 +867,12 @@ static void handle_vcsl_command(uint16_t szDataBits) { case TAG_TYPE_MF0UL11: case TAG_TYPE_MF0UL21: if (szDataBits < 168) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); break; } break; default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); break; } @@ -934,7 +931,7 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) break; } default: - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); break; } return; From 1e3533a65a635026e78bc5b74e796df2da039cd0 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Tue, 9 Jul 2024 22:35:03 +0300 Subject: [PATCH 54/60] Fix unsuccessful auth attempts counting. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 89d29e7e..435b400f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -828,13 +828,16 @@ static void handle_pwd_auth_command(uint8_t *p_data) { uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET]; uint32_t supplied_pwd = *(uint32_t *)&p_data[1]; if (pwd != supplied_pwd) { - if (auth_lim) cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] |= (auth_cnt + 1) & MF0_NTAG_AUTHLIM_MASK_IN_CTR; + if (auth_lim) { + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR; + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] |= (auth_cnt + 1) & MF0_NTAG_AUTHLIM_MASK_IN_CTR; + } nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); return; } // reset authentication attempts counter and authenticate user - cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] = 0; + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR; m_tag_authenticated = true; // TODO: this should be possible to reset somehow // Send the PACK value back From a42837787873ef2d2e0c21260de87d8fff65aba8 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 15 Jul 2024 01:19:59 +0300 Subject: [PATCH 55/60] Make `hf mfu dump` command properly detect card size. --- software/script/chameleon_cli_unit.py | 87 ++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 6e9da954..67a41670 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -2059,7 +2059,18 @@ def do_dump(self, args: argparse.Namespace, param, fd, save_as_eml): if args.qty is not None: stop_page = min(args.page + args.qty, 256) else: - stop_page = 256 + stop_page = None + + tags = self.cmd.hf14a_scan() + if len(tags) > 1: + print(f'- {CR}Collision detected, leave only one tag.{C0}') + return + elif len(tags) == 0: + print(f'- {CR}No tag detected.{C0}') + return + elif tags[0]['atqa'] != b'\x44\x00' or tags[0]['sak'] != b'\x00': + print(f'- {CR}Tag is not Mifare Ultralight compatible (ATQA {tags[0]["atqa"].hex()} SAK {tags[0]["sak"].hex()}).{C0}') + return options = { 'activate_rf_field': 0, @@ -2070,10 +2081,78 @@ def do_dump(self, args: argparse.Namespace, param, fd, save_as_eml): 'check_response_crc': 1, } + # if stop page isn't set manually, try autodetection + if stop_page is None: + tag_name = None + + # first try sending the GET_VERSION command + try: + version = self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0x60)) + if len(version) == 0: + version = None + except: + version = None + + # try sending AUTHENTICATE command and observe the result + try: + supports_auth = len(self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0x1A))) != 0 + except: + supports_auth = False + + if version is not None and not supports_auth: + # either ULEV1 or NTAG + assert len(version) == 8 + + is_mikron_ulev1 = version[1] == 0x34 and version[2] == 0x21 + if (version[2] == 3 or is_mikron_ulev1) and version[4] == 1 and version[5] == 0: + # Ultralight EV1 V0 + size_map = { + 0x0B: ('Mifare Ultralight EV1 48b', 20), + 0x0E: ('Mifare Ultralight EV1 128b', 41), + } + elif version[2] == 4 and version[4] == 1 and version[5] == 0: + # NTAG 210/212/213/215/216 V0 + size_map = { + 0x0B: ('NTAG 210', 20), + 0x0E: ('NTAG 212', 41), + 0x0F: ('NTAG 213', 45), + 0x11: ('NTAG 215', 135), + 0x13: ('NTAG 216', 231), + } + else: + size_map = {} + + if version[6] in size_map: + tag_name, stop_page = size_map[version[6]] + elif version is None and supports_auth: + # Ultralight C + tag_name = 'Mifare Ultralight C' + stop_page = 48 + elif version is None and not supports_auth: + try: + # Invalid command returning a NAK means that's some old type of NTAG. + self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0xFF)) + + print(f' - {CY}Tag is likely NTAG 20x, reading until first error.{C0}') + stop_page = 256 + except: + # Regular Ultralight + tag_name = 'Mifare Ultralight' + stop_page = 16 + else: + # This is probably Ultralight AES, but we don't support this one yet. + pass + + if tag_name is not None: + print(f' - Detected tag type as {tag_name}.') + + if stop_page is None: + print(f' - {CY}Couldn\'t autodetect the expected card size, reading until first error.{C0}') + stop_page = 256 + needs_stop = False if param.key is not None: - options['keep_rf_field'] = 1 try: resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) @@ -2088,7 +2167,7 @@ def do_dump(self, args: argparse.Namespace, param, fd, save_as_eml): # this handles auth failure if needs_stop: - print(f" {CR}- Auth failed{C0}") + print(f" - {CR}Auth failed{C0}") if fd is not None: fd.close() fd = None @@ -2131,6 +2210,8 @@ def do_dump(self, args: argparse.Namespace, param, fd, save_as_eml): else: fd.write(data) + if needs_stop and stop_page != 256: + print(f' - {CY}Dump is shorter than expected.{C0}') if args.file != '': print(f" - {CG}Dump written in {args.file}.{C0}") From 82c46b3c471616bb8851e1208747b24f0394d592 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 15 Jul 2024 02:14:02 +0300 Subject: [PATCH 56/60] Fix a bug breaking NTAG counter access by NFC commands. --- firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 435b400f..539cc68c 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -433,7 +433,7 @@ static uint8_t *get_counter_data_by_index(uint8_t index, bool external) { // check that counter index is in bounds if ((index < first_index) || ((index - first_index) >= (ctr_page_end - ctr_page_off))) return NULL; - return m_tag_information->memory[ctr_page_off + index]; + return m_tag_information->memory[ctr_page_off + index - first_index]; } uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { From f1e2250178b3d1817cbe39670ce2d216c54ef9a6 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 15 Jul 2024 02:34:45 +0300 Subject: [PATCH 57/60] Add support for NTAG 210/212. --- firmware/application/src/app_main.c | 2 + .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 112 ++++++++++++++++-- .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 4 + .../src/rfid/nfctag/tag_base_type.h | 6 +- .../src/rfid/nfctag/tag_emulation.c | 2 + software/script/chameleon_enum.py | 6 + 6 files changed, 123 insertions(+), 9 deletions(-) diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 5a6d3225..28213510 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -651,6 +651,8 @@ static void btn_fn_copy_ic_uid(void) { break; } + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: case TAG_TYPE_NTAG_213: case TAG_TYPE_NTAG_215: case TAG_TYPE_NTAG_216: diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 539cc68c..6d55f397 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -22,6 +22,8 @@ NRF_LOG_MODULE_REGISTER(); #define VERSION_MINOR_PRODUCT 0x00 #define MF0UL11_VERSION_STORAGE_SIZE 0x0B #define MF0UL21_VERSION_STORAGE_SIZE 0x0E +#define NTAG210_VERSION_STORAGE_SIZE 0x0B +#define NTAG212_VERSION_STORAGE_SIZE 0x0E #define NTAG213_VERSION_STORAGE_SIZE 0x0F #define NTAG215_VERSION_STORAGE_SIZE 0x11 #define NTAG216_VERSION_STORAGE_SIZE 0x13 @@ -59,6 +61,10 @@ NRF_LOG_MODULE_REGISTER(); #define MF0UL11_USER_MEMORY_END (MF0UL11_FIRST_CFG_PAGE) #define MF0UL21_FIRST_CFG_PAGE 0x25 #define MF0UL21_USER_MEMORY_END 0x24 +#define NTAG210_FIRST_CFG_PAGE 0x10 +#define NTAG210_USER_MEMORY_END (NTAG210_FIRST_CFG_PAGE) +#define NTAG212_FIRST_CFG_PAGE 0x25 +#define NTAG212_USER_MEMORY_END 0x24 #define NTAG213_FIRST_CFG_PAGE 0x29 #define NTAG213_USER_MEMORY_END 0x28 #define NTAG215_FIRST_CFG_PAGE 0x83 @@ -145,6 +151,12 @@ int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { case TAG_TYPE_MF0UL21: nr_pages = MF0UL21_PAGES; break; + case TAG_TYPE_NTAG_210: + nr_pages = NTAG210_PAGES; + break; + case TAG_TYPE_NTAG_212: + nr_pages = NTAG212_PAGES; + break; case TAG_TYPE_NTAG_213: nr_pages = NTAG213_PAGES; break; @@ -184,6 +196,12 @@ static int get_total_pages_by_tag_type(tag_specific_type_t tag_type) { case TAG_TYPE_MF0UL21: nr_pages = MF0UL21_TOTAL_PAGES; break; + case TAG_TYPE_NTAG_210: + nr_pages = NTAG210_TOTAL_PAGES; + break; + case TAG_TYPE_NTAG_212: + nr_pages = NTAG212_TOTAL_PAGES; + break; case TAG_TYPE_NTAG_213: nr_pages = NTAG213_TOTAL_PAGES; break; @@ -211,6 +229,12 @@ static int get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { case TAG_TYPE_MF0UL21: page = MF0UL21_FIRST_CFG_PAGE; break; + case TAG_TYPE_NTAG_210: + page = NTAG210_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_212: + page = NTAG212_FIRST_CFG_PAGE; + break; case TAG_TYPE_NTAG_213: page = NTAG213_FIRST_CFG_PAGE; break; @@ -245,6 +269,8 @@ static int get_block_max_by_tag_type(tag_specific_type_t tag_type, bool read) { static bool is_ntag() { switch (m_tag_type) { + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: case TAG_TYPE_NTAG_213: case TAG_TYPE_NTAG_215: case TAG_TYPE_NTAG_216: @@ -264,6 +290,14 @@ int get_version_page_by_tag_type(tag_specific_type_t tag_type) { case TAG_TYPE_MF0UL21: version_page_off = MF0UL21_PAGES + MF0ULx1_NUM_CTRS; break; + // NTAG 210 and 212 don't have a counter, but we still allocate space for one to + // record unsuccessful auth attempts + case TAG_TYPE_NTAG_210: + version_page_off = NTAG210_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_212: + version_page_off = NTAG212_PAGES + NTAG_NUM_CTRS; + break; case TAG_TYPE_NTAG_213: version_page_off = NTAG213_PAGES + NTAG_NUM_CTRS; break; @@ -291,6 +325,12 @@ int get_signature_page_by_tag_type(tag_specific_type_t tag_type) { case TAG_TYPE_MF0UL21: version_page_off = MF0UL21_PAGES + MF0ULx1_NUM_CTRS + PAGES_PER_VERSION; break; + case TAG_TYPE_NTAG_210: + version_page_off = NTAG210_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_NTAG_212: + version_page_off = NTAG212_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; case TAG_TYPE_NTAG_213: version_page_off = NTAG213_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; break; @@ -378,6 +418,12 @@ static int get_user_data_end_by_tag_type(tag_specific_type_t type) { case TAG_TYPE_MF0UL21: nr_pages = MF0UL21_USER_MEMORY_END; break; + case TAG_TYPE_NTAG_210: + nr_pages = NTAG210_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_212: + nr_pages = NTAG212_USER_MEMORY_END; + break; case TAG_TYPE_NTAG_213: nr_pages = NTAG213_USER_MEMORY_END; break; @@ -409,6 +455,16 @@ static uint8_t *get_counter_data_by_index(uint8_t index, bool external) { ctr_page_off = MF0UL21_PAGES; ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; break; + case TAG_TYPE_NTAG_210: + if (external) return NULL; // NTAG 210 tags don't really have a counter + ctr_page_off = NTAG210_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_212: + if (external) return NULL; // NTAG 212 tags don't really have a counter + ctr_page_off = NTAG212_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + break; case TAG_TYPE_NTAG_213: ctr_page_off = NTAG213_PAGES; ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; @@ -476,6 +532,16 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ mirror_mode = (mirror & MIRROR_BYTE_CONF_MASK) >> MIRROR_BYTE_CONF_SHIFT; mirror_byte_off = (mirror & MIRROR_BYTE_BYTE_MASK) >> MIRROR_BYTE_BYTE_SHIFT; + // NTAG 210/212 don't have a counter thus no mirror mode + switch (m_tag_type) { + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: + mirror_mode = MIRROR_CONF_UID; + break; + default: + break; + } + if ((mirror_page_off > 3) && (mirror_mode != MIRROR_CONF_DISABLED)) { mirror_size = mirror_size_for_mode(mirror_mode); int user_data_end = get_user_data_end_by_tag_type(m_tag_type); @@ -543,7 +609,10 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ NRF_LOG_DEBUG("READ handled %02x %02x %02x", block_num, block_cnt, block_max); // update counter for NTAG cards if needed - if (is_ntag() && !m_did_first_read) { + switch (m_tag_type) { + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: if (!m_did_first_read) { m_did_first_read = true; int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); @@ -557,6 +626,10 @@ static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_ ctr[1] = (uint8_t)(counter >> 8); ctr[2] = (uint8_t)(counter); } + break; + } + default: + break; } nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, ((int)block_cnt) * NFC_TAG_MF0_NTAG_DATA_SIZE, true); @@ -582,6 +655,8 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { { case TAG_TYPE_MF0UL11: case TAG_TYPE_MF0UL21: + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: case TAG_TYPE_NTAG_213: case TAG_TYPE_NTAG_215: case TAG_TYPE_NTAG_216: @@ -662,6 +737,14 @@ static bool check_ro_lock_on_page(int block_num) { } break; } + case TAG_TYPE_NTAG_210: + user_memory_end = NTAG210_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 0; // NTAG 210 doesn't have dynamic lock bits + break; + case TAG_TYPE_NTAG_212: + user_memory_end = NTAG212_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 2; + break; case TAG_TYPE_NTAG_213: user_memory_end = NTAG213_USER_MEMORY_END; dyn_lock_bit_page_cnt = 2; @@ -680,6 +763,8 @@ static bool check_ro_lock_on_page(int block_num) { } if (block_num < user_memory_end) { + ASSERT(dyn_lock_bit_page_cnt > 0); + p_lock_bytes = m_tag_information->memory[user_memory_end]; uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; @@ -745,6 +830,13 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) { } static void handle_read_cnt_command(uint8_t index) { + // first check if the counter even exists for external commands + uint8_t *cnt_data = get_counter_data_by_index(index, true); + if (cnt_data == NULL) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + // deny counter reading when counter password protection is enabled and reader is not authenticated if (is_ntag() && !m_tag_information->config.mode_uid_magic) { int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); @@ -756,12 +848,6 @@ static void handle_read_cnt_command(uint8_t index) { } } - uint8_t *cnt_data = get_counter_data_by_index(index, true); - if (cnt_data == NULL) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); - return; - } - memcpy(m_tag_tx_buffer.tx_buffer, cnt_data, 3); nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 3, true); } @@ -1075,6 +1161,16 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { version_data[6] = MF0UL21_VERSION_STORAGE_SIZE; version_data[2] = MF0ULx1_VERSION_PRODUCT_TYPE; break; + case TAG_TYPE_NTAG_210: + version_data[6] = NTAG210_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + version_data[3] = VERSION_PRODUCT_SUBTYPE_17pF; + break; + case TAG_TYPE_NTAG_212: + version_data[6] = NTAG212_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + version_data[3] = VERSION_PRODUCT_SUBTYPE_17pF; + break; case TAG_TYPE_NTAG_213: version_data[6] = NTAG213_VERSION_STORAGE_SIZE; version_data[2] = NTAG_VERSION_PRODUCT_TYPE; @@ -1094,7 +1190,7 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { version_data[0] = VERSION_FIXED_HEADER; version_data[1] = VERSION_VENDOR_ID; - version_data[3] = VERSION_PRODUCT_SUBTYPE_50pF; // TODO: make configurable for MF0ULx1 + if (version_data[3] == 0) version_data[3] = VERSION_PRODUCT_SUBTYPE_50pF; // TODO: make configurable for MF0ULx1 version_data[4] = VERSION_MAJOR_PRODUCT; version_data[5] = VERSION_MINOR_PRODUCT; version_data[7] = VERSION_PROTOCOL_TYPE; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index 5a833561..92be868f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -18,6 +18,10 @@ #define MF0ULx1_EXTRA_PAGES (MF0ULx1_NUM_CTRS + NFC_TAG_MF0_NTAG_VER_PAGES + NFC_TAG_MF0_NTAG_SIG_PAGES) #define NTAG_EXTRA_PAGES (NTAG_NUM_CTRS + NFC_TAG_MF0_NTAG_VER_PAGES + NFC_TAG_MF0_NTAG_SIG_PAGES) +#define NTAG210_PAGES 20 //20 pages total for ntag210, from 0 to 44 +#define NTAG210_TOTAL_PAGES (NTAG210_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter +#define NTAG212_PAGES 41 //41 pages total for ntag212, from 0 to 44 +#define NTAG212_TOTAL_PAGES (NTAG212_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter #define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 #define NTAG213_TOTAL_PAGES (NTAG213_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter #define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 diff --git a/firmware/application/src/rfid/nfctag/tag_base_type.h b/firmware/application/src/rfid/nfctag/tag_base_type.h index c4d3dac7..5e833199 100644 --- a/firmware/application/src/rfid/nfctag/tag_base_type.h +++ b/firmware/application/src/rfid/nfctag/tag_base_type.h @@ -79,6 +79,8 @@ typedef enum { TAG_TYPE_MF0ICU2, TAG_TYPE_MF0UL11, TAG_TYPE_MF0UL21, + TAG_TYPE_NTAG_210, + TAG_TYPE_NTAG_212, // MIFARE Plus series 1200 // DESFire series 1300 @@ -114,7 +116,9 @@ typedef enum { TAG_TYPE_MF0ICU1,\ TAG_TYPE_MF0ICU2,\ TAG_TYPE_MF0UL11,\ - TAG_TYPE_MF0UL21 + TAG_TYPE_MF0UL21,\ + TAG_TYPE_NTAG_210,\ + TAG_TYPE_NTAG_212 typedef struct { tag_specific_type_t tag_hf; diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index b7cd281d..f189d127 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -100,6 +100,8 @@ static tag_base_handler_map_t tag_base_map[] = { { TAG_SENSE_HF, TAG_TYPE_MIFARE_2048, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf }, { TAG_SENSE_HF, TAG_TYPE_MIFARE_4096, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf }, // NTAG tag simulation + { TAG_SENSE_HF, TAG_TYPE_NTAG_210, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_212, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, { TAG_SENSE_HF, TAG_TYPE_NTAG_213, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, { TAG_SENSE_HF, TAG_TYPE_NTAG_215, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, { TAG_SENSE_HF, TAG_TYPE_NTAG_216, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index 4592d8bc..70d440f6 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -281,6 +281,8 @@ class TagSpecificType(enum.IntEnum): MF0ICU2 = 1104 MF0UL11 = 1105 MF0UL21 = 1106 + NTAG_210 = 1107 + NTAG_212 = 1108 # MIFARE Plus series 1200 # DESFire series 1300 @@ -332,6 +334,10 @@ def __str__(self): return "Mifare Ultralight EV1 (640 bit)" elif self == TagSpecificType.MF0UL21: return "Mifare Ultralight EV1 (1312 bit)" + elif self == TagSpecificType.NTAG_210: + return "NTAG 210" + elif self == TagSpecificType.NTAG_212: + return "NTAG 212" elif self < TagSpecificType.OLD_TAG_TYPES_END: return "Old tag type, must be migrated! Upgrade fw!" return "Invalid" From d1c9b4b21b2d4d39719481eabca43db400e59e10 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 15 Jul 2024 02:35:37 +0300 Subject: [PATCH 58/60] Fix `hf mfu e(r|w)cnt` commands switching device into reader mode. --- software/script/chameleon_cli_unit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 67a41670..bf9bc2a1 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1671,7 +1671,7 @@ def on_exec(self, args: argparse.Namespace): @hf_mfu.command('ercnt') -class HFMFUVERSION(ReaderRequiredUnit): +class HFMFUVERSION(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() parser.description = 'Read MIFARE Ultralight / NTAG counter value.' @@ -1688,7 +1688,7 @@ def on_exec(self, args: argparse.Namespace): @hf_mfu.command('ewcnt') -class HFMFUVERSION(ReaderRequiredUnit): +class HFMFUVERSION(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() parser.description = 'Read MIFARE Ultralight / NTAG counter value.' From 384490e23196397c5603516693a319dbbd5ed59c Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 15 Jul 2024 03:17:25 +0300 Subject: [PATCH 59/60] Fix an old bug in `hf mfu econfig` that prevented anticollision resolution data from being updated sometimes. --- firmware/application/src/app_cmd.c | 44 ++++++++++++++++--- .../src/rfid/nfctag/hf/nfc_mf0_ntag.c | 4 +- .../src/rfid/nfctag/hf/nfc_mf0_ntag.h | 1 + 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 85462cac..18713da1 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -688,13 +688,44 @@ static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t s return data_frame_make(cmd, STATUS_SUCCESS, LF_EM410X_TAG_ID_SIZE, responseData); } -static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static nfc_tag_14a_coll_res_reference_t *get_coll_res_data(bool write) { + nfc_tag_14a_coll_res_reference_t *info; tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types); - if (tag_types.tag_hf == TAG_TYPE_UNDEFINED) { - return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); // no data in slot, don't send garbage - } - nfc_tag_14a_coll_res_reference_t *info = get_saved_mifare_coll_res(); + + switch (tag_types.tag_hf) { + case TAG_TYPE_MIFARE_1024: + case TAG_TYPE_MIFARE_2048: + case TAG_TYPE_MIFARE_4096: + case TAG_TYPE_MIFARE_Mini: + info = write ? get_mifare_coll_res() : get_saved_mifare_coll_res(); + break; + case TAG_TYPE_MF0ICU1: + case TAG_TYPE_MF0ICU2: + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + info = nfc_tag_mf0_ntag_get_coll_res(); + break; + default: + // no collision resolution data for slot + info = NULL; + break; + } + + return info; +} + +static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + nfc_tag_14a_coll_res_reference_t *info = get_coll_res_data(false); + + if (info == NULL) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct uint8_t payload[1 + *info->size + 2 + 1 + 1 + 254]; @@ -947,7 +978,8 @@ static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uin (length < 1 + data[0] + 2 + 1 + 1 + data[1 + data[0] + 2 + 1])) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); + nfc_tag_14a_coll_res_reference_t *info = get_coll_res_data(true); + uint16_t offset = 0; *(info->size) = (nfc_tag_14a_uid_size)data[offset]; offset++; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c index 6d55f397..125d9e19 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -1026,7 +1026,7 @@ static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) return; } -static nfc_tag_14a_coll_res_reference_t *get_coll_res() { +nfc_tag_14a_coll_res_reference_t *nfc_tag_mf0_ntag_get_coll_res() { // Use a separate anti -conflict information instead of using the information in the sector m_shadow_coll_res.sak = m_tag_information->res_coll.sak; m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa; @@ -1070,7 +1070,7 @@ int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *bu m_tag_type = type; // Register 14A communication management interface nfc_tag_14a_handler_t handler_for_14a = { - .get_coll_res = get_coll_res, + .get_coll_res = nfc_tag_mf0_ntag_get_coll_res, .cb_state = nfc_tag_mf0_ntag_state_handler, .cb_reset = nfc_tag_mf0_ntag_reset_handler, }; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h index 92be868f..b65a01e1 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -75,6 +75,7 @@ int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type); uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index); uint8_t *nfc_tag_mf0_ntag_get_version_data(void); uint8_t *nfc_tag_mf0_ntag_get_signature_data(void); +nfc_tag_14a_coll_res_reference_t *nfc_tag_mf0_ntag_get_coll_res(void); int nfc_tag_mf0_ntag_get_uid_mode(void); bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled); From 25a1230865dc5ab74f475d96ce544fe31a312892 Mon Sep 17 00:00:00 2001 From: turbocool3r Date: Mon, 15 Jul 2024 03:18:14 +0300 Subject: [PATCH 60/60] Fix `hf mfu econfig` not working for NTAG 210/212. --- software/script/chameleon_cli_unit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index bf9bc2a1..6bcf9e9b 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -2356,6 +2356,8 @@ def on_exec(self, args: argparse.Namespace): TagSpecificType.MF0ICU2, TagSpecificType.MF0UL11, TagSpecificType.MF0UL21, + TagSpecificType.NTAG_210, + TagSpecificType.NTAG_212, TagSpecificType.NTAG_213, TagSpecificType.NTAG_215, TagSpecificType.NTAG_216,