diff options
Diffstat (limited to 'lib/nfc/protocols')
-rw-r--r-- | lib/nfc/protocols/crypto1.c | 75 | ||||
-rw-r--r-- | lib/nfc/protocols/crypto1.h | 23 | ||||
-rw-r--r-- | lib/nfc/protocols/emv.c | 429 | ||||
-rwxr-xr-x | lib/nfc/protocols/emv.h | 87 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_classic.c | 989 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_classic.h | 145 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_common.c | 17 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_common.h | 12 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_desfire.c | 623 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_desfire.h | 169 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_ultralight.c | 1791 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_ultralight.h | 222 | ||||
-rw-r--r-- | lib/nfc/protocols/nfc_util.c | 47 | ||||
-rw-r--r-- | lib/nfc/protocols/nfc_util.h | 11 | ||||
-rwxr-xr-x | lib/nfc/protocols/nfca.c | 142 | ||||
-rw-r--r-- | lib/nfc/protocols/nfca.h | 28 |
16 files changed, 4810 insertions, 0 deletions
diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c new file mode 100644 index 00000000..f08164ba --- /dev/null +++ b/lib/nfc/protocols/crypto1.c @@ -0,0 +1,75 @@ +#include "crypto1.h" +#include "nfc_util.h" +#include <furi.h> + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define SWAPENDIAN(x) (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16) +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) + +#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) + +void crypto1_reset(Crypto1* crypto1) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; +} + +void crypto1_init(Crypto1* crypto1, uint64_t key) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; + for(int8_t i = 47; i > 0; i -= 2) { + crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); + crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); + } +} + +uint32_t crypto1_filter(uint32_t in) { + uint32_t out = 0; + out = 0xf22c0 >> (in & 0xf) & 16; + out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; + out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; + out |= 0x1e458 >> (in >> 12 & 0xf) & 2; + out |= 0x0d938 >> (in >> 16 & 0xf) & 1; + return FURI_BIT(0xEC57E80A, out); +} + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = crypto1_filter(crypto1->odd); + uint32_t feed = out & (!!is_encrypted); + feed ^= !!in; + feed ^= LF_POLY_ODD & crypto1->odd; + feed ^= LF_POLY_EVEN & crypto1->even; + crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); + + FURI_SWAP(crypto1->odd, crypto1->even); + return out; +} + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = 0; + for(uint8_t i = 0; i < 8; i++) { + out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; + } + return out; +} + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { + furi_assert(crypto1); + uint32_t out = 0; + for(uint8_t i = 0; i < 32; i++) { + out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); + } + return out; +} + +uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + + return SWAPENDIAN(x); +} diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/crypto1.h new file mode 100644 index 00000000..07b39c22 --- /dev/null +++ b/lib/nfc/protocols/crypto1.h @@ -0,0 +1,23 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +typedef struct { + uint32_t odd; + uint32_t even; +} Crypto1; + +void crypto1_reset(Crypto1* crypto1); + +void crypto1_init(Crypto1* crypto1, uint64_t key); + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); + +uint32_t crypto1_filter(uint32_t in); + +uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c new file mode 100644 index 00000000..935c9f63 --- /dev/null +++ b/lib/nfc/protocols/emv.c @@ -0,0 +1,429 @@ +#include "emv.h" + +#include <core/common_defines.h> + +#define TAG "Emv" + +const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information +const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type +const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator +const PDOLValue pdol_term_trans_qualifies = { + 0x9F66, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_addtnl_term_qualifies = { + 0x9F40, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_amount_authorise = { + 0x9F02, + {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised +const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount +const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code +const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code +const PDOLValue pdol_term_verification = { + 0x95, + {0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results +const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date +const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type +const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert +const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number + +const PDOLValue* const pdol_values[] = { + &pdol_term_info, + &pdol_term_type, + &pdol_merchant_type, + &pdol_term_trans_qualifies, + &pdol_addtnl_term_qualifies, + &pdol_amount_authorise, + &pdol_amount, + &pdol_country_code, + &pdol_currency_code, + &pdol_term_verification, + &pdol_transaction_date, + &pdol_transaction_type, + &pdol_transaction_cert, + &pdol_unpredict_number, +}; + +static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, + 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, + 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07, + 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x50, 0x04, + 0x56, 0x49, 0x53, 0x41, 0x87, 0x01, 0x01, 0x90, 0x00}; +static const uint8_t select_app_ans[] = {0x6F, 0x20, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, + 0x10, 0x10, 0xA5, 0x15, 0x50, 0x04, 0x56, 0x49, 0x53, + 0x41, 0x9F, 0x38, 0x0C, 0x9F, 0x66, 0x04, 0x9F, 0x02, + 0x06, 0x9F, 0x37, 0x04, 0x5F, 0x2A, 0x02, 0x90, 0x00}; +static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x13, 0x55, 0x70, + 0x73, 0x83, 0x85, 0x87, 0x73, 0x31, 0xD1, 0x80, 0x22, 0x01, + 0x38, 0x84, 0x77, 0x94, 0x00, 0x00, 0x1F, 0x5F, 0x34, 0x01, + 0x00, 0x9F, 0x10, 0x07, 0x06, 0x01, 0x11, 0x03, 0x80, 0x00, + 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96, + 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06, + 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00}; + +static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { + if(furi_log_get_level() == FuriLogLevelTrace) { + FURI_LOG_T(TAG, "%s", message); + printf("TX: "); + for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) { + printf("%02X ", tx_rx->tx_data[i]); + } + printf("\r\nRX: "); + for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) { + printf("%02X ", tx_rx->rx_data[i]); + } + printf("\r\n"); + } +} + +static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) { + uint16_t i = 0; + uint16_t tag = 0, first_byte = 0; + uint16_t tlen = 0; + bool success = false; + + while(i < len) { + first_byte = buff[i]; + if((first_byte & 31) == 31) { // 2-byte tag + tag = buff[i] << 8 | buff[i + 1]; + i++; + FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag); + } else { + tag = buff[i]; + FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag); + } + i++; + tlen = buff[i]; + if((tlen & 128) == 128) { // long length value + i++; + tlen = buff[i]; + FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen); + } else { + FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen); + } + i++; + if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse + FURI_LOG_T(TAG, "Constructed TLV %x", tag); + if(!emv_decode_response(&buff[i], tlen, app)) { + FURI_LOG_T(TAG, "Failed to decode response for %x", tag); + // return false; + } else { + success = true; + } + } else { + switch(tag) { + case EMV_TAG_AID: + app->aid_len = tlen; + memcpy(app->aid, &buff[i], tlen); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AID %x", tag); + break; + case EMV_TAG_PRIORITY: + memcpy(&app->priority, &buff[i], tlen); + success = true; + break; + case EMV_TAG_CARD_NAME: + memcpy(app->name, &buff[i], tlen); + app->name[tlen] = '\0'; + app->name_found = true; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name); + break; + case EMV_TAG_PDOL: + memcpy(app->pdol.data, &buff[i], tlen); + app->pdol.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_AFL: + memcpy(app->afl.data, &buff[i], tlen); + app->afl.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_CARD_NUM: // Track 2 Equivalent Data. 0xD0 delimits PAN from expiry (YYMM) + for(int x = 1; x < tlen; x++) { + if(buff[i + x + 1] > 0xD0) { + memcpy(app->card_number, &buff[i], x + 1); + app->card_number_len = x + 1; + break; + } + } + success = true; + FURI_LOG_T( + TAG, + "found EMV_TAG_CARD_NUM %x (len=%d)", + EMV_TAG_CARD_NUM, + app->card_number_len); + break; + case EMV_TAG_PAN: + memcpy(app->card_number, &buff[i], tlen); + app->card_number_len = tlen; + success = true; + break; + case EMV_TAG_EXP_DATE: + app->exp_year = buff[i]; + app->exp_month = buff[i + 1]; + success = true; + break; + case EMV_TAG_CURRENCY_CODE: + app->currency_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_COUNTRY_CODE: + app->country_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + } + } + i += tlen; + } + return success; +} + +bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool app_aid_found = false; + const uint8_t emv_select_ppse_cmd[] = { + 0x00, 0xA4, // SELECT ppse + 0x04, 0x00, // P1:By name, P2: empty + 0x0e, // Lc: Data length + 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string: + 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE) + 0x00 // Le + }; + + memcpy(tx_rx->tx_data, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd)); + tx_rx->tx_bits = sizeof(emv_select_ppse_cmd) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + FURI_LOG_D(TAG, "Send select PPSE"); + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "Select PPSE answer:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + app_aid_found = true; + } else { + FURI_LOG_E(TAG, "Failed to parse application"); + } + } else { + FURI_LOG_E(TAG, "Failed select PPSE"); + } + + return app_aid_found; +} + +bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool select_app_success = false; + const uint8_t emv_select_header[] = { + 0x00, + 0xA4, // SELECT application + 0x04, + 0x00 // P1:By name, P2:First or only occurence + }; + uint16_t size = sizeof(emv_select_header); + + // Copy header + memcpy(tx_rx->tx_data, emv_select_header, size); + // Copy AID + tx_rx->tx_data[size++] = app->aid_len; + memcpy(&tx_rx->tx_data[size], app->aid, app->aid_len); + size += app->aid_len; + tx_rx->tx_data[size++] = 0x00; + tx_rx->tx_bits = size * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + FURI_LOG_D(TAG, "Start application"); + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "Start application answer:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + select_app_success = true; + } else { + FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); + } + } else { + FURI_LOG_E(TAG, "Failed to start application"); + } + + return select_app_success; +} + +static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { + bool tag_found; + for(uint16_t i = 0; i < src->size; i++) { + tag_found = false; + for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) { + if(src->data[i] == pdol_values[j]->tag) { + // Found tag with 1 byte length + uint8_t len = src->data[++i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) { + // Found tag with 2 byte length + i += 2; + uint8_t len = src->data[i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } + } + if(!tag_found) { + // Unknown tag, fill zeros + i += 2; + uint8_t len = src->data[i]; + memset(dest->data + dest->size, 0, len); + dest->size += len; + } + } + return dest->size; +} + +static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool card_num_read = false; + const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; + uint16_t size = sizeof(emv_gpo_header); + + // Copy header + memcpy(tx_rx->tx_data, emv_gpo_header, size); + APDU pdol_data = {0, {0}}; + // Prepare and copy pdol parameters + emv_prepare_pdol(&pdol_data, &app->pdol); + tx_rx->tx_data[size++] = 0x02 + pdol_data.size; + tx_rx->tx_data[size++] = 0x83; + tx_rx->tx_data[size++] = pdol_data.size; + memcpy(tx_rx->tx_data + size, pdol_data.data, pdol_data.size); + size += pdol_data.size; + tx_rx->tx_data[size++] = 0; + tx_rx->tx_bits = size * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + FURI_LOG_D(TAG, "Get proccessing options"); + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "Get processing options answer:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(app->card_number_len > 0) { + card_num_read = true; + } + } + } else { + FURI_LOG_E(TAG, "Failed to get processing options"); + } + + return card_num_read; +} + +static bool emv_read_sfi_record( + FuriHalNfcTxRxContext* tx_rx, + EmvApplication* app, + uint8_t sfi, + uint8_t record_num) { + bool card_num_read = false; + uint8_t sfi_param = (sfi << 3) | (1 << 2); + uint8_t emv_sfi_header[] = { + 0x00, + 0xB2, // READ RECORD + record_num, // P1:record_number + sfi_param, // P2:SFI + 0x00 // Le + }; + + memcpy(tx_rx->tx_data, emv_sfi_header, sizeof(emv_sfi_header)); + tx_rx->tx_bits = sizeof(emv_sfi_header) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "SFI record:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + card_num_read = true; + } + } else { + FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num); + } + + return card_num_read; +} + +static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool card_num_read = false; + + if(app->afl.size == 0) { + return false; + } + + FURI_LOG_D(TAG, "Search PAN in SFI"); + // Iterate through all files + for(size_t i = 0; i < app->afl.size; i += 4) { + uint8_t sfi = app->afl.data[i] >> 3; + uint8_t record_start = app->afl.data[i + 1]; + uint8_t record_end = app->afl.data[i + 2]; + // Iterate through all records in file + for(uint8_t record = record_start; record <= record_end; ++record) { + card_num_read |= emv_read_sfi_record(tx_rx, app, sfi, record); + } + } + + return card_num_read; +} + +bool emv_search_application(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { + furi_assert(tx_rx); + furi_assert(emv_app); + memset(emv_app, 0, sizeof(EmvApplication)); + + return emv_select_ppse(tx_rx, emv_app); +} + +bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { + furi_assert(tx_rx); + furi_assert(emv_app); + bool card_num_read = false; + memset(emv_app, 0, sizeof(EmvApplication)); + + do { + if(!emv_select_ppse(tx_rx, emv_app)) break; + if(!emv_select_app(tx_rx, emv_app)) break; + if(emv_get_processing_options(tx_rx, emv_app)) { + card_num_read = true; + } else { + card_num_read = emv_read_files(tx_rx, emv_app); + } + } while(false); + + return card_num_read; +} + +bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { + furi_assert(tx_rx); + bool emulation_complete = false; + tx_rx->tx_bits = 0; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + do { + FURI_LOG_D(TAG, "Read select PPSE command"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + memcpy(tx_rx->tx_data, select_ppse_ans, sizeof(select_ppse_ans)); + tx_rx->tx_bits = sizeof(select_ppse_ans) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + FURI_LOG_D(TAG, "Send select PPSE answer and read select App command"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + memcpy(tx_rx->tx_data, select_app_ans, sizeof(select_app_ans)); + tx_rx->tx_bits = sizeof(select_app_ans) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + FURI_LOG_D(TAG, "Send select App answer and read get PDOL command"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + memcpy(tx_rx->tx_data, pdol_ans, sizeof(pdol_ans)); + tx_rx->tx_bits = sizeof(pdol_ans) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + FURI_LOG_D(TAG, "Send get PDOL answer"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + emulation_complete = true; + } while(false); + + return emulation_complete; +} diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h new file mode 100755 index 00000000..b5a0c574 --- /dev/null +++ b/lib/nfc/protocols/emv.h @@ -0,0 +1,87 @@ +#pragma once + +#include <furi_hal_nfc.h> + +#define MAX_APDU_LEN 255 + +#define EMV_TAG_APP_TEMPLATE 0x61 +#define EMV_TAG_AID 0x4F +#define EMV_TAG_PRIORITY 0x87 +#define EMV_TAG_PDOL 0x9F38 +#define EMV_TAG_CARD_NAME 0x50 +#define EMV_TAG_FCI 0xBF0C +#define EMV_TAG_LOG_CTRL 0x9F4D +#define EMV_TAG_CARD_NUM 0x57 +#define EMV_TAG_PAN 0x5A +#define EMV_TAG_AFL 0x94 +#define EMV_TAG_EXP_DATE 0x5F24 +#define EMV_TAG_COUNTRY_CODE 0x5F28 +#define EMV_TAG_CURRENCY_CODE 0x9F42 +#define EMV_TAG_CARDHOLDER_NAME 0x5F20 + +typedef struct { + char name[32]; + uint8_t aid[16]; + uint16_t aid_len; + uint8_t number[10]; + uint8_t number_len; + uint8_t exp_mon; + uint8_t exp_year; + uint16_t country_code; + uint16_t currency_code; +} EmvData; + +typedef struct { + uint16_t tag; + uint8_t data[]; +} PDOLValue; + +typedef struct { + uint8_t size; + uint8_t data[MAX_APDU_LEN]; +} APDU; + +typedef struct { + uint8_t priority; + uint8_t aid[16]; + uint8_t aid_len; + char name[32]; + bool name_found; + uint8_t card_number[10]; + uint8_t card_number_len; + uint8_t exp_month; + uint8_t exp_year; + uint16_t country_code; + uint16_t currency_code; + APDU pdol; + APDU afl; +} EmvApplication; + +/** Read bank card data + * @note Search EMV Application, start it, try to read AID, PAN, card name, + * expiration date, currency and country codes + * + * @param tx_rx FuriHalNfcTxRxContext instance + * @param emv_app EmvApplication instance + * + * @return true on success + */ +bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); + +/** Search for EMV Application + * + * @param tx_rx FuriHalNfcTxRxContext instance + * @param emv_app EmvApplication instance + * + * @return true on success + */ +bool emv_search_application(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); + +/** Emulate bank card + * @note Answer to application selection and PDOL + * + * @param tx_rx FuriHalNfcTxRxContext instance + * + * @return true on success + */ +bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c new file mode 100644 index 00000000..93fe6f69 --- /dev/null +++ b/lib/nfc/protocols/mifare_classic.c @@ -0,0 +1,989 @@ +#include "mifare_classic.h" +#include "nfca.h" +#include "nfc_util.h" +#include <furi_hal_rtc.h> + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define TAG "MfClassic" + +#define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U) +#define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U) +#define MF_CLASSIC_READ_SECT_CMD (0x30) + +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + +const char* mf_classic_get_type_str(MfClassicType type) { + if(type == MfClassicType1k) { + return "MIFARE Classic 1K"; + } else if(type == MfClassicType4k) { + return "MIFARE Classic 4K"; + } else { + return "Unknown"; + } +} + +static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { + furi_assert(sector < 40); + if(sector < 32) { + return sector * 4; + } else { + return 32 * 4 + (sector - 32) * 16; + } +} + +uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) { + furi_assert(sector < 40); + if(sector < 32) { + return sector * 4 + 3; + } else { + return 32 * 4 + (sector - 32) * 16 + 15; + } +} + +uint8_t mf_classic_get_sector_by_block(uint8_t block) { + if(block < 128) { + return (block | 0x03) / 4; + } else { + return 32 + ((block | 0xf) - 32 * 4) / 16; + } +} + +static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { + furi_assert(sector < 40); + return sector < 32 ? 4 : 16; +} + +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { + if(block < 128) { + return block | 0x03; + } else { + return block | 0x0f; + } +} + +bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer_num_by_block(block); +} + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) { + furi_assert(data); + uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector); + return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value; +} + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { + if(type == MfClassicType1k) { + return MF_CLASSIC_1K_TOTAL_SECTORS_NUM; + } else if(type == MfClassicType4k) { + return MF_CLASSIC_4K_TOTAL_SECTORS_NUM; + } else { + return 0; + } +} + +static uint16_t mf_classic_get_total_block_num(MfClassicType type) { + if(type == MfClassicType1k) { + return 64; + } else if(type == MfClassicType4k) { + return 256; + } else { + return 0; + } +} + +bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) { + furi_assert(data); + + return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1); +} + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) { + furi_assert(data); + + if(mf_classic_is_sector_trailer(block_num)) { + memcpy(&data->block[block_num].value[6], &block_data->value[6], 4); + } else { + memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE); + } + FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); +} + +bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { + furi_assert(data); + + bool key_found = false; + if(key_type == MfClassicKeyA) { + key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1); + } else if(key_type == MfClassicKeyB) { + key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1); + } + + return key_found; +} + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKey key_type, + uint64_t key) { + furi_assert(data); + + uint8_t key_arr[6] = {}; + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + nfc_util_num2bytes(key, 6, key_arr); + if(key_type == MfClassicKeyA) { + memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a)); + FURI_BIT_SET(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyB) { + memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b)); + FURI_BIT_SET(data->key_b_mask, sector_num); + } +} + +bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + bool sector_read = false; + do { + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break; + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break; + uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); + uint8_t block_read = true; + for(size_t i = start_block; i < start_block + total_blocks; i++) { + block_read = mf_classic_is_block_read(data, i); + if(!block_read) break; + } + sector_read = block_read; + } while(false); + + return sector_read; +} + +void mf_classic_get_read_sectors_and_keys( + MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found) { + furi_assert(data); + *sectors_read = 0; + *keys_found = 0; + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + for(size_t i = 0; i < sectors_total; i++) { + if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { + *keys_found += 1; + } + if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { + *keys_found += 1; + } + uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); + uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); + bool blocks_read = true; + for(size_t i = first_block; i < first_block + total_blocks_in_sec; i++) { + blocks_read = mf_classic_is_block_read(data, i); + if(!blocks_read) break; + } + if(blocks_read) { + *sectors_read += 1; + } + } +} + +static bool mf_classic_is_allowed_access_sector_trailer( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = emulator->data.block[block_num].value; + uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | + ((sector_trailer[8] >> 7) & 0x01); + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionKeyBRead: { + return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionKeyBWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionACRead: { + return ( + (key == MfClassicKeyA) || + (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + } + case MfClassicActionACWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +static bool mf_classic_is_allowed_access_data_block( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = + emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; + + uint8_t sector_block; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) | + ((sector_trailer[8] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) | + ((sector_trailer[8] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) | + ((sector_trailer[8] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key == MfClassicKeyB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +static bool mf_classic_is_allowed_access( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + if(mf_classic_is_sector_trailer(block_num)) { + return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action); + } else { + return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action); + } +} + +bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + UNUSED(ATQA1); + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + return true; + } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { + return true; + } else { + return false; + } +} + +MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + UNUSED(ATQA1); + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + return MfClassicType1k; + } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { + return MfClassicType4k; + } + return MfClassicType1k; +} + +bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader) { + UNUSED(ATQA1); + furi_assert(reader); + memset(reader, 0, sizeof(MfClassicReader)); + + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + reader->type = MfClassicType1k; + } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { + reader->type = MfClassicType4k; + } else { + return false; + } + return true; +} + +void mf_classic_reader_add_sector( + MfClassicReader* reader, + uint8_t sector, + uint64_t key_a, + uint64_t key_b) { + furi_assert(reader); + furi_assert(sector < MF_CLASSIC_SECTORS_MAX); + furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)); + + if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) { + reader->sector_reader[reader->sectors_to_read].key_a = key_a; + reader->sector_reader[reader->sectors_to_read].key_b = key_b; + reader->sector_reader[reader->sectors_to_read].sector_num = sector; + reader->sectors_to_read++; + } +} + +void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) { + furi_assert(auth_ctx); + auth_ctx->sector = sector; + auth_ctx->key_a = MF_CLASSIC_NO_KEY; + auth_ctx->key_b = MF_CLASSIC_NO_KEY; +} + +static bool mf_classic_auth( + FuriHalNfcTxRxContext* tx_rx, + uint32_t block, + uint64_t key, + MfClassicKey key_type, + Crypto1* crypto) { + bool auth_success = false; + uint32_t cuid = 0; + memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); + memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + do { + if(!furi_hal_nfc_activate_nfca(200, &cuid)) break; + if(key_type == MfClassicKeyA) { + tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD; + } else { + tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_B_CMD; + } + tx_rx->tx_data[1] = block; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc; + tx_rx->tx_bits = 2 * 8; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; + + uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4); + crypto1_init(crypto, key); + crypto1_word(crypto, nt ^ cuid, 0); + uint8_t nr[4] = {}; + nfc_util_num2bytes(prng_successor(DWT->CYCCNT, 32), 4, nr); + for(uint8_t i = 0; i < 4; i++) { + tx_rx->tx_data[i] = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; + tx_rx->tx_parity[0] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01) << (7 - i)); + } + nt = prng_successor(nt, 32); + for(uint8_t i = 4; i < 8; i++) { + nt = prng_successor(nt, 8); + tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ (nt & 0xff); + tx_rx->tx_parity[0] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt & 0xff)) & 0x01) + << (7 - i)); + } + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + tx_rx->tx_bits = 8 * 8; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; + if(tx_rx->rx_bits == 32) { + crypto1_word(crypto, 0, 0); + auth_success = true; + } + } while(false); + + return auth_success; +} + +bool mf_classic_authenticate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type) { + furi_assert(tx_rx); + + Crypto1 crypto = {}; + bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto); + furi_hal_nfc_sleep(); + return key_found; +} + +bool mf_classic_auth_attempt( + FuriHalNfcTxRxContext* tx_rx, + MfClassicAuthContext* auth_ctx, + uint64_t key) { + furi_assert(tx_rx); + furi_assert(auth_ctx); + bool found_key = false; + bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) && + (auth_ctx->key_b == MF_CLASSIC_NO_KEY); + + Crypto1 crypto; + if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) { + // Try AUTH with key A + if(mf_classic_auth( + tx_rx, + mf_classic_get_first_block_num_of_sector(auth_ctx->sector), + key, + MfClassicKeyA, + &crypto)) { + auth_ctx->key_a = key; + found_key = true; + } + } + + if(need_halt) { + furi_hal_nfc_sleep(); + } + + if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) { + // Try AUTH with key B + if(mf_classic_auth( + tx_rx, + mf_classic_get_first_block_num_of_sector(auth_ctx->sector), + key, + MfClassicKeyB, + &crypto)) { + auth_ctx->key_b = key; + found_key = true; + } + } + + return found_key; +} + +bool mf_classic_read_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* block) { + furi_assert(tx_rx); + furi_assert(crypto); + furi_assert(block); + + bool read_block_success = false; + uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00}; + nfca_append_crc16(plain_cmd, 2); + memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); + memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); + + for(uint8_t i = 0; i < 4; i++) { + tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i]; + tx_rx->tx_parity[0] |= + ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_cmd[i])) & 0x01) << (7 - i); + } + tx_rx->tx_bits = 4 * 9; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { + uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; + for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) { + block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; + } + uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); + uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | + block_received[MF_CLASSIC_BLOCK_SIZE]; + if(crc_received != crc_calc) { + FURI_LOG_E( + TAG, + "Incorrect CRC while reading block %d. Expected %04X, Received %04X", + block_num, + crc_received, + crc_calc); + } else { + memcpy(block->value, block_received, MF_CLASSIC_BLOCK_SIZE); + read_block_success = true; + } + } + } + return read_block_success; +} + +void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) { + furi_assert(tx_rx); + furi_assert(data); + + furi_hal_nfc_sleep(); + bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA); + bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB); + uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); + MfClassicBlock block_tmp = {}; + uint64_t key = 0; + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num); + Crypto1 crypto = {}; + + uint8_t blocks_read = 0; + do { + if(!key_a_found) break; + FURI_LOG_D(TAG, "Try to read blocks with key A"); + key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto)) break; + for(size_t i = start_block; i < start_block + total_blocks; i++) { + if(!mf_classic_is_block_read(data, i)) { + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } + } else { + blocks_read++; + } + } + FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); + } while(false); + do { + if(blocks_read == total_blocks) break; + if(!key_b_found) break; + FURI_LOG_D(TAG, "Try to read blocks with key B"); + key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto)) break; + for(size_t i = start_block; i < start_block + total_blocks; i++) { + if(!mf_classic_is_block_read(data, i)) { + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } + } else { + blocks_read++; + } + } + FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); + } while(false); +} + +static bool mf_classic_read_sector_with_reader( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + MfClassicSectorReader* sector_reader, + MfClassicSector* sector) { + furi_assert(tx_rx); + furi_assert(sector_reader); + furi_assert(sector); + + uint64_t key; + MfClassicKey key_type; + uint8_t first_block; + bool sector_read = false; + + furi_hal_nfc_sleep(); + do { + // Activate card + first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num); + if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { + key = sector_reader->key_a; + key_type = MfClassicKeyA; + } else if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { + key = sector_reader->key_b; + key_type = MfClassicKeyB; + } else { + break; + } + + // Auth to first block in sector + if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break; + sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); + + // Read blocks + for(uint8_t i = 0; i < sector->total_blocks; i++) { + mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]); + } + // Save sector keys in last block + if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { + nfc_util_num2bytes( + sector_reader->key_a, 6, §or->block[sector->total_blocks - 1].value[0]); + } + if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { + nfc_util_num2bytes( + sector_reader->key_b, 6, §or->block[sector->total_blocks - 1].value[10]); + } + + sector_read = true; + } while(false); + + return sector_read; +} + +uint8_t mf_classic_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfClassicReader* reader, + MfClassicData* data) { + furi_assert(tx_rx); + furi_assert(reader); + furi_assert(data); + + uint8_t sectors_read = 0; + data->type = reader->type; + data->key_a_mask = 0; + data->key_b_mask = 0; + MfClassicSector temp_sector = {}; + for(uint8_t i = 0; i < reader->sectors_to_read; i++) { + if(mf_classic_read_sector_with_reader( + tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) { + uint8_t first_block = + mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num); + for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { + mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); + } + if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) { + mf_classic_set_key_found( + data, + reader->sector_reader[i].sector_num, + MfClassicKeyA, + reader->sector_reader[i].key_a); + } + if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) { + mf_classic_set_key_found( + data, + reader->sector_reader[i].sector_num, + MfClassicKeyB, + reader->sector_reader[i].key_b); + } + sectors_read++; + } + } + + return sectors_read; +} + +uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) { + furi_assert(tx_rx); + furi_assert(data); + + uint8_t sectors_read = 0; + Crypto1 crypto = {}; + uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); + uint64_t key_a = 0; + uint64_t key_b = 0; + MfClassicSectorReader sec_reader = {}; + MfClassicSector temp_sector = {}; + + for(size_t i = 0; i < total_sectors; i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); + // Load key A + if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { + sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6); + } else { + sec_reader.key_a = MF_CLASSIC_NO_KEY; + } + // Load key B + if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { + sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6); + } else { + sec_reader.key_b = MF_CLASSIC_NO_KEY; + } + if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) { + sec_reader.sector_num = i; + if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) { + uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); + for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { + mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); + } + sectors_read++; + } + } + } + return sectors_read; +} + +void mf_crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data) { + if(encrypted_data_bits < 8) { + uint8_t decrypted_byte = 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; + decrypted_data[0] = decrypted_byte; + } else { + for(size_t i = 0; i < encrypted_data_bits / 8; i++) { + decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + } + } +} + +void mf_crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity) { + if(plain_data_bits < 8) { + encrypted_data[0] = 0; + for(size_t i = 0; i < plain_data_bits; i++) { + encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + } else { + memset(encrypted_parity, 0, plain_data_bits / 8 + 1); + for(uint8_t i = 0; i < plain_data_bits / 8; i++) { + encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + encrypted_parity[i / 8] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) + << (7 - (i & 0x0007))); + } + } +} + +bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(emulator); + furi_assert(tx_rx); + bool command_processed = false; + bool is_encrypted = false; + uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; + MfClassicKey access_key = MfClassicKeyA; + + // Read command + while(!command_processed) { + if(!is_encrypted) { + memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); + } else { + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + FURI_LOG_D( + TAG, + "Error in tx rx. Tx :%d bits, Rx: %d bits", + tx_rx->tx_bits, + tx_rx->rx_bits); + break; + } + mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + } + + if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { + FURI_LOG_T(TAG, "Halt received"); + furi_hal_nfc_listen_sleep(); + command_processed = true; + break; + } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { + uint8_t block = plain_data[1]; + uint64_t key = 0; + uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); + MfClassicSectorTrailer* sector_trailer = + (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; + if(plain_data[0] == 0x60) { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); + access_key = MfClassicKeyA; + } else { + key = nfc_util_bytes2num(sector_trailer->key_b, 6); + access_key = MfClassicKeyB; + } + + uint32_t nonce = prng_successor(DWT->CYCCNT, 32); + uint8_t nt[4]; + uint8_t nt_keystream[4]; + nfc_util_num2bytes(nonce, 4, nt); + nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream); + crypto1_init(&emulator->crypto, key); + if(!is_encrypted) { + crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); + memcpy(tx_rx->tx_data, nt, sizeof(nt)); + tx_rx->tx_parity[0] = 0; + for(size_t i = 0; i < sizeof(nt); i++) { + tx_rx->tx_parity[0] |= nfc_util_odd_parity8(nt[i]) << (7 - i); + } + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } else { + mf_crypto1_encrypt( + &emulator->crypto, + nt_keystream, + nt, + sizeof(nt) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { + FURI_LOG_E(TAG, "Error in NT exchange"); + command_processed = true; + break; + } + + if(tx_rx->rx_bits != 64) { + FURI_LOG_W(TAG, "Incorrect nr + ar"); + command_processed = true; + break; + } + + uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); + uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4); + + FURI_LOG_D( + TAG, + "%08x key%c block %d nt/nr/ar: %08x %08x %08x", + emulator->cuid, + access_key == MfClassicKeyA ? 'A' : 'B', + sector_trailer_block, + nonce, + nr, + ar); + + crypto1_word(&emulator->crypto, nr, 1); + uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); + if(cardRr != prng_successor(nonce, 64)) { + FURI_LOG_T(TAG, "Wrong AUTH! %08X != %08X", cardRr, prng_successor(nonce, 64)); + // Don't send NACK, as tag don't send it + command_processed = true; + break; + } + + uint32_t ans = prng_successor(nonce, 96); + uint8_t responce[4] = {}; + nfc_util_num2bytes(ans, 4, responce); + mf_crypto1_encrypt( + &emulator->crypto, + NULL, + responce, + sizeof(responce) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(responce) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + + is_encrypted = true; + } else if(is_encrypted && plain_data[0] == 0x30) { + uint8_t block = plain_data[1]; + uint8_t block_data[18] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyARead)) { + memset(block_data, 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBRead)) { + memset(&block_data[10], 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACRead)) { + memset(&block_data[6], 0, 4); + } + } else { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead)) { + memset(block_data, 0, 16); + } + } + nfca_append_crc16(block_data, 16); + + mf_crypto1_encrypt( + &emulator->crypto, + NULL, + block_data, + sizeof(block_data) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = 18 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } else if(is_encrypted && plain_data[0] == 0xA0) { + uint8_t block = plain_data[1]; + if(block > mf_classic_get_total_block_num(emulator->data.type)) { + break; + } + // Send ACK + uint8_t ack = 0x0A; + mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + if(tx_rx->rx_bits != 18 * 8) break; + + mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + uint8_t block_data[16] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyAWrite)) { + memcpy(block_data, plain_data, 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBWrite)) { + memcpy(&block_data[10], &plain_data[10], 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACWrite)) { + memcpy(&block_data[6], &plain_data[6], 4); + } + } else { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataWrite)) { + memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); + } + } + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) { + memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); + emulator->data_changed = true; + } + // Send ACK + ack = 0x0A; + mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + } else { + // Unknown command + break; + } + } + + if(!command_processed) { + // Send NACK + uint8_t nack = 0x04; + if(is_encrypted) { + mf_crypto1_encrypt( + &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); + } else { + tx_rx->tx_data[0] = nack; + } + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + furi_hal_nfc_tx_rx(tx_rx, 300); + } + + return true; +} diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h new file mode 100644 index 00000000..85f67b11 --- /dev/null +++ b/lib/nfc/protocols/mifare_classic.h @@ -0,0 +1,145 @@ +#pragma once + +#include <furi_hal_nfc.h> + +#include "crypto1.h" + +#define MF_CLASSIC_BLOCK_SIZE (16) +#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) +#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16) +#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40) + +#define MF_CLASSIC_SECTORS_MAX (40) +#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16) + +#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF) +#define MF_CLASSIC_MAX_DATA_SIZE (16) +#define MF_CLASSIC_KEY_SIZE (6) +#define MF_CLASSIC_ACCESS_BYTES_SIZE (4) + +typedef enum { + MfClassicType1k, + MfClassicType4k, +} MfClassicType; + +typedef enum { + MfClassicKeyA, + MfClassicKeyB, +} MfClassicKey; + +typedef struct { + uint8_t value[MF_CLASSIC_BLOCK_SIZE]; +} MfClassicBlock; + +typedef struct { + uint8_t key_a[MF_CLASSIC_KEY_SIZE]; + uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE]; + uint8_t key_b[MF_CLASSIC_KEY_SIZE]; +} MfClassicSectorTrailer; + +typedef struct { + uint8_t total_blocks; + MfClassicBlock block[MF_CLASSIC_BLOCKS_IN_SECTOR_MAX]; +} MfClassicSector; + +typedef struct { + MfClassicType type; + uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32]; + uint64_t key_a_mask; + uint64_t key_b_mask; + MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; +} MfClassicData; + +typedef struct { + uint8_t sector; + uint64_t key_a; + uint64_t key_b; +} MfClassicAuthContext; + +typedef struct { + uint8_t sector_num; + uint64_t key_a; + uint64_t key_b; +} MfClassicSectorReader; + +typedef struct { + MfClassicType type; + Crypto1 crypto; + uint8_t sectors_to_read; + MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; +} MfClassicReader; + +typedef struct { + uint32_t cuid; + Crypto1 crypto; + MfClassicData data; + bool data_changed; +} MfClassicEmulator; + +const char* mf_classic_get_type_str(MfClassicType type); + +bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader); + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type); + +uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector); + +bool mf_classic_is_sector_trailer(uint8_t block); + +uint8_t mf_classic_get_sector_by_block(uint8_t block); + +bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKey key_type, + uint64_t key); + +bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); + +bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); + +void mf_classic_get_read_sectors_and_keys( + MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found); + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector); + +void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector); + +bool mf_classic_authenticate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type); + +bool mf_classic_auth_attempt( + FuriHalNfcTxRxContext* tx_rx, + MfClassicAuthContext* auth_ctx, + uint64_t key); + +void mf_classic_reader_add_sector( + MfClassicReader* reader, + uint8_t sector, + uint64_t key_a, + uint64_t key_b); + +void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num); + +uint8_t mf_classic_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfClassicReader* reader, + MfClassicData* data); + +uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); + +bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c new file mode 100644 index 00000000..fd622765 --- /dev/null +++ b/lib/nfc/protocols/mifare_common.c @@ -0,0 +1,17 @@ +#include "mifare_common.h" + +MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + MifareType type = MifareTypeUnknown; + + if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { + type = MifareTypeUltralight; + } else if( + ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || + ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18))) { + type = MifareTypeClassic; + } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { + type = MifareTypeDesfire; + } + + return type; +} diff --git a/lib/nfc/protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h new file mode 100644 index 00000000..2b694d90 --- /dev/null +++ b/lib/nfc/protocols/mifare_common.h @@ -0,0 +1,12 @@ +#pragma once + +#include <stdint.h> + +typedef enum { + MifareTypeUnknown, + MifareTypeUltralight, + MifareTypeClassic, + MifareTypeDesfire, +} MifareType; + +MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c new file mode 100644 index 00000000..1822d5c1 --- /dev/null +++ b/lib/nfc/protocols/mifare_desfire.c @@ -0,0 +1,623 @@ +#include "mifare_desfire.h" +#include <furi.h> +#include <furi_hal_nfc.h> + +#define TAG "MifareDESFire" + +void mf_df_clear(MifareDesfireData* data) { + free(data->free_memory); + if(data->master_key_settings) { + MifareDesfireKeyVersion* key_version = data->master_key_settings->key_version_head; + while(key_version) { + MifareDesfireKeyVersion* next_key_version = key_version->next; + free(key_version); + key_version = next_key_version; + } + } + free(data->master_key_settings); + MifareDesfireApplication* app = data->app_head; + while(app) { + MifareDesfireApplication* next_app = app->next; + if(app->key_settings) { + MifareDesfireKeyVersion* key_version = app->key_settings->key_version_head; + while(key_version) { + MifareDesfireKeyVersion* next_key_version = key_version->next; + free(key_version); + key_version = next_key_version; + } + } + free(app->key_settings); + MifareDesfireFile* file = app->file_head; + while(file) { + MifareDesfireFile* next_file = file->next; + free(file->contents); + free(file); + file = next_file; + } + free(app); + app = next_app; + } + data->free_memory = NULL; + data->master_key_settings = NULL; + data->app_head = NULL; +} + +void mf_df_cat_data(MifareDesfireData* data, string_t out) { + mf_df_cat_card_info(data, out); + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + mf_df_cat_application(app, out); + } +} + +void mf_df_cat_card_info(MifareDesfireData* data, string_t out) { + mf_df_cat_version(&data->version, out); + if(data->free_memory) { + mf_df_cat_free_mem(data->free_memory, out); + } + if(data->master_key_settings) { + mf_df_cat_key_settings(data->master_key_settings, out); + } +} + +void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { + string_cat_printf( + out, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + version->uid[0], + version->uid[1], + version->uid[2], + version->uid[3], + version->uid[4], + version->uid[5], + version->uid[6]); + string_cat_printf( + out, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + version->hw_vendor, + version->hw_type, + version->hw_subtype, + version->hw_major, + version->hw_minor, + version->hw_storage, + version->hw_proto); + string_cat_printf( + out, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + version->sw_vendor, + version->sw_type, + version->sw_subtype, + version->sw_major, + version->sw_minor, + version->sw_storage, + version->sw_proto); + string_cat_printf( + out, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + version->batch[0], + version->batch[1], + version->batch[2], + version->batch[3], + version->batch[4], + version->prod_week, + version->prod_year); +} + +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out) { + string_cat_printf(out, "freeMem %d\n", free_mem->bytes); +} + +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out) { + string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); + string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); + string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); + string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); + string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); + if(ks->flags) { + string_cat_printf(out, "flags %d\n", ks->flags); + } + string_cat_printf(out, "maxKeys %d\n", ks->max_keys); + for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { + string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); + } +} + +void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out) { + string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); + if(app->key_settings) { + mf_df_cat_key_settings(app->key_settings, out); + } +} + +void mf_df_cat_application(MifareDesfireApplication* app, string_t out) { + mf_df_cat_application_info(app, out); + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + mf_df_cat_file(file, out); + } +} + +void mf_df_cat_file(MifareDesfireFile* file, string_t out) { + char* type = "unknown"; + switch(file->type) { + case MifareDesfireFileTypeStandard: + type = "standard"; + break; + case MifareDesfireFileTypeBackup: + type = "backup"; + break; + case MifareDesfireFileTypeValue: + type = "value"; + break; + case MifareDesfireFileTypeLinearRecord: + type = "linear"; + break; + case MifareDesfireFileTypeCyclicRecord: + type = "cyclic"; + break; + } + char* comm = "unknown"; + switch(file->comm) { + case MifareDesfireFileCommunicationSettingsPlaintext: + comm = "plain"; + break; + case MifareDesfireFileCommunicationSettingsAuthenticated: + comm = "auth"; + break; + case MifareDesfireFileCommunicationSettingsEnciphered: + comm = "enciphered"; + break; + } + string_cat_printf(out, "File %d\n", file->id); + string_cat_printf(out, "%s %s\n", type, comm); + string_cat_printf( + out, + "r %d w %d rw %d c %d\n", + file->access_rights >> 12 & 0xF, + file->access_rights >> 8 & 0xF, + file->access_rights >> 4 & 0xF, + file->access_rights & 0xF); + uint16_t size = 0; + uint16_t num = 1; + switch(file->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + size = file->settings.data.size; + string_cat_printf(out, "size %d\n", size); + break; + case MifareDesfireFileTypeValue: + size = 4; + string_cat_printf( + out, "lo %d hi %d\n", file->settings.value.lo_limit, file->settings.value.hi_limit); + string_cat_printf( + out, + "limit %d enabled %d\n", + file->settings.value.limited_credit_value, + file->settings.value.limited_credit_enabled); + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + size = file->settings.record.size; + num = file->settings.record.cur; + string_cat_printf(out, "size %d\n", size); + string_cat_printf(out, "num %d max %d\n", num, file->settings.record.max); + break; + } + uint8_t* data = file->contents; + if(data) { + for(int rec = 0; rec < num; rec++) { + for(int ch = 0; ch < size; ch++) { + string_cat_printf(out, "%02x", data[rec * size + ch]); + } + string_cat_printf(out, " \n"); + } + } +} + +bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20; +} + +uint16_t mf_df_prepare_get_version(uint8_t* dest) { + dest[0] = MF_DF_GET_VERSION; + return 1; +} + +bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len < sizeof(MifareDesfireVersion)) { + return false; + } + memcpy(out, buf, sizeof(MifareDesfireVersion)); + return true; +} + +uint16_t mf_df_prepare_get_free_memory(uint8_t* dest) { + dest[0] = MF_DF_GET_FREE_MEMORY; + return 1; +} + +bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len != 3) { + return false; + } + out->bytes = buf[0] | (buf[1] << 8) | (buf[2] << 16); + return true; +} + +uint16_t mf_df_prepare_get_key_settings(uint8_t* dest) { + dest[0] = MF_DF_GET_KEY_SETTINGS; + return 1; +} + +bool mf_df_parse_get_key_settings_response( + uint8_t* buf, + uint16_t len, + MifareDesfireKeySettings* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len < 2) { + return false; + } + out->change_key_id = buf[0] >> 4; + out->config_changeable = (buf[0] & 0x8) != 0; + out->free_create_delete = (buf[0] & 0x4) != 0; + out->free_directory_list = (buf[0] & 0x2) != 0; + out->master_key_changeable = (buf[0] & 0x1) != 0; + out->flags = buf[1] >> 4; + out->max_keys = buf[1] & 0xF; + return true; +} + +uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id) { + dest[0] = MF_DF_GET_KEY_VERSION; + dest[1] = key_id; + return 2; +} + +bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out) { + if(len != 2 || *buf) { + return false; + } + out->version = buf[1]; + return true; +} + +uint16_t mf_df_prepare_get_application_ids(uint8_t* dest) { + dest[0] = MF_DF_GET_APPLICATION_IDS; + return 1; +} + +bool mf_df_parse_get_application_ids_response( + uint8_t* buf, + uint16_t len, + MifareDesfireApplication** app_head) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len % 3 != 0) { + return false; + } + while(len) { + MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); + memset(app, 0, sizeof(MifareDesfireApplication)); + memcpy(app->id, buf, 3); + len -= 3; + buf += 3; + *app_head = app; + app_head = &app->next; + } + return true; +} + +uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]) { + dest[0] = MF_DF_SELECT_APPLICATION; + dest[1] = id[0]; + dest[2] = id[1]; + dest[3] = id[2]; + return 4; +} + +bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len) { + return len == 1 && !*buf; +} + +uint16_t mf_df_prepare_get_file_ids(uint8_t* dest) { + dest[0] = MF_DF_GET_FILE_IDS; + return 1; +} + +bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + while(len) { + MifareDesfireFile* file = malloc(sizeof(MifareDesfireFile)); + memset(file, 0, sizeof(MifareDesfireFile)); + file->id = *buf; + len--; + buf++; + *file_head = file; + file_head = &file->next; + } + return true; +} + +uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id) { + dest[0] = MF_DF_GET_FILE_SETTINGS; + dest[1] = file_id; + return 2; +} + +bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { + if(len < 5 || *buf) { + return false; + } + len--; + buf++; + out->type = buf[0]; + out->comm = buf[1]; + out->access_rights = buf[2] | (buf[3] << 8); + switch(out->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + if(len != 7) { + return false; + } + out->settings.data.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); + break; + case MifareDesfireFileTypeValue: + if(len != 17) { + return false; + } + out->settings.value.lo_limit = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); + out->settings.value.hi_limit = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24); + out->settings.value.limited_credit_value = buf[12] | (buf[13] << 8) | (buf[14] << 16) | + (buf[15] << 24); + out->settings.value.limited_credit_enabled = buf[16]; + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + if(len != 13) { + return false; + } + out->settings.record.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); + out->settings.record.max = buf[7] | (buf[8] << 8) | (buf[9] << 16); + out->settings.record.cur = buf[10] | (buf[11] << 8) | (buf[12] << 16); + break; + default: + return false; + } + return true; +} + +uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { + dest[0] = MF_DF_READ_DATA; + dest[1] = file_id; + dest[2] = offset; + dest[3] = offset >> 8; + dest[4] = offset >> 16; + dest[5] = len; + dest[6] = len >> 8; + dest[7] = len >> 16; + return 8; +} + +uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id) { + dest[0] = MF_DF_GET_VALUE; + dest[1] = file_id; + return 2; +} + +uint16_t + mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { + dest[0] = MF_DF_READ_RECORDS; + dest[1] = file_id; + dest[2] = offset; + dest[3] = offset >> 8; + dest[4] = offset >> 16; + dest[5] = len; + dest[6] = len >> 8; + dest[7] = len >> 16; + return 8; +} + +bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + out->contents = malloc(len); + memcpy(out->contents, buf, len); + return true; +} + +bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) { + furi_assert(tx_rx); + furi_assert(data); + + bool card_read = false; + do { + // Get version + tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting version"); + break; + } + if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce"); + } + + // Get free memory + tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data); + if(furi_hal_nfc_tx_rx_full(tx_rx)) { + data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); + if(!mf_df_parse_get_free_memory_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) { + FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)"); + free(data->free_memory); + data->free_memory = NULL; + } + } + + // Get key settings + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_D(TAG, "Bad exchange getting key settings"); + } else { + data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); + if(!mf_df_parse_get_key_settings_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); + free(data->master_key_settings); + data->master_key_settings = NULL; + } else { + MifareDesfireKeyVersion** key_version_head = + &data->master_key_settings->key_version_head; + for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) { + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key version"); + continue; + } + MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); + memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); + key_version->id = key_id; + if(!mf_df_parse_get_key_version_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); + free(key_version); + continue; + } + *key_version_head = key_version; + key_version_head = &key_version->next; + } + } + } + + // Get application IDs + tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting application IDs"); + break; + } else { + if(!mf_df_parse_get_application_ids_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) { + FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response"); + break; + } + } + + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id); + if(!furi_hal_nfc_tx_rx_full(tx_rx) || + !mf_df_parse_select_application_response(tx_rx->rx_data, tx_rx->rx_bits / 8)) { + FURI_LOG_W(TAG, "Bad exchange selecting application"); + continue; + } + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key settings"); + } else { + app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!mf_df_parse_get_key_settings_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); + free(app->key_settings); + app->key_settings = NULL; + continue; + } + + MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; + for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) { + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key version"); + continue; + } + MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); + memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); + key_version->id = key_id; + if(!mf_df_parse_get_key_version_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); + free(key_version); + continue; + } + *key_version_head = key_version; + key_version_head = &key_version->next; + } + } + + tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting file IDs"); + } else { + if(!mf_df_parse_get_file_ids_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) { + FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response"); + } + } + + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting file settings"); + continue; + } + if(!mf_df_parse_get_file_settings_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { + FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response"); + continue; + } + switch(file->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0); + break; + case MifareDesfireFileTypeValue: + tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id); + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + tx_rx->tx_bits = + 8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0); + break; + } + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id); + continue; + } + if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { + FURI_LOG_W(TAG, "Bad response reading file %d", file->id); + continue; + } + } + } + + card_read = true; + } while(false); + + return card_read; +} diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h new file mode 100644 index 00000000..e59743a2 --- /dev/null +++ b/lib/nfc/protocols/mifare_desfire.h @@ -0,0 +1,169 @@ +#pragma once + +#include <m-string.h> +#include <stdint.h> +#include <stdbool.h> + +#include <furi_hal_nfc.h> + +#define MF_DF_GET_VERSION (0x60) +#define MF_DF_GET_FREE_MEMORY (0x6E) +#define MF_DF_GET_KEY_SETTINGS (0x45) +#define MF_DF_GET_KEY_VERSION (0x64) +#define MF_DF_GET_APPLICATION_IDS (0x6A) +#define MF_DF_SELECT_APPLICATION (0x5A) +#define MF_DF_GET_FILE_IDS (0x6F) +#define MF_DF_GET_FILE_SETTINGS (0xF5) + +#define MF_DF_READ_DATA (0xBD) +#define MF_DF_GET_VALUE (0x6C) +#define MF_DF_READ_RECORDS (0xBB) + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[7]; + uint8_t batch[5]; + uint8_t prod_week; + uint8_t prod_year; +} MifareDesfireVersion; + +typedef struct { + uint32_t bytes; +} MifareDesfireFreeMemory; // EV1+ only + +typedef struct MifareDesfireKeyVersion { + uint8_t id; + uint8_t version; + struct MifareDesfireKeyVersion* next; +} MifareDesfireKeyVersion; + +typedef struct { + uint8_t change_key_id; + bool config_changeable; + bool free_create_delete; + bool free_directory_list; + bool master_key_changeable; + uint8_t flags; + uint8_t max_keys; + MifareDesfireKeyVersion* key_version_head; +} MifareDesfireKeySettings; + +typedef enum { + MifareDesfireFileTypeStandard = 0, + MifareDesfireFileTypeBackup = 1, + MifareDesfireFileTypeValue = 2, + MifareDesfireFileTypeLinearRecord = 3, + MifareDesfireFileTypeCyclicRecord = 4, +} MifareDesfireFileType; + +typedef enum { + MifareDesfireFileCommunicationSettingsPlaintext = 0, + MifareDesfireFileCommunicationSettingsAuthenticated = 1, + MifareDesfireFileCommunicationSettingsEnciphered = 3, +} MifareDesfireFileCommunicationSettings; + +typedef struct MifareDesfireFile { + uint8_t id; + MifareDesfireFileType type; + MifareDesfireFileCommunicationSettings comm; + uint16_t access_rights; + union { + struct { + uint32_t size; + } data; + struct { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + bool limited_credit_enabled; + } value; + struct { + uint32_t size; + uint32_t max; + uint32_t cur; + } record; + } settings; + uint8_t* contents; + + struct MifareDesfireFile* next; +} MifareDesfireFile; + +typedef struct MifareDesfireApplication { + uint8_t id[3]; + MifareDesfireKeySettings* key_settings; + MifareDesfireFile* file_head; + + struct MifareDesfireApplication* next; +} MifareDesfireApplication; + +typedef struct { + MifareDesfireVersion version; + MifareDesfireFreeMemory* free_memory; + MifareDesfireKeySettings* master_key_settings; + MifareDesfireApplication* app_head; +} MifareDesfireData; + +void mf_df_clear(MifareDesfireData* data); + +void mf_df_cat_data(MifareDesfireData* data, string_t out); +void mf_df_cat_card_info(MifareDesfireData* data, string_t out); +void mf_df_cat_version(MifareDesfireVersion* version, string_t out); +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out); +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out); +void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out); +void mf_df_cat_application(MifareDesfireApplication* app, string_t out); +void mf_df_cat_file(MifareDesfireFile* file, string_t out); + +bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +uint16_t mf_df_prepare_get_version(uint8_t* dest); +bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out); + +uint16_t mf_df_prepare_get_free_memory(uint8_t* dest); +bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out); + +uint16_t mf_df_prepare_get_key_settings(uint8_t* dest); +bool mf_df_parse_get_key_settings_response( + uint8_t* buf, + uint16_t len, + MifareDesfireKeySettings* out); + +uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id); +bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out); + +uint16_t mf_df_prepare_get_application_ids(uint8_t* dest); +bool mf_df_parse_get_application_ids_response( + uint8_t* buf, + uint16_t len, + MifareDesfireApplication** app_head); + +uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]); +bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len); + +uint16_t mf_df_prepare_get_file_ids(uint8_t* dest); +bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head); + +uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id); +bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); + +uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); +uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id); +uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); +bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); + +bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data); diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c new file mode 100644 index 00000000..9dcd1d6a --- /dev/null +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -0,0 +1,1791 @@ +#include <limits.h> +#include "mifare_ultralight.h" +#include <furi.h> +#include <m-string.h> + +#define TAG "MfUltralight" + +bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { + return true; + } + return false; +} + +static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { + switch(type) { + case MfUltralightTypeUL11: + case MfUltralightTypeUL21: + return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | + MfUltralightSupportReadCounter | MfUltralightSupportIncrCounter | + MfUltralightSupportAuth | MfUltralightSupportSignature | + MfUltralightSupportTearingFlags | MfUltralightSupportVcsl; + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | + MfUltralightSupportReadCounter | MfUltralightSupportAuth | + MfUltralightSupportSignature | MfUltralightSupportSingleCounter | + MfUltralightSupportAsciiMirror; + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2C2K: + return MfUltralightSupportFastRead | MfUltralightSupportSectorSelect; + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + return MfUltralightSupportFastRead | MfUltralightSupportAuth | + MfUltralightSupportFastWrite | MfUltralightSupportSignature | + MfUltralightSupportSectorSelect; + case MfUltralightTypeNTAG203: + return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory; + default: + // Assumed original MFUL 512-bit + return MfUltralightSupportCompatWrite; + } +} + +static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightData* data) { + data->type = MfUltralightTypeUnknown; + 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, + MfUltralightData* data) { + bool version_read = false; + + do { + FURI_LOG_D(TAG, "Reading 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) || tx_rx->rx_bits != 64) { + FURI_LOG_D(TAG, "Failed reading version"); + mf_ul_set_default_version(reader, data); + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + break; + } + MfUltralightVersion* version = (MfUltralightVersion*)tx_rx->rx_data; + data->version = *version; + if(version->storage_size == 0x0B || version->storage_size == 0x00) { + data->type = MfUltralightTypeUL11; + reader->pages_to_read = 20; + } else if(version->storage_size == 0x0E) { + data->type = MfUltralightTypeUL21; + reader->pages_to_read = 41; + } else if(version->storage_size == 0x0F) { + data->type = MfUltralightTypeNTAG213; + reader->pages_to_read = 45; + } else if(version->storage_size == 0x11) { + data->type = MfUltralightTypeNTAG215; + reader->pages_to_read = 135; + } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { + // NTAG I2C + bool known = false; + if(version->prod_ver_minor == 1) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2C1K; + reader->pages_to_read = 231; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2C2K; + reader->pages_to_read = 485; + known = true; + } + } else if(version->prod_ver_minor == 2) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2CPlus1K; + reader->pages_to_read = 236; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2CPlus2K; + reader->pages_to_read = 492; + known = true; + } + } + + if(!known) { + mf_ul_set_default_version(reader, data); + } + } else if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAG216; + reader->pages_to_read = 231; + } else { + mf_ul_set_default_version(reader, data); + break; + } + version_read = true; + } while(false); + + reader->supported_features = mf_ul_get_features(data->type); + return version_read; +} + +static int16_t mf_ultralight_page_addr_to_tag_addr(uint8_t sector, uint8_t page) { + return sector * 256 + page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 226: sector 0 + // 227 - 228: config registers + // 229 - 230: session registers + + if(linear_address > 230) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 229) { + *sector = 3; + *valid_pages = 2 - (linear_address - 229); + return linear_address - 229 + 248; + } else if(linear_address >= 227) { + *sector = 0; + *valid_pages = 2 - (linear_address - 227); + return linear_address - 227 + 232; + } else { + *sector = 0; + *valid_pages = 227 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 255: sector 0 + // 256 - 480: sector 1 + // 481 - 482: config registers + // 483 - 484: session registers + + if(linear_address > 484) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 483) { + *sector = 3; + *valid_pages = 2 - (linear_address - 483); + return linear_address - 483 + 248; + } else if(linear_address >= 481) { + *sector = 1; + *valid_pages = 2 - (linear_address - 481); + return linear_address - 481 + 232; + } else if(linear_address >= 256) { + *sector = 1; + *valid_pages = 225 - (linear_address - 256); + return linear_address - 256; + } else { + *sector = 0; + *valid_pages = 256 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + + if(linear_address > 235) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + // 236 - 491: sector 1 + + if(linear_address > 491) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 236) { + *sector = 1; + *valid_pages = 256 - (linear_address - 236); + return linear_address - 236; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag( + MfUltralightData* data, + MfUltralightReader* reader, + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(linear_address, sector, valid_pages); + + default: + *sector = 0xff; + *valid_pages = reader->pages_to_read - linear_address; + return linear_address; + } +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 226) { + *valid_pages = 227 - page; + translated_page = page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 227; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 229; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + *valid_pages = 256 - page; + translated_page = page; + valid = true; + } else if(sector == 1) { + if(page <= 224) { + *valid_pages = 225 - page; + translated_page = 256 + page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 481; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 483; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 1) { + *valid_pages = 256 - page; + translated_page = page + 236; + valid = true; + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin( + MfUltralightData* data, + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(page, sector, valid_pages); + + default: + *valid_pages = data->data_size / 4 - page; + return page; + } +} + +static MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) { + if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) { + return (MfUltralightConfigPages*)&data->data[data->data_size - 4 * 4]; + } else if( + data->type >= MfUltralightTypeNTAGI2CPlus1K && + data->type <= MfUltralightTypeNTAGI2CPlus2K) { + return (MfUltralightConfigPages*)&data->data[0xe3 * 4]; + } else { + return NULL; + } +} + +static uint16_t mf_ultralight_calc_auth_count(MfUltralightData* data) { + if(mf_ul_get_features(data->type) & MfUltralightSupportAuth) { + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + uint16_t scaled_authlim = config->access.authlim; + // NTAG I2C Plus uses 2^AUTHLIM attempts rather than the direct number + if(scaled_authlim > 0 && data->type >= MfUltralightTypeNTAGI2CPlus1K && + data->type <= MfUltralightTypeNTAGI2CPlus2K) { + scaled_authlim = 1 << scaled_authlim; + } + return scaled_authlim; + } + + return 0; +} + +// NTAG21x will NAK if NFC_CNT_EN unset, so preempt +static bool mf_ultralight_should_read_counters(MfUltralightData* data) { + if(data->type < MfUltralightTypeNTAG213 || data->type > MfUltralightTypeNTAG216) return true; + + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + return config->access.nfc_cnt_en; +} + +static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t sector) { + FURI_LOG_D(TAG, "Selecting sector %u", sector); + tx_rx->tx_data[0] = MF_UL_SECTOR_SELECT; + tx_rx->tx_data[1] = 0xff; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to issue sector select command"); + return false; + } + + tx_rx->tx_data[0] = sector; + tx_rx->tx_data[1] = 0x00; + tx_rx->tx_data[2] = 0x00; + tx_rx->tx_data[3] = 0x00; + tx_rx->tx_bits = 32; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + // This is NOT a typo! The tag ACKs by not sending a response within 1ms. + if(furi_hal_nfc_tx_rx(tx_rx, 20)) { + // TODO: what gets returned when an actual NAK is received? + FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); + return false; + } + + 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, + MfUltralightData* data) { + uint8_t pages_read_cnt = 0; + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + for(size_t i = 0; i < reader->pages_to_read; i += pages_read_cnt) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, (int16_t)i, &tag_sector, &valid_pages); + + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1); + tx_rx->tx_data[0] = MF_UL_READ_CMD; + tx_rx->tx_data[1] = tag_page; + 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", + i, + i + (valid_pages > 4 ? 4 : valid_pages) - 1); + break; + } + if(valid_pages > 4) { + pages_read_cnt = 4; + } else { + pages_read_cnt = valid_pages; + } + reader->pages_read += pages_read_cnt; + data->data_size = reader->pages_read * 4; + memcpy(&data->data[i * 4], tx_rx->rx_data, pages_read_cnt * 4); + } + + return reader->pages_read == reader->pages_to_read; +} + +bool mf_ultralight_fast_read_pages( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data) { + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + while(reader->pages_read < reader->pages_to_read) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, reader->pages_read, &tag_sector, &valid_pages); + + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D( + TAG, "Reading pages %d - %d", reader->pages_read, reader->pages_read + valid_pages - 1); + tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; + tx_rx->tx_data[1] = tag_page; + tx_rx->tx_data[2] = valid_pages - 1; + tx_rx->tx_bits = 24; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + memcpy(&data->data[reader->pages_read * 4], tx_rx->rx_data, valid_pages * 4); + reader->pages_read += valid_pages; + data->data_size = reader->pages_read * 4; + } else { + FURI_LOG_D( + TAG, + "Failed to read pages %d - %d", + reader->pages_read, + reader->pages_read + valid_pages - 1); + break; + } + } + + return reader->pages_read == reader->pages_to_read; +} + +bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { + bool signature_read = false; + + FURI_LOG_D(TAG, "Reading signature"); + tx_rx->tx_data[0] = MF_UL_READ_SIG; + tx_rx->tx_data[1] = 0; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + memcpy(data->signature, tx_rx->rx_data, sizeof(data->signature)); + signature_read = true; + } else { + FURI_LOG_D(TAG, "Failed redaing signature"); + } + + return signature_read; +} + +bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { + uint8_t counter_read = 0; + + FURI_LOG_D(TAG, "Reading counters"); + bool is_single_counter = (mf_ul_get_features(data->type) & MfUltralightSupportSingleCounter) != + 0; + for(size_t i = is_single_counter ? 2 : 0; i < 3; i++) { + tx_rx->tx_data[0] = MF_UL_READ_CNT; + tx_rx->tx_data[1] = i; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to read %d counter", i); + break; + } + data->counter[i] = (tx_rx->rx_data[2] << 16) | (tx_rx->rx_data[1] << 8) | + tx_rx->rx_data[0]; + counter_read++; + } + + return counter_read == (is_single_counter ? 1 : 3); +} + +bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { + uint8_t flag_read = 0; + + FURI_LOG_D(TAG, "Reading tearing flags"); + for(size_t i = 0; i < 3; i++) { + tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; + tx_rx->rx_data[1] = i; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to read %d tearing flag", i); + break; + } + data->tearing[i] = tx_rx->rx_data[0]; + flag_read++; + } + + return flag_read == 2; +} + +bool mf_ul_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data) { + furi_assert(tx_rx); + furi_assert(reader); + furi_assert(data); + + bool card_read = false; + + // Read Mifare Ultralight version + if(mf_ultralight_read_version(tx_rx, reader, data)) { + if(reader->supported_features & MfUltralightSupportSignature) { + // 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); + + if(card_read) { + if(reader->supported_features & MfUltralightSupportReadCounter && + mf_ultralight_should_read_counters(data)) { + mf_ultralight_read_counters(tx_rx, data); + } + if(reader->supported_features & MfUltralightSupportTearingFlags) { + mf_ultralight_read_tearing_flags(tx_rx, data); + } + data->curr_authlim = 0; + } + + return card_read; +} + +static void mf_ul_protect_auth_data_on_read_command_i2c( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + // Blank out PWD and PACK + if(start_page <= 229 && end_page >= 229) { + uint16_t offset = (229 - start_page) * 4; + uint8_t count = 4; + if(end_page >= 230) count += 2; + memset(&tx_buff[offset], 0, count); + } + + // Handle AUTH0 for sector 0 + if(!emulator->auth_success) { + if(emulator->config_cache.access.prot) { + uint8_t auth0 = emulator->config_cache.auth0; + if(auth0 < end_page) { + // start_page is always < auth0; otherwise is NAK'd already + uint8_t page_offset = auth0 - start_page; + uint8_t page_count = end_page - auth0; + memset(&tx_buff[page_offset * 4], 0, page_count * 4); + } + } + } + } +} + +static void mf_ul_ntag_i2c_fill_cross_area_read( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + // For copying config or session registers in fast read + int16_t tx_page_offset; + int16_t data_page_offset; + uint8_t page_length; + bool apply = false; + MfUltralightType type = emulator->data.type; + if(emulator->curr_sector == 0) { + if(type == MfUltralightTypeNTAGI2C1K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 227; + page_length = 2; + apply = true; + } + } else if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(start_page <= 237 && end_page >= 236) { + tx_page_offset = start_page - 236; + data_page_offset = 234; + page_length = 2; + apply = true; + } + } + } else if(emulator->curr_sector == 1) { + if(type == MfUltralightTypeNTAGI2C2K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 483; + page_length = 2; + apply = true; + } + } + } + + if(apply) { + while(tx_page_offset < 0 && page_length > 0) { + ++tx_page_offset; + ++data_page_offset; + --page_length; + } + memcpy( + &tx_buff[tx_page_offset * 4], + &emulator->data.data[data_page_offset * 4], + page_length * 4); + } +} + +static bool mf_ul_check_auth(MfUltralightEmulator* emulator, uint8_t start_page, bool is_write) { + if(!emulator->auth_success) { + if(start_page >= emulator->config_cache.auth0 && + (emulator->config_cache.access.prot || is_write)) + return false; + } + + if(is_write && emulator->config_cache.access.cfglck) { + uint16_t config_start_page = emulator->page_num - 4; + if(start_page == config_start_page || start_page == config_start_page + 1) return false; + } + + return true; +} + +static bool mf_ul_ntag_i2c_plus_check_auth( + MfUltralightEmulator* emulator, + uint8_t start_page, + bool is_write) { + if(!emulator->auth_success) { + // Check NFC_PROT + if(emulator->curr_sector == 0 && (emulator->config_cache.access.prot || is_write)) { + if(start_page >= emulator->config_cache.auth0) return false; + } else if(emulator->curr_sector == 1) { + // We don't have to specifically check for type because this is done + // by address translator + uint8_t pt_i2c = emulator->data.data[231 * 4]; + // Check 2K_PROT + if(pt_i2c & 0x08) return false; + } + } + + if(emulator->curr_sector == 1) { + // Check NFC_DIS_SEC1 + if(emulator->config_cache.access.nfc_dis_sec1) return false; + } + + return true; +} + +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: + case MfUltralightTypeNTAG216: + return data->data_size / 4 - 5; + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + return 0xe2; + case MfUltralightTypeNTAGI2C2K: + return 0x1e0; + default: + return -1; // No dynamic lock bytes + } +} + +// Returns true if page not locked +// write_page is tag address +static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) { + if(write_page < 2) return false; // Page 0-1 is always locked + if(write_page == 2) return true; // Page 2 does not have a lock flag + + // Check static lock bytes + if(write_page <= 15) { + uint16_t static_lock_bytes = emulator->data.data[10] | (emulator->data.data[11] << 8); + return (static_lock_bytes & (1 << write_page)) == 0; + } + + // Check dynamic lock bytes + + // 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: + case MfUltralightTypeNTAG216: + if(write_page >= emulator->page_num - 5) return true; + break; + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + if(write_page > 225) return true; + break; + case MfUltralightTypeNTAGI2C2K: + if(write_page > 479) return true; + break; + case MfUltralightTypeNTAGI2CPlus2K: + if(write_page >= 226 && write_page <= 255) return true; + if(write_page >= 512) return true; + break; + default: + furi_assert(false); + return true; + } + + int16_t dynamic_lock_index = mf_ul_get_dynamic_lock_page_addr(&emulator->data); + if(dynamic_lock_index == -1) return true; + // Run address through converter because NTAG I2C 2K is special + uint16_t valid_pages; // unused + dynamic_lock_index = + mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, dynamic_lock_index & 0xff, dynamic_lock_index >> 8, &valid_pages) * + 4; + + uint16_t dynamic_lock_bytes = emulator->data.data[dynamic_lock_index] | + (emulator->data.data[dynamic_lock_index + 1] << 8); + uint8_t shift; + + 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 + shift = (write_page - 16) / 2; + break; + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + // 16-31, 128-129 + // 16-31, 128-143 + shift = (write_page - 16) / 16; + break; + case MfUltralightTypeNTAGI2C2K: + // 16-47, 240-271 + shift = (write_page - 16) / 32; + break; + case MfUltralightTypeNTAGI2CPlus2K: + // 16-47, 256-271 + if(write_page >= 208 && write_page <= 225) + shift = 6; + else if(write_page >= 256 && write_page <= 271) + shift = 7; + else + shift = (write_page - 16) / 32; + break; + default: + furi_assert(false); + shift = 0; + break; + } + + return (dynamic_lock_bytes & (1 << shift)) == 0; +} + +static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, string_t str) { + // Locals to improve readability + uint8_t mirror_page = emulator->config->mirror_page; + uint8_t mirror_byte = emulator->config->mirror.mirror_byte; + MfUltralightMirrorConf mirror_conf = emulator->config_cache.mirror.mirror_conf; + uint16_t last_user_page_index = emulator->page_num - 6; + bool uid_printed = false; + + if(mirror_conf == MfUltralightMirrorUid || mirror_conf == MfUltralightMirrorUidCounter) { + // UID range check + if(mirror_page < 4 || mirror_page > last_user_page_index - 3 || + (mirror_page == last_user_page_index - 3 && mirror_byte > 2)) { + if(mirror_conf == MfUltralightMirrorUid) return; + // NTAG21x has the peculiar behavior when UID+counter selected, if UID does not fit but + // counter will fit, it will actually mirror the counter + string_cat_str(str, " "); + } else { + for(int i = 0; i < 3; ++i) { + string_cat_printf(str, "%02X", emulator->data.data[i]); + } + // Skip BCC0 + for(int i = 4; i < 8; ++i) { + string_cat_printf(str, "%02X", emulator->data.data[i]); + } + uid_printed = true; + } + + uint16_t next_byte_offset = mirror_page * 4 + mirror_byte + 14; + if(mirror_conf == MfUltralightMirrorUidCounter) ++next_byte_offset; + mirror_page = next_byte_offset / 4; + mirror_byte = next_byte_offset % 4; + } + + if(mirror_conf == MfUltralightMirrorCounter || mirror_conf == MfUltralightMirrorUidCounter) { + // Counter is only printed if counter enabled + if(emulator->config_cache.access.nfc_cnt_en) { + // Counter protection check + if(emulator->config_cache.access.nfc_cnt_pwd_prot && !emulator->auth_success) return; + // Counter range check + if(mirror_page < 4) return; + if(mirror_page > last_user_page_index - 1) return; + if(mirror_page == last_user_page_index - 1 && mirror_byte > 2) return; + + if(mirror_conf == MfUltralightMirrorUidCounter) + string_cat_str(str, uid_printed ? "x" : " "); + + string_cat_printf(str, "%06X", emulator->data.counter[2]); + } + } +} + +static void mf_ul_increment_single_counter(MfUltralightEmulator* emulator) { + if(!emulator->read_counter_incremented && emulator->config_cache.access.nfc_cnt_en) { + if(emulator->data.counter[2] < 0xFFFFFF) { + ++emulator->data.counter[2]; + emulator->data_changed = true; + } + emulator->read_counter_incremented = true; + } +} + +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, + int16_t write_page, + uint8_t* page_buff) { + // Assumption: all access checks have been completed + + if(tag_addr == 2) { + // Handle static locks + uint16_t orig_static_locks = emulator->data.data[write_page * 4 + 2] | + (emulator->data.data[write_page * 4 + 3] << 8); + uint16_t new_static_locks = page_buff[2] | (page_buff[3] << 8); + if(orig_static_locks & 1) new_static_locks &= ~0x08; + if(orig_static_locks & 2) new_static_locks &= ~0xF0; + if(orig_static_locks & 4) new_static_locks &= 0xFF; + new_static_locks |= orig_static_locks; + page_buff[0] = emulator->data.data[write_page * 4]; + page_buff[1] = emulator->data.data[write_page * 4 + 1]; + page_buff[2] = new_static_locks & 0xff; + page_buff[3] = new_static_locks >> 8; + } else if(tag_addr == 3) { + // Handle OTP/capability container + *(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 + 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]; + + 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; + } + } + + memcpy(&emulator->data.data[write_page * 4], page_buff, 4); + emulator->data_changed = true; +} + +void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) { + emulator->curr_sector = 0; + emulator->ntag_i2c_plus_sector3_lockout = false; + emulator->auth_success = false; + if(is_power_cycle) { + if(emulator->config != NULL) emulator->config_cache = *emulator->config; + + 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 + // MIRROR_CONF is not; don't know about STRG_MOD_EN, but we're not using that anyway + emulator->config_cache.access.value = (emulator->config->access.value & 0xBF) | + (emulator->config_cache.access.value & 0x40); + 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) { + FURI_LOG_D(TAG, "Prepare emulation"); + emulator->data = *data; + emulator->supported_features = mf_ul_get_features(data->type); + emulator->config = mf_ultralight_get_config_pages(&emulator->data); + emulator->page_num = emulator->data.data_size / 4; + emulator->data_changed = false; + emulator->comp_write_cmd_started = false; + emulator->sector_select_cmd_started = false; + mf_ul_reset_emulation(emulator, true); +} + +bool mf_ul_prepare_emulation_response( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len, + uint32_t* data_type, + void* context) { + furi_assert(context); + MfUltralightEmulator* emulator = context; + uint16_t tx_bytes = 0; + uint16_t tx_bits = 0; + bool command_parsed = false; + bool send_ack = false; + bool respond_nothing = false; + bool reset_idle = false; + +#ifdef FURI_DEBUG + string_t debug_buf; + string_init(debug_buf); + for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { + string_cat_printf(debug_buf, "%02x ", buff_rx[i]); + } + string_strim(debug_buf); + FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, string_get_cstr(debug_buf)); + string_reset(debug_buf); +#endif + + // Check composite commands + if(emulator->comp_write_cmd_started) { + if(buff_rx_len == 16 * 8) { + 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) { + if(buff_rx_len == 4 * 8) { + if(buff_rx[0] <= 0xFE) { + emulator->curr_sector = buff_rx[0] > 3 ? 0 : buff_rx[0]; + emulator->ntag_i2c_plus_sector3_lockout = false; + command_parsed = true; + respond_nothing = true; + FURI_LOG_D(TAG, "Changing sector to %d", emulator->curr_sector); + } + } + emulator->sector_select_cmd_started = false; + } else if(buff_rx_len >= 8) { + uint8_t cmd = buff_rx[0]; + if(cmd == MF_UL_GET_VERSION_CMD) { + 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); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } else if(cmd == MF_UL_READ_CMD) { + if(buff_rx_len == (1 + 1) * 8) { + int16_t start_page = buff_rx[1]; + tx_bytes = 16; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if(start_page < emulator->page_num) { + do { + uint8_t copied_pages = 0; + uint8_t src_page = start_page; + uint8_t last_page_plus_one = start_page + 4; + uint8_t pwd_page = emulator->page_num - 2; + string_t ascii_mirror; + size_t ascii_mirror_len = 0; + const char* ascii_mirror_cptr = NULL; + uint8_t ascii_mirror_curr_page = 0; + uint8_t ascii_mirror_curr_byte = 0; + if(last_page_plus_one > emulator->page_num) + last_page_plus_one = emulator->page_num; + if(emulator->supported_features & MfUltralightSupportAuth) { + if(!mf_ul_check_auth(emulator, start_page, false)) break; + if(!emulator->auth_success && emulator->config_cache.access.prot && + emulator->config_cache.auth0 < last_page_plus_one) + last_page_plus_one = emulator->config_cache.auth0; + } + if(emulator->supported_features & MfUltralightSupportSingleCounter) + mf_ul_increment_single_counter(emulator); + if(emulator->supported_features & MfUltralightSupportAsciiMirror && + emulator->config_cache.mirror.mirror_conf != + MfUltralightMirrorNone) { + ascii_mirror_curr_byte = emulator->config->mirror.mirror_byte; + ascii_mirror_curr_page = emulator->config->mirror_page; + // Try to avoid wasting time making mirror if we won't copy it + // Conservatively check with UID+counter mirror size + if(last_page_plus_one > ascii_mirror_curr_page && + start_page + 3 >= ascii_mirror_curr_page && + start_page <= ascii_mirror_curr_page + 6) { + string_init(ascii_mirror); + mf_ul_make_ascii_mirror(emulator, ascii_mirror); + ascii_mirror_len = string_length_u(ascii_mirror); + ascii_mirror_cptr = string_get_cstr(ascii_mirror); + // Move pointer to where it should be to start copying + if(ascii_mirror_len > 0 && + ascii_mirror_curr_page < start_page && + ascii_mirror_curr_byte != 0) { + uint8_t diff = 4 - ascii_mirror_curr_byte; + ascii_mirror_len -= diff; + ascii_mirror_cptr += diff; + ascii_mirror_curr_byte = 0; + ++ascii_mirror_curr_page; + } + while(ascii_mirror_len > 0 && + ascii_mirror_curr_page < start_page) { + uint8_t diff = ascii_mirror_len > 4 ? 4 : ascii_mirror_len; + ascii_mirror_len -= diff; + ascii_mirror_cptr += diff; + ++ascii_mirror_curr_page; + } + } + } + + uint8_t* dest_ptr = buff_tx; + while(copied_pages < 4) { + // Copy page + memcpy(dest_ptr, &emulator->data.data[src_page * 4], 4); + + // Note: don't have to worry about roll-over with ASCII mirror because + // lowest valid page for it is 4, while roll-over will at best read + // pages 0-2 + if(ascii_mirror_len > 0 && src_page == ascii_mirror_curr_page) { + // Copy ASCII mirror + size_t copy_len = 4 - ascii_mirror_curr_byte; + if(copy_len > ascii_mirror_len) copy_len = ascii_mirror_len; + for(size_t i = 0; i < copy_len; ++i) { + if(*ascii_mirror_cptr != ' ') + dest_ptr[ascii_mirror_curr_byte] = + (uint8_t)*ascii_mirror_cptr; + ++ascii_mirror_curr_byte; + ++ascii_mirror_cptr; + } + ascii_mirror_len -= copy_len; + // Don't care if this is inaccurate after ascii_mirror_len = 0 + ascii_mirror_curr_byte = 0; + ++ascii_mirror_curr_page; + } + + if(emulator->supported_features & MfUltralightSupportAuth) { + if(src_page == pwd_page || src_page == pwd_page + 1) { + // Blank out PWD and PACK pages + memset(dest_ptr, 0, 4); + } + } + + dest_ptr += 4; + ++copied_pages; + ++src_page; + if(src_page >= last_page_plus_one) src_page = 0; + } + if(ascii_mirror_cptr != NULL) { + string_clear(ascii_mirror); + } + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } while(false); + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && valid_pages == 1) { + // Rewind back a sector to match behavior on a real tag + --start_page; + ++valid_pages; + } + + uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page valid, %02x:%02x -> %d, %d", + emulator->curr_sector, + buff_rx[1], + start_page, + valid_pages); + memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); + // For NTAG I2C, there's no roll-over; remainder is filled by null bytes + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + // Special case: NTAG I2C Plus sector 0 page 233 read crosses into page 236 + if(start_page == 233) + memcpy( + &buff_tx[12], &emulator->data.data[(start_page + 1) * 4], 4); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page invalid, %02x:%02x", + emulator->curr_sector, + buff_rx[1]); + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && + !emulator->ntag_i2c_plus_sector3_lockout) { + // NTAG I2C Plus has a weird behavior where if you read sector 3 + // at an invalid address, it responds with zeroes then locks + // the read out, while if you read the mirrored session registers, + // it returns both session registers on either pages + memset(buff_tx, 0, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + emulator->ntag_i2c_plus_sector3_lockout = true; + } + } + } + if(!command_parsed) tx_bytes = 0; + } + } else if(cmd == MF_UL_FAST_READ_CMD) { + if(emulator->supported_features & MfUltralightSupportFastRead) { + if(buff_rx_len == (1 + 2) * 8) { + int16_t start_page = buff_rx[1]; + uint8_t end_page = buff_rx[2]; + if(start_page <= end_page) { + tx_bytes = ((end_page + 1) - start_page) * 4; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if((start_page < emulator->page_num) && + (end_page < emulator->page_num)) { + do { + if(emulator->supported_features & MfUltralightSupportAuth) { + // NAK if not authenticated and requested pages cross over AUTH0 + if(!emulator->auth_success && + emulator->config_cache.access.prot && + (start_page >= emulator->config_cache.auth0 || + end_page >= emulator->config_cache.auth0)) + break; + } + if(emulator->supported_features & + MfUltralightSupportSingleCounter) + mf_ul_increment_single_counter(emulator); + + // Copy requested pages + memcpy( + buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + + if(emulator->supported_features & + MfUltralightSupportAsciiMirror && + emulator->config_cache.mirror.mirror_conf != + MfUltralightMirrorNone) { + // Copy ASCII mirror + // Less stringent check here, because expecting FAST_READ to + // only be issued once rather than repeatedly + string_t ascii_mirror; + string_init(ascii_mirror); + mf_ul_make_ascii_mirror(emulator, ascii_mirror); + size_t ascii_mirror_len = string_length_u(ascii_mirror); + const char* ascii_mirror_cptr = + string_get_cstr(ascii_mirror); + int16_t mirror_start_offset = + (emulator->config->mirror_page - start_page) * 4 + + emulator->config->mirror.mirror_byte; + if(mirror_start_offset < 0) { + if(mirror_start_offset < -(int16_t)ascii_mirror_len) { + // Past ASCII mirror, don't copy + ascii_mirror_len = 0; + } else { + ascii_mirror_cptr += -mirror_start_offset; + ascii_mirror_len -= -mirror_start_offset; + mirror_start_offset = 0; + } + } + if(ascii_mirror_len > 0) { + int16_t mirror_end_offset = + mirror_start_offset + ascii_mirror_len; + if(mirror_end_offset > (end_page + 1) * 4) { + mirror_end_offset = (end_page + 1) * 4; + ascii_mirror_len = + mirror_end_offset - mirror_start_offset; + } + for(size_t i = 0; i < ascii_mirror_len; ++i) { + if(*ascii_mirror_cptr != ' ') + buff_tx[mirror_start_offset] = + (uint8_t)*ascii_mirror_cptr; + ++mirror_start_offset; + ++ascii_mirror_cptr; + } + } + string_clear(ascii_mirror); + } + + if(emulator->supported_features & MfUltralightSupportAuth) { + // Clear PWD and PACK pages + uint8_t pwd_page = emulator->page_num - 2; + int16_t pwd_page_offset = pwd_page - start_page; + // PWD page + if(pwd_page_offset >= 0 && pwd_page <= end_page) { + memset(&buff_tx[pwd_page_offset * 4], 0, 4); + // PACK page + if(pwd_page + 1 <= end_page) + memset(&buff_tx[(pwd_page_offset + 1) * 4], 0, 4); + } + } + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } while(false); + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + uint16_t copy_count = tx_bytes; + if(copy_count > valid_pages * 4) copy_count = valid_pages * 4; + memcpy( + buff_tx, &emulator->data.data[start_page * 4], copy_count); + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + mf_ul_ntag_i2c_fill_cross_area_read( + buff_tx, buff_rx[1], buff_rx[2], emulator); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, + start_page, + start_page + copy_count / 4 - 1, + emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } + if(!command_parsed) tx_bytes = 0; + } + } + } + } else if(cmd == MF_UL_WRITE) { + if(buff_rx_len == (1 + 5) * 8) { + do { + uint8_t orig_write_page = buff_rx[1]; + int16_t write_page = orig_write_page; + uint16_t valid_pages; // unused + write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, write_page, emulator->curr_sector, &valid_pages); + if(write_page == -1) // NTAG I2C range check + break; + else if(write_page < 2 || write_page >= emulator->page_num) // Other MFUL/NTAG range check + break; + + if(emulator->supported_features & MfUltralightSupportAuth) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + if(!mf_ul_ntag_i2c_plus_check_auth(emulator, orig_write_page, true)) + break; + } else { + if(!mf_ul_check_auth(emulator, orig_write_page, true)) break; + } + } + 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; + 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) { + if(emulator->supported_features & MfUltralightSupportFastWrite) { + if(buff_rx_len == (1 + 66) * 8) { + if(buff_rx[1] == 0xF0 && buff_rx[2] == 0xFF) { + // TODO: update when SRAM emulation implemented + send_ack = true; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_COMP_WRITE) { + if(emulator->supported_features & MfUltralightSupportCompatWrite) { + if(buff_rx_len == (1 + 1) * 8) { + uint8_t write_page = buff_rx[1]; + do { + if(write_page < 2 || write_page >= emulator->page_num) break; + if(emulator->supported_features & MfUltralightSupportAuth && + !mf_ul_check_auth(emulator, write_page, true)) + break; + // Note we don't convert to tag addr here because there's only one sector + if(!mf_ul_check_lock(emulator, write_page)) break; + + emulator->comp_write_cmd_started = true; + emulator->comp_write_page_addr = write_page; + send_ack = true; + command_parsed = true; + } while(false); + } + } + } else if(cmd == MF_UL_READ_CNT) { + if(emulator->supported_features & MfUltralightSupportReadCounter) { + if(buff_rx_len == (1 + 1) * 8) { + do { + uint8_t cnt_num = buff_rx[1]; + + // NTAG21x checks + if(emulator->supported_features & MfUltralightSupportSingleCounter) { + if(cnt_num != 2) break; // Only counter 2 is available + if(!emulator->config_cache.access.nfc_cnt_en) + break; // NAK if counter not enabled + if(emulator->config_cache.access.nfc_cnt_pwd_prot && + !emulator->auth_success) + break; + } + + if(cnt_num < 3) { + buff_tx[0] = emulator->data.counter[cnt_num] & 0xFF; + buff_tx[1] = (emulator->data.counter[cnt_num] >> 8) & 0xFF; + buff_tx[2] = (emulator->data.counter[cnt_num] >> 16) & 0xFF; + tx_bytes = 3; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } while(false); + } + } + } else if(cmd == MF_UL_INC_CNT) { + if(emulator->supported_features & MfUltralightSupportIncrCounter) { + if(buff_rx_len == (1 + 5) * 8) { + uint8_t cnt_num = buff_rx[1]; + uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); + // TODO: can you increment by 0 when counter is at 0xffffff? + if((cnt_num < 3) && (emulator->data.counter[cnt_num] != 0x00FFFFFF) && + (emulator->data.counter[cnt_num] + inc <= 0x00FFFFFF)) { + emulator->data.counter[cnt_num] += inc; + // We're RAM-backed, so tearing never happens + emulator->data.tearing[cnt_num] = MF_UL_TEARING_FLAG_DEFAULT; + emulator->data_changed = true; + send_ack = true; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_AUTH) { + if(emulator->supported_features & MfUltralightSupportAuth) { + if(buff_rx_len == (1 + 4) * 8) { + uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data); + if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) { + if(emulator->data.curr_authlim != UINT16_MAX) { + // Handle case where AUTHLIM has been lowered or changed from 0 + emulator->data.curr_authlim = UINT16_MAX; + emulator->data_changed = true; + } + // AUTHLIM reached, always fail + buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + mf_ul_reset_emulation(emulator, false); + command_parsed = true; + } else { + if(memcmp(&buff_rx[1], emulator->config->auth_data.pwd.raw, 4) == 0) { + // Correct password + buff_tx[0] = emulator->config->auth_data.pack.raw[0]; + buff_tx[1] = emulator->config->auth_data.pack.raw[1]; + tx_bytes = 2; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + emulator->auth_success = true; + command_parsed = true; + if(emulator->data.curr_authlim != 0) { + // Reset current AUTHLIM + emulator->data.curr_authlim = 0; + emulator->data_changed = true; + } + } else if(!emulator->config->auth_data.pwd.value) { + // Unknown password, pretend to be an Amiibo + buff_tx[0] = 0x80; + buff_tx[1] = 0x80; + tx_bytes = 2; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + emulator->auth_success = true; + command_parsed = true; + } else { + // Wrong password, increase negative verification count + if(emulator->data.curr_authlim < UINT16_MAX) { + ++emulator->data.curr_authlim; + emulator->data_changed = true; + } + if(scaled_authlim != 0 && + emulator->data.curr_authlim >= scaled_authlim) { + emulator->data.curr_authlim = UINT16_MAX; + buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + mf_ul_reset_emulation(emulator, false); + command_parsed = true; + } else { + // Should delay here to slow brute forcing + } + } + } + } + } + } else if(cmd == MF_UL_READ_SIG) { + if(emulator->supported_features & MfUltralightSupportSignature) { + // Check 2nd byte = 0x00 - RFU + if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0x00) { + tx_bytes = sizeof(emulator->data.signature); + memcpy(buff_tx, emulator->data.signature, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } else if(cmd == MF_UL_CHECK_TEARING) { + if(emulator->supported_features & MfUltralightSupportTearingFlags) { + if(buff_rx_len == (1 + 1) * 8) { + uint8_t cnt_num = buff_rx[1]; + if(cnt_num < 3) { + buff_tx[0] = emulator->data.tearing[cnt_num]; + tx_bytes = 1; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_HALT_START) { + reset_idle = true; + FURI_LOG_D(TAG, "Received HLTA"); + } else if(cmd == MF_UL_SECTOR_SELECT) { + if(emulator->supported_features & MfUltralightSupportSectorSelect) { + if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0xFF) { + // Send ACK + emulator->sector_select_cmd_started = true; + send_ack = true; + command_parsed = true; + } + } + } else if(cmd == MF_UL_READ_VCSL) { + if(emulator->supported_features & MfUltralightSupportVcsl) { + if(buff_rx_len == (1 + 20) * 8) { + buff_tx[0] = emulator->config_cache.vctid; + tx_bytes = 1; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } else { + // 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 { + reset_idle = true; + FURI_LOG_D(TAG, "Received invalid buffer less than 8 bits in length"); + } + + if(reset_idle) { + mf_ul_reset_emulation(emulator, false); + tx_bits = 0; + command_parsed = true; + } + + if(!command_parsed) { + // Send NACK + buff_tx[0] = MF_UL_NAK_INVALID_ARGUMENT; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + // Every NAK should cause reset to IDLE + mf_ul_reset_emulation(emulator, false); + } else if(send_ack) { + buff_tx[0] = MF_UL_ACK; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } + + if(respond_nothing) { + *buff_tx_len = UINT16_MAX; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } else { + // Return tx buffer size in bits + if(tx_bytes) { + tx_bits = tx_bytes * 8; + } + *buff_tx_len = tx_bits; + } + +#ifdef FURI_DEBUG + if(*buff_tx_len == UINT16_MAX) { + FURI_LOG_T(TAG, "Emu TX: no reply"); + } else if(*buff_tx_len > 0) { + int count = (*buff_tx_len + 7) / 8; + for(int i = 0; i < count; ++i) { + string_cat_printf(debug_buf, "%02x ", buff_tx[i]); + } + string_strim(debug_buf); + FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, string_get_cstr(debug_buf)); + string_clear(debug_buf); + } else { + FURI_LOG_T(TAG, "Emu TX: HALT"); + } +#endif + + return tx_bits > 0; +} diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h new file mode 100644 index 00000000..77dbd1e4 --- /dev/null +++ b/lib/nfc/protocols/mifare_ultralight.h @@ -0,0 +1,222 @@ +#pragma once + +#include <furi_hal_nfc.h> + +// Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM +#define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) + +#define MF_UL_TEARING_FLAG_DEFAULT (0xBD) + +#define MF_UL_HALT_START (0x50) +#define MF_UL_GET_VERSION_CMD (0x60) +#define MF_UL_READ_CMD (0x30) +#define MF_UL_FAST_READ_CMD (0x3A) +#define MF_UL_WRITE (0xA2) +#define MF_UL_FAST_WRITE (0xA6) +#define MF_UL_COMP_WRITE (0xA0) +#define MF_UL_READ_CNT (0x39) +#define MF_UL_INC_CNT (0xA5) +#define MF_UL_AUTH (0x1B) +#define MF_UL_READ_SIG (0x3C) +#define MF_UL_CHECK_TEARING (0x3E) +#define MF_UL_READ_VCSL (0x4B) +#define MF_UL_SECTOR_SELECT (0xC2) + +#define MF_UL_ACK (0xa) +#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, + + // Keep last for number of types calculation + MfUltralightTypeNum, +} MfUltralightType; + +typedef enum { + MfUltralightSupportNone = 0, + MfUltralightSupportFastRead = 1 << 0, + MfUltralightSupportTearingFlags = 1 << 1, + MfUltralightSupportReadCounter = 1 << 2, + MfUltralightSupportIncrCounter = 1 << 3, + MfUltralightSupportSignature = 1 << 4, + MfUltralightSupportFastWrite = 1 << 5, + MfUltralightSupportCompatWrite = 1 << 6, + MfUltralightSupportAuth = 1 << 7, + MfUltralightSupportVcsl = 1 << 8, + MfUltralightSupportSectorSelect = 1 << 9, + // NTAG21x only has counter 2 + 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 { + MfUltralightMirrorNone, + MfUltralightMirrorUid, + MfUltralightMirrorCounter, + MfUltralightMirrorUidCounter, +} MfUltralightMirrorConf; + +typedef struct { + uint8_t header; + uint8_t vendor_id; + uint8_t prod_type; + uint8_t prod_subtype; + uint8_t prod_ver_major; + uint8_t prod_ver_minor; + uint8_t storage_size; + uint8_t protocol_type; +} MfUltralightVersion; + +typedef struct { + uint8_t sn0[3]; + uint8_t btBCC0; + uint8_t sn1[4]; + uint8_t btBCC1; + uint8_t internal; + uint8_t lock[2]; + uint8_t otp[4]; +} MfUltralightManufacturerBlock; + +typedef struct { + MfUltralightType type; + MfUltralightVersion version; + uint8_t signature[32]; + uint32_t counter[3]; + uint8_t tearing[3]; + uint16_t curr_authlim; + uint16_t data_size; + uint8_t data[MF_UL_MAX_DUMP_SIZE]; +} MfUltralightData; + +typedef struct __attribute__((packed)) { + union { + uint8_t raw[4]; + uint32_t value; + } pwd; + union { + uint8_t raw[2]; + uint16_t value; + } pack; +} MfUltralightAuth; + +// Common configuration pages for MFUL EV1, NTAG21x, and NTAG I2C Plus +typedef struct __attribute__((packed)) { + union { + uint8_t value; + struct { + uint8_t rfui1 : 2; + bool strg_mod_en : 1; + bool rfui2 : 1; + uint8_t mirror_byte : 2; + MfUltralightMirrorConf mirror_conf : 2; + }; + } mirror; + uint8_t rfui1; + uint8_t mirror_page; + uint8_t auth0; + union { + uint8_t value; + struct { + uint8_t authlim : 3; + bool nfc_cnt_pwd_prot : 1; + bool nfc_cnt_en : 1; + bool nfc_dis_sec1 : 1; // NTAG I2C Plus only + bool cfglck : 1; + bool prot : 1; + }; + } access; + uint8_t vctid; + uint8_t rfui2[2]; + MfUltralightAuth auth_data; + uint8_t rfui3[2]; +} MfUltralightConfigPages; + +typedef struct { + uint16_t pages_to_read; + int16_t pages_read; + MfUltralightFeatures supported_features; +} MfUltralightReader; + +typedef struct { + MfUltralightData data; + MfUltralightConfigPages* config; + // Most config values don't apply until power cycle, so cache config pages + // for correct behavior + MfUltralightConfigPages config_cache; + MfUltralightFeatures supported_features; + uint16_t page_num; + bool data_changed; + bool comp_write_cmd_started; + uint8_t comp_write_page_addr; + bool auth_success; + uint8_t curr_sector; + bool sector_select_cmd_started; + bool ntag_i2c_plus_sector3_lockout; + bool read_counter_incremented; +} MfUltralightEmulator; + +bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +bool mf_ultralight_read_version( + FuriHalNfcTxRxContext* tx_rx, + 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, + MfUltralightData* data); + +bool mf_ultralight_fast_read_pages( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data); + +bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); + +bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); + +bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); + +bool mf_ul_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data); + +void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle); + +void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data); + +bool mf_ul_prepare_emulation_response( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len, + uint32_t* data_type, + void* context); diff --git a/lib/nfc/protocols/nfc_util.c b/lib/nfc/protocols/nfc_util.c new file mode 100644 index 00000000..9de6f982 --- /dev/null +++ b/lib/nfc/protocols/nfc_util.c @@ -0,0 +1,47 @@ +#include "nfc_util.h" + +#include <furi.h> + +static const uint8_t nfc_util_odd_byte_parity[256] = { + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, + 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, + 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, + 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, + 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, + 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, + 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; + +void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) { + furi_assert(dest); + furi_assert(len <= 8); + + while(len--) { + dest[len] = (uint8_t)src; + src >>= 8; + } +} + +uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len) { + furi_assert(src); + furi_assert(len <= 8); + + uint64_t res = 0; + while(len--) { + res = (res << 8) | (*src); + src++; + } + return res; +} + +uint8_t nfc_util_even_parity32(uint32_t data) { + // data ^= data >> 16; + // data ^= data >> 8; + // return !nfc_util_odd_byte_parity[data]; + return (__builtin_parity(data) & 0xFF); +} + +uint8_t nfc_util_odd_parity8(uint8_t data) { + return nfc_util_odd_byte_parity[data]; +} diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h new file mode 100644 index 00000000..d530badc --- /dev/null +++ b/lib/nfc/protocols/nfc_util.h @@ -0,0 +1,11 @@ +#pragma once + +#include <stdint.h> + +void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest); + +uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len); + +uint8_t nfc_util_even_parity32(uint32_t data); + +uint8_t nfc_util_odd_parity8(uint8_t data); diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c new file mode 100755 index 00000000..c401f8cc --- /dev/null +++ b/lib/nfc/protocols/nfca.c @@ -0,0 +1,142 @@ +#include "nfca.h" +#include <string.h> +#include <stdio.h> +#include <furi.h> + +#define NFCA_CMD_RATS (0xE0U) + +#define NFCA_CRC_INIT (0x6363) + +#define NFCA_F_SIG (13560000.0) +#define T_SIG 7374 //73.746ns*100 +#define T_SIG_x8 58992 //T_SIG*8 +#define T_SIG_x8_x8 471936 //T_SIG*8*8 +#define T_SIG_x8_x9 530928 //T_SIG*8*9 + +#define NFCA_SIGNAL_MAX_EDGES (1350) + +typedef struct { + uint8_t cmd; + uint8_t param; +} nfca_cmd_rats; + +static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; + +static uint8_t nfca_sleep_req[] = {0x50, 0x00}; + +uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { + uint16_t crc = NFCA_CRC_INIT; + uint8_t byte = 0; + + for(uint8_t i = 0; i < len; i++) { + byte = buff[i]; + byte ^= (uint8_t)(crc & 0xff); + byte ^= byte << 4; + crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ + (((uint16_t)byte) >> 4); + } + + return crc; +} + +void nfca_append_crc16(uint8_t* buff, uint16_t len) { + uint16_t crc = nfca_get_crc16(buff, len); + buff[len] = (uint8_t)crc; + buff[len + 1] = (uint8_t)(crc >> 8); +} + +bool nfca_emulation_handler( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len) { + bool sleep = false; + uint8_t rx_bytes = buff_rx_len / 8; + + if(rx_bytes == sizeof(nfca_sleep_req) && !memcmp(buff_rx, nfca_sleep_req, rx_bytes)) { + sleep = true; + } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { + memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); + *buff_tx_len = sizeof(nfca_default_ats) * 8; + } + + return sleep; +} + +static void nfca_add_bit(DigitalSignal* signal, bool bit) { + if(bit) { + signal->start_level = true; + for(size_t i = 0; i < 7; i++) { + signal->edge_timings[i] = T_SIG_x8; + } + signal->edge_timings[7] = T_SIG_x8_x9; + signal->edge_cnt = 8; + } else { + signal->start_level = false; + signal->edge_timings[0] = T_SIG_x8_x8; + for(size_t i = 1; i < 9; i++) { + signal->edge_timings[i] = T_SIG_x8; + } + signal->edge_cnt = 9; + } +} + +static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) { + for(uint8_t i = 0; i < 8; i++) { + if(byte & (1 << i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + if(parity) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } +} + +NfcaSignal* nfca_signal_alloc() { + NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal)); + nfca_signal->one = digital_signal_alloc(10); + nfca_signal->zero = digital_signal_alloc(10); + nfca_add_bit(nfca_signal->one, true); + nfca_add_bit(nfca_signal->zero, false); + nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES); + + return nfca_signal; +} + +void nfca_signal_free(NfcaSignal* nfca_signal) { + furi_assert(nfca_signal); + + digital_signal_free(nfca_signal->one); + digital_signal_free(nfca_signal->zero); + digital_signal_free(nfca_signal->tx_signal); + free(nfca_signal); +} + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) { + furi_assert(nfca_signal); + furi_assert(data); + furi_assert(parity); + + nfca_signal->tx_signal->edge_cnt = 0; + nfca_signal->tx_signal->start_level = true; + // Start of frame + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + + if(bits < 8) { + for(size_t i = 0; i < bits; i++) { + if(FURI_BIT(data[0], i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + } else { + for(size_t i = 0; i < bits / 8; i++) { + nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07)))); + } + } +} diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h new file mode 100644 index 00000000..498ef284 --- /dev/null +++ b/lib/nfc/protocols/nfca.h @@ -0,0 +1,28 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include <lib/digital_signal/digital_signal.h> + +typedef struct { + DigitalSignal* one; + DigitalSignal* zero; + DigitalSignal* tx_signal; +} NfcaSignal; + +uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len); + +void nfca_append_crc16(uint8_t* buff, uint16_t len); + +bool nfca_emulation_handler( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len); + +NfcaSignal* nfca_signal_alloc(); + +void nfca_signal_free(NfcaSignal* nfca_signal); + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity); |