From 4b3a78538cb5e7344358308cb2a1371e9333c5af Mon Sep 17 00:00:00 2001 From: Bjoern Kerler Date: Fri, 17 Apr 2020 21:06:32 +0200 Subject: [PATCH] HF MFDES dump + chk commands added --- client/dictionaries/mfdes_default_keys.dic | 37 + client/src/cmdhfmfdes.c | 749 +++++++++++++++++++-- client/src/fileutils.c | 38 ++ client/src/fileutils.h | 1 + 4 files changed, 783 insertions(+), 42 deletions(-) create mode 100644 client/dictionaries/mfdes_default_keys.dic diff --git a/client/dictionaries/mfdes_default_keys.dic b/client/dictionaries/mfdes_default_keys.dic new file mode 100644 index 000000000..9766e127b --- /dev/null +++ b/client/dictionaries/mfdes_default_keys.dic @@ -0,0 +1,37 @@ +0000000000000000 +00000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +404142434445464748494a4b4c4d4e4f +00112233445566778899aabbccddeeff +2b7e151628aed2a6abf7158809cf4f3c +fbeed618357133667c85e08f7236a8de +f7ddac306ae266ccf90bc11ee46d513b +54686973206973206D79206B65792020 +ffffffffffffffffffffffffffffffff +a0a1a2a3a4a5a6a7a0a1a2a3a4a5a6a7 +b0b1b2b3b4b5b6b7b0b1b2b3b4b5b6b7 +d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 +11111111111111111111111111111111 +22222222222222222222222222222222 +33333333333333333333333333333333 +44444444444444444444444444444444 +55555555555555555555555555555555 +66666666666666666666666666666666 +77777777777777777777777777777777 +88888888888888888888888888888888 +99999999999999999999999999999999 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +cccccccccccccccccccccccccccccccc +dddddddddddddddddddddddddddddddd +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +000102030405060708090a0b0c0d0e0f +0102030405060708090a0b0c0d0e0f10 +00010203040506070809101112131415 +01020304050607080910111213141516 +16151413121110090807060504030201 +15141312111009080706050403020100 +0f0e0d0c0b0a09080706050403020100 +100f0e0d0c0b0a090807060504030201 +404142434445464748494a4b4c4d4e4f +303132333435363738393a3b3c3d3e3f diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index d02c1ddfa..159e319f8 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -16,21 +16,21 @@ #include "cmdparser.h" // command_t #include "comms.h" #include "ui.h" -#include "cmdhw.h" #include "cmdhf14a.h" -#include "mbedtls/des.h" #include "mbedtls/aes.h" #include "../crypto/libpcrypto.h" #include "protocols.h" -#include "mifare.h" // desfire raw command options #include "cmdtrace.h" #include "cliparser/cliparser.h" #include "emv/apduinfo.h" // APDU manipulation / errorcodes #include "emv/emvcore.h" // APDU logging #include "util_posix.h" // msleep -#include "mifare/mifare4.h" // MIFARE Authenticate / MAC #include "mifare/desfire_crypto.h" #include "crapto1/crapto1.h" +#include "fileutils.h" + +#define MAX_KEY_LEN 24 +#define MAX_KEYS_LIST_LEN 1024 struct desfire_key defaultkey = {0}; static desfirekey_t sessionkey = &defaultkey; @@ -175,17 +175,11 @@ static char *cluster_to_text(uint8_t cluster) { case CL_ADMIN: return "card administration"; case CL_MISC1: - return "miscellaneous applications"; case CL_MISC2: - return "miscellaneous applications"; case CL_MISC3: - return "miscellaneous applications"; case CL_MISC4: - return "miscellaneous applications"; case CL_MISC5: - return "miscellaneous applications"; case CL_MISC6: - return "miscellaneous applications"; case CL_MISC7: return "miscellaneous applications"; case CL_AIRLINES: @@ -219,7 +213,6 @@ static char *cluster_to_text(uint8_t cluster) { case CL_CITYCARD: return "city card services"; case CL_ACCESS_CONTROL_1: - return "access control & security"; case CL_ACCESS_CONTROL_2: return "access control & security"; case CL_VIGIK: @@ -289,19 +282,12 @@ static char *cluster_to_text(uint8_t cluster) { case CL_MAIL: return "mail"; case CL_AMISC: - return "miscellaneous applications"; case CL_AMISC1: - return "miscellaneous applications"; case CL_AMISC2: - return "miscellaneous applications"; case CL_AMISC3: - return "miscellaneous applications"; case CL_AMISC4: - return "miscellaneous applications"; case CL_AMISC5: - return "miscellaneous applications"; case CL_AMISC6: - return "miscellaneous applications"; case CL_AMISC7: return "miscellaneous applications"; default: @@ -311,7 +297,7 @@ static char *cluster_to_text(uint8_t cluster) { } typedef enum { - UNKNOWN = 0, + DESFIRE_UNKNOWN = 0, DESFIRE_MF3ICD40, DESFIRE_EV1, DESFIRE_EV2, @@ -736,8 +722,7 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, sAPDU apdu = {0x90, subcommand, 0x00, 0x00, 0x01, data}; int res = send_desfire_cmd(&apdu, false, recv_data, &recv_len, &sw, 0, false); if (res != PM3_SUCCESS) { - PrintAndLogEx(SUCCESS, "Sending auth command %02X " _RED_("failed"), subcommand); - return PM3_ESOFT; + return 1; } } else if (payload->mode == MFDES_AUTH_PICC) { /*cmd[0] = AUTHENTICATE; @@ -747,13 +732,11 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, } if (!recv_len) { - PrintAndLogEx(ERR, "Authentication failed. Card timeout."); - return PM3_ESOFT; + return 2; } if (sw != status(MFDES_ADDITIONAL_FRAME)) { - PrintAndLogEx(ERR, "Authentication failed. Invalid key number."); - return PM3_ESOFT; + return 3; } int expectedlen = 8; @@ -762,8 +745,7 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, } if (recv_len != expectedlen) { - PrintAndLogEx(ERR, "Authentication failed. Length of answer %d doesn't match algo length %d.", recv_len, expectedlen); - return PM3_ESOFT; + return 4; } int rndlen = recv_len; @@ -778,8 +760,7 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, // Part 3 if (payload->algo == MFDES_ALGO_AES) { if (mbedtls_aes_setkey_dec(&ctx, key->data, 128) != 0) { - PrintAndLogEx(ERR, "mbedtls_aes_setkey_dec failed"); - return PM3_ESOFT; + return 5; } mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, rndlen, IV, encRndB, RndB); } else if (payload->algo == MFDES_ALGO_DES) @@ -848,8 +829,7 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, } if (payload->algo == MFDES_ALGO_AES) { if (mbedtls_aes_setkey_enc(&ctx, key->data, 128) != 0) { - PrintAndLogEx(ERR, "mbedtls_aes_setkey_enc failed"); - return PM3_ESOFT; + return 6; } mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, 32, IV, tmp, both); if (g_debugMode > 1) { @@ -866,8 +846,7 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, sAPDU apdu = {0x90, MFDES_ADDITIONAL_FRAME, 0x00, 0x00, bothlen, both}; int res = send_desfire_cmd(&apdu, false, recv_data, &recv_len, &sw, 0, false); if (res != PM3_SUCCESS) { - PrintAndLogEx(SUCCESS, "Sending auth command %02X " _RED_("failed"), subcommand); - return PM3_ESOFT; + return 7; } } else { /*cmd[0] = ADDITIONAL_FRAME; @@ -881,14 +860,12 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, } if (!recv_len) { - PrintAndLogEx(ERR, "Authentication failed. Card timeout."); - return PM3_ESOFT; + return 8; } if (payload->mode != MFDES_AUTH_PICC) { if (sw != status(MFDES_S_OPERATION_OK)) { - PrintAndLogEx(ERR, "Authentication failed."); - return PM3_ESOFT; + return 9; } } else { /*if (resp[1] != 0x00) { @@ -915,8 +892,7 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, tdes_nxp_receive(encRndA, encRndA, rndlen, key->data, IV, 3); } else if (payload->mode == MFDES_AUTH_AES) { if (mbedtls_aes_setkey_dec(&ctx, key->data, 128) != 0) { - PrintAndLogEx(ERR, "mbedtls_aes_setkey_dec failed"); - return PM3_ESOFT; + return 10; } mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, rndlen, IV, encRndA, encRndA); } @@ -924,12 +900,11 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, rol(RndA, rndlen); for (int x = 0; x < rndlen; x++) { if (RndA[x] != encRndA[x]) { - PrintAndLogEx(ERR, "Authentication failed. Cannot verify Session Key."); if (g_debugMode > 1) { PrintAndLogEx(INFO, "Expected_RndA : %s", sprint_hex(RndA, rndlen)); PrintAndLogEx(INFO, "Generated_RndA : %s", sprint_hex(encRndA, rndlen)); } - return PM3_ESOFT; + return 11; } } @@ -938,6 +913,45 @@ int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rpayload, return PM3_SUCCESS; } +void AuthToError(int error) { + switch (error) { + case 1: + PrintAndLogEx(SUCCESS, "Sending auth command failed"); + break; + case 2: + PrintAndLogEx(ERR, "Authentication failed. No data received"); + break; + case 3: + PrintAndLogEx(ERR, "Authentication failed. Invalid key number."); + break; + case 4: + PrintAndLogEx(ERR, "Authentication failed. Length of answer %d doesn't match algo length %d."); + break; + case 5: + PrintAndLogEx(ERR, "mbedtls_aes_setkey_dec failed"); + break; + case 6: + PrintAndLogEx(ERR, "mbedtls_aes_setkey_enc failed"); + break; + case 7: + PrintAndLogEx(SUCCESS, "Sending auth command failed"); + break; + case 8: + PrintAndLogEx(ERR, "Authentication failed. Card timeout."); + break; + case 9: + PrintAndLogEx(ERR, "Authentication failed."); + break; + case 10: + PrintAndLogEx(ERR, "mbedtls_aes_setkey_dec failed"); + break; + case 11: + PrintAndLogEx(ERR, "Authentication failed. Cannot verify Session Key."); + break; + default: + break; + } +} // -- test if card supports 0x0A static int test_desfire_authenticate() { uint8_t data[] = {0x00}; @@ -3051,6 +3065,149 @@ static int DecodeFileSettings(uint8_t *src, int src_len, int maclen) { return PM3_ESOFT; } +static int CmdHF14ADesDump(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + DropField(); + uint8_t aid[3] = {0}; + uint8_t app_ids[78] = {0}; + uint8_t app_ids_len = 0; + + uint8_t file_ids[33] = {0}; + uint8_t file_ids_len = 0; + + dfname_t dfnames[255]; + uint8_t dfname_count = 0; + + int res = 0; + + if (handler_desfire_appids(app_ids, &app_ids_len) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Can't get list of applications on tag"); + DropField(); + return PM3_ESOFT; + } + + if (handler_desfire_dfnames(dfnames, &dfname_count) != PM3_SUCCESS) { + PrintAndLogEx(WARNING, _RED_("Can't get DF Names")); + DropField(); + return PM3_ESOFT; + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "-- Mifare DESFire Dump ----------------------"); + PrintAndLogEx(INFO, "-------------------------------------------------------------"); + + for (int i = 0; i < app_ids_len; i += 3) { + + aid[0] = app_ids[i]; + aid[1] = app_ids[i + 1]; + aid[2] = app_ids[i + 2]; + + PrintAndLogEx(SUCCESS, " AID : " _GREEN_("%02X%02X%02X"), aid[2], aid[1], aid[0]); + PrintAndLogEx(SUCCESS, " AID Function Cluster 0x%02X: " _YELLOW_("%s"), aid[2], cluster_to_text(aid[2])); + + for (int m = 0; m < dfname_count; m++) { + if (dfnames[m].aid[0] == aid[0] && dfnames[m].aid[1] == aid[1] && dfnames[m].aid[2] == aid[2]) { + PrintAndLogEx(SUCCESS, " - DF " _YELLOW_("%02X%02X") " Name : " _YELLOW_("%s"), dfnames[m].fid[1], dfnames[m].fid[0], dfnames[m].name); + } + } + + uint8_t num_keys = 0; + uint8_t key_setting = 0; + res = handler_desfire_keysettings(&key_setting, &num_keys); + if (res != PM3_SUCCESS) return res; + + res = handler_desfire_select_application(aid); + + if (handler_desfire_fileids(file_ids, &file_ids_len) == PM3_SUCCESS) { + for (int j = file_ids_len - 1; j >= 0; j--) { + PrintAndLogEx(SUCCESS, "\n\n Fileid %d (0x%02x)", file_ids[j], file_ids[j]); + + uint8_t filesettings[20] = {0}; + int fileset_len = 0; + int res = handler_desfire_filesettings(file_ids[j], filesettings, &fileset_len); + int maclen = 0; // To be implemented + if (res == PM3_SUCCESS) { + //if (DecodeFileSettings(filesettings, fileset_len, maclen) != PM3_SUCCESS) { + if (fileset_len == 1 + 1 + 2 + 3 + maclen) { + int filesize = (filesettings[6] << 16) + (filesettings[5] << 8) + filesettings[4]; + mfdes_data_t fdata; + fdata.fileno = file_ids[j]; + memset(fdata.offset, 0, 3); + //memcpy(fdata.length,&filesettings[4],3); + memset(fdata.length, 0, 3); + uint8_t *data = (uint8_t *)malloc(filesize); + fdata.data = data; + if (data) { + res = handler_desfire_readdata(&fdata, MFDES_DATA_FILE); + if (res == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, "\nOffset | Data | Ascii"); + PrintAndLogEx(NORMAL, "----------------------------------------------------------------------------"); + int len = le24toh(fdata.length); + for (int i = 0; i < len; i += 16) { + PrintAndLogEx(NORMAL, "%02d/0x%02X | %s| %s", i, i, sprint_hex(&fdata.data[i], len > 16 ? 16 : len), sprint_ascii(&fdata.data[i], len > 16 ? 16 : len)); + } + free(data); + } else { + PrintAndLogEx(ERR, "Couldn't read value. Error %d", res); + res = handler_desfire_select_application(aid); + } + } + } else if (fileset_len == 1 + 1 + 2 + 4 + 4 + 4 + 1 + maclen) { + PrintAndLogEx(NORMAL, "\n\nValue file: 0x%0x", file_ids[j]); + mfdes_value_t value; + value.fileno = file_ids[j]; + int len = 0; + res = handler_desfire_getvalue(&value, &len); + if (res == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, "\nOffset | Value | Ascii"); + PrintAndLogEx(NORMAL, "----------------------------------------------------------------------------"); + for (int i = 0; i < len; i += 16) { + PrintAndLogEx(NORMAL, "%02d/0x%02X | %s| %s", i, i, sprint_hex(&value.value[i], len > 16 ? 16 : len), sprint_ascii(&value.value[i], len > 16 ? 16 : len)); + } + } else { + PrintAndLogEx(ERR, "Couldn't read value. Error %d", res); + res = handler_desfire_select_application(aid); + } + } else if (fileset_len == 1 + 1 + 2 + 3 + 3 + 3 + maclen) { + int maxrecords = (filesettings[9] << 16) + (filesettings[8] << 8) + filesettings[7]; + int filesize = (filesettings[6] << 16) + (filesettings[5] << 8) + filesettings[4]; + mfdes_data_t fdata; + fdata.fileno = file_ids[j]; + memset(fdata.length, 0, 3); + //memcpy(fdata.length,&filesettings[4],3); + uint8_t *data = (uint8_t *)malloc(filesize); + fdata.data = data; + if (data) { + for (int offset = 0; offset < maxrecords; offset++) { + PrintAndLogEx(NORMAL, "\n\nRecord offset: %024x", offset); + memset(data, 0, filesize); + fdata.offset[0] = offset & 0xFF; + fdata.offset[1] = (offset >> 8) & 0xFF; + fdata.offset[2] = (offset >> 16) & 0xFF; + res = handler_desfire_readdata(&fdata, MFDES_RECORD_FILE); + if (res == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, "\nOffset | Data | Ascii"); + PrintAndLogEx(NORMAL, "----------------------------------------------------------------------------"); + int len = le24toh(fdata.length); + for (int i = 0; i < len; i += 16) { + PrintAndLogEx(NORMAL, "%02d/0x%02X | %s| %s", i, i, sprint_hex(&fdata.data[i], len > 16 ? 16 : len), sprint_ascii(&fdata.data[i], len > 16 ? 16 : len)); + } + } else { + res = handler_desfire_select_application(aid); + } + } + free(data); + } + } + } + } + } + } + PrintAndLogEx(INFO, "-------------------------------------------------------------"); + DropField(); + return PM3_SUCCESS; +} + static int CmdHF14ADesEnumApplications(const char *Cmd) { (void)Cmd; // Cmd is not used so far DropField(); @@ -3305,17 +3462,523 @@ static int CmdHF14ADesAuth(const char *Cmd) { } */ mfdes_auth_res_t rpayload; - if (handler_desfire_auth(&payload, &rpayload, usedefaultkey) == PM3_SUCCESS) { + int error = handler_desfire_auth(&payload, &rpayload, usedefaultkey); + if (error == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, " Key : " _GREEN_("%s"), sprint_hex(key, keylength)); PrintAndLogEx(SUCCESS, " SESSION : " _GREEN_("%s"), sprint_hex(rpayload.sessionkey, keylength)); PrintAndLogEx(INFO, "-------------------------------------------------------------"); } else { + AuthToError(error); return PM3_ESOFT; } PrintAndLogEx(INFO, "-------------------------------------------------------------"); return PM3_SUCCESS; } +void DesFill2bPattern(uint8_t deskeyList[MAX_KEYS_LIST_LEN][8], size_t *deskeyListLen, uint8_t aeskeyList[MAX_KEYS_LIST_LEN][16], size_t *aeskeyListLen, uint8_t k3kkeyList[MAX_KEYS_LIST_LEN][24], size_t *k3kkeyListLen, uint32_t *startPattern) { + for (uint32_t pt = *startPattern; pt < 0x10000; pt++) { + if (*deskeyListLen != MAX_KEYS_LIST_LEN) { + deskeyList[*deskeyListLen][0] = (pt >> 8) & 0xff; + deskeyList[*deskeyListLen][1] = pt & 0xff; + memcpy(&deskeyList[*deskeyListLen][2], &deskeyList[*deskeyListLen][0], 2); + memcpy(&deskeyList[*deskeyListLen][4], &deskeyList[*deskeyListLen][0], 4); + (*deskeyListLen)++; + } + if (*aeskeyListLen != MAX_KEYS_LIST_LEN) { + aeskeyList[*aeskeyListLen][0] = (pt >> 8) & 0xff; + aeskeyList[*aeskeyListLen][1] = pt & 0xff; + memcpy(&aeskeyList[*aeskeyListLen][2], &aeskeyList[*aeskeyListLen][0], 2); + memcpy(&aeskeyList[*aeskeyListLen][4], &aeskeyList[*aeskeyListLen][0], 4); + memcpy(&aeskeyList[*aeskeyListLen][8], &aeskeyList[*aeskeyListLen][0], 8); + (*aeskeyListLen)++; + } + if (*k3kkeyListLen != MAX_KEYS_LIST_LEN) { + k3kkeyList[*k3kkeyListLen][0] = (pt >> 8) & 0xff; + k3kkeyList[*k3kkeyListLen][1] = pt & 0xff; + memcpy(&k3kkeyList[*k3kkeyListLen][2], &k3kkeyList[*k3kkeyListLen][0], 2); + memcpy(&k3kkeyList[*k3kkeyListLen][4], &k3kkeyList[*k3kkeyListLen][0], 4); + memcpy(&k3kkeyList[*k3kkeyListLen][8], &k3kkeyList[*k3kkeyListLen][0], 8); + memcpy(&k3kkeyList[*k3kkeyListLen][16], &k3kkeyList[*k3kkeyListLen][0], 4); + (*k3kkeyListLen)++; + } + + *startPattern = pt; + if ((*deskeyListLen == MAX_KEYS_LIST_LEN) && (*aeskeyListLen == MAX_KEYS_LIST_LEN) && (*k3kkeyListLen == MAX_KEYS_LIST_LEN)) + break; + } + (*startPattern)++; +} + +static int AuthCheckDesfire(uint8_t *aid, uint8_t deskeyList[MAX_KEYS_LIST_LEN][8], size_t deskeyListLen, uint8_t aeskeyList[MAX_KEYS_LIST_LEN][16], size_t aeskeyListLen, uint8_t k3kkeyList[MAX_KEYS_LIST_LEN][24], size_t k3kkeyListLen, uint8_t foundKeys[4][0xE][24 + 1], bool *result) { + int res = handler_desfire_select_application(aid); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "AID %X does not exist."); + return res; + } + + int usedkeys[0xF] = {0}; + bool des = false; + bool tdes = false; + bool aes = false; + bool k3kdes = false; + + if (memcmp(aid, "\x00\x00\x00", 3) != 0) { + uint8_t file_ids[33] = {0}; + uint8_t file_ids_len = 0; + // Get File IDs + if (handler_desfire_fileids(file_ids, &file_ids_len) == PM3_SUCCESS) { + for (int j = file_ids_len - 1; j >= 0; j--) { + uint8_t filesettings[20] = {0}; + int fileset_len = 0; + res = handler_desfire_filesettings(file_ids[j], filesettings, &fileset_len); + if (res == PM3_SUCCESS) { + uint16_t accrights = (filesettings[3] << 8) + filesettings[2]; + int change_access_rights = accrights & 0xF; + int read_write_access = (accrights >> 4) & 0xF; + int write_access = (accrights >> 8) & 0xF; + int read_access = (accrights >> 12) & 0xF; + if (change_access_rights == 0xE) change_access_rights = 0x0; + if (read_write_access == 0xE) read_write_access = 0x0; + if (write_access == 0xE) write_access = 0x0; + if (read_access == 0xE) read_access = 0x0; + usedkeys[change_access_rights] = 1; + usedkeys[read_write_access] = 1; + usedkeys[write_access] = 1; + usedkeys[read_access] = 1; + if (res == PM3_SUCCESS) { + switch (fileset_len >> 6) { + case 0: + des = true; + tdes = true; + break; + case 1: + k3kdes = true; + break; + case 2: + aes = true; + break; + default: + break; + } + } + } + } + if (file_ids_len == 0) { + for (int z = 0; z < 0xE; z++) { + usedkeys[z] = 1; + des = true; + tdes = true; + aes = true; + k3kdes = true; + } + } + } + } else des = true; + int error = PM3_SUCCESS; + bool badlen = false; + mfdes_authinput_t payload; + uint32_t curaid = (aid[0] & 0xFF) + ((aid[1] & 0xFF) << 8) + ((aid[2] & 0xFF) << 16); + if (des) { + for (int keyno = 0; keyno < 0xE; keyno++) + if (usedkeys[keyno] == 1 && foundKeys[0][keyno][0] == 0) { + for (int curkey = 0; curkey < deskeyListLen; curkey++) { + payload.keylen = 8; + memcpy(payload.key, deskeyList[curkey], 8); + payload.mode = MFDES_AUTH_DES; + payload.algo = MFDES_ALGO_DES; + payload.keyno = keyno; + mfdes_auth_res_t rpayload; + error = handler_desfire_auth(&payload, &rpayload, false); + if (error == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "AID 0x%06X, Found DES Key %d : " _GREEN_("%s"), curaid, keyno, sprint_hex(deskeyList[curkey], 8)); + foundKeys[0][keyno][0] = 0x01; + *result = true; + memcpy(&foundKeys[0][keyno][1], deskeyList[curkey], 8); + break; + } else if (error < 7) { + badlen = true; + DropField(); + res = handler_desfire_select_application(aid); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "AID %X does not exist."); + return res; + } + break; + } + } + if (badlen == true) { + badlen = false; + break; + } + } + } + if (tdes) { + for (int keyno = 0; keyno < 0xE; keyno++) + if (usedkeys[keyno] == 1 && foundKeys[1][keyno][0] == 0) { + for (int curkey = 0; curkey < aeskeyListLen; curkey++) { + payload.keylen = 16; + memcpy(payload.key, aeskeyList[curkey], 16); + payload.mode = MFDES_AUTH_DES; + payload.algo = MFDES_ALGO_3DES; + payload.keyno = keyno; + mfdes_auth_res_t rpayload; + error = handler_desfire_auth(&payload, &rpayload, false); + if (error == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "AID 0x%06X, Found 3DES Key %d : " _GREEN_("%s"), curaid, keyno, sprint_hex(aeskeyList[curkey], 16)); + foundKeys[1][keyno][0] = 0x01; + *result = true; + memcpy(&foundKeys[1][keyno][1], aeskeyList[curkey], 16); + break; + } else if (error < 7) { + badlen = true; + DropField(); + res = handler_desfire_select_application(aid); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "AID %X does not exist."); + return res; + } + break; + } + } + if (badlen == true) { + badlen = false; + break; + } + } + } + + if (aes) { + for (int keyno = 0; keyno < 0xE; keyno++) + if (usedkeys[keyno] == 1 && foundKeys[2][keyno][0] == 0) { + for (int curkey = 0; curkey < aeskeyListLen; curkey++) { + payload.keylen = 16; + memcpy(payload.key, aeskeyList[curkey], 16); + payload.mode = MFDES_AUTH_AES; + payload.algo = MFDES_ALGO_AES; + payload.keyno = keyno; + mfdes_auth_res_t rpayload; + error = handler_desfire_auth(&payload, &rpayload, false); + if (error == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "AID 0x%06X, Found AES Key %d : " _GREEN_("%s"), curaid, keyno, sprint_hex(aeskeyList[curkey], 16)); + foundKeys[2][keyno][0] = 0x01; + *result = true; + memcpy(&foundKeys[2][keyno][1], aeskeyList[curkey], 16); + break; + } else if (error < 7) { + badlen = true; + DropField(); + res = handler_desfire_select_application(aid); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "AID %X does not exist."); + return res; + } + break; + } + } + if (badlen == true) { + badlen = false; + break; + } + } + } + + if (k3kdes) { + for (int keyno = 0; keyno < 0xE; keyno++) + if (usedkeys[keyno] == 1 && foundKeys[3][keyno][0] == 0) { + for (int curkey = 0; curkey < k3kkeyListLen; curkey++) { + payload.keylen = 24; + memcpy(payload.key, k3kkeyList[curkey], 24); + payload.mode = MFDES_AUTH_ISO; + payload.algo = MFDES_ALGO_3K3DES; + payload.keyno = keyno; + mfdes_auth_res_t rpayload; + error = handler_desfire_auth(&payload, &rpayload, false); + if (error == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "AID 0x%06X, Found 3K3 Key %d : " _GREEN_("%s"), curaid, keyno, sprint_hex(k3kkeyList[curkey], 24)); + foundKeys[3][keyno][0] = 0x01; + *result = true; + memcpy(&foundKeys[3][keyno][1], k3kkeyList[curkey], 16); + break; + } else if (error < 7) { + badlen = true; + DropField(); + res = handler_desfire_select_application(aid); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "AID %X does not exist."); + return res; + } + break; + } + } + if (badlen == true) { + badlen = false; + break; + } + } + } + DropField(); + return PM3_SUCCESS; +} + +static int CmdHF14aDesChk(const char *Cmd) { + int res; + uint8_t deskeyList[MAX_KEYS_LIST_LEN][8] = {{0}}; + uint8_t aeskeyList[MAX_KEYS_LIST_LEN][16] = {{0}}; + uint8_t k3kkeyList[MAX_KEYS_LIST_LEN][MAX_KEY_LEN] = {{0}}; + size_t deskeyListLen = 0; + size_t aeskeyListLen = 0; + size_t k3kkeyListLen = 0; + uint8_t foundKeys[4][0xE][24 + 1] = {{{0}}}; + + CLIParserInit("hf mfdes chk", + "Checks keys with Mifare Desfire card.", + "Usage:\n" + " hf mfdes chk -a 123456 -k 000102030405060708090a0b0c0d0e0f -> check key on aid 0x123456\n" + " hf mfdes chk -d mfdes_default_keys -> check keys from dictionary against all existing aid on card\n" + " hf mfdes chk -d mfdes_default_keys -a 123456 -> check keys from dictionary against aid 0x123456\n" + " hf mfdes chk -a 123456 --pattern1b -j keys -> check all 1-byte keys pattern on aid 0x123456 and save found keys to json\n" + " hf mfdes chk -a 123456 --pattern2b --startp2b FA00 -> check all 2-byte keys pattern on aid 0x123456. Start from key FA00FA00...FA00\n"); + + void *argtable[] = { + arg_param_begin, + arg_strx0("aA", "aid", "", "Use specific AID (3 hex bytes, big endian)"), + arg_str0("kK", "key", "", "Key for checking (HEX 16 bytes)"), + arg_str0("dD", "dict", "", "File with keys dictionary"), + arg_lit0(NULL, "pattern1b", "Check all 1-byte combinations of key (0000...0000, 0101...0101, 0202...0202, ...)"), + arg_lit0(NULL, "pattern2b", "Check all 2-byte combinations of key (0000...0000, 0001...0001, 0002...0002, ...)"), + arg_str0(NULL, "startp2b", "", "Start key (2-byte HEX) for 2-byte search (use with `--pattern2b`)"), + arg_str0("jJ", "json", "", "Json file to save keys"), + arg_lit0("vV", "verbose", "Verbose mode."), + arg_param_end + }; + CLIExecWithReturn(Cmd, argtable, false); + + int aidlength = 0; + uint8_t aid[3] = {0}; + CLIGetHexWithReturn(1, aid, &aidlength); + swap24(aid); + uint8_t vkey[16] = {0}; + int vkeylen = 0; + CLIGetHexWithReturn(2, vkey, &vkeylen); + + if (vkeylen > 0) { + if (vkeylen == 8) { + memcpy(&deskeyList[deskeyListLen], vkey, 8); + deskeyListLen++; + } else if (vkeylen == 16) { + memcpy(&aeskeyList[aeskeyListLen], vkey, 16); + aeskeyListLen++; + } else if (vkeylen == 24) { + memcpy(&k3kkeyList[k3kkeyListLen], vkey, 16); + k3kkeyListLen++; + } else { + PrintAndLogEx(ERR, "Specified key must have 8, 16 or 24 bytes length."); + CLIParserFree(); + return PM3_EINVARG; + } + } + + uint8_t dict_filename[FILE_PATH_SIZE + 2] = {0}; + int dict_filenamelen = 0; + if (CLIParamStrToBuf(arg_get_str(3), dict_filename, FILE_PATH_SIZE, &dict_filenamelen)) { + PrintAndLogEx(FAILED, "File name too long or invalid."); + CLIParserFree(); + return PM3_EINVARG; + } + + bool pattern1b = arg_get_lit(4); + bool pattern2b = arg_get_lit(5); + + if (pattern1b && pattern2b) { + PrintAndLogEx(ERR, "Pattern search mode must be 2-byte or 1-byte only."); + CLIParserFree(); + return PM3_EINVARG; + } + + if (dict_filenamelen && (pattern1b || pattern2b)) { + PrintAndLogEx(ERR, "Pattern search mode and dictionary mode can't be used in one command."); + CLIParserFree(); + return PM3_EINVARG; + } + + uint32_t startPattern = 0x0000; + uint8_t vpattern[2]; + int vpatternlen = 0; + CLIGetHexWithReturn(6, vpattern, &vpatternlen); + if (vpatternlen > 0) { + if (vpatternlen > 0 && vpatternlen <= 2) { + startPattern = (vpattern[0] << 8) + vpattern[1]; + } else { + PrintAndLogEx(ERR, "Pattern must be 2-byte length."); + CLIParserFree(); + return PM3_EINVARG; + } + if (!pattern2b) + PrintAndLogEx(WARNING, "Pattern entered, but search mode not is 2-byte search."); + } + + uint8_t jsonname[250] = {0}; + int jsonnamelen = 0; + if (CLIParamStrToBuf(arg_get_str(7), jsonname, sizeof(jsonname), &jsonnamelen)) { + PrintAndLogEx(ERR, "Invalid json name."); + CLIParserFree(); + return PM3_EINVARG; + } + jsonname[jsonnamelen] = 0; + + bool verbose = arg_get_lit(8); + + CLIParserFree(); + + // 1-byte pattern search mode + if (pattern1b) { + for (int i = 0; i < 0x100; i++) + memset(aeskeyList[i], i, 16); + for (int i = 0; i < 0x100; i++) + memset(deskeyList[i], i, 8); + for (int i = 0; i < 0x100; i++) + memset(k3kkeyList[i], i, 24); + aeskeyListLen = 0x100; + deskeyListLen = 0x100; + k3kkeyListLen = 0x100; + } + + // 2-byte pattern search mode + if (pattern2b) { + DesFill2bPattern(deskeyList, &deskeyListLen, aeskeyList, &aeskeyListLen, k3kkeyList, &k3kkeyListLen, &startPattern); + } + + // dictionary mode + size_t endFilePosition = 0; + if (dict_filenamelen) { + uint16_t keycnt = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, deskeyList, sizeof(deskeyList), NULL, 8, &keycnt, 0, &endFilePosition, true); + deskeyListLen = keycnt; + if (endFilePosition) + PrintAndLogEx(SUCCESS, "First part of des dictionary successfully loaded."); + endFilePosition = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, aeskeyList, sizeof(aeskeyList), NULL, 16, &keycnt, 0, &endFilePosition, true); + aeskeyListLen = keycnt; + if (endFilePosition) + PrintAndLogEx(SUCCESS, "First part of aes dictionary successfully loaded."); + endFilePosition = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, k3kkeyList, sizeof(k3kkeyList), NULL, 24, &keycnt, 0, &endFilePosition, true); + k3kkeyListLen = keycnt; + if (endFilePosition) + PrintAndLogEx(SUCCESS, "First part of k3kdes dictionary successfully loaded."); + endFilePosition = 0; + + if (endFilePosition) + PrintAndLogEx(SUCCESS, "First part of dictionary successfully loaded."); + } + + if (aeskeyListLen == 0) { + PrintAndLogEx(ERR, "Aes key list is empty. Nothing to check."); + return PM3_EINVARG; + } else { + PrintAndLogEx(INFO, "Loaded " _YELLOW_("%zu") "aes keys", aeskeyListLen); + } + + if (deskeyListLen == 0) { + PrintAndLogEx(ERR, "Des key list is empty. Nothing to check."); + return PM3_EINVARG; + } else { + PrintAndLogEx(INFO, "Loaded " _YELLOW_("%zu") "des keys", deskeyListLen); + } + + if (k3kkeyListLen == 0) { + PrintAndLogEx(ERR, "K3k key list is empty. Nothing to check."); + return PM3_EINVARG; + } else { + PrintAndLogEx(INFO, "Loaded " _YELLOW_("%zu") "k3kdes keys", k3kkeyListLen); + } + + if (!verbose) + printf("Search keys:\n"); + + bool result = false; + uint8_t app_ids[78] = {0}; + uint8_t app_ids_len = 0; + + if (handler_desfire_appids(app_ids, &app_ids_len) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Can't get list of applications on tag"); + DropField(); + return PM3_ESOFT; + } + + if (aidlength != 0) { + memcpy(&app_ids[0], aid, 3); + app_ids_len = 1; + } + + for (int x = 0; x < app_ids_len / 3; x++) { + uint32_t curaid = (app_ids[x * 3] & 0xFF) + ((app_ids[(x * 3) + 1] & 0xFF) << 8) + ((app_ids[(x * 3) + 2] & 0xFF) << 16); + PrintAndLogEx(ERR, "Checking aid 0x%06X...", curaid); + res = AuthCheckDesfire(&app_ids[x * 3], deskeyList, deskeyListLen, aeskeyList, aeskeyListLen, k3kkeyList, k3kkeyListLen, foundKeys, &result); + if (res == PM3_EOPABORTED) { + break; + } + + if (pattern2b && startPattern < 0x10000) { + if (!verbose) + printf("p"); + aeskeyListLen = 0; + deskeyListLen = 0; + k3kkeyListLen = 0; + DesFill2bPattern(deskeyList, &deskeyListLen, aeskeyList, &aeskeyListLen, k3kkeyList, &k3kkeyListLen, &startPattern); + continue; + } + if (dict_filenamelen && endFilePosition) { + if (!verbose) + printf("d"); + uint16_t keycnt = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, deskeyList, sizeof(deskeyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false); + deskeyListLen = keycnt; + keycnt = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, aeskeyList, sizeof(aeskeyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false); + aeskeyListLen = keycnt; + keycnt = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, k3kkeyList, sizeof(k3kkeyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false); + k3kkeyListLen = keycnt; + continue; + } + } + if (!verbose) + printf("\n"); + + // save keys to json + if ((jsonnamelen > 0) && result) { + // Mifare Desfire info + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); + + PacketResponseNG resp; + WaitForResponse(CMD_ACK, &resp); + + iso14a_card_select_t card; + memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); + + uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision + + uint8_t data[10 + 1 + 2 + 1 + 256 + (4 * 0xE * (24 + 1))] = {0}; + uint8_t atslen = 0; + if (select_status == 1 || select_status == 2) { + memcpy(data, card.uid, card.uidlen); + data[10] = card.sak; + data[11] = card.atqa[1]; + data[12] = card.atqa[0]; + atslen = card.ats_len; + data[13] = atslen; + memcpy(&data[14], card.ats, atslen); + } + + // length: UID(10b)+SAK(1b)+ATQA(2b)+ATSlen(1b)+ATS(atslen)+foundKeys[2][64][AES_KEY_LEN + 1] + memcpy(&data[14 + atslen], foundKeys, 4 * 0xE * (24 + 1)); + saveFileJSON((char *)jsonname, jsfMfDesfireKeys, data, 0xE); + } + + return PM3_SUCCESS; +} + static int CmdHF14ADesList(const char *Cmd) { (void)Cmd; // Cmd is not used so far return CmdTraceList("des"); @@ -3364,6 +4027,8 @@ static command_t CommandTable[] = { {"getvalue", CmdHF14ADesGetValueData, IfPm3Iso14443a, "Get value of file"}, {"changevalue", CmdHF14ADesChangeValue, IfPm3Iso14443a, "Write value of a value file (credit/debit/clear)"}, {"formatpicc", CmdHF14ADesFormatPICC, IfPm3Iso14443a, "Format PICC"}, + {"dump", CmdHF14ADesDump, IfPm3Iso14443a, "Dump all files"}, + {"chk", CmdHF14aDesChk, IfPm3Iso14443a, "Check keys"}, /* ToDo: diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 066a796b5..2e9ae43d6 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -426,6 +426,44 @@ int saveFileJSON(const char *preferredName, JSONFileType ftype, uint8_t *data, s } } break; + case jsfMfDesfireKeys: + JsonSaveStr(root, "FileType", "mfdes"); + JsonSaveBufAsHexCompact(root, "$.Card.UID", &data[0], 7); + JsonSaveBufAsHexCompact(root, "$.Card.SAK", &data[10], 1); + JsonSaveBufAsHexCompact(root, "$.Card.ATQA", &data[11], 2); + uint8_t datslen = data[13]; + if (datslen > 0) + JsonSaveBufAsHexCompact(root, "$.Card.ATS", &data[14], datslen); + + uint8_t dvdata[4][0xE][24 + 1] = {{{0}}}; + memcpy(dvdata, &data[14 + datslen], 4 * 0xE * (24 + 1)); + + for (int i = 0; i < (int)datalen; i++) { + char path[PATH_MAX_LENGTH] = {0}; + + if (dvdata[0][i][0]) { + memset(path, 0x00, sizeof(path)); + sprintf(path, "$.DES.%d.Key", i); + JsonSaveBufAsHexCompact(root, path, &dvdata[0][i][1], 8); + } + + if (dvdata[1][i][0]) { + memset(path, 0x00, sizeof(path)); + sprintf(path, "$.3DES.%d.Key", i); + JsonSaveBufAsHexCompact(root, path, &dvdata[1][i][1], 16); + } + if (dvdata[2][i][0]) { + memset(path, 0x00, sizeof(path)); + sprintf(path, "$.AES.%d.Key", i); + JsonSaveBufAsHexCompact(root, path, &dvdata[2][i][1], 16); + } + if (dvdata[3][i][0]) { + memset(path, 0x00, sizeof(path)); + sprintf(path, "$.K3KDES.%d.Key", i); + JsonSaveBufAsHexCompact(root, path, &dvdata[3][i][1], 24); + } + } + break; case jsfSettings: preferences_save_callback(root); break; diff --git a/client/src/fileutils.h b/client/src/fileutils.h index 06d3ff20a..ff43b3f7e 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -63,6 +63,7 @@ typedef enum { jsfT5555, jsfMfPlusKeys, jsfSettings, + jsfMfDesfireKeys, } JSONFileType; typedef enum {