From f8e0ec42c53b7b0710d17f5efcd264c8b4c94927 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 25 Jul 2022 09:21:05 -0600 Subject: [PATCH] nfc: NTAG203 support (#1383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: Fix original MFUL feature flags * nfc: Add NTAG203 read support * nfc: Update emulation lock byte handling for NTAG203 * nfc: Add NTAG203 counter emulation support * nfc: Add NTAG203 tag generator * nfc: Fix NTAG203 emulating GET_VERSION * nfc: Fix MFUL version reading * nfc: Complete NTAG203 counter emulation behavior * nfc: Complete NTAG203 emulation * nfc: Remove unnecessary init in MFUL emulation * nfc: Add notes about MFUL type enum Co-authored-by: gornekich Co-authored-by: あく --- applications/nfc/helpers/nfc_generators.c | 20 ++ applications/nfc/nfc_types.c | 2 + lib/nfc_protocols/mifare_ultralight.c | 257 +++++++++++++++++----- lib/nfc_protocols/mifare_ultralight.h | 15 ++ 4 files changed, 237 insertions(+), 57 deletions(-) diff --git a/applications/nfc/helpers/nfc_generators.c b/applications/nfc/helpers/nfc_generators.c index 67d9be7a..4121daf4 100644 --- a/applications/nfc/helpers/nfc_generators.c +++ b/applications/nfc/helpers/nfc_generators.c @@ -6,6 +6,8 @@ static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03}; static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03}; static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03}; +static const uint8_t default_data_ntag203[] = + {0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE}; static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE}; static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE}; static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE}; @@ -58,6 +60,18 @@ static void nfc_generate_mf_ul_orig(NfcDeviceData* data) { memset(&mful->data[4 * 4], 0xFF, 4); } +static void nfc_generate_mf_ul_ntag203(NfcDeviceData* data) { + nfc_generate_common_start(data); + nfc_generate_mf_ul_common(data); + + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeNTAG203; + mful->data_size = 42 * 4; + nfc_generate_mf_ul_copy_uid_with_bcc(data); + mful->data[9] = 0x48; // Internal byte + memcpy(&mful->data[3 * 4], default_data_ntag203, sizeof(default_data_ntag203)); +} + static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) { nfc_generate_common_start(data); nfc_generate_mf_ul_common(data); @@ -275,6 +289,11 @@ static const NfcGenerator mf_ul_h21_generator = { .generator_func = nfc_generate_mf_ul_h21, .next_scene = NfcSceneMifareUlMenu}; +static const NfcGenerator ntag203_generator = { + .name = "NTAG203", + .generator_func = nfc_generate_mf_ul_ntag203, + .next_scene = NfcSceneMifareUlMenu}; + static const NfcGenerator ntag213_generator = { .name = "NTAG213", .generator_func = nfc_generate_ntag213, @@ -316,6 +335,7 @@ const NfcGenerator* const nfc_generators[] = { &mf_ul_h11_generator, &mf_ul_21_generator, &mf_ul_h21_generator, + &ntag203_generator, &ntag213_generator, &ntag215_generator, &ntag216_generator, diff --git a/applications/nfc/nfc_types.c b/applications/nfc/nfc_types.c index 2d11c339..42762876 100644 --- a/applications/nfc/nfc_types.c +++ b/applications/nfc/nfc_types.c @@ -43,6 +43,8 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { return "NTAG I2C Plus 1K"; } else if(type == MfUltralightTypeNTAGI2CPlus2K) { return "NTAG I2C Plus 2K"; + } else if(type == MfUltralightTypeNTAG203) { + return "NTAG203"; } else if(type == MfUltralightTypeUL11 && full_name) { return "Mifare Ultralight 11"; } else if(type == MfUltralightTypeUL21 && full_name) { diff --git a/lib/nfc_protocols/mifare_ultralight.c b/lib/nfc_protocols/mifare_ultralight.c index 21dbd9c4..9dcd1d6a 100644 --- a/lib/nfc_protocols/mifare_ultralight.c +++ b/lib/nfc_protocols/mifare_ultralight.c @@ -35,9 +35,11 @@ static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { return MfUltralightSupportFastRead | MfUltralightSupportAuth | MfUltralightSupportFastWrite | MfUltralightSupportSignature | MfUltralightSupportSectorSelect; + case MfUltralightTypeNTAG203: + return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory; default: // Assumed original MFUL 512-bit - return MfUltralightSupportNone; + return MfUltralightSupportCompatWrite; } } @@ -46,6 +48,11 @@ static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightDa reader->pages_to_read = 16; } +static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightData* data) { + data->type = MfUltralightTypeNTAG203; + reader->pages_to_read = 42; +} + bool mf_ultralight_read_version( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, @@ -57,7 +64,7 @@ bool mf_ultralight_read_version( tx_rx->tx_data[0] = MF_UL_GET_VERSION_CMD; tx_rx->tx_bits = 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits != 64) { FURI_LOG_D(TAG, "Failed reading version"); mf_ul_set_default_version(reader, data); furi_hal_nfc_sleep(); @@ -468,6 +475,23 @@ static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t se return true; } +bool mf_ultralight_read_pages_direct( + FuriHalNfcTxRxContext* tx_rx, + uint8_t start_index, + uint8_t* data) { + FURI_LOG_D(TAG, "Reading pages %d - %d", start_index, start_index + 3); + tx_rx->tx_data[0] = MF_UL_READ_CMD; + tx_rx->tx_data[1] = start_index; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { + FURI_LOG_D(TAG, "Failed to read pages %d - %d", start_index, start_index + 3); + return false; + } + memcpy(data, tx_rx->rx_data, 16); + return true; +} + bool mf_ultralight_read_pages( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, @@ -632,6 +656,17 @@ bool mf_ul_read_card( // Read Signature mf_ultralight_read_signature(tx_rx, data); } + } else { + // No GET_VERSION command, check for NTAG203 by reading last page (41) + uint8_t dummy[16]; + if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) { + mf_ul_set_version_ntag203(reader, data); + reader->supported_features = mf_ul_get_features(data->type); + } else { + // We're really an original Mifare Ultralight, reset tag for safety + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + } } card_read = mf_ultralight_read_pages(tx_rx, reader, data); @@ -772,6 +807,8 @@ static bool mf_ul_ntag_i2c_plus_check_auth( static int16_t mf_ul_get_dynamic_lock_page_addr(MfUltralightData* data) { switch(data->type) { + case MfUltralightTypeNTAG203: + return 0x28; case MfUltralightTypeUL21: case MfUltralightTypeNTAG213: case MfUltralightTypeNTAG215: @@ -804,6 +841,10 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) // Check max page switch(emulator->data.type) { + case MfUltralightTypeNTAG203: + // Counter page can be locked and is after dynamic locks + if(write_page == 40) return true; + break; case MfUltralightTypeUL21: case MfUltralightTypeNTAG213: case MfUltralightTypeNTAG215: @@ -841,6 +882,19 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) switch(emulator->data.type) { // low byte LSB range, MSB range + case MfUltralightTypeNTAG203: + if(write_page >= 16 && write_page <= 27) + shift = (write_page - 16) / 4 + 1; + else if(write_page >= 28 && write_page <= 39) + shift = (write_page - 28) / 4 + 5; + else if(write_page == 41) + shift = 12; + else { + furi_assert(false); + shift = 0; + } + + break; case MfUltralightTypeUL21: case MfUltralightTypeNTAG213: // 16-17, 30-31 @@ -937,6 +991,42 @@ static void mf_ul_increment_single_counter(MfUltralightEmulator* emulator) { } } +static bool + mf_ul_emulate_ntag203_counter_write(MfUltralightEmulator* emulator, uint8_t* page_buff) { + // We'll reuse the existing counters for other NTAGs as staging + // Counter 0 stores original value, data is new value + uint32_t counter_value; + if(emulator->data.tearing[0] == MF_UL_TEARING_FLAG_DEFAULT) { + counter_value = emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | + (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); + } else { + // We've had a reset here, so load from original value + counter_value = emulator->data.counter[0]; + } + // Although the datasheet says increment by 0 is always possible, this is not the case on + // an actual tag. If the counter is at 0xFFFF, any writes are locked out. + if(counter_value == 0xFFFF) return false; + uint32_t increment = page_buff[0] | (page_buff[1] << 8); + if(counter_value == 0) { + counter_value = increment; + } else { + // Per datasheet specifying > 0x000F is supposed to NAK, but actual tag doesn't + increment &= 0x000F; + if(counter_value + increment > 0xFFFF) return false; + counter_value += increment; + } + // Commit to new value counter + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = (uint8_t)counter_value; + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = (uint8_t)(counter_value >> 8); + emulator->data.tearing[0] = MF_UL_TEARING_FLAG_DEFAULT; + if(counter_value == 0xFFFF) { + // Tag will lock out counter if final number is 0xFFFF, even if you try to roll it back + emulator->data.counter[1] = 0xFFFF; + } + emulator->data_changed = true; + return true; +} + static void mf_ul_emulate_write( MfUltralightEmulator* emulator, int16_t tag_addr, @@ -962,53 +1052,75 @@ static void mf_ul_emulate_write( *(uint32_t*)page_buff |= *(uint32_t*)&emulator->data.data[write_page * 4]; } else if(tag_addr == mf_ul_get_dynamic_lock_page_addr(&emulator->data)) { // Handle dynamic locks - uint16_t orig_locks = emulator->data.data[write_page * 4] | - (emulator->data.data[write_page * 4 + 1] << 8); - uint8_t orig_block_locks = emulator->data.data[write_page * 4 + 2]; - uint16_t new_locks = page_buff[0] | (page_buff[1] << 8); - uint8_t new_block_locks = page_buff[2]; + if(emulator->data.type == MfUltralightTypeNTAG203) { + // NTAG203 lock bytes are a bit different from the others + uint8_t orig_page_lock_byte = emulator->data.data[write_page * 4]; + uint8_t orig_cnt_lock_byte = emulator->data.data[write_page * 4 + 1]; + uint8_t new_page_lock_byte = page_buff[0]; + uint8_t new_cnt_lock_byte = page_buff[1]; - int block_lock_count; - switch(emulator->data.type) { - case MfUltralightTypeUL21: - block_lock_count = 5; - break; - case MfUltralightTypeNTAG213: - block_lock_count = 6; - break; - case MfUltralightTypeNTAG215: - block_lock_count = 4; - break; - case MfUltralightTypeNTAG216: - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - block_lock_count = 7; - break; - case MfUltralightTypeNTAGI2C2K: - case MfUltralightTypeNTAGI2CPlus2K: - block_lock_count = 8; - break; - default: - furi_assert(false); - block_lock_count = 0; - break; + if(orig_page_lock_byte & 0x01) // Block lock bits 1-3 + new_page_lock_byte &= ~0x0E; + if(orig_page_lock_byte & 0x10) // Block lock bits 5-7 + new_page_lock_byte &= ~0xE0; + for(uint8_t i = 0; i < 4; ++i) { + if(orig_cnt_lock_byte & (1 << i)) // Block lock counter bit + new_cnt_lock_byte &= ~(1 << (4 + i)); + } + + new_page_lock_byte |= orig_page_lock_byte; + new_cnt_lock_byte |= orig_cnt_lock_byte; + page_buff[0] = new_page_lock_byte; + page_buff[1] = new_cnt_lock_byte; + } else { + uint16_t orig_locks = emulator->data.data[write_page * 4] | + (emulator->data.data[write_page * 4 + 1] << 8); + uint8_t orig_block_locks = emulator->data.data[write_page * 4 + 2]; + uint16_t new_locks = page_buff[0] | (page_buff[1] << 8); + uint8_t new_block_locks = page_buff[2]; + + int block_lock_count; + switch(emulator->data.type) { + case MfUltralightTypeUL21: + block_lock_count = 5; + break; + case MfUltralightTypeNTAG213: + block_lock_count = 6; + break; + case MfUltralightTypeNTAG215: + block_lock_count = 4; + break; + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + block_lock_count = 7; + break; + case MfUltralightTypeNTAGI2C2K: + case MfUltralightTypeNTAGI2CPlus2K: + block_lock_count = 8; + break; + default: + furi_assert(false); + block_lock_count = 0; + break; + } + + for(int i = 0; i < block_lock_count; ++i) { + if(orig_block_locks & (1 << i)) new_locks &= ~(3 << (2 * i)); + } + + new_locks |= orig_locks; + new_block_locks |= orig_block_locks; + + page_buff[0] = new_locks & 0xff; + page_buff[1] = new_locks >> 8; + page_buff[2] = new_block_locks; + if(emulator->data.type >= MfUltralightTypeUL21 && + emulator->data.type <= MfUltralightTypeNTAG216) + page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT; + else + page_buff[3] = 0; } - - for(int i = 0; i < block_lock_count; ++i) { - if(orig_block_locks & (1 << i)) new_locks &= ~(3 << (2 * i)); - } - - new_locks |= orig_locks; - new_block_locks |= orig_block_locks; - - page_buff[0] = new_locks & 0xff; - page_buff[1] = new_locks >> 8; - page_buff[2] = new_block_locks; - if(emulator->data.type >= MfUltralightTypeUL21 && - emulator->data.type <= MfUltralightTypeNTAG216) - page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT; - else - page_buff[3] = 0; } memcpy(&emulator->data.data[write_page * 4], page_buff, 4); @@ -1025,6 +1137,18 @@ void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) if(emulator->supported_features & MfUltralightSupportSingleCounter) { emulator->read_counter_incremented = false; } + + if(emulator->data.type == MfUltralightTypeNTAG203) { + // Apply lockout if counter ever reached 0xFFFF + if(emulator->data.counter[1] == 0xFFFF) { + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = 0xFF; + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = 0xFF; + } + // Copy original counter value from data + emulator->data.counter[0] = + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | + (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); + } } else { if(emulator->config != NULL) { // ACCESS (less CFGLCK) and AUTH0 are updated when reactivated @@ -1034,6 +1158,10 @@ void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) emulator->config_cache.auth0 = emulator->config->auth0; } } + if(emulator->data.type == MfUltralightTypeNTAG203) { + // Mark counter as dirty + emulator->data.tearing[0] = 0; + } } void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) { @@ -1077,12 +1205,20 @@ bool mf_ul_prepare_emulation_response( // Check composite commands if(emulator->comp_write_cmd_started) { - // Compatibility write is the only one composit command if(buff_rx_len == 16 * 8) { - mf_ul_emulate_write( - emulator, emulator->comp_write_page_addr, emulator->comp_write_page_addr, buff_rx); - send_ack = true; - command_parsed = true; + if(emulator->data.type == MfUltralightTypeNTAG203 && + emulator->comp_write_page_addr == MF_UL_NTAG203_COUNTER_PAGE) { + send_ack = mf_ul_emulate_ntag203_counter_write(emulator, buff_rx); + command_parsed = send_ack; + } else { + mf_ul_emulate_write( + emulator, + emulator->comp_write_page_addr, + emulator->comp_write_page_addr, + buff_rx); + send_ack = true; + command_parsed = true; + } } emulator->comp_write_cmd_started = false; } else if(emulator->sector_select_cmd_started) { @@ -1099,7 +1235,7 @@ bool mf_ul_prepare_emulation_response( } else if(buff_rx_len >= 8) { uint8_t cmd = buff_rx[0]; if(cmd == MF_UL_GET_VERSION_CMD) { - if(emulator->data.type != MfUltralightTypeUnknown) { + if(emulator->data.type >= MfUltralightTypeUL11) { if(buff_rx_len == 1 * 8) { tx_bytes = sizeof(emulator->data.version); memcpy(buff_tx, &emulator->data.version, tx_bytes); @@ -1409,9 +1545,15 @@ bool mf_ul_prepare_emulation_response( int16_t tag_addr = mf_ultralight_page_addr_to_tag_addr( emulator->curr_sector, orig_write_page); if(!mf_ul_check_lock(emulator, tag_addr)) break; - mf_ul_emulate_write(emulator, tag_addr, write_page, &buff_rx[2]); - send_ack = true; - command_parsed = true; + if(emulator->data.type == MfUltralightTypeNTAG203 && + orig_write_page == MF_UL_NTAG203_COUNTER_PAGE) { + send_ack = mf_ul_emulate_ntag203_counter_write(emulator, &buff_rx[2]); + command_parsed = send_ack; + } else { + mf_ul_emulate_write(emulator, tag_addr, write_page, &buff_rx[2]); + send_ack = true; + command_parsed = true; + } } while(false); } } else if(cmd == MF_UL_FAST_WRITE) { @@ -1590,7 +1732,8 @@ bool mf_ul_prepare_emulation_response( } } } else { - reset_idle = true; + // NTAG203 appears to NAK instead of just falling off on invalid commands + if(emulator->data.type != MfUltralightTypeNTAG203) reset_idle = true; FURI_LOG_D(TAG, "Received invalid command"); } } else { diff --git a/lib/nfc_protocols/mifare_ultralight.h b/lib/nfc_protocols/mifare_ultralight.h index 36b81fdf..77dbd1e4 100644 --- a/lib/nfc_protocols/mifare_ultralight.h +++ b/lib/nfc_protocols/mifare_ultralight.h @@ -26,15 +26,23 @@ #define MF_UL_NAK_INVALID_ARGUMENT (0x0) #define MF_UL_NAK_AUTHLIM_REACHED (0x4) +#define MF_UL_NTAG203_COUNTER_PAGE (41) + +// Important: order matters; some features are based on positioning in this enum typedef enum { MfUltralightTypeUnknown, + MfUltralightTypeNTAG203, + // Below have config pages and GET_VERSION support MfUltralightTypeUL11, MfUltralightTypeUL21, MfUltralightTypeNTAG213, MfUltralightTypeNTAG215, MfUltralightTypeNTAG216, + // Below also have sector select + // NTAG I2C's *does not* have regular config pages, so it's a bit of an odd duck MfUltralightTypeNTAGI2C1K, MfUltralightTypeNTAGI2C2K, + // NTAG I2C Plus has stucture expected from NTAG21x MfUltralightTypeNTAGI2CPlus1K, MfUltralightTypeNTAGI2CPlus2K, @@ -58,6 +66,8 @@ typedef enum { MfUltralightSupportSingleCounter = 1 << 10, // ASCII mirror is not a command, but handy to have as a flag MfUltralightSupportAsciiMirror = 1 << 11, + // NTAG203 counter that's in memory rather than through a command + MfUltralightSupportCounterInMemory = 1 << 12, } MfUltralightFeatures; typedef enum { @@ -173,6 +183,11 @@ bool mf_ultralight_read_version( MfUltralightReader* reader, MfUltralightData* data); +bool mf_ultralight_read_pages_direct( + FuriHalNfcTxRxContext* tx_rx, + uint8_t start_index, + uint8_t* data); + bool mf_ultralight_read_pages( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader,