diff --git a/client/Makefile b/client/Makefile index a6d987560..4babb528f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -134,6 +134,7 @@ CORESRCS = uart_posix.c \ CMDSRCS = crapto1/crapto1.c \ crapto1/crypto1.c \ + mifare/mifaredefault.c \ mifare/mfkey.c \ tea.c \ fido/additional_ca.c \ diff --git a/client/cmdhf14a.c b/client/cmdhf14a.c index c8f8093d6..73b19ae0f 100644 --- a/client/cmdhf14a.c +++ b/client/cmdhf14a.c @@ -551,30 +551,29 @@ int CmdHF14ASniff(const char *Cmd) { return PM3_SUCCESS; } -int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { +int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, bool silentMode) { static uint8_t responseNum = 0; uint16_t cmdc = 0; *dataoutlen = 0; if (activateField) { - responseNum = 1; PacketResponseNG resp; // Anticollision + SELECT card SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - PrintAndLogEx(ERR, "Proxmark3 connection timeout."); + if (!silentMode) PrintAndLogEx(ERR, "Proxmark3 connection timeout."); return 1; } // check result if (resp.oldarg[0] == 0) { - PrintAndLogEx(ERR, "No card in field."); + if (!silentMode) PrintAndLogEx(ERR, "No card in field."); return 1; } if (resp.oldarg[0] != 1 && resp.oldarg[0] != 2) { - PrintAndLogEx(ERR, "Card not in iso14443-4. res=%" PRId64 ".", resp.oldarg[0]); + if (!silentMode) PrintAndLogEx(ERR, "Card not in iso14443-4. res=%" PRId64 ".", resp.oldarg[0]); return 1; } @@ -583,12 +582,12 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav uint8_t rats[] = { 0xE0, 0x80 }; // FSDI=8 (FSD=256), CID=0 SendCommandOLD(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, 2, 0, rats, 2); if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - PrintAndLogEx(ERR, "Proxmark3 connection timeout."); + if (!silentMode) PrintAndLogEx(ERR, "Proxmark3 connection timeout."); return 1; } if (resp.oldarg[0] == 0) { // ats_len - PrintAndLogEx(ERR, "Can't get ATS."); + if (!silentMode) PrintAndLogEx(ERR, "Can't get ATS."); return 1; } } @@ -610,7 +609,7 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav int iLen = resp.oldarg[0]; if (!iLen) { - PrintAndLogEx(ERR, "No card response."); + if (!silentMode) PrintAndLogEx(ERR, "No card response."); return 1; } @@ -619,12 +618,12 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav *dataoutlen = 0; if (maxdataoutlen && *dataoutlen > maxdataoutlen) { - PrintAndLogEx(ERR, "Buffer too small(%d). Needs %d bytes", *dataoutlen, maxdataoutlen); + if (!silentMode) PrintAndLogEx(ERR, "Buffer too small(%d). Needs %d bytes", *dataoutlen, maxdataoutlen); return 2; } if (recv[0] != data[0]) { - PrintAndLogEx(ERR, "iso14443-4 framing error. Card send %2x must be %2x", dataout[0], data[0]); + if (!silentMode) PrintAndLogEx(ERR, "iso14443-4 framing error. Card send %2x must be %2x", dataout[0], data[0]); return 2; } @@ -632,12 +631,12 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav // CRC Check if (iLen == -1) { - PrintAndLogEx(ERR, "ISO 14443A CRC error."); + if (!silentMode) PrintAndLogEx(ERR, "ISO 14443A CRC error."); return 3; } } else { - PrintAndLogEx(ERR, "Reply timeout."); + if (!silentMode) PrintAndLogEx(ERR, "Reply timeout."); return 4; } diff --git a/client/cmdhf14a.h b/client/cmdhf14a.h index fcea5f5cf..83fb2c69d 100644 --- a/client/cmdhf14a.h +++ b/client/cmdhf14a.h @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2010 iZsh -// 2011, Merlok -// 2015,216,2017 iceman, marshmellow, piwi +// 2011,2019 Merlok +// 2015,2016,2017 iceman, marshmellow, piwi // This code is licensed to you under the terms of the GNU GPL, version 2 or, // at your option, any later version. See the LICENSE.txt file for the text of // the license. @@ -30,6 +30,6 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search); const char *getTagInfo(uint8_t uid); int Hf14443_4aGetCardData(iso14a_card_select_t *card); int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); -int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); +int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, bool silentMode); #endif diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index 23b418599..ad6fe31b2 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -13,7 +13,7 @@ #include #include "cmdparser.h" // command_t -#include "commonutil.h" // ARRAYLEN +#include "commonutil.h" // ARRAYLEN #include "comms.h" // clearCommandBuffer #include "fileutils.h" #include "cmdtrace.h" @@ -4211,7 +4211,7 @@ static int CmdHF14AMfAuth4(const char *Cmd) { return PM3_ESOFT; } - return MifareAuth4(NULL, keyn, key, true, false, true); + return MifareAuth4(NULL, keyn, key, true, false, true, true, false); } // https://www.nxp.com/docs/en/application-note/AN10787.pdf diff --git a/client/cmdhfmfp.c b/client/cmdhfmfp.c index c6af6305a..2c0fc56eb 100644 --- a/client/cmdhfmfp.c +++ b/client/cmdhfmfp.c @@ -25,6 +25,8 @@ #include "cliparser/cliparser.h" #include "emv/dump.h" #include "mifare/mifaredefault.h" +#include "util_posix.h" +#include "fileutils.h" static const uint8_t DefaultKey[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; @@ -88,7 +90,7 @@ static int CmdHFMFPInfo(const char *cmd) { int datalen = 0; // https://github.com/Proxmark/proxmark3/blob/master/client/luascripts/mifarePlus.lua#L161 uint8_t cmd[3 + 16] = {0xa8, 0x90, 0x90, 0x00}; - int res = ExchangeRAW14a(cmd, sizeof(cmd), false, false, data, sizeof(data), &datalen); + int res = ExchangeRAW14a(cmd, sizeof(cmd), false, false, data, sizeof(data), &datalen, false); if (!res && datalen > 1 && data[0] == 0x09) { SLmode = 0; } @@ -105,7 +107,7 @@ static int CmdHFMFPInfo(const char *cmd) { DropField(); - return 0; + return PM3_SUCCESS; } static int CmdHFMFPWritePerso(const char *cmd) { @@ -169,7 +171,7 @@ static int CmdHFMFPWritePerso(const char *cmd) { } PrintAndLogEx(INFO, "Write OK."); - return 0; + return PM3_SUCCESS; } uint16_t CardAddresses[] = {0x9000, 0x9001, 0x9002, 0x9003, 0x9004, 0xA000, 0xA001, 0xA080, 0xA081, 0xC000, 0xC001}; @@ -245,7 +247,7 @@ static int CmdHFMFPInitPerso(const char *cmd) { PrintAndLogEx(INFO, "Done."); - return 0; + return PM3_SUCCESS; } static int CmdHFMFPCommitPerso(const char *cmd) { @@ -286,7 +288,7 @@ static int CmdHFMFPCommitPerso(const char *cmd) { } PrintAndLogEx(INFO, "Switch level OK."); - return 0; + return PM3_SUCCESS; } static int CmdHFMFPAuth(const char *cmd) { @@ -324,7 +326,7 @@ static int CmdHFMFPAuth(const char *cmd) { return 1; } - return MifareAuth4(NULL, keyn, key, true, false, verbose); + return MifareAuth4(NULL, keyn, key, true, false, true, verbose, false); } static int CmdHFMFPRdbl(const char *cmd) { @@ -392,7 +394,7 @@ static int CmdHFMFPRdbl(const char *cmd) { PrintAndLogEx(INFO, "--block:%d sector[%d]:%02x key:%04x", blockn, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum); mf4Session mf4session; - int res = MifareAuth4(&mf4session, keyn, key, true, true, verbose); + int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); if (res) { PrintAndLogEx(ERR, "Authentication error: %d", res); return res; @@ -491,7 +493,7 @@ static int CmdHFMFPRdsc(const char *cmd) { PrintAndLogEx(INFO, "--sector[%d]:%02x key:%04x", mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum); mf4Session mf4session; - int res = MifareAuth4(&mf4session, keyn, key, true, true, verbose); + int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); if (res) { PrintAndLogEx(ERR, "Authentication error: %d", res); return res; @@ -532,7 +534,7 @@ static int CmdHFMFPRdsc(const char *cmd) { } DropField(); - return 0; + return PM3_SUCCESS; } static int CmdHFMFPWrbl(const char *cmd) { @@ -595,7 +597,7 @@ static int CmdHFMFPWrbl(const char *cmd) { PrintAndLogEx(INFO, "--block:%d sector[%d]:%02x key:%04x", blockNum & 0xff, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum); mf4Session mf4session; - int res = MifareAuth4(&mf4session, keyn, key, true, true, verbose); + int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); if (res) { PrintAndLogEx(ERR, "Authentication error: %d", res); return res; @@ -634,7 +636,333 @@ static int CmdHFMFPWrbl(const char *cmd) { DropField(); PrintAndLogEx(INFO, "Write OK."); - return 0; + return PM3_SUCCESS; +} + +#define AES_KEY_LEN 16 +#define MAX_KEYS_LIST_LEN 1024 + + int MFPKeyCheck(uint8_t startSector, uint8_t endSector, uint8_t startKeyAB, uint8_t endKeyAB, + uint8_t keyList[MAX_KEYS_LIST_LEN][AES_KEY_LEN], size_t keyListLen, uint8_t foundKeys[2][64][AES_KEY_LEN + 1], + bool verbose) { + int res; + bool selectCard = true; + uint8_t keyn[2] = {0}; + + // sector number from 0 + for (uint8_t sector = startSector; sector <= endSector; sector++) { + // 0-keyA 1-keyB + for(uint8_t keyAB = startKeyAB; keyAB <= endKeyAB; keyAB++) { + // main cycle with key check + for (int i = 0; i < keyListLen; i++) { + if (i % 10 == 0) { + if (!verbose) + printf("."); + if (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\nAborted via keyboard!\n"); + DropField(); + return PM3_EOPABORTED; + } + } + + uint16_t uKeyNum = 0x4000 + sector * 2 + keyAB; + keyn[0] = uKeyNum >> 8; + keyn[1] = uKeyNum & 0xff; + + for (int retry = 0; retry < 4; retry++) { + res = MifareAuth4(NULL, keyn, keyList[i], selectCard, true, false, false, true); + if (res != 2) + break; + + if (verbose) + PrintAndLogEx(WARNING, "retried[%d]...", retry); + else + printf("R"); + + DropField(); + selectCard = true; + msleep(100); + } + + if (verbose) + PrintAndLogEx(WARNING, "sector %02d key %d [%s] res: %d", sector, keyAB, sprint_hex_inrow(keyList[i], 16), res); + + // key for [sector,keyAB] found + if (res == 0) { + if (verbose) + PrintAndLogEx(INFO, "Found key for sector %d key %s [%s]", sector, keyAB == 0 ? "A" : "B", sprint_hex_inrow(keyList[i], 16)); + else + printf("+"); + foundKeys[keyAB][sector][0] = 0x01; + memcpy(&foundKeys[keyAB][sector][1], keyList[i], AES_KEY_LEN); + DropField(); + selectCard = true; + msleep(50); + break; + } + + // 5 - auth error (rnd not equal) + if (res != 5) { + if (verbose) + PrintAndLogEx(ERR, "Exchange error. Aborted."); + else + printf("E"); + DropField(); + return PM3_ECARDEXCHANGE; + } + + selectCard = false; + } + } + } + + DropField(); + return PM3_SUCCESS; +} + +void Fill2bPattern(uint8_t keyList[MAX_KEYS_LIST_LEN][AES_KEY_LEN], size_t *keyListLen, uint32_t *startPattern) { + for (uint32_t pt = *startPattern; pt < 0x10000; pt++) { + keyList[*keyListLen][0] = (pt >> 8) & 0xff; + keyList[*keyListLen][1] = pt & 0xff; + memcpy(&keyList[*keyListLen][2], &keyList[*keyListLen][0], 2); + memcpy(&keyList[*keyListLen][4], &keyList[*keyListLen][0], 4); + memcpy(&keyList[*keyListLen][8], &keyList[*keyListLen][0], 8); + (*keyListLen)++; + *startPattern = pt; + if (*keyListLen == MAX_KEYS_LIST_LEN) + break; + } + (*startPattern)++; +} + +static int CmdHFMFPChk(const char *cmd) { + int res; + uint8_t keyList[MAX_KEYS_LIST_LEN][AES_KEY_LEN] = {{0}}; + size_t keyListLen = 0; + uint8_t foundKeys[2][64][AES_KEY_LEN + 1] = {{{0}}}; + + CLIParserInit("hf mfp chk", + "Checks keys with Mifare Plus card.", + "Usage:\n" + " hf mfp chk -k 000102030405060708090a0b0c0d0e0f -> check key on sector 0 as key A and B\n" + " hf mfp chk -s 2 -a -> check default key list on sector 2, key A\n" + " hf mfp chk -d mfp_default_keys -s0 -e6 -> check keys from dictionary against sectors 0-6\n" + " hf mfp chk --pattern1b -j keys -> check all 1-byte keys pattern and save found keys to json\n" + " hf mfp chk --pattern2b --startp2b FA00 -> check all 2-byte keys pattern. Start from key FA00FA00...FA00\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("aA", "keya", "check only key A (by default check all keys)."), + arg_lit0("bB", "keyb", "check only key B (by default check all keys)."), + arg_int0("sS", "startsec", "Start sector Num (0..255)", NULL), + arg_int0("eE", "endsec", "End sector Num (0..255)", NULL), + 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, true); + + bool keyA = arg_get_lit(1); + bool keyB = arg_get_lit(2); + uint8_t startSector = arg_get_int_def(3, 0); + uint8_t endSector = arg_get_int_def(4, 0); + + uint8_t vkey[16] = {0}; + int vkeylen = 0; + CLIGetHexWithReturn(5, vkey, &vkeylen); + if (vkeylen > 0) { + if (vkeylen == 16) { + memcpy(&keyList[keyListLen], vkey, 16); + keyListLen++; + } else { + PrintAndLogEx(ERR, "Specified key must have 16 bytes length."); + CLIParserFree(); + return PM3_EINVARG; + } + } + + uint8_t dict_filename[FILE_PATH_SIZE + 2] = {0}; + int dict_filenamelen = 0; + if (CLIParamStrToBuf(arg_get_str(6), dict_filename, FILE_PATH_SIZE, &dict_filenamelen)) { + PrintAndLogEx(FAILED, "File name too long or invalid."); + CLIParserFree(); + return PM3_EINVARG; + } + + bool pattern1b = arg_get_lit(7); + bool pattern2b = arg_get_lit(8); + + 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(9, 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(10), jsonname, sizeof(jsonname), &jsonnamelen)) { + PrintAndLogEx(ERR, "Invalid json name."); + CLIParserFree(); + return PM3_EINVARG; + } + jsonname[jsonnamelen] = 0; + + bool verbose = arg_get_lit(11); + + CLIParserFree(); + + uint8_t startKeyAB = 0; + uint8_t endKeyAB = 1; + if (keyA && !keyB) + endKeyAB = 0; + if (!keyA && keyB) + startKeyAB = 1; + + if (endSector < startSector) + endSector = startSector; + + // 1-byte pattern search mode + if (pattern1b) { + for (int i = 0; i < 0x100; i++) + memset(keyList[i], i, 16); + + keyListLen = 0x100; + } + + // 2-byte pattern search mode + if (pattern2b) + Fill2bPattern(keyList, &keyListLen, &startPattern); + + // dictionary mode + size_t endFilePosition = 0; + if (dict_filenamelen) { + uint16_t keycnt = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, 0, &endFilePosition, true); + keyListLen = keycnt; + if (endFilePosition) + PrintAndLogEx(SUCCESS, "First part of dictionary successfully loaded."); + } + + if (keyListLen == 0) { + for (int i = 0; i < g_mifare_plus_default_keys_len; i++) { + if (hex_to_bytes(g_mifare_plus_default_keys[i], keyList[keyListLen], 16) != 16) + break; + + keyListLen++; + } + } + + if (keyListLen == 0) { + PrintAndLogEx(ERR, "Key list is empty. Nothing to check."); + return PM3_EINVARG; + } + + if (!verbose) + printf("Search keys:"); + while (true) { + res = MFPKeyCheck(startSector, endSector, startKeyAB, endKeyAB, keyList, keyListLen, foundKeys, verbose); + if (res == PM3_EOPABORTED) + break; + if (pattern2b && startPattern < 0x10000) { + if (!verbose) + printf("p"); + keyListLen = 0; + Fill2bPattern(keyList, &keyListLen, &startPattern); + continue; + } + if (dict_filenamelen && endFilePosition) { + if (!verbose) + printf("d"); + uint16_t keycnt = 0; + res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false); + keyListLen = keycnt; + continue; + } + break; + } + if (!verbose) + printf("\n"); + + // print result + bool printedHeader = false; + for (uint8_t sector = startSector; sector <= endSector; sector++) { + if (foundKeys[0][sector][0] || foundKeys[1][sector][0]) { + if (!printedHeader) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, ".------.--------------------------------.--------------------------------."); + PrintAndLogEx(INFO, "|sector| key A | key B |"); + PrintAndLogEx(INFO, "|------|--------------------------------|--------------------------------|"); + printedHeader = true; + } + PrintAndLogEx(INFO, "| %02d |%32s|%32s|", + sector, + (foundKeys[0][sector][0] == 0) ? "------ " : sprint_hex_inrow(&foundKeys[0][sector][1], AES_KEY_LEN), + (foundKeys[1][sector][0] == 0) ? "------ " : sprint_hex_inrow(&foundKeys[1][sector][1], AES_KEY_LEN)); + } + } + if (!printedHeader) + PrintAndLogEx(INFO, "No keys found("); + else + PrintAndLogEx(INFO, "'------'--------------------------------'--------------------------------'\n"); + + // save keys to json + if ((jsonnamelen > 0) && printedHeader) { + // Mifare Plus 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 + 2 * 64 * (AES_KEY_LEN + 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, 2 * 64 * (AES_KEY_LEN + 1)); + saveFileJSON((char *)jsonname, jsfMfPlusKeys, data, 64); + } + + return PM3_SUCCESS; } static int CmdHFMFPMAD(const char *cmd) { @@ -728,7 +1056,7 @@ static int CmdHFMFPMAD(const char *cmd) { } } - return 0; + return PM3_SUCCESS; } static int CmdHFMFPNDEF(const char *cmd) { @@ -832,7 +1160,7 @@ static int CmdHFMFPNDEF(const char *cmd) { NDEFDecodeAndPrint(data, datalen, verbose); - return 0; + return PM3_SUCCESS; } static command_t CommandTable[] = { @@ -845,6 +1173,7 @@ static command_t CommandTable[] = { {"rdbl", CmdHFMFPRdbl, IfPm3Iso14443a, "Read blocks"}, {"rdsc", CmdHFMFPRdsc, IfPm3Iso14443a, "Read sectors"}, {"wrbl", CmdHFMFPWrbl, IfPm3Iso14443a, "Write blocks"}, + {"chk", CmdHFMFPChk, IfPm3Iso14443a, "Check keys"}, {"mad", CmdHFMFPMAD, IfPm3Iso14443a, "Checks and prints MAD"}, {"ndef", CmdHFMFPNDEF, IfPm3Iso14443a, "Prints NDEF records from card"}, {NULL, NULL, 0, NULL} @@ -853,7 +1182,7 @@ static command_t CommandTable[] = { static int CmdHelp(const char *Cmd) { (void)Cmd; // Cmd is not used so far CmdsHelp(CommandTable); - return 0; + return PM3_SUCCESS; } int CmdHFMFP(const char *Cmd) { diff --git a/client/dictionaries/mfp_default_keys.dic b/client/dictionaries/mfp_default_keys.dic new file mode 100644 index 000000000..0f20b69bf --- /dev/null +++ b/client/dictionaries/mfp_default_keys.dic @@ -0,0 +1,27 @@ +ffffffffffffffffffffffffffffffff +00000000000000000000000000000000 +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 diff --git a/client/fileutils.c b/client/fileutils.c index ebdb662ca..624c0259d 100644 --- a/client/fileutils.c +++ b/client/fileutils.c @@ -352,6 +352,36 @@ int saveFileJSON(const char *preferredName, JSONFileType ftype, uint8_t *data, s case jsf15: case jsfLegic: case jsfT5555: + case jsfMfPlusKeys: + JsonSaveStr(root, "FileType", "mfp"); + + JsonSaveBufAsHexCompact(root, "$.Card.UID", &data[0], 7); + JsonSaveBufAsHexCompact(root, "$.Card.SAK", &data[10], 1); + JsonSaveBufAsHexCompact(root, "$.Card.ATQA", &data[11], 2); + uint8_t atslen = data[13]; + if (atslen > 0) + JsonSaveBufAsHexCompact(root, "$.Card.ATS", &data[14], atslen); + + uint8_t vdata[2][64][16 + 1] = {0}; + memcpy(vdata, &data[14 + atslen], 2 * 64 * 17); + + for (size_t i = 0; i < datalen; i++) { + char path[PATH_MAX_LENGTH] = {0}; + + if (vdata[0][i][0]) { + memset(path, 0x00, sizeof(path)); + sprintf(path, "$.SectorKeys.%d.KeyA", mfSectorNum(i)); + JsonSaveBufAsHexCompact(root, path, &vdata[0][i][1], 16); + } + + if (vdata[1][i][0]) { + memset(path, 0x00, sizeof(path)); + sprintf(path, "$.SectorKeys.%d.KeyB", mfSectorNum(i)); + JsonSaveBufAsHexCompact(root, path, &vdata[1][i][1], 16); + } + + } + break; default: break; } @@ -715,20 +745,29 @@ out: } int loadFileDICTIONARY(const char *preferredName, void *data, size_t *datalen, uint8_t keylen, uint16_t *keycnt) { + // t5577 == 4bytes + // mifare == 6 bytes + // mf plus == 16 bytes + // iclass == 8 bytes + // default to 6 bytes. + if (keylen != 4 && keylen != 6 && keylen != 8 && keylen != 16) { + keylen = 6; + } + + return loadFileDICTIONARYEx(preferredName, data, 0, datalen, keylen, keycnt, 0, NULL, true); +} +int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, uint8_t keylen, uint16_t *keycnt, + size_t startFilePosition, size_t *endFilePosition, bool verbose) { + if (endFilePosition) + *endFilePosition = 0; if (data == NULL) return PM3_EINVARG; + uint16_t vkeycnt = 0; + char *path; if (searchFile(&path, DICTIONARIES_SUBDIR, preferredName, ".dic", false) != PM3_SUCCESS) return PM3_EFILE; - // t5577 == 4bytes - // mifare == 6 bytes - // iclass == 8 bytes - // default to 6 bytes. - if (keylen != 4 && keylen != 6 && keylen != 8) { - keylen = 6; - } - // double up since its chars keylen <<= 1; @@ -743,10 +782,19 @@ int loadFileDICTIONARY(const char *preferredName, void *data, size_t *datalen, u retval = PM3_EFILE; goto out; } - + + if (startFilePosition) + fseek(f, startFilePosition, SEEK_SET); + // read file - while (fgets(line, sizeof(line), f)) { - + while (!feof(f)) { + size_t filepos = ftell(f); + if (!fgets(line, sizeof(line), f)) { + if (endFilePosition) + *endFilePosition = 0; + break; + } + // add null terminator line[keylen] = 0; @@ -758,23 +806,32 @@ int loadFileDICTIONARY(const char *preferredName, void *data, size_t *datalen, u if (line[0] == '#') continue; - if (!isxdigit(line[0])) { - PrintAndLogEx(FAILED, "file content error. '%s' must include " _BLUE_("%2d") "HEX symbols", line, keylen); + if (!CheckStringIsHEXValue(line)) continue; + + // cant store more data + if (maxdatalen && (counter + (keylen >> 1) > maxdatalen)) { + retval = 1; + if (endFilePosition) + *endFilePosition = filepos; + break; } - - uint64_t key = strtoull(line, NULL, 16); - - num_to_bytes(key, keylen >> 1, data + counter); - (*keycnt)++; + + if (hex_to_bytes(line, data + counter, keylen >> 1) != (keylen >> 1)) + continue; + + vkeycnt++; memset(line, 0, sizeof(line)); counter += (keylen >> 1); } fclose(f); - PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%2d") "keys from dictionary file " _YELLOW_("%s"), *keycnt, path); + if (verbose) + PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%2d") "keys from dictionary file " _YELLOW_("%s"), vkeycnt, path); if (datalen) *datalen = counter; + if (keycnt) + *keycnt = vkeycnt; out: free(path); return retval; @@ -790,9 +847,10 @@ int loadFileDICTIONARY_safe(const char *preferredName, void **pdata, uint8_t key // t5577 == 4bytes // mifare == 6 bytes + // mf plus == 16 bytes // iclass == 8 bytes // default to 6 bytes. - if (keylen != 4 && keylen != 6 && keylen != 8) { + if (keylen != 4 && keylen != 6 && keylen != 8 && keylen != 16) { keylen = 6; } @@ -848,10 +906,8 @@ int loadFileDICTIONARY_safe(const char *preferredName, void **pdata, uint8_t key if (line[0] == '#') continue; - if (!isxdigit(line[0])) { - PrintAndLogEx(FAILED, "file content error. '%s' must include " _BLUE_("%2d") "HEX symbols", line, keylen); + if (CheckStringIsHEXValue(line)) continue; - } uint64_t key = strtoull(line, NULL, 16); diff --git a/client/fileutils.h b/client/fileutils.h index eb4b57c82..c9de3b4fb 100644 --- a/client/fileutils.h +++ b/client/fileutils.h @@ -61,6 +61,7 @@ typedef enum { jsfLegic, jsfT55x7, jsfT5555, + jsfMfPlusKeys, } JSONFileType; typedef enum { @@ -175,13 +176,32 @@ int loadFileJSON(const char *preferredName, void *data, size_t maxdatalen, size_ * * @param preferredName * @param data The data array to store the loaded bytes from file - * @param maxdatalen maximum size of data array in bytes - * @param datalen the number of bytes loaded from file + * @param datalen the number of bytes loaded from file. may be NULL * @param keylen the number of bytes a key per row is + * @param keycnt key count that lays in data. may be NULL * @return 0 for ok, 1 for failz */ int loadFileDICTIONARY(const char *preferredName, void *data, size_t *datalen, uint8_t keylen, uint16_t *keycnt); +/** + * @brief Utility function to load data from a DICTIONARY textfile. This method takes a preferred name. + * E.g. mfc_default_keys.dic + * can be executed several times for big dictionaries and checks length of buffer + * + * @param preferredName + * @param data The data array to store the loaded bytes from file + * @param maxdatalen maximum size of data array in bytes + * @param datalen the number of bytes loaded from file. may be NULL + * @param keylen the number of bytes a key per row is + * @param keycnt key count that lays in data. may be NULL + * @param startFilePosition start position in dictionary file. used for big dictionaries. + * @param endFilePosition in case we have keys in file and maxdatalen reached it returns current key position in file. may be NULL + * @param verbose print messages if true + * @return 0 for ok, 1 for failz +*/ +int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, uint8_t keylen, uint16_t *keycnt, + size_t startFilePosition, size_t *endFilePosition, bool verbose); + /** * @brief Utility function to load data safely from a DICTIONARY textfile. This method takes a preferred name. * E.g. mfc_default_keys.dic diff --git a/client/mifare/mifare4.c b/client/mifare/mifare4.c index 5cdca2e14..7eecbe751 100644 --- a/client/mifare/mifare4.c +++ b/client/mifare/mifare4.c @@ -168,21 +168,24 @@ int CalculateMAC(mf4Session *session, MACType_t mtype, uint8_t blockNum, uint8_t return aes_cmac8(NULL, session->Kmac, macdata, mac, macdatalen); } -int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool verbose) { +int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool dropFieldIfError, bool verbose, bool silentMode) { uint8_t data[257] = {0}; int datalen = 0; uint8_t RndA[17] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}; uint8_t RndB[17] = {0}; + if (silentMode) + verbose = false; + if (session) session->Authenticated = false; uint8_t cmd1[] = {0x70, keyn[1], keyn[0], 0x00}; - int res = ExchangeRAW14a(cmd1, sizeof(cmd1), activateField, true, data, sizeof(data), &datalen); + int res = ExchangeRAW14a(cmd1, sizeof(cmd1), activateField, true, data, sizeof(data), &datalen, silentMode); if (res) { - PrintAndLogEx(ERR, "Exchande raw error: %d", res); - DropField(); + if (!silentMode) PrintAndLogEx(ERR, "Exchande raw error: %d", res); + if (dropFieldIfError) DropField(); return 2; } @@ -190,20 +193,20 @@ int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateF PrintAndLogEx(INFO, "phase2: %s", sprint_hex(cmd2, 33)); - res = ExchangeRAW14a(cmd2, sizeof(cmd2), false, true, data, sizeof(data), &datalen); + res = ExchangeRAW14a(cmd2, sizeof(cmd2), false, true, data, sizeof(data), &datalen, silentMode); if (res) { - PrintAndLogEx(ERR, "Exchande raw error: %d", res); - DropField(); + if (!silentMode) PrintAndLogEx(ERR, "Exchande raw error: %d", res); + if (dropFieldIfError) DropField(); return 4; } @@ -241,12 +244,12 @@ int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateF } if (memcmp(&raw[4], &RndA[1], 16)) { - PrintAndLogEx(ERR, "\nAuthentication FAILED. rnd not equal"); + if (!silentMode) PrintAndLogEx(ERR, "\nAuthentication FAILED. rnd is not equal"); if (verbose) { PrintAndLogEx(ERR, "RndA reader: %s", sprint_hex(&RndA[1], 16)); PrintAndLogEx(ERR, "RndA card: %s", sprint_hex(&raw[4], 16)); } - DropField(); + if (dropFieldIfError) DropField(); return 5; } @@ -311,7 +314,7 @@ static int intExchangeRAW14aPlus(uint8_t *datain, int datainlen, bool activateFi if (VerboseMode) PrintAndLogEx(INFO, ">>> %s", sprint_hex(datain, datainlen)); - int res = ExchangeRAW14a(datain, datainlen, activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); + int res = ExchangeRAW14a(datain, datainlen, activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen, false); if (VerboseMode) PrintAndLogEx(INFO, "<<< %s", sprint_hex(dataout, *dataoutlen)); @@ -380,7 +383,7 @@ int mfpReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *data PrintAndLogEx(INFO, "--sector[%d]:%02x key:%04x", mfNumBlocksPerSector(sectorNo), sectorNo, uKeyNum); mf4Session session; - int res = MifareAuth4(&session, keyn, key, true, true, verbose); + int res = MifareAuth4(&session, keyn, key, true, true, true, verbose, false); if (res) { PrintAndLogEx(ERR, "Sector %d authentication error: %d", sectorNo, res); return res; diff --git a/client/mifare/mifare4.h b/client/mifare/mifare4.h index 31eac1fdb..fe07aceaf 100644 --- a/client/mifare/mifare4.h +++ b/client/mifare/mifare4.h @@ -45,7 +45,7 @@ void mfpSetVerboseMode(bool verbose); const char *mfpGetErrorDescription(uint8_t errorCode); int CalculateMAC(mf4Session *session, MACType_t mtype, uint8_t blockNum, uint8_t blockCount, uint8_t *data, int datalen, uint8_t *mac, bool verbose); -int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool verbose); +int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool dropFieldIfError, bool verbose, bool silentMode); int MFPWritePerso(uint8_t *keyNum, uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); diff --git a/client/mifare/mifaredefault.c b/client/mifare/mifaredefault.c new file mode 100644 index 000000000..9d3b77d4f --- /dev/null +++ b/client/mifare/mifaredefault.c @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2017 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Mifare default constants +//----------------------------------------------------------------------------- + +#include "mifaredefault.h" +#include "commonutil.h" // ARRAYLEN + +const char* g_mifare_plus_default_keys[] = { + "ffffffffffffffffffffffffffffffff", // default key + "00000000000000000000000000000000", + "a0a1a2a3a4a5a6a7a0a1a2a3a4a5a6a7", // MAD key + "b0b1b2b3b4b5b6b7b0b1b2b3b4b5b6b7", + "d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7", // NDEF key + "11111111111111111111111111111111", + "22222222222222222222222222222222", + "33333333333333333333333333333333", + "44444444444444444444444444444444", + "55555555555555555555555555555555", + "66666666666666666666666666666666", + "77777777777777777777777777777777", + "88888888888888888888888888888888", + "99999999999999999999999999999999", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "cccccccccccccccccccccccccccccccc", + "dddddddddddddddddddddddddddddddd", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "000102030405060708090a0b0c0d0e0f", + "0102030405060708090a0b0c0d0e0f10", + "00010203040506070809101112131415", + "01020304050607080910111213141516" +}; +size_t g_mifare_plus_default_keys_len = ARRAYLEN(g_mifare_plus_default_keys); \ No newline at end of file diff --git a/client/mifare/mifaredefault.h b/client/mifare/mifaredefault.h index 7232510a0..fdccfe22b 100644 --- a/client/mifare/mifaredefault.h +++ b/client/mifare/mifaredefault.h @@ -44,4 +44,7 @@ static const uint8_t g_mifare_ndef_key[] = {0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}; static const uint8_t g_mifarep_mad_key[] = {0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7}; static const uint8_t g_mifarep_ndef_key[] = {0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}; +extern const char* g_mifare_plus_default_keys[]; +extern size_t g_mifare_plus_default_keys_len; + #endif diff --git a/client/util.c b/client/util.c index 58dce8eca..887f52857 100644 --- a/client/util.c +++ b/client/util.c @@ -394,6 +394,46 @@ void print_blocks(uint32_t *data, size_t len) { } } +int hex_to_bytes(const char *hexValue, uint8_t *bytesValue, size_t maxBytesValueLen) { + char buf[4] = {0}; + int indx = 0; + int bytesValueLen = 0; + while (hexValue[indx]) { + if (hexValue[indx] == '\t' || hexValue[indx] == ' ') { + indx++; + continue; + } + + if (isxdigit(hexValue[indx])) { + buf[strlen(buf)] = hexValue[indx]; + } else { + // if we have symbols other than spaces and hex + return -1; + } + + if (maxBytesValueLen && bytesValueLen >= maxBytesValueLen) { + // if we dont have space in buffer and have symbols to translate + return -2; + } + + if (strlen(buf) >= 2) { + uint32_t temp = 0; + sscanf(buf, "%x", &temp); + bytesValue[bytesValueLen] = (uint8_t)(temp & 0xff); + memset(buf, 0, sizeof(buf)); + bytesValueLen++; + } + + indx++; + } + + if (strlen(buf) > 0) + //error when not completed hex bytes + return -3; + + return bytesValueLen; +} + // takes a number (uint64_t) and creates a binarray in dest. void num_to_bytebits(uint64_t n, size_t len, uint8_t *dest) { while (len--) { diff --git a/client/util.h b/client/util.h index d549458c4..afa12811b 100644 --- a/client/util.h +++ b/client/util.h @@ -55,6 +55,7 @@ char *sprint_ascii_ex(const uint8_t *data, const size_t len, const size_t min_st void print_blocks(uint32_t *data, size_t len); +int hex_to_bytes(const char *hexValue, uint8_t *bytesValue, size_t maxBytesValueLen); void num_to_bytebits(uint64_t n, size_t len, uint8_t *dest); void num_to_bytebitsLSBF(uint64_t n, size_t len, uint8_t *dest); uint8_t *SwapEndian64(const uint8_t *src, const size_t len, const uint8_t blockSize); diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 892ec533f..e775b3413 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -590,6 +590,8 @@ typedef struct { #define PM3_EWRONGANSVER -16 // Memory out-of-bounds error client/pm3: error when a read/write is outside the expected array #define PM3_EOUTOFBOUND -17 +// exchange with card error client/pm3: error when cant get answer from card or got an incorrect answer +#define PM3_ECARDEXCHANGE -18 // No data pm3: no data available, no host frame available (not really an error) #define PM3_ENODATA -98 // Quit program client: reserved, order to quit the program