#include "mifare_desfire.h" #include #include #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; } MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]) { if(!data) { return NULL; } for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { if(memcmp(aid, app->id, 3) == 0) { return app; } } return NULL; } MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id) { if(!app) { return NULL; } for(MifareDesfireFile* file = app->file_head; file; file = file->next) { if(file->id == id) { return file; } } return NULL; } void mf_df_cat_data(MifareDesfireData* data, FuriString* 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, FuriString* 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, FuriString* out) { furi_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]); furi_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); furi_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); furi_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, FuriString* out) { furi_string_cat_printf(out, "freeMem %lu\n", free_mem->bytes); } void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { furi_string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); furi_string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); furi_string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); furi_string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); furi_string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); if(ks->flags) { furi_string_cat_printf(out, "flags %d\n", ks->flags); } furi_string_cat_printf(out, "maxKeys %d\n", ks->max_keys); for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { furi_string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); } } void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out) { furi_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, FuriString* 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, FuriString* 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; } furi_string_cat_printf(out, "File %d\n", file->id); furi_string_cat_printf(out, "%s %s\n", type, comm); furi_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; furi_string_cat_printf(out, "size %d\n", size); break; case MifareDesfireFileTypeValue: size = 4; furi_string_cat_printf( out, "lo %lu hi %lu\n", file->settings.value.lo_limit, file->settings.value.hi_limit); furi_string_cat_printf( out, "limit %lu 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; furi_string_cat_printf(out, "size %d\n", size); furi_string_cat_printf(out, "num %d max %lu\n", num, file->settings.record.max); break; } uint8_t* data = file->contents; if(data) { for(int rec = 0; rec < num; rec++) { furi_string_cat_printf(out, "record %d\n", rec); for(int ch = 0; ch < size; ch += 4) { furi_string_cat_printf(out, "%03x|", ch); for(int i = 0; i < 4; i++) { if(ch + i < size) { furi_string_cat_printf(out, "%02x ", data[rec * size + ch + i]); } else { furi_string_cat_printf(out, " "); } } for(int i = 0; i < 4 && ch + i < size; i++) { const size_t data_index = rec * size + ch + i; if(isprint(data[data_index])) { furi_string_cat_printf(out, "%c", data[data_index]); } else { furi_string_cat_printf(out, "."); } } furi_string_cat_printf(out, "\n"); } furi_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)) { //-V1051 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; }