From fdd9395d1a0f331f9cc74d6cdd6dd71447524e6c Mon Sep 17 00:00:00 2001 From: Oleg Moiseenko <807634+merlokk@users.noreply.github.com> Date: Tue, 19 Mar 2019 08:51:10 +0200 Subject: [PATCH] Ndef and MAD (#801) * move mifare stuff to separate folder * add mad and ndef --- CHANGELOG.md | 2 + client/Makefile | 8 +- client/cmdhf14a.c | 2 +- client/cmdhflist.c | 4 +- client/cmdhfmf.c | 206 ++- client/cmdhfmf.h | 3 +- client/cmdhfmfp.c | 324 ++-- client/cmdhfmfp.h | 2 +- client/mifare/mad.c | 256 ++++ client/mifare/mad.h | 29 + client/{ => mifare}/mfkey.c | 0 client/{ => mifare}/mfkey.h | 0 client/mifare/mifare4.c | 469 ++++++ client/{ => mifare}/mifare4.h | 9 + client/{ => mifare}/mifaredefault.h | 5 + client/{ => mifare}/mifarehost.c | 2201 ++++++++++++++------------- client/{ => mifare}/mifarehost.h | 138 +- client/mifare/ndef.c | 359 +++++ client/mifare/ndef.h | 62 + client/mifare4.c | 311 ---- client/obj/mifare/.dummy | 0 client/scripting.c | 2 +- common/crc.c | 24 + common/crc.h | 5 + include/mifare.h | 6 + tools/mfkey/Makefile | 2 +- tools/mfkey/mfkey32.c | 2 +- 27 files changed, 2843 insertions(+), 1588 deletions(-) create mode 100644 client/mifare/mad.c create mode 100644 client/mifare/mad.h rename client/{ => mifare}/mfkey.c (100%) rename client/{ => mifare}/mfkey.h (100%) create mode 100644 client/mifare/mifare4.c rename client/{ => mifare}/mifare4.h (65%) rename client/{ => mifare}/mifaredefault.h (70%) rename client/{ => mifare}/mifarehost.c (96%) rename client/{ => mifare}/mifarehost.h (96%) create mode 100644 client/mifare/ndef.c create mode 100644 client/mifare/ndef.h delete mode 100644 client/mifare4.c create mode 100644 client/obj/mifare/.dummy diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bf52c5..50c33d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added `hf 15 snoop` (piwi) - Added support for standard USB Smartcard Readers (piwi) - Added `hf plot` (piwi) +- Added `hf mfp mad` `hf mf mad` parsing MAD1 and MAD2 (Merlok) +- Added `hf mfp ndef` `hf mf ndef` parsing NDEF records (Merlok) ## [v3.1.0][2018-10-10] diff --git a/client/Makefile b/client/Makefile index 5fc1d226..2b5e9ae6 100644 --- a/client/Makefile +++ b/client/Makefile @@ -134,15 +134,17 @@ CMDSRCS = $(SRC_SMARTCARD) \ fido/cose.c \ fido/cbortools.c \ fido/fidocore.c \ - mfkey.c \ + mifare/mfkey.c \ loclass/cipher.c \ loclass/cipherutils.c \ loclass/ikeys.c \ loclass/elite_crack.c\ loclass/fileutils.c\ whereami.c\ - mifarehost.c\ - mifare4.c\ + mifare/mifarehost.c\ + mifare/mifare4.c\ + mifare/mad.c \ + mifare/ndef.c \ parity.c\ crc.c \ crc16.c \ diff --git a/client/cmdhf14a.c b/client/cmdhf14a.c index 9611a2d1..90032022 100644 --- a/client/cmdhf14a.c +++ b/client/cmdhf14a.c @@ -27,7 +27,7 @@ #include "cmdmain.h" #include "mifare.h" #include "cmdhfmfu.h" -#include "mifarehost.h" +#include "mifare/mifarehost.h" #include "cliparser/cliparser.h" #include "emv/apduinfo.h" #include "emv/emvcore.h" diff --git a/client/cmdhflist.c b/client/cmdhflist.c index db3e5287..1aa501e6 100644 --- a/client/cmdhflist.c +++ b/client/cmdhflist.c @@ -24,8 +24,8 @@ #include "parity.h" #include "protocols.h" #include "crapto1/crapto1.h" -#include "mifarehost.h" -#include "mifaredefault.h" +#include "mifare/mifarehost.h" +#include "mifare/mifaredefault.h" #include "usb_cmd.h" #include "pcsc.h" diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index 5bf3324a..1c006fbf 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -23,13 +23,16 @@ #include "util_posix.h" #include "usb_cmd.h" #include "ui.h" -#include "mifarehost.h" +#include "mifare/mifarehost.h" #include "mifare.h" -#include "mfkey.h" +#include "mifare/mfkey.h" #include "hardnested/hardnested_bf_core.h" #include "cliparser/cliparser.h" #include "cmdhf14a.h" -#include "mifare4.h" +#include "mifare/mifare4.h" +#include "mifare/mad.h" +#include "mifare/ndef.h" +#include "emv/dump.h" #define NESTED_SECTOR_RETRY 10 // how often we try mfested() until we give up @@ -2712,6 +2715,201 @@ int CmdHF14AMfAuth4(const char *cmd) { return MifareAuth4(NULL, keyn, key, true, false, true); } +// https://www.nxp.com/docs/en/application-note/AN10787.pdf +int CmdHF14AMfMAD(const char *cmd) { + + CLIParserInit("hf mf mad", + "Checks and prints Mifare Application Directory (MAD)", + "Usage:\n\thf mf mad -> shows MAD if exists\n" + "\thf mf mad -a 03e1 -k ffffffffffff -b -> shows NDEF data if exists. read card with custom key and key B\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("vV", "verbose", "show technical data"), + arg_str0("aA", "aid", "print all sectors with aid", NULL), + arg_str0("kK", "key", "key for printing sectors", NULL), + arg_lit0("bB", "keyb", "use key B for access printing sectors (by default: key A)"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + bool verbose = arg_get_lit(1); + uint8_t aid[2] = {0}; + int aidlen; + CLIGetHexWithReturn(2, aid, &aidlen); + uint8_t key[6] = {0}; + int keylen; + CLIGetHexWithReturn(3, key, &keylen); + bool keyB = arg_get_lit(4); + + CLIParserFree(); + + if (aidlen != 2 && keylen > 0) { + PrintAndLogEx(WARNING, "do not need a key without aid."); + } + + uint8_t sector0[16 * 4] = {0}; + uint8_t sector10[16 * 4] = {0}; + if (mfReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifare_mad_key, sector0)) { + PrintAndLogEx(ERR, "read sector 0 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + + if (verbose) { + for (int i = 0; i < 4; i ++) + PrintAndLogEx(NORMAL, "[%d] %s", i, sprint_hex(§or0[i * 16], 16)); + } + + bool haveMAD2 = false; + MAD1DecodeAndPrint(sector0, verbose, &haveMAD2); + + if (haveMAD2) { + if (mfReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifare_mad_key, sector10)) { + PrintAndLogEx(ERR, "read sector 0x10 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + + MAD2DecodeAndPrint(sector10, verbose); + } + + if (aidlen == 2) { + uint16_t aaid = (aid[0] << 8) + aid[1]; + PrintAndLogEx(NORMAL, "\n-------------- AID 0x%04x ---------------", aaid); + + uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; + size_t madlen = 0; + if (MADDecode(sector0, sector10, mad, &madlen)) { + PrintAndLogEx(ERR, "can't decode mad."); + return 10; + } + + uint8_t akey[6] = {0}; + memcpy(akey, g_mifare_ndef_key, 6); + if (keylen == 6) { + memcpy(akey, key, 6); + } + + for (int i = 0; i < madlen; i++) { + if (aaid == mad[i]) { + uint8_t vsector[16 * 4] = {0}; + if (mfReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector)) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(ERR, "read sector %d error.", i + 1); + return 2; + } + + for (int j = 0; j < (verbose ? 4 : 3); j ++) + PrintAndLogEx(NORMAL, " [%03d] %s", (i + 1) * 4 + j, sprint_hex(&vsector[j * 16], 16)); + } + } + } + + return 0; +} + +int CmdHFMFNDEF(const char *cmd) { + + CLIParserInit("hf mf ndef", + "Prints NFC Data Exchange Format (NDEF)", + "Usage:\n\thf mf ndef -> shows NDEF data\n" + "\thf mf ndef -a 03e1 -k ffffffffffff -b -> shows NDEF data with custom AID, key and with key B\n"); + + void *argtable[] = { + arg_param_begin, + arg_litn("vV", "verbose", 0, 2, "show technical data"), + arg_str0("aA", "aid", "replace default aid for NDEF", NULL), + arg_str0("kK", "key", "replace default key for NDEF", NULL), + arg_lit0("bB", "keyb", "use key B for access sectors (by default: key A)"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool verbose = arg_get_lit(1); + bool verbose2 = arg_get_lit(1) > 1; + uint8_t aid[2] = {0}; + int aidlen; + CLIGetHexWithReturn(2, aid, &aidlen); + uint8_t key[6] = {0}; + int keylen; + CLIGetHexWithReturn(3, key, &keylen); + bool keyB = arg_get_lit(4); + + CLIParserFree(); + + uint16_t ndefAID = 0x03e1; + if (aidlen == 2) + ndefAID = (aid[0] << 8) + aid[1]; + + uint8_t ndefkey[6] = {0}; + memcpy(ndefkey, g_mifare_ndef_key, 6); + if (keylen == 6) { + memcpy(ndefkey, key, 6); + } + + uint8_t sector0[16 * 4] = {0}; + uint8_t sector10[16 * 4] = {0}; + uint8_t data[4096] = {0}; + int datalen = 0; + + PrintAndLogEx(NORMAL, ""); + + if (mfReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifare_mad_key, sector0)) { + PrintAndLogEx(ERR, "read sector 0 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + + bool haveMAD2 = false; + int res = MADCheck(sector0, NULL, verbose, &haveMAD2); + if (res) { + PrintAndLogEx(ERR, "MAD error %d.", res); + return res; + } + + if (haveMAD2) { + if (mfReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifare_mad_key, sector10)) { + PrintAndLogEx(ERR, "read sector 0x10 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + } + + uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; + size_t madlen = 0; + if (MADDecode(sector0, (haveMAD2 ? sector10 : NULL), mad, &madlen)) { + PrintAndLogEx(ERR, "can't decode mad."); + return 10; + } + + printf("data reading:"); + for (int i = 0; i < madlen; i++) { + if (ndefAID == mad[i]) { + uint8_t vsector[16 * 4] = {0}; + if (mfReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, ndefkey, vsector)) { + PrintAndLogEx(ERR, "read sector %d error.", i + 1); + return 2; + } + + memcpy(&data[datalen], vsector, 16 * 3); + datalen += 16 * 3; + + printf("."); + } + } + printf(" OK\n"); + + if (!datalen) { + PrintAndLogEx(ERR, "no NDEF data."); + return 11; + } + + if (verbose2) { + PrintAndLogEx(NORMAL, "NDEF data:"); + dump_buffer(data, datalen, stdout, 1); + } + + NDEFDecodeAndPrint(data, datalen, verbose); + + return 0; +} + static command_t CommandTable[] = { {"help", CmdHelp, 1, "This help"}, @@ -2743,6 +2941,8 @@ static command_t CommandTable[] = {"cload", CmdHF14AMfCLoad, 0, "Load dump into magic Chinese card"}, {"csave", CmdHF14AMfCSave, 0, "Save dump from magic Chinese card into file or emulator"}, {"decrypt", CmdDecryptTraceCmds, 1, "[nt] [ar_enc] [at_enc] [data] - to decrypt snoop or trace"}, + {"mad", CmdHF14AMfMAD, 0, "Checks and prints MAD"}, + {"ndef", CmdHFMFNDEF, 0, "Prints NDEF records from card"}, {NULL, NULL, 0, NULL} }; diff --git a/client/cmdhfmf.h b/client/cmdhfmf.h index 3bd3e95a..746fcbc1 100644 --- a/client/cmdhfmf.h +++ b/client/cmdhfmf.h @@ -11,8 +11,9 @@ #ifndef CMDHFMF_H__ #define CMDHFMF_H__ -#include "mifaredefault.h" +#include "mifare/mifaredefault.h" +extern int CmdHFMF(const char *Cmd); extern int CmdHFMF(const char *Cmd); extern int CmdHF14AMfDbg(const char* cmd); diff --git a/client/cmdhfmfp.c b/client/cmdhfmfp.c index 1e5bbe1a..5255e5a8 100644 --- a/client/cmdhfmfp.c +++ b/client/cmdhfmfp.c @@ -22,109 +22,17 @@ #include "ui.h" #include "cmdhf14a.h" #include "mifare.h" -#include "mifare4.h" +#include "mifare/mifare4.h" +#include "mifare/mad.h" +#include "mifare/ndef.h" #include "cliparser/cliparser.h" #include "crypto/libpcrypto.h" +#include "emv/dump.h" static const uint8_t DefaultKey[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; -typedef struct { - uint8_t Code; - const char *Description; -} PlusErrorsElm; - -static const PlusErrorsElm PlusErrors[] = { - {0xFF, ""}, - {0x00, "Transfer cannot be granted within the current authentication."}, - {0x06, "Access Conditions not fulfilled. Block does not exist, block is not a value block."}, - {0x07, "Too many read or write commands in the session or in the transaction."}, - {0x08, "Invalid MAC in command or response"}, - {0x09, "Block Number is not valid"}, - {0x0a, "Invalid block number, not existing block number"}, - {0x0b, "The current command code not available at the current card state."}, - {0x0c, "Length error"}, - {0x0f, "General Manipulation Error. Failure in the operation of the PICC (cannot write to the data block), etc."}, - {0x90, "OK"}, -}; -int PlusErrorsLen = sizeof(PlusErrors) / sizeof(PlusErrorsElm); - -const char * GetErrorDescription(uint8_t errorCode) { - for(int i = 0; i < PlusErrorsLen; i++) - if (errorCode == PlusErrors[i].Code) - return PlusErrors[i].Description; - - return PlusErrors[0].Description; -} - static int CmdHelp(const char *Cmd); -static bool VerboseMode = false; -void SetVerboseMode(bool verbose) { - VerboseMode = verbose; -} - -int intExchangeRAW14aPlus(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { - if(VerboseMode) - PrintAndLog(">>> %s", sprint_hex(datain, datainlen)); - - int res = ExchangeRAW14a(datain, datainlen, activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); - - if(VerboseMode) - PrintAndLog("<<< %s", sprint_hex(dataout, *dataoutlen)); - - return res; -} - -int MFPWritePerso(uint8_t *keyNum, uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { - uint8_t rcmd[3 + 16] = {0xa8, keyNum[1], keyNum[0], 0x00}; - memmove(&rcmd[3], key, 16); - - return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); -} - -int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { - uint8_t rcmd[1] = {0xaa}; - - return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); -} - -int MFPReadBlock(mf4Session *session, bool plain, uint8_t blockNum, uint8_t blockCount, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac) { - uint8_t rcmd[4 + 8] = {(plain?(0x37):(0x33)), blockNum, 0x00, blockCount}; - if (!plain && session) - CalculateMAC(session, mtypReadCmd, blockNum, blockCount, rcmd, 4, &rcmd[4], VerboseMode); - - int res = intExchangeRAW14aPlus(rcmd, plain?4:sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); - if(res) - return res; - - if (session) - session->R_Ctr++; - - if(session && mac && *dataoutlen > 11) - CalculateMAC(session, mtypReadResp, blockNum, blockCount, dataout, *dataoutlen - 8 - 2, mac, VerboseMode); - - return 0; -} - -int MFPWriteBlock(mf4Session *session, uint8_t blockNum, uint8_t *data, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac) { - uint8_t rcmd[1 + 2 + 16 + 8] = {0xA3, blockNum, 0x00}; - memmove(&rcmd[3], data, 16); - if (session) - CalculateMAC(session, mtypWriteCmd, blockNum, 1, rcmd, 19, &rcmd[19], VerboseMode); - - int res = intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); - if(res) - return res; - - if (session) - session->W_Ctr++; - - if(session && mac && *dataoutlen > 3) - CalculateMAC(session, mtypWriteResp, blockNum, 1, dataout, *dataoutlen, mac, VerboseMode); - - return 0; -} - int CmdHFMFPInfo(const char *cmd) { if (cmd && strlen(cmd) > 0) @@ -229,7 +137,7 @@ int CmdHFMFPWritePerso(const char *cmd) { CLIGetHexWithReturn(3, key, &keyLen); CLIParserFree(); - SetVerboseMode(verbose); + mfpSetVerboseMode(verbose); if (!keyLen) { memmove(key, DefaultKey, 16); @@ -260,7 +168,7 @@ int CmdHFMFPWritePerso(const char *cmd) { } if (data[0] != 0x90) { - PrintAndLog("Command error: %02x %s", data[0], GetErrorDescription(data[0])); + PrintAndLog("Command error: %02x %s", data[0], mfpGetErrorDescription(data[0])); return 1; } PrintAndLog("Write OK."); @@ -304,7 +212,7 @@ int CmdHFMFPInitPerso(const char *cmd) { if (!keyLen) memmove(key, DefaultKey, 16); - SetVerboseMode(verbose2); + mfpSetVerboseMode(verbose2); for (uint16_t sn = 0x4000; sn < 0x4050; sn++) { keyNum[0] = sn >> 8; keyNum[1] = sn & 0xff; @@ -319,7 +227,7 @@ int CmdHFMFPInitPerso(const char *cmd) { } } - SetVerboseMode(verbose); + mfpSetVerboseMode(verbose); for (int i = 0; i < sizeof(CardAddresses) / 2; i++) { keyNum[0] = CardAddresses[i] >> 8; keyNum[1] = CardAddresses[i] & 0xff; @@ -360,7 +268,7 @@ int CmdHFMFPCommitPerso(const char *cmd) { bool verbose = arg_get_lit(1); CLIParserFree(); - SetVerboseMode(verbose); + mfpSetVerboseMode(verbose); uint8_t data[250] = {0}; int datalen = 0; @@ -377,7 +285,7 @@ int CmdHFMFPCommitPerso(const char *cmd) { } if (data[0] != 0x90) { - PrintAndLog("Command error: %02x %s", data[0], GetErrorDescription(data[0])); + PrintAndLog("Command error: %02x %s", data[0], mfpGetErrorDescription(data[0])); return 1; } PrintAndLog("Switch level OK."); @@ -453,7 +361,7 @@ int CmdHFMFPRdbl(const char *cmd) { CLIGetHexWithReturn(6, key, &keylen); CLIParserFree(); - SetVerboseMode(verbose); + mfpSetVerboseMode(verbose); if (!keylen) { memmove(key, DefaultKey, 16); @@ -504,7 +412,7 @@ int CmdHFMFPRdbl(const char *cmd) { } if (datalen && data[0] != 0x90) { - PrintAndLog("Card read error: %02x %s", data[0], GetErrorDescription(data[0])); + PrintAndLog("Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0])); return 6; } @@ -563,7 +471,7 @@ int CmdHFMFPRdsc(const char *cmd) { CLIGetHexWithReturn(5, key, &keylen); CLIParserFree(); - SetVerboseMode(verbose); + mfpSetVerboseMode(verbose); if (!keylen) { memmove(key, DefaultKey, 16); @@ -605,7 +513,7 @@ int CmdHFMFPRdsc(const char *cmd) { } if (datalen && data[0] != 0x90) { - PrintAndLog("Card read error: %02x %s", data[0], GetErrorDescription(data[0])); + PrintAndLog("Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0])); DropField(); return 6; } @@ -661,7 +569,7 @@ int CmdHFMFPWrbl(const char *cmd) { CLIGetHexWithReturn(5, key, &keylen); CLIParserFree(); - SetVerboseMode(verbose); + mfpSetVerboseMode(verbose); if (!keylen) { memmove(key, DefaultKey, 16); @@ -714,7 +622,7 @@ int CmdHFMFPWrbl(const char *cmd) { } if (datalen && data[0] != 0x90) { - PrintAndLog("Card write error: %02x %s", data[0], GetErrorDescription(data[0])); + PrintAndLog("Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0])); DropField(); return 6; } @@ -733,6 +641,204 @@ int CmdHFMFPWrbl(const char *cmd) { return 0; } +int CmdHFMFPMAD(const char *cmd) { + + CLIParserInit("hf mfp mad", + "Checks and prints Mifare Application Directory (MAD)", + "Usage:\n\thf mfp mad -> shows MAD if exists\n" + "\thf mfp mad -a 03e1 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> shows NDEF data if exists\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("vV", "verbose", "show technical data"), + arg_str0("aA", "aid", "print all sectors with aid", NULL), + arg_str0("kK", "key", "key for printing sectors", NULL), + arg_lit0("bB", "keyb", "use key B for access printing sectors (by default: key A)"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool verbose = arg_get_lit(1); + uint8_t aid[2] = {0}; + int aidlen; + CLIGetHexWithReturn(2, aid, &aidlen); + uint8_t key[16] = {0}; + int keylen; + CLIGetHexWithReturn(3, key, &keylen); + bool keyB = arg_get_lit(4); + + CLIParserFree(); + + if (aidlen != 2 && keylen > 0) { + PrintAndLogEx(WARNING, "do not need a key without aid."); + } + + uint8_t sector0[16 * 4] = {0}; + uint8_t sector10[16 * 4] = {0}; + + if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(ERR, "read sector 0 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + + if (verbose) { + for (int i = 0; i < 4; i ++) + PrintAndLogEx(NORMAL, "[%d] %s", i, sprint_hex(§or0[i * 16], 16)); + } + + bool haveMAD2 = false; + MAD1DecodeAndPrint(sector0, verbose, &haveMAD2); + + if (haveMAD2) { + if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(ERR, "read sector 0x10 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + + MAD2DecodeAndPrint(sector10, verbose); + } + + if (aidlen == 2) { + uint16_t aaid = (aid[0] << 8) + aid[1]; + PrintAndLogEx(NORMAL, "\n-------------- AID 0x%04x ---------------", aaid); + + uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; + size_t madlen = 0; + if (MADDecode(sector0, sector10, mad, &madlen)) { + PrintAndLogEx(ERR, "can't decode mad."); + return 10; + } + + uint8_t akey[16] = {0}; + memcpy(akey, g_mifarep_ndef_key, 16); + if (keylen == 16) { + memcpy(akey, key, 16); + } + + for (int i = 0; i < madlen; i++) { + if (aaid == mad[i]) { + uint8_t vsector[16 * 4] = {0}; + if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector, false)) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(ERR, "read sector %d error.", i + 1); + return 2; + } + + for (int j = 0; j < (verbose ? 4 : 3); j ++) + PrintAndLogEx(NORMAL, " [%03d] %s", (i + 1) * 4 + j, sprint_hex(&vsector[j * 16], 16)); + } + } + } + + return 0; +} + +int CmdHFMFPNDEF(const char *cmd) { + + CLIParserInit("hf mfp ndef", + "Prints NFC Data Exchange Format (NDEF)", + "Usage:\n\thf mfp ndef -> shows NDEF data\n" + "\thf mfp ndef -a 03e1 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> shows NDEF data with custom AID and key\n"); + + void *argtable[] = { + arg_param_begin, + arg_litn("vV", "verbose", 0, 2, "show technical data"), + arg_str0("aA", "aid", "replace default aid for NDEF", NULL), + arg_str0("kK", "key", "replace default key for NDEF", NULL), + arg_lit0("bB", "keyb", "use key B for access sectors (by default: key A)"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool verbose = arg_get_lit(1); + bool verbose2 = arg_get_lit(1) > 1; + uint8_t aid[2] = {0}; + int aidlen; + CLIGetHexWithReturn(2, aid, &aidlen); + uint8_t key[16] = {0}; + int keylen; + CLIGetHexWithReturn(3, key, &keylen); + bool keyB = arg_get_lit(4); + + CLIParserFree(); + + uint16_t ndefAID = 0x03e1; + if (aidlen == 2) + ndefAID = (aid[0] << 8) + aid[1]; + + uint8_t ndefkey[16] = {0}; + memcpy(ndefkey, g_mifarep_ndef_key, 16); + if (keylen == 16) { + memcpy(ndefkey, key, 16); + } + + uint8_t sector0[16 * 4] = {0}; + uint8_t sector10[16 * 4] = {0}; + uint8_t data[4096] = {0}; + int datalen = 0; + + PrintAndLogEx(NORMAL, ""); + + if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) { + PrintAndLogEx(ERR, "read sector 0 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + + bool haveMAD2 = false; + int res = MADCheck(sector0, NULL, verbose, &haveMAD2); + if (res) { + PrintAndLogEx(ERR, "MAD error %d.", res); + return res; + } + + if (haveMAD2) { + if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) { + PrintAndLogEx(ERR, "read sector 0x10 error. card don't have MAD or don't have MAD on default keys."); + return 2; + } + } + + uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; + size_t madlen = 0; + if (MADDecode(sector0, (haveMAD2 ? sector10 : NULL), mad, &madlen)) { + PrintAndLogEx(ERR, "can't decode mad."); + return 10; + } + + printf("data reading:"); + for (int i = 0; i < madlen; i++) { + if (ndefAID == mad[i]) { + uint8_t vsector[16 * 4] = {0}; + if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, ndefkey, vsector, false)) { + PrintAndLogEx(ERR, "read sector %d error.", i + 1); + return 2; + } + + memcpy(&data[datalen], vsector, 16 * 3); + datalen += 16 * 3; + + printf("."); + } + } + printf(" OK\n"); + + if (!datalen) { + PrintAndLogEx(ERR, "no NDEF data."); + return 11; + } + + if (verbose2) { + PrintAndLogEx(NORMAL, "NDEF data:"); + dump_buffer(data, datalen, stdout, 1); + } + + NDEFDecodeAndPrint(data, datalen, verbose); + + return 0; +} + static command_t CommandTable[] = { {"help", CmdHelp, 1, "This help"}, @@ -744,6 +850,8 @@ static command_t CommandTable[] = {"rdbl", CmdHFMFPRdbl, 0, "Read blocks"}, {"rdsc", CmdHFMFPRdsc, 0, "Read sectors"}, {"wrbl", CmdHFMFPWrbl, 0, "Write blocks"}, + {"mad", CmdHFMFPMAD, 0, "Checks and prints MAD"}, + {"ndef", CmdHFMFPNDEF, 0, "Prints NDEF records from card"}, {NULL, NULL, 0, NULL} }; diff --git a/client/cmdhfmfp.h b/client/cmdhfmfp.h index b1ac7c34..a5cacb51 100644 --- a/client/cmdhfmfp.h +++ b/client/cmdhfmfp.h @@ -10,7 +10,7 @@ #ifndef CMDHFMFP_H__ #define CMDHFMFP_H__ -#include "mifaredefault.h" +#include "mifare/mifaredefault.h" extern int CmdHFMFP(const char *Cmd); diff --git a/client/mifare/mad.c b/client/mifare/mad.c new file mode 100644 index 00000000..4c3d2102 --- /dev/null +++ b/client/mifare/mad.c @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2019 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 Application Directory (MAD) functions +//----------------------------------------------------------------------------- + +#include "mad.h" +#include "ui.h" +#include "crc.h" +#include "util.h" + +// https://www.nxp.com/docs/en/application-note/AN10787.pdf +static madAIDDescr madKnownAIDs[] = { + {0x0000, "free"}, + {0x0001, "defect, e.g. access keys are destroyed or unknown"}, + {0x0002, "reserved"}, + {0x0003, "contains additional directory info"}, + {0x0004, "contains card holder information in ASCII format."}, + {0x0005, "not applicable (above memory size)"}, + + {0x03e1, "NDEF"}, +}; + +static madAIDDescr madKnownClusterCodes[] = { + {0x00, "cluster: card administration"}, + {0x01, "cluster: miscellaneous applications"}, + {0x02, "cluster: miscellaneous applications"}, + {0x03, "cluster: miscellaneous applications"}, + {0x04, "cluster: miscellaneous applications"}, + {0x05, "cluster: miscellaneous applications"}, + {0x06, "cluster: miscellaneous applications"}, + {0x07, "cluster: miscellaneous applications"}, + {0x08, "cluster: airlines"}, + {0x09, "cluster: ferry traffic"}, + {0x10, "cluster: railway services"}, + {0x11, "cluster: miscellaneous applications"}, + {0x12, "cluster: transport"}, + {0x14, "cluster: security solutions"}, + {0x18, "cluster: city traffic"}, + {0x19, "cluster: Czech Railways"}, + {0x20, "cluster: bus services"}, + {0x21, "cluster: multi modal transit"}, + {0x28, "cluster: taxi"}, + {0x30, "cluster: road toll"}, + {0x31, "cluster: generic transport"}, + {0x38, "cluster: company services"}, + {0x40, "cluster: city card services"}, + {0x47, "cluster: access control & security"}, + {0x48, "cluster: access control & security"}, + {0x49, "cluster: VIGIK"}, + {0x4A, "cluster: Ministry of Defence, Netherlands"}, + {0x4B, "cluster: Bosch Telecom, Germany"}, + {0x4C, "cluster: European Union Institutions"}, + {0x50, "cluster: ski ticketing"}, + {0x51, "cluster: access control & security"}, + {0x52, "cluster: access control & security"}, + {0x53, "cluster: access control & security"}, + {0x54, "cluster: access control & security"}, + {0x55, "cluster: SOAA standard for offline access standard"}, + {0x56, "cluster: access control & security"}, + {0x58, "cluster: academic services"}, + {0x60, "cluster: food"}, + {0x68, "cluster: non-food trade"}, + {0x70, "cluster: hotel"}, + {0x71, "cluster: loyalty"}, + {0x75, "cluster: airport services"}, + {0x78, "cluster: car rental"}, + {0x79, "cluster: Dutch government"}, + {0x80, "cluster: administration services"}, + {0x88, "cluster: electronic purse"}, + {0x90, "cluster: television"}, + {0x91, "cluster: cruise ship"}, + {0x95, "cluster: IOPTA"}, + {0x97, "cluster: metering"}, + {0x98, "cluster: telephone"}, + {0xA0, "cluster: health services"}, + {0xA8, "cluster: warehouse"}, + {0xB0, "cluster: electronic trade"}, + {0xB8, "cluster: banking"}, + {0xC0, "cluster: entertainment & sports"}, + {0xC8, "cluster: car parking"}, + {0xC9, "cluster: fleet management"}, + {0xD0, "cluster: fuel, gasoline"}, + {0xD8, "cluster: info services"}, + {0xE0, "cluster: press"}, + {0xE1, "cluster: NFC Forum"}, + {0xE8, "cluster: computer"}, + {0xF0, "cluster: mail"}, + {0xF8, "cluster: miscellaneous applications"}, +}; + +static const char unknownAID[] = ""; + +static const char *GetAIDDescription(uint16_t AID) { + for (int i = 0; i < ARRAYLEN(madKnownAIDs); i++) + if (madKnownAIDs[i].AID == AID) + return madKnownAIDs[i].Description; + + for (int i = 0; i < ARRAYLEN(madKnownClusterCodes); i++) + if (madKnownClusterCodes[i].AID == (AID >> 8)) // high byte - cluster code + return madKnownClusterCodes[i].Description; + + return unknownAID; +} + +int madCRCCheck(uint8_t *sector, bool verbose, int MADver) { + if (MADver == 1) { + uint8_t crc = CRC8Mad(§or[16 + 1], 15 + 16); + if (crc != sector[16]) { + PrintAndLogEx(WARNING, "Wrong MAD%d CRC. Calculated: 0x%02x, from card: 0x%02x", MADver, crc, sector[16]); + return 3; + }; + } else { + uint8_t crc = CRC8Mad(§or[1], 15 + 16 + 16); + if (crc != sector[0]) { + PrintAndLogEx(WARNING, "Wrong MAD%d CRC. Calculated: 0x%02x, from card: 0x%02x", MADver, crc, sector[16]); + return 3; + }; + } + + return 0; +} + +uint16_t madGetAID(uint8_t *sector, int MADver, int sectorNo) { + if (MADver == 1) + return (sector[16 + 2 + (sectorNo - 1) * 2] << 8) + (sector[16 + 2 + (sectorNo - 1) * 2 + 1]); + else + return (sector[2 + (sectorNo - 1) * 2] << 8) + (sector[2 + (sectorNo - 1) * 2 + 1]); +} + +int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2) { + int res = 0; + + if (!sector0) + return 1; + + uint8_t GPB = sector0[3 * 16 + 9]; + if (verbose) + PrintAndLogEx(NORMAL, "GPB: 0x%02x", GPB); + + // DA (MAD available) + if (!(GPB & 0x80)) { + PrintAndLogEx(ERR, "DA=0! MAD not available."); + return 1; + } + + // MA (multi-application card) + if (verbose) { + if (GPB & 0x40) + PrintAndLogEx(NORMAL, "Multi application card."); + else + PrintAndLogEx(NORMAL, "Single application card."); + } + + uint8_t MADVer = GPB & 0x03; + if (verbose) + PrintAndLogEx(NORMAL, "MAD version: %d", MADVer); + + // MAD version + if ((MADVer != 0x01) && (MADVer != 0x02)) { + PrintAndLogEx(ERR, "Wrong MAD version: 0x%02x", MADVer); + return 2; + }; + + if (haveMAD2) + *haveMAD2 = (MADVer == 2); + + res = madCRCCheck(sector0, true, 1); + + if (verbose && !res) + PrintAndLogEx(NORMAL, "CRC8-MAD1 OK."); + + if (MADVer == 2 && sector10) { + int res2 = madCRCCheck(sector10, true, 2); + if (!res) + res = res2; + + if (verbose & !res2) + PrintAndLogEx(NORMAL, "CRC8-MAD2 OK."); + } + + return res; +} + +int MADDecode(uint8_t *sector0, uint8_t *sector10, uint16_t *mad, size_t *madlen) { + *madlen = 0; + bool haveMAD2 = false; + MADCheck(sector0, sector10, false, &haveMAD2); + + for (int i = 1; i < 16; i++) { + mad[*madlen] = madGetAID(sector0, 1, i); + (*madlen)++; + } + + if (haveMAD2) { + // mad2 sector (0x10 == 16dec) here + mad[*madlen] = 0x0005; + (*madlen)++; + + for (int i = 1; i < 24; i++) { + mad[*madlen] = madGetAID(sector10, 2, i); + (*madlen)++; + } + } + + return 0; +} + + +int MAD1DecodeAndPrint(uint8_t *sector, bool verbose, bool *haveMAD2) { + + // check MAD1 only + MADCheck(sector, NULL, verbose, haveMAD2); + + // info byte + uint8_t InfoByte = sector[16 + 1] & 0x3f; + if (InfoByte) { + PrintAndLogEx(NORMAL, "Card publisher sector: 0x%02x", InfoByte); + } else { + if (verbose) + PrintAndLogEx(NORMAL, "Card publisher sector not present."); + } + if (InfoByte == 0x10 || InfoByte >= 0x28) + PrintAndLogEx(WARNING, "Info byte error"); + + PrintAndLogEx(NORMAL, "00 MAD1"); + for (int i = 1; i < 16; i++) { + uint16_t AID = madGetAID(sector, 1, i); + PrintAndLogEx(NORMAL, "%02d [%04X] %s", i, AID, GetAIDDescription(AID)); + }; + + return 0; +}; + +int MAD2DecodeAndPrint(uint8_t *sector, bool verbose) { + PrintAndLogEx(NORMAL, "16 MAD2"); + + int res = madCRCCheck(sector, true, 2); + + if (verbose && !res) + PrintAndLogEx(NORMAL, "CRC8-MAD2 OK."); + + uint8_t InfoByte = sector[1] & 0x3f; + PrintAndLogEx(NORMAL, "MAD2 Card publisher sector: 0x%02x", InfoByte); + + for (int i = 1; i < 8 + 8 + 7 + 1; i++) { + uint16_t AID = madGetAID(sector, 2, i); + PrintAndLogEx(NORMAL, "%02d [%04X] %s", i + 16, AID, GetAIDDescription(AID)); + }; + + return 0; +}; diff --git a/client/mifare/mad.h b/client/mifare/mad.h new file mode 100644 index 00000000..89fcd795 --- /dev/null +++ b/client/mifare/mad.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2019 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 Application Directory (MAD) functions +//----------------------------------------------------------------------------- + +#ifndef _MAD_H_ +#define _MAD_H_ + +#include +#include +#include + +typedef struct { + uint16_t AID; + const char *Description; +} madAIDDescr; + +extern int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2); +extern int MADDecode(uint8_t *sector0, uint8_t *sector10, uint16_t *mad, size_t *madlen); +extern int MAD1DecodeAndPrint(uint8_t *sector, bool verbose, bool *haveMAD2); +extern int MAD2DecodeAndPrint(uint8_t *sector, bool verbose); + + +#endif // _MAD_H_ diff --git a/client/mfkey.c b/client/mifare/mfkey.c similarity index 100% rename from client/mfkey.c rename to client/mifare/mfkey.c diff --git a/client/mfkey.h b/client/mifare/mfkey.h similarity index 100% rename from client/mfkey.h rename to client/mifare/mfkey.h diff --git a/client/mifare/mifare4.c b/client/mifare/mifare4.c new file mode 100644 index 00000000..dfb137d8 --- /dev/null +++ b/client/mifare/mifare4.c @@ -0,0 +1,469 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// Copyright (C) 2018 drHatson +// +// 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. +//----------------------------------------------------------------------------- +// iso14443-4 mifare commands +//----------------------------------------------------------------------------- + +#include "mifare4.h" +#include +#include +#include "cmdhf14a.h" +#include "util.h" +#include "ui.h" +#include "crypto/libpcrypto.h" + +static bool VerboseMode = false; +void mfpSetVerboseMode(bool verbose) { + VerboseMode = verbose; +} + +typedef struct { + uint8_t Code; + const char *Description; +} PlusErrorsElm; + +static const PlusErrorsElm PlusErrors[] = { + {0xFF, ""}, + {0x00, "Transfer cannot be granted within the current authentication."}, + {0x06, "Access Conditions not fulfilled. Block does not exist, block is not a value block."}, + {0x07, "Too many read or write commands in the session or in the transaction."}, + {0x08, "Invalid MAC in command or response"}, + {0x09, "Block Number is not valid"}, + {0x0a, "Invalid block number, not existing block number"}, + {0x0b, "The current command code not available at the current card state."}, + {0x0c, "Length error"}, + {0x0f, "General Manipulation Error. Failure in the operation of the PICC (cannot write to the data block), etc."}, + {0x90, "OK"}, +}; +int PlusErrorsLen = sizeof(PlusErrors) / sizeof(PlusErrorsElm); + +const char *mfpGetErrorDescription(uint8_t errorCode) { + for (int i = 0; i < PlusErrorsLen; i++) + if (errorCode == PlusErrors[i].Code) + return PlusErrors[i].Description; + + return PlusErrors[0].Description; +} + +AccessConditions_t MFAccessConditions[] = { + {0x00, "read AB; write AB; increment AB; decrement transfer restore AB"}, + {0x01, "read AB; decrement transfer restore AB"}, + {0x02, "read AB"}, + {0x03, "read B; write B"}, + {0x04, "read AB; writeB"}, + {0x05, "read B"}, + {0x06, "read AB; write B; increment B; decrement transfer restore AB"}, + {0x07, "none"} +}; + +AccessConditions_t MFAccessConditionsTrailer[] = { + {0x00, "read A by A; read ACCESS by A; read B by A; write B by A"}, + {0x01, "write A by A; read ACCESS by A write ACCESS by A; read B by A; write B by A"}, + {0x02, "read ACCESS by A; read B by A"}, + {0x03, "write A by B; read ACCESS by AB; write ACCESS by B; write B by B"}, + {0x04, "write A by B; read ACCESS by AB; write B by B"}, + {0x05, "read ACCESS by AB; write ACCESS by B"}, + {0x06, "read ACCESS by AB"}, + {0x07, "read ACCESS by AB"} +}; + +char *mfGetAccessConditionsDesc(uint8_t blockn, uint8_t *data) { + static char StaticNone[] = "none"; + + uint8_t data1 = ((data[1] >> 4) & 0x0f) >> blockn; + uint8_t data2 = ((data[2]) & 0x0f) >> blockn; + uint8_t data3 = ((data[2] >> 4) & 0x0f) >> blockn; + + uint8_t cond = (data1 & 0x01) << 2 | (data2 & 0x01) << 1 | (data3 & 0x01); + + if (blockn == 3) { + for (int i = 0; i < ARRAYLEN(MFAccessConditionsTrailer); i++) + if (MFAccessConditionsTrailer[i].cond == cond) { + return MFAccessConditionsTrailer[i].description; + } + } else { + for (int i = 0; i < ARRAYLEN(MFAccessConditions); i++) + if (MFAccessConditions[i].cond == cond) { + return MFAccessConditions[i].description; + } + }; + + return StaticNone; +}; + +int CalculateEncIVCommand(mf4Session *session, uint8_t *iv, bool verbose) { + memcpy(&iv[0], session->TI, 4); + memcpy(&iv[4], &session->R_Ctr, 2); + memcpy(&iv[6], &session->W_Ctr, 2); + memcpy(&iv[8], &session->R_Ctr, 2); + memcpy(&iv[10], &session->W_Ctr, 2); + memcpy(&iv[12], &session->R_Ctr, 2); + memcpy(&iv[14], &session->W_Ctr, 2); + + return 0; +} + +int CalculateEncIVResponse(mf4Session *session, uint8_t *iv, bool verbose) { + memcpy(&iv[0], &session->R_Ctr, 2); + memcpy(&iv[2], &session->W_Ctr, 2); + memcpy(&iv[4], &session->R_Ctr, 2); + memcpy(&iv[6], &session->W_Ctr, 2); + memcpy(&iv[8], &session->R_Ctr, 2); + memcpy(&iv[10], &session->W_Ctr, 2); + memcpy(&iv[12], session->TI, 4); + + return 0; +} + + +int CalculateMAC(mf4Session *session, MACType_t mtype, uint8_t blockNum, uint8_t blockCount, uint8_t *data, int datalen, uint8_t *mac, bool verbose) { + if (!session || !session->Authenticated || !mac || !data || !datalen || datalen < 1) + return 1; + + memset(mac, 0x00, 8); + + uint16_t ctr = session->R_Ctr; + switch (mtype) { + case mtypWriteCmd: + case mtypWriteResp: + ctr = session->W_Ctr; + break; + case mtypReadCmd: + case mtypReadResp: + break; + } + + uint8_t macdata[2049] = {data[0], (ctr & 0xFF), (ctr >> 8), 0}; + int macdatalen = datalen; + memcpy(&macdata[3], session->TI, 4); + + switch (mtype) { + case mtypReadCmd: + memcpy(&macdata[7], &data[1], datalen - 1); + macdatalen = datalen + 6; + break; + case mtypReadResp: + macdata[7] = blockNum; + macdata[8] = 0; + macdata[9] = blockCount; + memcpy(&macdata[10], &data[1], datalen - 1); + macdatalen = datalen + 9; + break; + case mtypWriteCmd: + memcpy(&macdata[7], &data[1], datalen - 1); + macdatalen = datalen + 6; + break; + case mtypWriteResp: + macdatalen = 1 + 6; + break; + } + + if (verbose) + PrintAndLog("MAC data[%d]: %s", macdatalen, sprint_hex(macdata, macdatalen)); + + 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) { + 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 (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); + if (res) { + PrintAndLogEx(ERR, "Exchande raw error: %d", res); + DropField(); + return 2; + } + + if (verbose) + PrintAndLogEx(INFO, "phase2: %s", sprint_hex(cmd2, 33)); + + res = ExchangeRAW14a(cmd2, sizeof(cmd2), false, true, data, sizeof(data), &datalen); + if (res) { + PrintAndLogEx(ERR, "Exchande raw error: %d", res); + DropField(); + return 4; + } + + if (verbose) + PrintAndLogEx(INFO, "Authenticated = true; + session->R_Ctr = 0; + session->W_Ctr = 0; + session->KeyNum = keyn[1] + (keyn[0] << 8); + memmove(session->RndA, RndA, 16); + memmove(session->RndB, RndB, 16); + memmove(session->Key, key, 16); + memmove(session->TI, raw, 4); + memmove(session->PICCap2, &raw[20], 6); + memmove(session->PCDCap2, &raw[26], 6); + memmove(session->Kenc, kenc, 16); + memmove(session->Kmac, kmac, 16); + } + + if (verbose) + PrintAndLogEx(INFO, "Authentication OK"); + + return 0; +} + +int intExchangeRAW14aPlus(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { + if (VerboseMode) + PrintAndLogEx(INFO, ">>> %s", sprint_hex(datain, datainlen)); + + int res = ExchangeRAW14a(datain, datainlen, activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); + + if (VerboseMode) + PrintAndLogEx(INFO, "<<< %s", sprint_hex(dataout, *dataoutlen)); + + return res; +} + +int MFPWritePerso(uint8_t *keyNum, uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { + uint8_t rcmd[3 + 16] = {0xa8, keyNum[1], keyNum[0], 0x00}; + memmove(&rcmd[3], key, 16); + + return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); +} + +int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { + uint8_t rcmd[1] = {0xaa}; + + return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); +} + +int MFPReadBlock(mf4Session *session, bool plain, uint8_t blockNum, uint8_t blockCount, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac) { + uint8_t rcmd[4 + 8] = {(plain ? (0x37) : (0x33)), blockNum, 0x00, blockCount}; + if (!plain && session) + CalculateMAC(session, mtypReadCmd, blockNum, blockCount, rcmd, 4, &rcmd[4], VerboseMode); + + int res = intExchangeRAW14aPlus(rcmd, plain ? 4 : sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); + if (res) + return res; + + if (session) + session->R_Ctr++; + + if (session && mac && *dataoutlen > 11) + CalculateMAC(session, mtypReadResp, blockNum, blockCount, dataout, *dataoutlen - 8 - 2, mac, VerboseMode); + + return 0; +} + +int MFPWriteBlock(mf4Session *session, uint8_t blockNum, uint8_t *data, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac) { + uint8_t rcmd[1 + 2 + 16 + 8] = {0xA3, blockNum, 0x00}; + memmove(&rcmd[3], data, 16); + if (session) + CalculateMAC(session, mtypWriteCmd, blockNum, 1, rcmd, 19, &rcmd[19], VerboseMode); + + int res = intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); + if (res) + return res; + + if (session) + session->W_Ctr++; + + if (session && mac && *dataoutlen > 3) + CalculateMAC(session, mtypWriteResp, blockNum, 1, dataout, *dataoutlen, mac, VerboseMode); + + return 0; +} + +int mfpReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *dataout, bool verbose) { + uint8_t keyn[2] = {0}; + bool plain = false; + + uint16_t uKeyNum = 0x4000 + sectorNo * 2 + (keyType ? 1 : 0); + keyn[0] = uKeyNum >> 8; + keyn[1] = uKeyNum & 0xff; + if (verbose) + PrintAndLogEx(INFO, "--sector[%d]:%02x key:%04x", mfNumBlocksPerSector(sectorNo), sectorNo, uKeyNum); + + mf4Session session; + int res = MifareAuth4(&session, keyn, key, true, true, verbose); + if (res) { + PrintAndLogEx(ERR, "Sector %d authentication error: %d", sectorNo, res); + return res; + } + + uint8_t data[250] = {0}; + int datalen = 0; + uint8_t mac[8] = {0}; + uint8_t firstBlockNo = mfFirstBlockOfSector(sectorNo); + for (int n = firstBlockNo; n < firstBlockNo + mfNumBlocksPerSector(sectorNo); n++) { + res = MFPReadBlock(&session, plain, n & 0xff, 1, false, true, data, sizeof(data), &datalen, mac); + if (res) { + PrintAndLogEx(ERR, "Sector %d read error: %d", sectorNo, res); + DropField(); + return res; + } + + if (datalen && data[0] != 0x90) { + PrintAndLogEx(ERR, "Sector %d card read error: %02x %s", sectorNo, data[0], mfpGetErrorDescription(data[0])); + DropField(); + return 5; + } + if (datalen != 1 + 16 + 8 + 2) { + PrintAndLogEx(ERR, "Sector %d error returned data length:%d", sectorNo, datalen); + DropField(); + return 6; + } + + memcpy(&dataout[(n - firstBlockNo) * 16], &data[1], 16); + + if (verbose) + PrintAndLogEx(INFO, "data[%03d]: %s", n, sprint_hex(&data[1], 16)); + + if (memcmp(&data[1 + 16], mac, 8)) { + PrintAndLogEx(WARNING, "WARNING: mac on block %d not equal...", n); + PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1 + 16], 8)); + PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8)); + + if (!verbose) + return 7; + } else { + if (verbose) + PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1 + 16], 8)); + } + } + DropField(); + + return 0; +} + +// Mifare Memory Structure: up to 32 Sectors with 4 blocks each (1k and 2k cards), +// plus evtl. 8 sectors with 16 blocks each (4k cards) +uint8_t mfNumBlocksPerSector(uint8_t sectorNo) { + if (sectorNo < 32) + return 4; + else + return 16; +} + +uint8_t mfFirstBlockOfSector(uint8_t sectorNo) { + if (sectorNo < 32) + return sectorNo * 4; + else + return 32 * 4 + (sectorNo - 32) * 16; +} + +uint8_t mfSectorTrailer(uint8_t blockNo) { + if (blockNo < 32 * 4) { + return (blockNo | 0x03); + } else { + return (blockNo | 0x0f); + } +} + +bool mfIsSectorTrailer(uint8_t blockNo) { + return (blockNo == mfSectorTrailer(blockNo)); +} + +uint8_t mfSectorNum(uint8_t blockNo) { + if (blockNo < 32 * 4) + return blockNo / 4; + else + return 32 + (blockNo - 32 * 4) / 16; + +} diff --git a/client/mifare4.h b/client/mifare/mifare4.h similarity index 65% rename from client/mifare4.h rename to client/mifare/mifare4.h index 5d9735da..0659bea3 100644 --- a/client/mifare4.h +++ b/client/mifare/mifare4.h @@ -43,9 +43,18 @@ typedef struct { char *description; } AccessConditions_t; +extern void mfpSetVerboseMode(bool verbose); +extern const char *mfpGetErrorDescription(uint8_t errorCode); + extern int CalculateMAC(mf4Session *session, MACType_t mtype, uint8_t blockNum, uint8_t blockCount, uint8_t *data, int datalen, uint8_t *mac, bool verbose); extern int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool verbose); +extern int MFPWritePerso(uint8_t *keyNum, uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); +extern int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); +extern int MFPReadBlock(mf4Session *session, bool plain, uint8_t blockNum, uint8_t blockCount, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac); +extern int MFPWriteBlock(mf4Session *session, uint8_t blockNum, uint8_t *data, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac); +extern int mfpReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *dataout, bool verbose); + extern char *mfGetAccessConditionsDesc(uint8_t blockn, uint8_t *data); extern uint8_t mfNumBlocksPerSector(uint8_t sectorNo); diff --git a/client/mifaredefault.h b/client/mifare/mifaredefault.h similarity index 70% rename from client/mifaredefault.h rename to client/mifare/mifaredefault.h index 78231c86..55675bad 100644 --- a/client/mifaredefault.h +++ b/client/mifare/mifaredefault.h @@ -37,4 +37,9 @@ static const uint64_t MifareDefaultKeys[] = 0x8fd0a4f256e9 }; +static const uint8_t g_mifare_mad_key[] = {0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}; +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}; + #endif diff --git a/client/mifarehost.c b/client/mifare/mifarehost.c similarity index 96% rename from client/mifarehost.c rename to client/mifare/mifarehost.c index 9be04b4d..90c69878 100644 --- a/client/mifarehost.c +++ b/client/mifare/mifarehost.c @@ -1,1087 +1,1114 @@ -// Merlok, 2011, 2012 -// people from mifare@nethemba.com, 2010 -// -// 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 commands -//----------------------------------------------------------------------------- - -#include "mifarehost.h" - -#include -#include -#include -#include - -#include "crapto1/crapto1.h" -#include "comms.h" -#include "usb_cmd.h" -#include "cmdmain.h" -#include "ui.h" -#include "parity.h" -#include "util.h" -#include "iso14443crc.h" - -#include "mifare.h" - -// mifare tracer flags used in mfTraceDecode() -#define TRACE_IDLE 0x00 -#define TRACE_AUTH1 0x01 -#define TRACE_AUTH2 0x02 -#define TRACE_AUTH_OK 0x03 -#define TRACE_READ_DATA 0x04 -#define TRACE_WRITE_OK 0x05 -#define TRACE_WRITE_DATA 0x06 -#define TRACE_ERROR 0xFF - - -static int compare_uint64(const void *a, const void *b) { - // didn't work: (the result is truncated to 32 bits) - //return (*(int64_t*)b - *(int64_t*)a); - - // better: - if (*(uint64_t*)b == *(uint64_t*)a) return 0; - else if (*(uint64_t*)b < *(uint64_t*)a) return 1; - else return -1; -} - - -// create the intersection (common members) of two sorted lists. Lists are terminated by -1. Result will be in list1. Number of elements is returned. -static uint32_t intersection(uint64_t *list1, uint64_t *list2) -{ - if (list1 == NULL || list2 == NULL) { - return 0; - } - uint64_t *p1, *p2, *p3; - p1 = p3 = list1; - p2 = list2; - - while ( *p1 != -1 && *p2 != -1 ) { - if (compare_uint64(p1, p2) == 0) { - *p3++ = *p1++; - p2++; - } - else { - while (compare_uint64(p1, p2) < 0) ++p1; - while (compare_uint64(p1, p2) > 0) ++p2; - } - } - *p3 = -1; - return p3 - list1; -} - - -// Darkside attack (hf mf mifare) -static uint32_t nonce2key(uint32_t uid, uint32_t nt, uint32_t nr, uint32_t ar, uint64_t par_info, uint64_t ks_info, uint64_t **keys) { - struct Crypto1State *states; - uint32_t i, pos; - uint8_t bt, ks3x[8], par[8][8]; - uint64_t key_recovered; - uint64_t *keylist; - - // Reset the last three significant bits of the reader nonce - nr &= 0xffffff1f; - - for (pos=0; pos<8; pos++) { - ks3x[7-pos] = (ks_info >> (pos*8)) & 0x0f; - bt = (par_info >> (pos*8)) & 0xff; - for (i=0; i<8; i++) { - par[7-pos][i] = (bt >> i) & 0x01; - } - } - - states = lfsr_common_prefix(nr, ar, ks3x, par, (par_info == 0)); - - if (states == NULL) { - *keys = NULL; - return 0; - } - - keylist = (uint64_t*)states; - - for (i = 0; keylist[i]; i++) { - lfsr_rollback_word(states+i, uid^nt, 0); - crypto1_get_lfsr(states+i, &key_recovered); - keylist[i] = key_recovered; - } - keylist[i] = -1; - - *keys = keylist; - return i; -} - - -int mfDarkside(uint64_t *key) -{ - uint32_t uid = 0; - uint32_t nt = 0, nr = 0, ar = 0; - uint64_t par_list = 0, ks_list = 0; - uint64_t *keylist = NULL, *last_keylist = NULL; - uint32_t keycount = 0; - int16_t isOK = 0; - - UsbCommand c = {CMD_READER_MIFARE, {true, 0, 0}}; - - // message - printf("-------------------------------------------------------------------------\n"); - printf("Executing command. Expected execution time: 25sec on average\n"); - printf("Press button on the proxmark3 device to abort both proxmark3 and client.\n"); - printf("-------------------------------------------------------------------------\n"); - - - while (true) { - clearCommandBuffer(); - SendCommand(&c); - - //flush queue - while (ukbhit()) { - int c = getchar(); (void) c; - } - - // wait cycle - while (true) { - printf("."); - fflush(stdout); - if (ukbhit()) { - return -5; - break; - } - - UsbCommand resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { - isOK = resp.arg[0]; - if (isOK < 0) { - return isOK; - } - uid = (uint32_t)bytes_to_num(resp.d.asBytes + 0, 4); - nt = (uint32_t)bytes_to_num(resp.d.asBytes + 4, 4); - par_list = bytes_to_num(resp.d.asBytes + 8, 8); - ks_list = bytes_to_num(resp.d.asBytes + 16, 8); - nr = (uint32_t)bytes_to_num(resp.d.asBytes + 24, 4); - ar = (uint32_t)bytes_to_num(resp.d.asBytes + 28, 4); - break; - } - } - - if (par_list == 0 && c.arg[0] == true) { - PrintAndLog("Parity is all zero. Most likely this card sends NACK on every failed authentication."); - } - c.arg[0] = false; - - keycount = nonce2key(uid, nt, nr, ar, par_list, ks_list, &keylist); - - if (keycount == 0) { - PrintAndLog("Key not found (lfsr_common_prefix list is null). Nt=%08x", nt); - PrintAndLog("This is expected to happen in 25%% of all cases. Trying again with a different reader nonce..."); - continue; - } - - if (par_list == 0) { - qsort(keylist, keycount, sizeof(*keylist), compare_uint64); - keycount = intersection(last_keylist, keylist); - if (keycount == 0) { - free(last_keylist); - last_keylist = keylist; - continue; - } - } - - if (keycount > 1) { - PrintAndLog("Found %u possible keys. Trying to authenticate with each of them ...\n", keycount); - } else { - PrintAndLog("Found a possible key. Trying to authenticate...\n"); - } - - *key = -1; - uint8_t keyBlock[USB_CMD_DATA_SIZE]; - int max_keys = USB_CMD_DATA_SIZE/6; - for (int i = 0; i < keycount; i += max_keys) { - int size = keycount - i > max_keys ? max_keys : keycount - i; - for (int j = 0; j < size; j++) { - if (par_list == 0) { - num_to_bytes(last_keylist[i*max_keys + j], 6, keyBlock+(j*6)); - } else { - num_to_bytes(keylist[i*max_keys + j], 6, keyBlock+(j*6)); - } - } - if (!mfCheckKeys(0, 0, false, size, keyBlock, key)) { - break; - } - } - - if (*key != -1) { - free(last_keylist); - free(keylist); - break; - } else { - PrintAndLog("Authentication failed. Trying again..."); - free(last_keylist); - last_keylist = keylist; - } - } - - return 0; -} - - -int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key){ - - *key = -1; - - UsbCommand c = {CMD_MIFARE_CHKKEYS, {((blockNo & 0xff) | ((keyType & 0xff) << 8)), clear_trace, keycnt}}; - memcpy(c.d.asBytes, keyBlock, 6 * keycnt); - SendCommand(&c); - - UsbCommand resp; - if (!WaitForResponseTimeout(CMD_ACK,&resp,3000)) return 1; - if ((resp.arg[0] & 0xff) != 0x01) return 2; - *key = bytes_to_num(resp.d.asBytes, 6); - return 0; -} - -int mfCheckKeysSec(uint8_t sectorCnt, uint8_t keyType, uint8_t timeout14a, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, sector_t * e_sector){ - - uint8_t keyPtr = 0; - - if (e_sector == NULL) - return -1; - - UsbCommand c = {CMD_MIFARE_CHKKEYS, {((sectorCnt & 0xff) | ((keyType & 0xff) << 8)), (clear_trace | 0x02)|((timeout14a & 0xff) << 8), keycnt}}; - memcpy(c.d.asBytes, keyBlock, 6 * keycnt); - SendCommand(&c); - - UsbCommand resp; - if (!WaitForResponseTimeoutW(CMD_ACK, &resp, MAX(3000, 1000 + 13 * sectorCnt * keycnt * (keyType == 2 ? 2 : 1)), false)) return 1; // timeout: 13 ms / fail auth - if ((resp.arg[0] & 0xff) != 0x01) return 2; - - bool foundAKey = false; - for(int sec = 0; sec < sectorCnt; sec++){ - for(int keyAB = 0; keyAB < 2; keyAB++){ - keyPtr = *(resp.d.asBytes + keyAB * 40 + sec); - if (keyPtr){ - e_sector[sec].foundKey[keyAB] = true; - e_sector[sec].Key[keyAB] = bytes_to_num(keyBlock + (keyPtr - 1) * 6, 6); - foundAKey = true; - } - } - } - return foundAKey ? 0 : 3; -} - -// Compare 16 Bits out of cryptostate -int Compare16Bits(const void * a, const void * b) { - if ((*(uint64_t*)b & 0x00ff000000ff0000) == (*(uint64_t*)a & 0x00ff000000ff0000)) return 0; - else if ((*(uint64_t*)b & 0x00ff000000ff0000) > (*(uint64_t*)a & 0x00ff000000ff0000)) return 1; - else return -1; -} - -typedef - struct { - union { - struct Crypto1State *slhead; - uint64_t *keyhead; - } head; - union { - struct Crypto1State *sltail; - uint64_t *keytail; - } tail; - uint32_t len; - uint32_t uid; - uint32_t blockNo; - uint32_t keyType; - uint32_t nt; - uint32_t ks1; - } StateList_t; - - -// wrapper function for multi-threaded lfsr_recovery32 -void -#ifdef __has_attribute -#if __has_attribute(force_align_arg_pointer) -__attribute__((force_align_arg_pointer)) -#endif -#endif -*nested_worker_thread(void *arg) -{ - struct Crypto1State *p1; - StateList_t *statelist = arg; - - statelist->head.slhead = lfsr_recovery32(statelist->ks1, statelist->nt ^ statelist->uid); - for (p1 = statelist->head.slhead; *(uint64_t *)p1 != 0; p1++); - statelist->len = p1 - statelist->head.slhead; - statelist->tail.sltail = --p1; - qsort(statelist->head.slhead, statelist->len, sizeof(uint64_t), Compare16Bits); - - return statelist->head.slhead; -} - - -int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool calibrate) -{ - uint16_t i; - uint32_t uid; - UsbCommand resp; - - StateList_t statelists[2]; - struct Crypto1State *p1, *p2, *p3, *p4; - - // flush queue - (void)WaitForResponseTimeout(CMD_ACK,NULL,100); - - UsbCommand c = {CMD_MIFARE_NESTED, {blockNo + keyType * 0x100, trgBlockNo + trgKeyType * 0x100, calibrate}}; - memcpy(c.d.asBytes, key, 6); - SendCommand(&c); - - if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - return -1; - } - - if (resp.arg[0]) { - return resp.arg[0]; // error during nested - } - - memcpy(&uid, resp.d.asBytes, 4); - PrintAndLog("uid:%08x trgbl=%d trgkey=%x", uid, (uint16_t)resp.arg[2] & 0xff, (uint16_t)resp.arg[2] >> 8); - - for (i = 0; i < 2; i++) { - statelists[i].blockNo = resp.arg[2] & 0xff; - statelists[i].keyType = (resp.arg[2] >> 8) & 0xff; - statelists[i].uid = uid; - memcpy(&statelists[i].nt, (void *)(resp.d.asBytes + 4 + i * 8 + 0), 4); - memcpy(&statelists[i].ks1, (void *)(resp.d.asBytes + 4 + i * 8 + 4), 4); - } - - // calc keys - - pthread_t thread_id[2]; - - // create and run worker threads - for (i = 0; i < 2; i++) { - pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]); - } - - // wait for threads to terminate: - for (i = 0; i < 2; i++) { - pthread_join(thread_id[i], (void*)&statelists[i].head.slhead); - } - - - // the first 16 Bits of the cryptostate already contain part of our key. - // Create the intersection of the two lists based on these 16 Bits and - // roll back the cryptostate - p1 = p3 = statelists[0].head.slhead; - p2 = p4 = statelists[1].head.slhead; - while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) { - if (Compare16Bits(p1, p2) == 0) { - struct Crypto1State savestate, *savep = &savestate; - savestate = *p1; - while(Compare16Bits(p1, savep) == 0 && p1 <= statelists[0].tail.sltail) { - *p3 = *p1; - lfsr_rollback_word(p3, statelists[0].nt ^ statelists[0].uid, 0); - p3++; - p1++; - } - savestate = *p2; - while(Compare16Bits(p2, savep) == 0 && p2 <= statelists[1].tail.sltail) { - *p4 = *p2; - lfsr_rollback_word(p4, statelists[1].nt ^ statelists[1].uid, 0); - p4++; - p2++; - } - } - else { - while (Compare16Bits(p1, p2) == -1) p1++; - while (Compare16Bits(p1, p2) == 1) p2++; - } - } - *(uint64_t*)p3 = -1; - *(uint64_t*)p4 = -1; - statelists[0].len = p3 - statelists[0].head.slhead; - statelists[1].len = p4 - statelists[1].head.slhead; - statelists[0].tail.sltail=--p3; - statelists[1].tail.sltail=--p4; - - // the statelists now contain possible keys. The key we are searching for must be in the - // intersection of both lists. Create the intersection: - qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64); - qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64); - statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead); - - memset(resultKey, 0, 6); - // The list may still contain several key candidates. Test each of them with mfCheckKeys - for (i = 0; i < statelists[0].len; i++) { - uint8_t keyBlock[6]; - uint64_t key64; - crypto1_get_lfsr(statelists[0].head.slhead + i, &key64); - num_to_bytes(key64, 6, keyBlock); - key64 = 0; - if (!mfCheckKeys(statelists[0].blockNo, statelists[0].keyType, false, 1, keyBlock, &key64)) { - num_to_bytes(key64, 6, resultKey); - break; - } - } - - free(statelists[0].head.slhead); - free(statelists[1].head.slhead); - - return 0; -} - -// EMULATOR - -int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount) { - UsbCommand c = {CMD_MIFARE_EML_MEMGET, {blockNum, blocksCount, 0}}; - SendCommand(&c); - - UsbCommand resp; - if (!WaitForResponseTimeout(CMD_ACK,&resp,1500)) return 1; - memcpy(data, resp.d.asBytes, blocksCount * 16); - return 0; -} - -int mfEmlSetMem(uint8_t *data, int blockNum, int blocksCount) { - UsbCommand c = {CMD_MIFARE_EML_MEMSET, {blockNum, blocksCount, 0}}; - memcpy(c.d.asBytes, data, blocksCount * 16); - SendCommand(&c); - return 0; -} - -// "MAGIC" CARD - -int mfCGetBlock(uint8_t blockNo, uint8_t *data, uint8_t params) { - uint8_t isOK = 0; - - UsbCommand c = {CMD_MIFARE_CGETBLOCK, {params, 0, blockNo}}; - SendCommand(&c); - - UsbCommand resp; - if (WaitForResponseTimeout(CMD_ACK,&resp,1500)) { - isOK = resp.arg[0] & 0xff; - memcpy(data, resp.d.asBytes, 16); - if (!isOK) return 2; - } else { - PrintAndLog("Command execute timeout"); - return 1; - } - return 0; -} - -int mfCSetBlock(uint8_t blockNo, uint8_t *data, uint8_t *uid, bool wantWipe, uint8_t params) { - - uint8_t isOK = 0; - UsbCommand c = {CMD_MIFARE_CSETBLOCK, {wantWipe, params & (0xFE | (uid == NULL ? 0:1)), blockNo}}; - memcpy(c.d.asBytes, data, 16); - SendCommand(&c); - - UsbCommand resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - isOK = resp.arg[0] & 0xff; - if (uid != NULL) - memcpy(uid, resp.d.asBytes, 4); - if (!isOK) - return 2; - } else { - PrintAndLog("Command execute timeout"); - return 1; - } - - return 0; -} - -int mfCWipe(uint32_t numSectors, bool gen1b, bool wantWipe, bool wantFill) { - uint8_t isOK = 0; - uint8_t cmdParams = wantWipe + wantFill * 0x02 + gen1b * 0x04; - UsbCommand c = {CMD_MIFARE_CWIPE, {numSectors, cmdParams, 0}}; - SendCommand(&c); - - UsbCommand resp; - WaitForResponse(CMD_ACK,&resp); - isOK = resp.arg[0] & 0xff; - - return isOK; -} - -int mfCSetUID(uint8_t *uid, uint8_t *atqa, uint8_t *sak, uint8_t *oldUID) { - uint8_t oldblock0[16] = {0x00}; - uint8_t block0[16] = {0x00}; - int gen = 0, res; - - gen = mfCIdentify(); - - /* generation 1a magic card by default */ - uint8_t cmdParams = CSETBLOCK_SINGLE_OPER; - if (gen == 2) { - /* generation 1b magic card */ - cmdParams = CSETBLOCK_SINGLE_OPER | CSETBLOCK_MAGIC_1B; - } - - res = mfCGetBlock(0, oldblock0, cmdParams); - - if (res == 0) { - memcpy(block0, oldblock0, 16); - PrintAndLog("old block 0: %s", sprint_hex(block0,16)); - } else { - PrintAndLog("Couldn't get old data. Will write over the last bytes of Block 0."); - } - - // fill in the new values - // UID - memcpy(block0, uid, 4); - // Mifare UID BCC - block0[4] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3]; - // mifare classic SAK(byte 5) and ATQA(byte 6 and 7, reversed) - if (sak != NULL) - block0[5] = sak[0]; - if (atqa != NULL) { - block0[6] = atqa[1]; - block0[7] = atqa[0]; - } - PrintAndLog("new block 0: %s", sprint_hex(block0, 16)); - - res = mfCSetBlock(0, block0, oldUID, false, cmdParams); - if (res) { - PrintAndLog("Can't set block 0. Error: %d", res); - return res; - } - - return 0; -} - -int mfCIdentify() { - UsbCommand c = {CMD_MIFARE_CIDENT, {0, 0, 0}}; - SendCommand(&c); - UsbCommand resp; - WaitForResponse(CMD_ACK,&resp); - - uint8_t isGeneration = resp.arg[0] & 0xff; - switch( isGeneration ){ - case 1: PrintAndLog("Chinese magic backdoor commands (GEN 1a) detected"); break; - case 2: PrintAndLog("Chinese magic backdoor command (GEN 1b) detected"); break; - default: PrintAndLog("No chinese magic backdoor command detected"); break; - } - - return (int) isGeneration; -} - - -// SNIFFER - -// constants -static uint8_t trailerAccessBytes[4] = {0x08, 0x77, 0x8F, 0x00}; - -// variables -char logHexFileName[FILE_PATH_SIZE] = {0x00}; -static uint8_t traceCard[4096] = {0x00}; -static char traceFileName[FILE_PATH_SIZE] = {0x00}; -static int traceState = TRACE_IDLE; -static uint8_t traceCurBlock = 0; -static uint8_t traceCurKey = 0; - -struct Crypto1State *traceCrypto1 = NULL; - -struct Crypto1State *revstate; -uint64_t lfsr; -uint64_t ui64Key; -uint32_t ks2; -uint32_t ks3; - -uint32_t uid; // serial number -uint32_t nt; // tag challenge -uint32_t nt_enc; // encrypted tag challenge -uint8_t nt_enc_par; // encrypted tag challenge parity -uint32_t nr_enc; // encrypted reader challenge -uint32_t ar_enc; // encrypted reader response -uint8_t ar_enc_par; // encrypted reader response parity -uint32_t at_enc; // encrypted tag response -uint8_t at_enc_par; // encrypted tag response parity - -int isTraceCardEmpty(void) { - return ((traceCard[0] == 0) && (traceCard[1] == 0) && (traceCard[2] == 0) && (traceCard[3] == 0)); -} - -int isBlockEmpty(int blockN) { - for (int i = 0; i < 16; i++) - if (traceCard[blockN * 16 + i] != 0) return 0; - - return 1; -} - -int isBlockTrailer(int blockN) { - return ((blockN & 0x03) == 0x03); -} - -int saveTraceCard(void) { - FILE * f; - - if ((!strlen(traceFileName)) || (isTraceCardEmpty())) return 0; - - f = fopen(traceFileName, "w+"); - if ( !f ) return 1; - - for (int i = 0; i < 64; i++) { // blocks - for (int j = 0; j < 16; j++) // bytes - fprintf(f, "%02x", *(traceCard + i * 16 + j)); - if (i < 63) - fprintf(f,"\n"); - } - fclose(f); - return 0; -} - -int loadTraceCard(uint8_t *tuid) { - FILE * f; - char buf[64] = {0x00}; - uint8_t buf8[64] = {0x00}; - int i, blockNum; - - if (!isTraceCardEmpty()) - saveTraceCard(); - - memset(traceCard, 0x00, 4096); - memcpy(traceCard, tuid + 3, 4); - - FillFileNameByUID(traceFileName, tuid, ".eml", 7); - - f = fopen(traceFileName, "r"); - if (!f) return 1; - - blockNum = 0; - - while(!feof(f)){ - - memset(buf, 0, sizeof(buf)); - if (fgets(buf, sizeof(buf), f) == NULL) { - PrintAndLog("File reading error."); - fclose(f); - return 2; - } - - if (strlen(buf) < 32){ - if (feof(f)) break; - PrintAndLog("File content error. Block data must include 32 HEX symbols"); - fclose(f); - return 2; - } - for (i = 0; i < 32; i += 2) - sscanf(&buf[i], "%02x", (unsigned int *)&buf8[i / 2]); - - memcpy(traceCard + blockNum * 16, buf8, 16); - - blockNum++; - } - fclose(f); - - return 0; -} - -int mfTraceInit(uint8_t *tuid, uint8_t *atqa, uint8_t sak, bool wantSaveToEmlFile) { - - if (traceCrypto1) - crypto1_destroy(traceCrypto1); - - traceCrypto1 = NULL; - - if (wantSaveToEmlFile) - loadTraceCard(tuid); - - traceCard[4] = traceCard[0] ^ traceCard[1] ^ traceCard[2] ^ traceCard[3]; - traceCard[5] = sak; - memcpy(&traceCard[6], atqa, 2); - traceCurBlock = 0; - uid = bytes_to_num(tuid + 3, 4); - - traceState = TRACE_IDLE; - - return 0; -} - -void mf_crypto1_decrypt(struct Crypto1State *pcs, uint8_t *data, int len, bool isEncrypted){ - uint8_t bt = 0; - int i; - - if (len != 1) { - for (i = 0; i < len; i++) - data[i] = crypto1_byte(pcs, 0x00, isEncrypted) ^ data[i]; - } else { - bt = 0; - for (i = 0; i < 4; i++) - bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], i)) << i; - - data[0] = bt; - } - return; -} - -bool NTParityCheck(uint32_t ntx) { - if ( - (oddparity8(ntx >> 8 & 0xff) ^ (ntx & 0x01) ^ ((nt_enc_par >> 5) & 0x01) ^ (nt_enc & 0x01)) || - (oddparity8(ntx >> 16 & 0xff) ^ (ntx >> 8 & 0x01) ^ ((nt_enc_par >> 6) & 0x01) ^ (nt_enc >> 8 & 0x01)) || - (oddparity8(ntx >> 24 & 0xff) ^ (ntx >> 16 & 0x01) ^ ((nt_enc_par >> 7) & 0x01) ^ (nt_enc >> 16 & 0x01)) - ) - return false; - - uint32_t ar = prng_successor(ntx, 64); - if ( - (oddparity8(ar >> 8 & 0xff) ^ (ar & 0x01) ^ ((ar_enc_par >> 5) & 0x01) ^ (ar_enc & 0x01)) || - (oddparity8(ar >> 16 & 0xff) ^ (ar >> 8 & 0x01) ^ ((ar_enc_par >> 6) & 0x01) ^ (ar_enc >> 8 & 0x01)) || - (oddparity8(ar >> 24 & 0xff) ^ (ar >> 16 & 0x01) ^ ((ar_enc_par >> 7) & 0x01) ^ (ar_enc >> 16 & 0x01)) - ) - return false; - - uint32_t at = prng_successor(ntx, 96); - if ( - (oddparity8(ar & 0xff) ^ (at >> 24 & 0x01) ^ ((ar_enc_par >> 4) & 0x01) ^ (at_enc >> 24 & 0x01)) || - (oddparity8(at >> 8 & 0xff) ^ (at & 0x01) ^ ((at_enc_par >> 5) & 0x01) ^ (at_enc & 0x01)) || - (oddparity8(at >> 16 & 0xff) ^ (at >> 8 & 0x01) ^ ((at_enc_par >> 6) & 0x01) ^ (at_enc >> 8 & 0x01)) || - (oddparity8(at >> 24 & 0xff) ^ (at >> 16 & 0x01) ^ ((at_enc_par >> 7) & 0x01) ^ (at_enc >> 16 & 0x01)) - ) - return false; - - return true; -} - - -int mfTraceDecode(uint8_t *data_src, int len, uint8_t parity, bool wantSaveToEmlFile) { - uint8_t data[64]; - - if (traceState == TRACE_ERROR) return 1; - if (len > 64) { - traceState = TRACE_ERROR; - return 1; - } - - memcpy(data, data_src, len); - if ((traceCrypto1) && ((traceState == TRACE_IDLE) || (traceState > TRACE_AUTH_OK))) { - mf_crypto1_decrypt(traceCrypto1, data, len, 0); - uint8_t parity[16]; - oddparitybuf(data, len, parity); - PrintAndLog("dec> %s [%s]", sprint_hex(data, len), printBitsPar(parity, len)); - AddLogHex(logHexFileName, "dec> ", data, len); - } - - switch (traceState) { - case TRACE_IDLE: - // check packet crc16! - if ((len >= 4) && (!CheckCrc14443(CRC_14443_A, data, len))) { - PrintAndLog("dec> CRC ERROR!!!"); - AddLogLine(logHexFileName, "dec> ", "CRC ERROR!!!"); - traceState = TRACE_ERROR; // do not decrypt the next commands - return 1; - } - - // AUTHENTICATION - if ((len ==4) && ((data[0] == 0x60) || (data[0] == 0x61))) { - traceState = TRACE_AUTH1; - traceCurBlock = data[1]; - traceCurKey = data[0] == 60 ? 1:0; - return 0; - } - - // READ - if ((len ==4) && ((data[0] == 0x30))) { - traceState = TRACE_READ_DATA; - traceCurBlock = data[1]; - return 0; - } - - // WRITE - if ((len ==4) && ((data[0] == 0xA0))) { - traceState = TRACE_WRITE_OK; - traceCurBlock = data[1]; - return 0; - } - - // HALT - if ((len ==4) && ((data[0] == 0x50) && (data[1] == 0x00))) { - traceState = TRACE_ERROR; // do not decrypt the next commands - return 0; - } - - return 0; - break; - - case TRACE_READ_DATA: - if (len == 18) { - traceState = TRACE_IDLE; - - if (isBlockTrailer(traceCurBlock)) { - memcpy(traceCard + traceCurBlock * 16 + 6, data + 6, 4); - } else { - memcpy(traceCard + traceCurBlock * 16, data, 16); - } - if (wantSaveToEmlFile) saveTraceCard(); - return 0; - } else { - traceState = TRACE_ERROR; - return 1; - } - break; - - case TRACE_WRITE_OK: - if ((len == 1) && (data[0] == 0x0a)) { - traceState = TRACE_WRITE_DATA; - - return 0; - } else { - traceState = TRACE_ERROR; - return 1; - } - break; - - case TRACE_WRITE_DATA: - if (len == 18) { - traceState = TRACE_IDLE; - - memcpy(traceCard + traceCurBlock * 16, data, 16); - if (wantSaveToEmlFile) saveTraceCard(); - return 0; - } else { - traceState = TRACE_ERROR; - return 1; - } - break; - - case TRACE_AUTH1: - if (len == 4) { - traceState = TRACE_AUTH2; - if (!traceCrypto1) { - nt = bytes_to_num(data, 4); - } else { - nt_enc = bytes_to_num(data, 4); - nt_enc_par = parity; - } - return 0; - } else { - traceState = TRACE_ERROR; - return 1; - } - break; - - case TRACE_AUTH2: - if (len == 8) { - traceState = TRACE_AUTH_OK; - - nr_enc = bytes_to_num(data, 4); - ar_enc = bytes_to_num(data + 4, 4); - ar_enc_par = parity << 4; - return 0; - } else { - traceState = TRACE_ERROR; - return 1; - } - break; - - case TRACE_AUTH_OK: - if (len ==4) { - traceState = TRACE_IDLE; - - at_enc = bytes_to_num(data, 4); - at_enc_par = parity; - if (!traceCrypto1) { - - // decode key here) - ks2 = ar_enc ^ prng_successor(nt, 64); - ks3 = at_enc ^ prng_successor(nt, 96); - revstate = lfsr_recovery64(ks2, ks3); - lfsr_rollback_word(revstate, 0, 0); - lfsr_rollback_word(revstate, 0, 0); - lfsr_rollback_word(revstate, nr_enc, 1); - lfsr_rollback_word(revstate, uid ^ nt, 0); - - crypto1_get_lfsr(revstate, &lfsr); - crypto1_destroy(revstate); - ui64Key = lfsr; - printf("key> probable key:%x%x Prng:%s ks2:%08x ks3:%08x\n", - (unsigned int)((lfsr & 0xFFFFFFFF00000000) >> 32), (unsigned int)(lfsr & 0xFFFFFFFF), - validate_prng_nonce(nt) ? "WEAK": "HARDEND", - ks2, - ks3); - AddLogUint64(logHexFileName, "key> ", lfsr); - } else { - if (validate_prng_nonce(nt)) { - struct Crypto1State *pcs; - pcs = crypto1_create(ui64Key); - uint32_t nt1 = crypto1_word(pcs, nt_enc ^ uid, 1) ^ nt_enc; - uint32_t ar = prng_successor(nt1, 64); - uint32_t at = prng_successor(nt1, 96); - printf("key> nested auth uid: %08x nt: %08x nt_parity: %s ar: %08x at: %08x\n", uid, nt1, printBitsPar(&nt_enc_par, 4), ar, at); - uint32_t nr1 = crypto1_word(pcs, nr_enc, 1) ^ nr_enc; - uint32_t ar1 = crypto1_word(pcs, 0, 0) ^ ar_enc; - uint32_t at1 = crypto1_word(pcs, 0, 0) ^ at_enc; - crypto1_destroy(pcs); - printf("key> the same key test. nr1: %08x ar1: %08x at1: %08x \n", nr1, ar1, at1); - - if (NTParityCheck(nt1)) - printf("key> the same key test OK. key=%x%x\n", (unsigned int)((ui64Key & 0xFFFFFFFF00000000) >> 32), (unsigned int)(ui64Key & 0xFFFFFFFF)); - else - printf("key> the same key test. check nt parity error.\n"); - - uint32_t ntc = prng_successor(nt, 90); - uint32_t ntx = 0; - int ntcnt = 0; - for (int i = 0; i < 16383; i++) { - ntc = prng_successor(ntc, 1); - if (NTParityCheck(ntc)){ - if (!ntcnt) - ntx = ntc; - ntcnt++; - } - } - if (ntcnt) - printf("key> nt candidate=%08x nonce distance=%d candidates count=%d\n", ntx, nonce_distance(nt, ntx), ntcnt); - else - printf("key> don't have any nt candidate( \n"); - - nt = ntx; - ks2 = ar_enc ^ prng_successor(ntx, 64); - ks3 = at_enc ^ prng_successor(ntx, 96); - - // decode key - revstate = lfsr_recovery64(ks2, ks3); - lfsr_rollback_word(revstate, 0, 0); - lfsr_rollback_word(revstate, 0, 0); - lfsr_rollback_word(revstate, nr_enc, 1); - lfsr_rollback_word(revstate, uid ^ nt, 0); - - crypto1_get_lfsr(revstate, &lfsr); - crypto1_destroy(revstate); - ui64Key = lfsr; - printf("key> probable key:%x%x ks2:%08x ks3:%08x\n", - (unsigned int)((lfsr & 0xFFFFFFFF00000000) >> 32), (unsigned int)(lfsr & 0xFFFFFFFF), - ks2, - ks3); - AddLogUint64(logHexFileName, "key> ", lfsr); - } else { - printf("key> hardnested not implemented!\n"); - - crypto1_destroy(traceCrypto1); - - // not implemented - traceState = TRACE_ERROR; - } - } - - int blockShift = ((traceCurBlock & 0xFC) + 3) * 16; - if (isBlockEmpty((traceCurBlock & 0xFC) + 3)) memcpy(traceCard + blockShift + 6, trailerAccessBytes, 4); - - if (traceCurKey) { - num_to_bytes(lfsr, 6, traceCard + blockShift + 10); - } else { - num_to_bytes(lfsr, 6, traceCard + blockShift); - } - if (wantSaveToEmlFile) saveTraceCard(); - - if (traceCrypto1) { - crypto1_destroy(traceCrypto1); - } - - // set cryptosystem state - traceCrypto1 = lfsr_recovery64(ks2, ks3); - return 0; - } else { - traceState = TRACE_ERROR; - return 1; - } - break; - - default: - traceState = TRACE_ERROR; - return 1; - } - - return 0; -} - -// DECODING - -int tryDecryptWord(uint32_t nt, uint32_t ar_enc, uint32_t at_enc, uint8_t *data, int len){ - /* - uint32_t nt; // tag challenge - uint32_t ar_enc; // encrypted reader response - uint32_t at_enc; // encrypted tag response - */ - if (traceCrypto1) { - crypto1_destroy(traceCrypto1); - } - ks2 = ar_enc ^ prng_successor(nt, 64); - ks3 = at_enc ^ prng_successor(nt, 96); - traceCrypto1 = lfsr_recovery64(ks2, ks3); - - mf_crypto1_decrypt(traceCrypto1, data, len, 0); - - PrintAndLog("Decrypted data: [%s]", sprint_hex(data,len) ); - crypto1_destroy(traceCrypto1); - return 0; -} - -/** validate_prng_nonce - * Determine if nonce is deterministic. ie: Suspectable to Darkside attack. - * returns - * true = weak prng - * false = hardend prng - */ -bool validate_prng_nonce(uint32_t nonce) { - uint16_t *dist = 0; - uint16_t x, i; - - dist = malloc(2 << 16); - if(!dist) - return -1; - - // init prng table: - for (x = i = 1; i; ++i) { - dist[(x & 0xff) << 8 | x >> 8] = i; - x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; - } - - uint32_t res = (65535 - dist[nonce >> 16] + dist[nonce & 0xffff]) % 65535; - - free(dist); - return (res == 16); -} - -/* Detect Tag Prng, -* function performs a partial AUTH, where it tries to authenticate against block0, key A, but only collects tag nonce. -* the tag nonce is check to see if it has a predictable PRNG. -* @returns -* TRUE if tag uses WEAK prng (ie Now the NACK bug also needs to be present for Darkside attack) -* FALSE is tag uses HARDEND prng (ie hardnested attack possible, with known key) -*/ -int DetectClassicPrng(void){ - - UsbCommand resp, respA; - uint8_t cmd[] = {0x60, 0x00}; // MIFARE_AUTH_KEYA - uint32_t flags = ISO14A_CONNECT | ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS; - - UsbCommand c = {CMD_READER_ISO_14443a, {flags, sizeof(cmd), 0}}; - memcpy(c.d.asBytes, cmd, sizeof(cmd)); - - clearCommandBuffer(); - SendCommand(&c); - if (!WaitForResponseTimeout(CMD_ACK, &resp, 2000)) { - PrintAndLog("PRNG UID: Reply timeout."); - return -1; - } - - // if select tag failed. - if (resp.arg[0] == 0) { - PrintAndLog("PRNG error: selecting tag failed, can't detect prng."); - return -1; - } - - if (!WaitForResponseTimeout(CMD_ACK, &respA, 5000)) { - PrintAndLog("PRNG data: Reply timeout."); - return -1; - } - - // check respA - if (respA.arg[0] != 4) { - PrintAndLog("PRNG data error: Wrong length: %d", respA.arg[0]); - return -1; - } - - uint32_t nonce = bytes_to_num(respA.d.asBytes, respA.arg[0]); - return validate_prng_nonce(nonce); -} +// Merlok, 2011, 2012 +// people from mifare@nethemba.com, 2010 +// +// 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 commands +//----------------------------------------------------------------------------- + +#include "mifarehost.h" + +#include +#include +#include +#include + +#include "crapto1/crapto1.h" +#include "comms.h" +#include "usb_cmd.h" +#include "cmdmain.h" +#include "ui.h" +#include "parity.h" +#include "util.h" +#include "iso14443crc.h" + +#include "mifare.h" +#include "mifare4.h" + +// mifare tracer flags used in mfTraceDecode() +#define TRACE_IDLE 0x00 +#define TRACE_AUTH1 0x01 +#define TRACE_AUTH2 0x02 +#define TRACE_AUTH_OK 0x03 +#define TRACE_READ_DATA 0x04 +#define TRACE_WRITE_OK 0x05 +#define TRACE_WRITE_DATA 0x06 +#define TRACE_ERROR 0xFF + + +static int compare_uint64(const void *a, const void *b) { + // didn't work: (the result is truncated to 32 bits) + //return (*(int64_t*)b - *(int64_t*)a); + + // better: + if (*(uint64_t*)b == *(uint64_t*)a) return 0; + else if (*(uint64_t*)b < *(uint64_t*)a) return 1; + else return -1; +} + + +// create the intersection (common members) of two sorted lists. Lists are terminated by -1. Result will be in list1. Number of elements is returned. +static uint32_t intersection(uint64_t *list1, uint64_t *list2) +{ + if (list1 == NULL || list2 == NULL) { + return 0; + } + uint64_t *p1, *p2, *p3; + p1 = p3 = list1; + p2 = list2; + + while ( *p1 != -1 && *p2 != -1 ) { + if (compare_uint64(p1, p2) == 0) { + *p3++ = *p1++; + p2++; + } + else { + while (compare_uint64(p1, p2) < 0) ++p1; + while (compare_uint64(p1, p2) > 0) ++p2; + } + } + *p3 = -1; + return p3 - list1; +} + + +// Darkside attack (hf mf mifare) +static uint32_t nonce2key(uint32_t uid, uint32_t nt, uint32_t nr, uint32_t ar, uint64_t par_info, uint64_t ks_info, uint64_t **keys) { + struct Crypto1State *states; + uint32_t i, pos; + uint8_t bt, ks3x[8], par[8][8]; + uint64_t key_recovered; + uint64_t *keylist; + + // Reset the last three significant bits of the reader nonce + nr &= 0xffffff1f; + + for (pos=0; pos<8; pos++) { + ks3x[7-pos] = (ks_info >> (pos*8)) & 0x0f; + bt = (par_info >> (pos*8)) & 0xff; + for (i=0; i<8; i++) { + par[7-pos][i] = (bt >> i) & 0x01; + } + } + + states = lfsr_common_prefix(nr, ar, ks3x, par, (par_info == 0)); + + if (states == NULL) { + *keys = NULL; + return 0; + } + + keylist = (uint64_t*)states; + + for (i = 0; keylist[i]; i++) { + lfsr_rollback_word(states+i, uid^nt, 0); + crypto1_get_lfsr(states+i, &key_recovered); + keylist[i] = key_recovered; + } + keylist[i] = -1; + + *keys = keylist; + return i; +} + + +int mfDarkside(uint64_t *key) +{ + uint32_t uid = 0; + uint32_t nt = 0, nr = 0, ar = 0; + uint64_t par_list = 0, ks_list = 0; + uint64_t *keylist = NULL, *last_keylist = NULL; + uint32_t keycount = 0; + int16_t isOK = 0; + + UsbCommand c = {CMD_READER_MIFARE, {true, 0, 0}}; + + // message + printf("-------------------------------------------------------------------------\n"); + printf("Executing command. Expected execution time: 25sec on average\n"); + printf("Press button on the proxmark3 device to abort both proxmark3 and client.\n"); + printf("-------------------------------------------------------------------------\n"); + + + while (true) { + clearCommandBuffer(); + SendCommand(&c); + + //flush queue + while (ukbhit()) { + int c = getchar(); (void) c; + } + + // wait cycle + while (true) { + printf("."); + fflush(stdout); + if (ukbhit()) { + return -5; + break; + } + + UsbCommand resp; + if (WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { + isOK = resp.arg[0]; + if (isOK < 0) { + return isOK; + } + uid = (uint32_t)bytes_to_num(resp.d.asBytes + 0, 4); + nt = (uint32_t)bytes_to_num(resp.d.asBytes + 4, 4); + par_list = bytes_to_num(resp.d.asBytes + 8, 8); + ks_list = bytes_to_num(resp.d.asBytes + 16, 8); + nr = (uint32_t)bytes_to_num(resp.d.asBytes + 24, 4); + ar = (uint32_t)bytes_to_num(resp.d.asBytes + 28, 4); + break; + } + } + + if (par_list == 0 && c.arg[0] == true) { + PrintAndLog("Parity is all zero. Most likely this card sends NACK on every failed authentication."); + } + c.arg[0] = false; + + keycount = nonce2key(uid, nt, nr, ar, par_list, ks_list, &keylist); + + if (keycount == 0) { + PrintAndLog("Key not found (lfsr_common_prefix list is null). Nt=%08x", nt); + PrintAndLog("This is expected to happen in 25%% of all cases. Trying again with a different reader nonce..."); + continue; + } + + if (par_list == 0) { + qsort(keylist, keycount, sizeof(*keylist), compare_uint64); + keycount = intersection(last_keylist, keylist); + if (keycount == 0) { + free(last_keylist); + last_keylist = keylist; + continue; + } + } + + if (keycount > 1) { + PrintAndLog("Found %u possible keys. Trying to authenticate with each of them ...\n", keycount); + } else { + PrintAndLog("Found a possible key. Trying to authenticate...\n"); + } + + *key = -1; + uint8_t keyBlock[USB_CMD_DATA_SIZE]; + int max_keys = USB_CMD_DATA_SIZE/6; + for (int i = 0; i < keycount; i += max_keys) { + int size = keycount - i > max_keys ? max_keys : keycount - i; + for (int j = 0; j < size; j++) { + if (par_list == 0) { + num_to_bytes(last_keylist[i*max_keys + j], 6, keyBlock+(j*6)); + } else { + num_to_bytes(keylist[i*max_keys + j], 6, keyBlock+(j*6)); + } + } + if (!mfCheckKeys(0, 0, false, size, keyBlock, key)) { + break; + } + } + + if (*key != -1) { + free(last_keylist); + free(keylist); + break; + } else { + PrintAndLog("Authentication failed. Trying again..."); + free(last_keylist); + last_keylist = keylist; + } + } + + return 0; +} + + +int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key){ + + *key = -1; + + UsbCommand c = {CMD_MIFARE_CHKKEYS, {((blockNo & 0xff) | ((keyType & 0xff) << 8)), clear_trace, keycnt}}; + memcpy(c.d.asBytes, keyBlock, 6 * keycnt); + SendCommand(&c); + + UsbCommand resp; + if (!WaitForResponseTimeout(CMD_ACK,&resp,3000)) return 1; + if ((resp.arg[0] & 0xff) != 0x01) return 2; + *key = bytes_to_num(resp.d.asBytes, 6); + return 0; +} + +int mfCheckKeysSec(uint8_t sectorCnt, uint8_t keyType, uint8_t timeout14a, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, sector_t * e_sector){ + + uint8_t keyPtr = 0; + + if (e_sector == NULL) + return -1; + + UsbCommand c = {CMD_MIFARE_CHKKEYS, {((sectorCnt & 0xff) | ((keyType & 0xff) << 8)), (clear_trace | 0x02)|((timeout14a & 0xff) << 8), keycnt}}; + memcpy(c.d.asBytes, keyBlock, 6 * keycnt); + SendCommand(&c); + + UsbCommand resp; + if (!WaitForResponseTimeoutW(CMD_ACK, &resp, MAX(3000, 1000 + 13 * sectorCnt * keycnt * (keyType == 2 ? 2 : 1)), false)) return 1; // timeout: 13 ms / fail auth + if ((resp.arg[0] & 0xff) != 0x01) return 2; + + bool foundAKey = false; + for(int sec = 0; sec < sectorCnt; sec++){ + for(int keyAB = 0; keyAB < 2; keyAB++){ + keyPtr = *(resp.d.asBytes + keyAB * 40 + sec); + if (keyPtr){ + e_sector[sec].foundKey[keyAB] = true; + e_sector[sec].Key[keyAB] = bytes_to_num(keyBlock + (keyPtr - 1) * 6, 6); + foundAKey = true; + } + } + } + return foundAKey ? 0 : 3; +} + +// Compare 16 Bits out of cryptostate +int Compare16Bits(const void * a, const void * b) { + if ((*(uint64_t*)b & 0x00ff000000ff0000) == (*(uint64_t*)a & 0x00ff000000ff0000)) return 0; + else if ((*(uint64_t*)b & 0x00ff000000ff0000) > (*(uint64_t*)a & 0x00ff000000ff0000)) return 1; + else return -1; +} + +typedef + struct { + union { + struct Crypto1State *slhead; + uint64_t *keyhead; + } head; + union { + struct Crypto1State *sltail; + uint64_t *keytail; + } tail; + uint32_t len; + uint32_t uid; + uint32_t blockNo; + uint32_t keyType; + uint32_t nt; + uint32_t ks1; + } StateList_t; + + +// wrapper function for multi-threaded lfsr_recovery32 +void +#ifdef __has_attribute +#if __has_attribute(force_align_arg_pointer) +__attribute__((force_align_arg_pointer)) +#endif +#endif +*nested_worker_thread(void *arg) +{ + struct Crypto1State *p1; + StateList_t *statelist = arg; + + statelist->head.slhead = lfsr_recovery32(statelist->ks1, statelist->nt ^ statelist->uid); + for (p1 = statelist->head.slhead; *(uint64_t *)p1 != 0; p1++); + statelist->len = p1 - statelist->head.slhead; + statelist->tail.sltail = --p1; + qsort(statelist->head.slhead, statelist->len, sizeof(uint64_t), Compare16Bits); + + return statelist->head.slhead; +} + + +int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool calibrate) +{ + uint16_t i; + uint32_t uid; + UsbCommand resp; + + StateList_t statelists[2]; + struct Crypto1State *p1, *p2, *p3, *p4; + + // flush queue + (void)WaitForResponseTimeout(CMD_ACK,NULL,100); + + UsbCommand c = {CMD_MIFARE_NESTED, {blockNo + keyType * 0x100, trgBlockNo + trgKeyType * 0x100, calibrate}}; + memcpy(c.d.asBytes, key, 6); + SendCommand(&c); + + if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { + return -1; + } + + if (resp.arg[0]) { + return resp.arg[0]; // error during nested + } + + memcpy(&uid, resp.d.asBytes, 4); + PrintAndLog("uid:%08x trgbl=%d trgkey=%x", uid, (uint16_t)resp.arg[2] & 0xff, (uint16_t)resp.arg[2] >> 8); + + for (i = 0; i < 2; i++) { + statelists[i].blockNo = resp.arg[2] & 0xff; + statelists[i].keyType = (resp.arg[2] >> 8) & 0xff; + statelists[i].uid = uid; + memcpy(&statelists[i].nt, (void *)(resp.d.asBytes + 4 + i * 8 + 0), 4); + memcpy(&statelists[i].ks1, (void *)(resp.d.asBytes + 4 + i * 8 + 4), 4); + } + + // calc keys + + pthread_t thread_id[2]; + + // create and run worker threads + for (i = 0; i < 2; i++) { + pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]); + } + + // wait for threads to terminate: + for (i = 0; i < 2; i++) { + pthread_join(thread_id[i], (void*)&statelists[i].head.slhead); + } + + + // the first 16 Bits of the cryptostate already contain part of our key. + // Create the intersection of the two lists based on these 16 Bits and + // roll back the cryptostate + p1 = p3 = statelists[0].head.slhead; + p2 = p4 = statelists[1].head.slhead; + while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) { + if (Compare16Bits(p1, p2) == 0) { + struct Crypto1State savestate, *savep = &savestate; + savestate = *p1; + while(Compare16Bits(p1, savep) == 0 && p1 <= statelists[0].tail.sltail) { + *p3 = *p1; + lfsr_rollback_word(p3, statelists[0].nt ^ statelists[0].uid, 0); + p3++; + p1++; + } + savestate = *p2; + while(Compare16Bits(p2, savep) == 0 && p2 <= statelists[1].tail.sltail) { + *p4 = *p2; + lfsr_rollback_word(p4, statelists[1].nt ^ statelists[1].uid, 0); + p4++; + p2++; + } + } + else { + while (Compare16Bits(p1, p2) == -1) p1++; + while (Compare16Bits(p1, p2) == 1) p2++; + } + } + *(uint64_t*)p3 = -1; + *(uint64_t*)p4 = -1; + statelists[0].len = p3 - statelists[0].head.slhead; + statelists[1].len = p4 - statelists[1].head.slhead; + statelists[0].tail.sltail=--p3; + statelists[1].tail.sltail=--p4; + + // the statelists now contain possible keys. The key we are searching for must be in the + // intersection of both lists. Create the intersection: + qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64); + qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64); + statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead); + + memset(resultKey, 0, 6); + // The list may still contain several key candidates. Test each of them with mfCheckKeys + for (i = 0; i < statelists[0].len; i++) { + uint8_t keyBlock[6]; + uint64_t key64; + crypto1_get_lfsr(statelists[0].head.slhead + i, &key64); + num_to_bytes(key64, 6, keyBlock); + key64 = 0; + if (!mfCheckKeys(statelists[0].blockNo, statelists[0].keyType, false, 1, keyBlock, &key64)) { + num_to_bytes(key64, 6, resultKey); + break; + } + } + + free(statelists[0].head.slhead); + free(statelists[1].head.slhead); + + return 0; +} + +// MIFARE +int mfReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *data) { + + UsbCommand c = {CMD_MIFARE_READSC, {sectorNo, keyType, 0}}; + memcpy(c.d.asBytes, key, 6); + clearCommandBuffer(); + SendCommand(&c); + + UsbCommand resp; + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { + uint8_t isOK = resp.arg[0] & 0xff; + + if (isOK) { + memcpy(data, resp.d.asBytes, mfNumBlocksPerSector(sectorNo) * 16); + return 0; + } else { + return 1; + } + } else { + PrintAndLogEx(ERR, "Command execute timeout"); + return 2; + } + + return 0; +} + +// EMULATOR + +int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount) { + UsbCommand c = {CMD_MIFARE_EML_MEMGET, {blockNum, blocksCount, 0}}; + SendCommand(&c); + + UsbCommand resp; + if (!WaitForResponseTimeout(CMD_ACK,&resp,1500)) return 1; + memcpy(data, resp.d.asBytes, blocksCount * 16); + return 0; +} + +int mfEmlSetMem(uint8_t *data, int blockNum, int blocksCount) { + UsbCommand c = {CMD_MIFARE_EML_MEMSET, {blockNum, blocksCount, 0}}; + memcpy(c.d.asBytes, data, blocksCount * 16); + SendCommand(&c); + return 0; +} + +// "MAGIC" CARD + +int mfCGetBlock(uint8_t blockNo, uint8_t *data, uint8_t params) { + uint8_t isOK = 0; + + UsbCommand c = {CMD_MIFARE_CGETBLOCK, {params, 0, blockNo}}; + SendCommand(&c); + + UsbCommand resp; + if (WaitForResponseTimeout(CMD_ACK,&resp,1500)) { + isOK = resp.arg[0] & 0xff; + memcpy(data, resp.d.asBytes, 16); + if (!isOK) return 2; + } else { + PrintAndLog("Command execute timeout"); + return 1; + } + return 0; +} + +int mfCSetBlock(uint8_t blockNo, uint8_t *data, uint8_t *uid, bool wantWipe, uint8_t params) { + + uint8_t isOK = 0; + UsbCommand c = {CMD_MIFARE_CSETBLOCK, {wantWipe, params & (0xFE | (uid == NULL ? 0:1)), blockNo}}; + memcpy(c.d.asBytes, data, 16); + SendCommand(&c); + + UsbCommand resp; + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { + isOK = resp.arg[0] & 0xff; + if (uid != NULL) + memcpy(uid, resp.d.asBytes, 4); + if (!isOK) + return 2; + } else { + PrintAndLog("Command execute timeout"); + return 1; + } + + return 0; +} + +int mfCWipe(uint32_t numSectors, bool gen1b, bool wantWipe, bool wantFill) { + uint8_t isOK = 0; + uint8_t cmdParams = wantWipe + wantFill * 0x02 + gen1b * 0x04; + UsbCommand c = {CMD_MIFARE_CWIPE, {numSectors, cmdParams, 0}}; + SendCommand(&c); + + UsbCommand resp; + WaitForResponse(CMD_ACK,&resp); + isOK = resp.arg[0] & 0xff; + + return isOK; +} + +int mfCSetUID(uint8_t *uid, uint8_t *atqa, uint8_t *sak, uint8_t *oldUID) { + uint8_t oldblock0[16] = {0x00}; + uint8_t block0[16] = {0x00}; + int gen = 0, res; + + gen = mfCIdentify(); + + /* generation 1a magic card by default */ + uint8_t cmdParams = CSETBLOCK_SINGLE_OPER; + if (gen == 2) { + /* generation 1b magic card */ + cmdParams = CSETBLOCK_SINGLE_OPER | CSETBLOCK_MAGIC_1B; + } + + res = mfCGetBlock(0, oldblock0, cmdParams); + + if (res == 0) { + memcpy(block0, oldblock0, 16); + PrintAndLog("old block 0: %s", sprint_hex(block0,16)); + } else { + PrintAndLog("Couldn't get old data. Will write over the last bytes of Block 0."); + } + + // fill in the new values + // UID + memcpy(block0, uid, 4); + // Mifare UID BCC + block0[4] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3]; + // mifare classic SAK(byte 5) and ATQA(byte 6 and 7, reversed) + if (sak != NULL) + block0[5] = sak[0]; + if (atqa != NULL) { + block0[6] = atqa[1]; + block0[7] = atqa[0]; + } + PrintAndLog("new block 0: %s", sprint_hex(block0, 16)); + + res = mfCSetBlock(0, block0, oldUID, false, cmdParams); + if (res) { + PrintAndLog("Can't set block 0. Error: %d", res); + return res; + } + + return 0; +} + +int mfCIdentify() { + UsbCommand c = {CMD_MIFARE_CIDENT, {0, 0, 0}}; + SendCommand(&c); + UsbCommand resp; + WaitForResponse(CMD_ACK,&resp); + + uint8_t isGeneration = resp.arg[0] & 0xff; + switch( isGeneration ){ + case 1: PrintAndLog("Chinese magic backdoor commands (GEN 1a) detected"); break; + case 2: PrintAndLog("Chinese magic backdoor command (GEN 1b) detected"); break; + default: PrintAndLog("No chinese magic backdoor command detected"); break; + } + + return (int) isGeneration; +} + + +// SNIFFER + +// constants +static uint8_t trailerAccessBytes[4] = {0x08, 0x77, 0x8F, 0x00}; + +// variables +char logHexFileName[FILE_PATH_SIZE] = {0x00}; +static uint8_t traceCard[4096] = {0x00}; +static char traceFileName[FILE_PATH_SIZE] = {0x00}; +static int traceState = TRACE_IDLE; +static uint8_t traceCurBlock = 0; +static uint8_t traceCurKey = 0; + +struct Crypto1State *traceCrypto1 = NULL; + +struct Crypto1State *revstate; +uint64_t lfsr; +uint64_t ui64Key; +uint32_t ks2; +uint32_t ks3; + +uint32_t uid; // serial number +uint32_t nt; // tag challenge +uint32_t nt_enc; // encrypted tag challenge +uint8_t nt_enc_par; // encrypted tag challenge parity +uint32_t nr_enc; // encrypted reader challenge +uint32_t ar_enc; // encrypted reader response +uint8_t ar_enc_par; // encrypted reader response parity +uint32_t at_enc; // encrypted tag response +uint8_t at_enc_par; // encrypted tag response parity + +int isTraceCardEmpty(void) { + return ((traceCard[0] == 0) && (traceCard[1] == 0) && (traceCard[2] == 0) && (traceCard[3] == 0)); +} + +int isBlockEmpty(int blockN) { + for (int i = 0; i < 16; i++) + if (traceCard[blockN * 16 + i] != 0) return 0; + + return 1; +} + +int isBlockTrailer(int blockN) { + return ((blockN & 0x03) == 0x03); +} + +int saveTraceCard(void) { + FILE * f; + + if ((!strlen(traceFileName)) || (isTraceCardEmpty())) return 0; + + f = fopen(traceFileName, "w+"); + if ( !f ) return 1; + + for (int i = 0; i < 64; i++) { // blocks + for (int j = 0; j < 16; j++) // bytes + fprintf(f, "%02x", *(traceCard + i * 16 + j)); + if (i < 63) + fprintf(f,"\n"); + } + fclose(f); + return 0; +} + +int loadTraceCard(uint8_t *tuid) { + FILE * f; + char buf[64] = {0x00}; + uint8_t buf8[64] = {0x00}; + int i, blockNum; + + if (!isTraceCardEmpty()) + saveTraceCard(); + + memset(traceCard, 0x00, 4096); + memcpy(traceCard, tuid + 3, 4); + + FillFileNameByUID(traceFileName, tuid, ".eml", 7); + + f = fopen(traceFileName, "r"); + if (!f) return 1; + + blockNum = 0; + + while(!feof(f)){ + + memset(buf, 0, sizeof(buf)); + if (fgets(buf, sizeof(buf), f) == NULL) { + PrintAndLog("File reading error."); + fclose(f); + return 2; + } + + if (strlen(buf) < 32){ + if (feof(f)) break; + PrintAndLog("File content error. Block data must include 32 HEX symbols"); + fclose(f); + return 2; + } + for (i = 0; i < 32; i += 2) + sscanf(&buf[i], "%02x", (unsigned int *)&buf8[i / 2]); + + memcpy(traceCard + blockNum * 16, buf8, 16); + + blockNum++; + } + fclose(f); + + return 0; +} + +int mfTraceInit(uint8_t *tuid, uint8_t *atqa, uint8_t sak, bool wantSaveToEmlFile) { + + if (traceCrypto1) + crypto1_destroy(traceCrypto1); + + traceCrypto1 = NULL; + + if (wantSaveToEmlFile) + loadTraceCard(tuid); + + traceCard[4] = traceCard[0] ^ traceCard[1] ^ traceCard[2] ^ traceCard[3]; + traceCard[5] = sak; + memcpy(&traceCard[6], atqa, 2); + traceCurBlock = 0; + uid = bytes_to_num(tuid + 3, 4); + + traceState = TRACE_IDLE; + + return 0; +} + +void mf_crypto1_decrypt(struct Crypto1State *pcs, uint8_t *data, int len, bool isEncrypted){ + uint8_t bt = 0; + int i; + + if (len != 1) { + for (i = 0; i < len; i++) + data[i] = crypto1_byte(pcs, 0x00, isEncrypted) ^ data[i]; + } else { + bt = 0; + for (i = 0; i < 4; i++) + bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], i)) << i; + + data[0] = bt; + } + return; +} + +bool NTParityCheck(uint32_t ntx) { + if ( + (oddparity8(ntx >> 8 & 0xff) ^ (ntx & 0x01) ^ ((nt_enc_par >> 5) & 0x01) ^ (nt_enc & 0x01)) || + (oddparity8(ntx >> 16 & 0xff) ^ (ntx >> 8 & 0x01) ^ ((nt_enc_par >> 6) & 0x01) ^ (nt_enc >> 8 & 0x01)) || + (oddparity8(ntx >> 24 & 0xff) ^ (ntx >> 16 & 0x01) ^ ((nt_enc_par >> 7) & 0x01) ^ (nt_enc >> 16 & 0x01)) + ) + return false; + + uint32_t ar = prng_successor(ntx, 64); + if ( + (oddparity8(ar >> 8 & 0xff) ^ (ar & 0x01) ^ ((ar_enc_par >> 5) & 0x01) ^ (ar_enc & 0x01)) || + (oddparity8(ar >> 16 & 0xff) ^ (ar >> 8 & 0x01) ^ ((ar_enc_par >> 6) & 0x01) ^ (ar_enc >> 8 & 0x01)) || + (oddparity8(ar >> 24 & 0xff) ^ (ar >> 16 & 0x01) ^ ((ar_enc_par >> 7) & 0x01) ^ (ar_enc >> 16 & 0x01)) + ) + return false; + + uint32_t at = prng_successor(ntx, 96); + if ( + (oddparity8(ar & 0xff) ^ (at >> 24 & 0x01) ^ ((ar_enc_par >> 4) & 0x01) ^ (at_enc >> 24 & 0x01)) || + (oddparity8(at >> 8 & 0xff) ^ (at & 0x01) ^ ((at_enc_par >> 5) & 0x01) ^ (at_enc & 0x01)) || + (oddparity8(at >> 16 & 0xff) ^ (at >> 8 & 0x01) ^ ((at_enc_par >> 6) & 0x01) ^ (at_enc >> 8 & 0x01)) || + (oddparity8(at >> 24 & 0xff) ^ (at >> 16 & 0x01) ^ ((at_enc_par >> 7) & 0x01) ^ (at_enc >> 16 & 0x01)) + ) + return false; + + return true; +} + + +int mfTraceDecode(uint8_t *data_src, int len, uint8_t parity, bool wantSaveToEmlFile) { + uint8_t data[64]; + + if (traceState == TRACE_ERROR) return 1; + if (len > 64) { + traceState = TRACE_ERROR; + return 1; + } + + memcpy(data, data_src, len); + if ((traceCrypto1) && ((traceState == TRACE_IDLE) || (traceState > TRACE_AUTH_OK))) { + mf_crypto1_decrypt(traceCrypto1, data, len, 0); + uint8_t parity[16]; + oddparitybuf(data, len, parity); + PrintAndLog("dec> %s [%s]", sprint_hex(data, len), printBitsPar(parity, len)); + AddLogHex(logHexFileName, "dec> ", data, len); + } + + switch (traceState) { + case TRACE_IDLE: + // check packet crc16! + if ((len >= 4) && (!CheckCrc14443(CRC_14443_A, data, len))) { + PrintAndLog("dec> CRC ERROR!!!"); + AddLogLine(logHexFileName, "dec> ", "CRC ERROR!!!"); + traceState = TRACE_ERROR; // do not decrypt the next commands + return 1; + } + + // AUTHENTICATION + if ((len ==4) && ((data[0] == 0x60) || (data[0] == 0x61))) { + traceState = TRACE_AUTH1; + traceCurBlock = data[1]; + traceCurKey = data[0] == 60 ? 1:0; + return 0; + } + + // READ + if ((len ==4) && ((data[0] == 0x30))) { + traceState = TRACE_READ_DATA; + traceCurBlock = data[1]; + return 0; + } + + // WRITE + if ((len ==4) && ((data[0] == 0xA0))) { + traceState = TRACE_WRITE_OK; + traceCurBlock = data[1]; + return 0; + } + + // HALT + if ((len ==4) && ((data[0] == 0x50) && (data[1] == 0x00))) { + traceState = TRACE_ERROR; // do not decrypt the next commands + return 0; + } + + return 0; + break; + + case TRACE_READ_DATA: + if (len == 18) { + traceState = TRACE_IDLE; + + if (isBlockTrailer(traceCurBlock)) { + memcpy(traceCard + traceCurBlock * 16 + 6, data + 6, 4); + } else { + memcpy(traceCard + traceCurBlock * 16, data, 16); + } + if (wantSaveToEmlFile) saveTraceCard(); + return 0; + } else { + traceState = TRACE_ERROR; + return 1; + } + break; + + case TRACE_WRITE_OK: + if ((len == 1) && (data[0] == 0x0a)) { + traceState = TRACE_WRITE_DATA; + + return 0; + } else { + traceState = TRACE_ERROR; + return 1; + } + break; + + case TRACE_WRITE_DATA: + if (len == 18) { + traceState = TRACE_IDLE; + + memcpy(traceCard + traceCurBlock * 16, data, 16); + if (wantSaveToEmlFile) saveTraceCard(); + return 0; + } else { + traceState = TRACE_ERROR; + return 1; + } + break; + + case TRACE_AUTH1: + if (len == 4) { + traceState = TRACE_AUTH2; + if (!traceCrypto1) { + nt = bytes_to_num(data, 4); + } else { + nt_enc = bytes_to_num(data, 4); + nt_enc_par = parity; + } + return 0; + } else { + traceState = TRACE_ERROR; + return 1; + } + break; + + case TRACE_AUTH2: + if (len == 8) { + traceState = TRACE_AUTH_OK; + + nr_enc = bytes_to_num(data, 4); + ar_enc = bytes_to_num(data + 4, 4); + ar_enc_par = parity << 4; + return 0; + } else { + traceState = TRACE_ERROR; + return 1; + } + break; + + case TRACE_AUTH_OK: + if (len ==4) { + traceState = TRACE_IDLE; + + at_enc = bytes_to_num(data, 4); + at_enc_par = parity; + if (!traceCrypto1) { + + // decode key here) + ks2 = ar_enc ^ prng_successor(nt, 64); + ks3 = at_enc ^ prng_successor(nt, 96); + revstate = lfsr_recovery64(ks2, ks3); + lfsr_rollback_word(revstate, 0, 0); + lfsr_rollback_word(revstate, 0, 0); + lfsr_rollback_word(revstate, nr_enc, 1); + lfsr_rollback_word(revstate, uid ^ nt, 0); + + crypto1_get_lfsr(revstate, &lfsr); + crypto1_destroy(revstate); + ui64Key = lfsr; + printf("key> probable key:%x%x Prng:%s ks2:%08x ks3:%08x\n", + (unsigned int)((lfsr & 0xFFFFFFFF00000000) >> 32), (unsigned int)(lfsr & 0xFFFFFFFF), + validate_prng_nonce(nt) ? "WEAK": "HARDEND", + ks2, + ks3); + AddLogUint64(logHexFileName, "key> ", lfsr); + } else { + if (validate_prng_nonce(nt)) { + struct Crypto1State *pcs; + pcs = crypto1_create(ui64Key); + uint32_t nt1 = crypto1_word(pcs, nt_enc ^ uid, 1) ^ nt_enc; + uint32_t ar = prng_successor(nt1, 64); + uint32_t at = prng_successor(nt1, 96); + printf("key> nested auth uid: %08x nt: %08x nt_parity: %s ar: %08x at: %08x\n", uid, nt1, printBitsPar(&nt_enc_par, 4), ar, at); + uint32_t nr1 = crypto1_word(pcs, nr_enc, 1) ^ nr_enc; + uint32_t ar1 = crypto1_word(pcs, 0, 0) ^ ar_enc; + uint32_t at1 = crypto1_word(pcs, 0, 0) ^ at_enc; + crypto1_destroy(pcs); + printf("key> the same key test. nr1: %08x ar1: %08x at1: %08x \n", nr1, ar1, at1); + + if (NTParityCheck(nt1)) + printf("key> the same key test OK. key=%x%x\n", (unsigned int)((ui64Key & 0xFFFFFFFF00000000) >> 32), (unsigned int)(ui64Key & 0xFFFFFFFF)); + else + printf("key> the same key test. check nt parity error.\n"); + + uint32_t ntc = prng_successor(nt, 90); + uint32_t ntx = 0; + int ntcnt = 0; + for (int i = 0; i < 16383; i++) { + ntc = prng_successor(ntc, 1); + if (NTParityCheck(ntc)){ + if (!ntcnt) + ntx = ntc; + ntcnt++; + } + } + if (ntcnt) + printf("key> nt candidate=%08x nonce distance=%d candidates count=%d\n", ntx, nonce_distance(nt, ntx), ntcnt); + else + printf("key> don't have any nt candidate( \n"); + + nt = ntx; + ks2 = ar_enc ^ prng_successor(ntx, 64); + ks3 = at_enc ^ prng_successor(ntx, 96); + + // decode key + revstate = lfsr_recovery64(ks2, ks3); + lfsr_rollback_word(revstate, 0, 0); + lfsr_rollback_word(revstate, 0, 0); + lfsr_rollback_word(revstate, nr_enc, 1); + lfsr_rollback_word(revstate, uid ^ nt, 0); + + crypto1_get_lfsr(revstate, &lfsr); + crypto1_destroy(revstate); + ui64Key = lfsr; + printf("key> probable key:%x%x ks2:%08x ks3:%08x\n", + (unsigned int)((lfsr & 0xFFFFFFFF00000000) >> 32), (unsigned int)(lfsr & 0xFFFFFFFF), + ks2, + ks3); + AddLogUint64(logHexFileName, "key> ", lfsr); + } else { + printf("key> hardnested not implemented!\n"); + + crypto1_destroy(traceCrypto1); + + // not implemented + traceState = TRACE_ERROR; + } + } + + int blockShift = ((traceCurBlock & 0xFC) + 3) * 16; + if (isBlockEmpty((traceCurBlock & 0xFC) + 3)) memcpy(traceCard + blockShift + 6, trailerAccessBytes, 4); + + if (traceCurKey) { + num_to_bytes(lfsr, 6, traceCard + blockShift + 10); + } else { + num_to_bytes(lfsr, 6, traceCard + blockShift); + } + if (wantSaveToEmlFile) saveTraceCard(); + + if (traceCrypto1) { + crypto1_destroy(traceCrypto1); + } + + // set cryptosystem state + traceCrypto1 = lfsr_recovery64(ks2, ks3); + return 0; + } else { + traceState = TRACE_ERROR; + return 1; + } + break; + + default: + traceState = TRACE_ERROR; + return 1; + } + + return 0; +} + +// DECODING + +int tryDecryptWord(uint32_t nt, uint32_t ar_enc, uint32_t at_enc, uint8_t *data, int len){ + /* + uint32_t nt; // tag challenge + uint32_t ar_enc; // encrypted reader response + uint32_t at_enc; // encrypted tag response + */ + if (traceCrypto1) { + crypto1_destroy(traceCrypto1); + } + ks2 = ar_enc ^ prng_successor(nt, 64); + ks3 = at_enc ^ prng_successor(nt, 96); + traceCrypto1 = lfsr_recovery64(ks2, ks3); + + mf_crypto1_decrypt(traceCrypto1, data, len, 0); + + PrintAndLog("Decrypted data: [%s]", sprint_hex(data,len) ); + crypto1_destroy(traceCrypto1); + return 0; +} + +/** validate_prng_nonce + * Determine if nonce is deterministic. ie: Suspectable to Darkside attack. + * returns + * true = weak prng + * false = hardend prng + */ +bool validate_prng_nonce(uint32_t nonce) { + uint16_t *dist = 0; + uint16_t x, i; + + dist = malloc(2 << 16); + if(!dist) + return -1; + + // init prng table: + for (x = i = 1; i; ++i) { + dist[(x & 0xff) << 8 | x >> 8] = i; + x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; + } + + uint32_t res = (65535 - dist[nonce >> 16] + dist[nonce & 0xffff]) % 65535; + + free(dist); + return (res == 16); +} + +/* Detect Tag Prng, +* function performs a partial AUTH, where it tries to authenticate against block0, key A, but only collects tag nonce. +* the tag nonce is check to see if it has a predictable PRNG. +* @returns +* TRUE if tag uses WEAK prng (ie Now the NACK bug also needs to be present for Darkside attack) +* FALSE is tag uses HARDEND prng (ie hardnested attack possible, with known key) +*/ +int DetectClassicPrng(void){ + + UsbCommand resp, respA; + uint8_t cmd[] = {0x60, 0x00}; // MIFARE_AUTH_KEYA + uint32_t flags = ISO14A_CONNECT | ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS; + + UsbCommand c = {CMD_READER_ISO_14443a, {flags, sizeof(cmd), 0}}; + memcpy(c.d.asBytes, cmd, sizeof(cmd)); + + clearCommandBuffer(); + SendCommand(&c); + if (!WaitForResponseTimeout(CMD_ACK, &resp, 2000)) { + PrintAndLog("PRNG UID: Reply timeout."); + return -1; + } + + // if select tag failed. + if (resp.arg[0] == 0) { + PrintAndLog("PRNG error: selecting tag failed, can't detect prng."); + return -1; + } + + if (!WaitForResponseTimeout(CMD_ACK, &respA, 5000)) { + PrintAndLog("PRNG data: Reply timeout."); + return -1; + } + + // check respA + if (respA.arg[0] != 4) { + PrintAndLog("PRNG data error: Wrong length: %d", respA.arg[0]); + return -1; + } + + uint32_t nonce = bytes_to_num(respA.d.asBytes, respA.arg[0]); + return validate_prng_nonce(nonce); +} diff --git a/client/mifarehost.h b/client/mifare/mifarehost.h similarity index 96% rename from client/mifarehost.h rename to client/mifare/mifarehost.h index 6a37fef1..18a8a5e1 100644 --- a/client/mifarehost.h +++ b/client/mifare/mifarehost.h @@ -1,68 +1,70 @@ -// Merlok, 2011, 2017 -// people from mifare@nethemba.com, 2010 -// -// 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. -//----------------------------------------------------------------------------- -// High frequency ISO14443A commands -//----------------------------------------------------------------------------- - -#ifndef MIFAREHOST_H -#define MIFAREHOST_H - -#include -#include -#include "crapto1/crapto1.h" -#include "util.h" - -// defaults -// timeout in units. (ms * 106)/10 or us*0.0106 -// 5 == 500us -#define MF_CHKKEYS_DEFTIMEOUT 5 - -// mfCSetBlock work flags -#define CSETBLOCK_UID 0x01 -#define CSETBLOCK_WUPC 0x02 -#define CSETBLOCK_HALT 0x04 -#define CSETBLOCK_INIT_FIELD 0x08 -#define CSETBLOCK_RESET_FIELD 0x10 -#define CSETBLOCK_SINGLE_OPER 0x1F -#define CSETBLOCK_MAGIC_1B 0x40 - -typedef struct { - uint64_t Key[2]; - int foundKey[2]; -} sector_t; - -extern char logHexFileName[FILE_PATH_SIZE]; - -extern int mfDarkside(uint64_t *key); -extern int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *ResultKeys, bool calibrate); -extern int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t *keyBlock, uint64_t *key); -extern int mfCheckKeysSec(uint8_t sectorCnt, uint8_t keyType, uint8_t timeout14a, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, sector_t * e_sector); - -extern int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount); -extern int mfEmlSetMem(uint8_t *data, int blockNum, int blocksCount); - -extern int mfCWipe(uint32_t numSectors, bool gen1b, bool wantWipe, bool wantFill); -extern int mfCSetUID(uint8_t *uid, uint8_t *atqa, uint8_t *sak, uint8_t *oldUID); -extern int mfCSetBlock(uint8_t blockNo, uint8_t *data, uint8_t *uid, bool wantWipe, uint8_t params); -extern int mfCGetBlock(uint8_t blockNo, uint8_t *data, uint8_t params); - -extern int mfTraceInit(uint8_t *tuid, uint8_t *atqa, uint8_t sak, bool wantSaveToEmlFile); -extern int mfTraceDecode(uint8_t *data_src, int len, uint8_t parity, bool wantSaveToEmlFile); - -extern int isTraceCardEmpty(void); -extern int isBlockEmpty(int blockN); -extern int isBlockTrailer(int blockN); -extern int loadTraceCard(uint8_t *tuid); -extern int saveTraceCard(void); -extern int tryDecryptWord(uint32_t nt, uint32_t ar_enc, uint32_t at_enc, uint8_t *data, int len); - -extern int mfCIdentify(); -extern int DetectClassicPrng(void); -extern bool validate_prng_nonce(uint32_t nonce); -extern void mf_crypto1_decrypt(struct Crypto1State *pcs, uint8_t *data, int len, bool isEncrypted); - -#endif +// Merlok, 2011, 2017 +// people from mifare@nethemba.com, 2010 +// +// 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. +//----------------------------------------------------------------------------- +// High frequency ISO14443A commands +//----------------------------------------------------------------------------- + +#ifndef MIFAREHOST_H +#define MIFAREHOST_H + +#include +#include +#include "crapto1/crapto1.h" +#include "util.h" + +// defaults +// timeout in units. (ms * 106)/10 or us*0.0106 +// 5 == 500us +#define MF_CHKKEYS_DEFTIMEOUT 5 + +// mfCSetBlock work flags +#define CSETBLOCK_UID 0x01 +#define CSETBLOCK_WUPC 0x02 +#define CSETBLOCK_HALT 0x04 +#define CSETBLOCK_INIT_FIELD 0x08 +#define CSETBLOCK_RESET_FIELD 0x10 +#define CSETBLOCK_SINGLE_OPER 0x1F +#define CSETBLOCK_MAGIC_1B 0x40 + +typedef struct { + uint64_t Key[2]; + int foundKey[2]; +} sector_t; + +extern char logHexFileName[FILE_PATH_SIZE]; + +extern int mfDarkside(uint64_t *key); +extern int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *ResultKeys, bool calibrate); +extern int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t *keyBlock, uint64_t *key); +extern int mfCheckKeysSec(uint8_t sectorCnt, uint8_t keyType, uint8_t timeout14a, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, sector_t * e_sector); + +extern int mfReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *data); + +extern int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount); +extern int mfEmlSetMem(uint8_t *data, int blockNum, int blocksCount); + +extern int mfCWipe(uint32_t numSectors, bool gen1b, bool wantWipe, bool wantFill); +extern int mfCSetUID(uint8_t *uid, uint8_t *atqa, uint8_t *sak, uint8_t *oldUID); +extern int mfCSetBlock(uint8_t blockNo, uint8_t *data, uint8_t *uid, bool wantWipe, uint8_t params); +extern int mfCGetBlock(uint8_t blockNo, uint8_t *data, uint8_t params); + +extern int mfTraceInit(uint8_t *tuid, uint8_t *atqa, uint8_t sak, bool wantSaveToEmlFile); +extern int mfTraceDecode(uint8_t *data_src, int len, uint8_t parity, bool wantSaveToEmlFile); + +extern int isTraceCardEmpty(void); +extern int isBlockEmpty(int blockN); +extern int isBlockTrailer(int blockN); +extern int loadTraceCard(uint8_t *tuid); +extern int saveTraceCard(void); +extern int tryDecryptWord(uint32_t nt, uint32_t ar_enc, uint32_t at_enc, uint8_t *data, int len); + +extern int mfCIdentify(); +extern int DetectClassicPrng(void); +extern bool validate_prng_nonce(uint32_t nonce); +extern void mf_crypto1_decrypt(struct Crypto1State *pcs, uint8_t *data, int len, bool isEncrypted); + +#endif diff --git a/client/mifare/ndef.c b/client/mifare/ndef.c new file mode 100644 index 00000000..adc15839 --- /dev/null +++ b/client/mifare/ndef.c @@ -0,0 +1,359 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2019 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. +//----------------------------------------------------------------------------- +// NFC Data Exchange Format (NDEF) functions +//----------------------------------------------------------------------------- + +#include "ndef.h" +#include "ui.h" +#include "emv/dump.h" +#include "crypto/asn1utils.h" +#include "util.h" +#include + +#define STRBOOL(p) ((p) ? "+" : "-") + +static const char *TypeNameFormat_s[] = { + "Empty Record", + "Well Known Record", + "MIME Media Record", + "Absolute URI Record", + "External Record", + "Unknown Record", + "Unchanged Record", + "n/a" +}; + +static const char *ndefSigType_s[] = { + "Not present", // No signature present + "RSASSA_PSS_SHA_1", // PKCS_1 + "RSASSA_PKCS1_v1_5_WITH_SHA_1", // PKCS_1 + "DSA", + "ECDSA", + "n/a" +}; + +static const char *ndefCertificateFormat_s[] = { + "X_509", + "X9_68", + "n/a" +}; + +static const char *URI_s[] = { + "", // 0x00 + "http://www.", // 0x01 + "https://www.", // 0x02 + "http://", // 0x03 + "https://", // 0x04 + "tel:", // 0x05 + "mailto:", // 0x06 + "ftp://anonymous:anonymous@", // 0x07 + "ftp://ftp.", // 0x08 + "ftps://", // 0x09 + "sftp://", // 0x0A + "smb://", // 0x0B + "nfs://", // 0x0C + "ftp://", // 0x0D + "dav://", // 0x0E + "news:", // 0x0F + "telnet://", // 0x10 + "imap:", // 0x11 + "rtsp://", // 0x12 + "urn:", // 0x13 + "pop:", // 0x14 + "sip:", // 0x15 + "sips:", // 0x16 + "tftp:", // 0x17 + "btspp://", // 0x18 + "btl2cap://", // 0x19 + "btgoep://", // 0x1A + "tcpobex://", // 0x1B + "irdaobex://", // 0x1C + "file://", // 0x1D + "urn:epc:id:", // 0x1E + "urn:epc:tag:", // 0x1F + "urn:epc:pat:", // 0x20 + "urn:epc:raw:", // 0x21 + "urn:epc:", // 0x22 + "urn:nfc:" // 0x23 +}; + +uint16_t ndefTLVGetLength(uint8_t *data, size_t *indx) { + uint16_t len = 0; + if (data[0] == 0xff) { + len = (data[1] << 8) + data[2]; + *indx += 3; + } else { + len = data[0]; + *indx += 1; + } + + return len; +} + +int ndefDecodeHeader(uint8_t *data, size_t datalen, NDEFHeader_t *header) { + header->Type = NULL; + header->Payload = NULL; + header->ID = NULL; + + header->MessageBegin = data[0] & 0x80; + header->MessageEnd = data[0] & 0x40; + header->ChunkFlag = data[0] & 0x20; + header->ShortRecordBit = data[0] & 0x10; + header->IDLenPresent = data[0] & 0x08; + header->TypeNameFormat = data[0] & 0x07; + header->len = 1 + 1 + (header->ShortRecordBit ? 1 : 4) + (header->IDLenPresent ? 1 : 0); // header + typelen + payloadlen + idlen + if (header->len > datalen) + return 1; + + header->TypeLen = data[1]; + header->Type = data + header->len; + + header->PayloadLen = (header->ShortRecordBit ? (data[2]) : ((data[2] << 24) + (data[3] << 16) + (data[4] << 8) + data[5])); + + if (header->IDLenPresent) { + header->IDLen = (header->ShortRecordBit ? (data[3]) : (data[6])); + header->Payload = header->Type + header->TypeLen; + } else { + header->IDLen = 0; + } + + header->Payload = header->Type + header->TypeLen + header->IDLen; + + header->RecLen = header->len + header->TypeLen + header->PayloadLen + header->IDLen; + + if (header->RecLen > datalen) + return 3; + + return 0; +} + +int ndefPrintHeader(NDEFHeader_t *header) { + PrintAndLogEx(INFO, "Header:"); + + PrintAndLogEx(NORMAL, "\tMessage Begin: %s", STRBOOL(header->MessageBegin)); + PrintAndLogEx(NORMAL, "\tMessage End: %s", STRBOOL(header->MessageEnd)); + PrintAndLogEx(NORMAL, "\tChunk Flag: %s", STRBOOL(header->ChunkFlag)); + PrintAndLogEx(NORMAL, "\tShort Record Bit: %s", STRBOOL(header->ShortRecordBit)); + PrintAndLogEx(NORMAL, "\tID Len Present: %s", STRBOOL(header->IDLenPresent)); + PrintAndLogEx(NORMAL, "\tType Name Format: [0x%02x] %s", header->TypeNameFormat, TypeNameFormat_s[header->TypeNameFormat]); + + PrintAndLogEx(NORMAL, "\tHeader length : %d", header->len); + PrintAndLogEx(NORMAL, "\tType length : %d", header->TypeLen); + PrintAndLogEx(NORMAL, "\tPayload length : %d", header->PayloadLen); + PrintAndLogEx(NORMAL, "\tID length : %d", header->IDLen); + PrintAndLogEx(NORMAL, "\tRecord length : %d", header->RecLen); + + return 0; +} + +int ndefDecodeSig(uint8_t *sig, size_t siglen) { + size_t indx = 0; + PrintAndLogEx(NORMAL, "\tsignature version: 0x%02x", sig[0]); + if (sig[0] != 0x01) { + PrintAndLogEx(ERR, "signature version unknown."); + return 1; + } + indx++; + + uint8_t sigType = sig[indx] & 0x7f; + bool sigURI = sig[indx] & 0x80; + + PrintAndLogEx(NORMAL, "\tsignature type: %s", ((sigType < stNA) ? ndefSigType_s[sigType] : ndefSigType_s[stNA])); + PrintAndLogEx(NORMAL, "\tsignature uri: %s", (sigURI ? "present" : "not present")); + + size_t intsiglen = (sig[indx + 1] << 8) + sig[indx + 2]; + // ecdsa 0x04 + if (sigType == stECDSA) { + indx += 3; + PrintAndLogEx(NORMAL, "\tsignature [%d]: %s", intsiglen, sprint_hex_inrow(&sig[indx], intsiglen)); + + uint8_t rval[300] = {0}; + uint8_t sval[300] = {0}; + int res = ecdsa_asn1_get_signature(&sig[indx], intsiglen, rval, sval); + if (!res) { + PrintAndLogEx(NORMAL, "\t\tr: %s", sprint_hex(rval, 32)); + PrintAndLogEx(NORMAL, "\t\ts: %s", sprint_hex(sval, 32)); + } + } + indx += intsiglen; + + if (sigURI) { + size_t intsigurilen = (sig[indx] << 8) + sig[indx + 1]; + indx += 2; + PrintAndLogEx(NORMAL, "\tsignature uri [%d]: %.*s", intsigurilen, intsigurilen, &sig[indx]); + indx += intsigurilen; + } + + uint8_t certFormat = (sig[indx] >> 4) & 0x07; + uint8_t certCount = sig[indx] & 0x0f; + bool certURI = sig[indx] & 0x80; + + PrintAndLogEx(NORMAL, "\tcertificate format: %s", ((certFormat < sfNA) ? ndefCertificateFormat_s[certFormat] : ndefCertificateFormat_s[sfNA])); + PrintAndLogEx(NORMAL, "\tcertificates count: %d", certCount); + + // print certificates + indx++; + for (int i = 0; i < certCount; i++) { + size_t intcertlen = (sig[indx + 1] << 8) + sig[indx + 2]; + indx += 2; + + PrintAndLogEx(NORMAL, "\tcertificate %d [%d]: %s", i + 1, intcertlen, sprint_hex_inrow(&sig[indx], intcertlen)); + indx += intcertlen; + } + + // have certificate uri + if ((indx <= siglen) && certURI) { + size_t inturilen = (sig[indx] << 8) + sig[indx + 1]; + indx += 2; + PrintAndLogEx(NORMAL, "\tcertificate uri [%d]: %.*s", inturilen, inturilen, &sig[indx]); + indx += inturilen; + } + + return 0; +}; + +int ndefDecodePayload(NDEFHeader_t *ndef) { + + switch (ndef->TypeNameFormat) { + case tnfWellKnownRecord: + PrintAndLogEx(INFO, "Well Known Record"); + PrintAndLogEx(NORMAL, "\ttype: %.*s", ndef->TypeLen, ndef->Type); + + if (!strncmp((char *)ndef->Type, "T", ndef->TypeLen)) { + PrintAndLogEx(NORMAL, "\ttext : %.*s", ndef->PayloadLen, ndef->Payload); + } + + if (!strncmp((char *)ndef->Type, "U", ndef->TypeLen)) { + PrintAndLogEx(NORMAL, "\turi : %s%.*s", (ndef->Payload[0] <= 0x23 ? URI_s[ndef->Payload[0]] : "[err]"), ndef->PayloadLen, &ndef->Payload[1]); + } + + if (!strncmp((char *)ndef->Type, "Sig", ndef->TypeLen)) { + ndefDecodeSig(ndef->Payload, ndef->PayloadLen); + } + + break; + case tnfAbsoluteURIRecord: + PrintAndLogEx(INFO, "Absolute URI Record"); + PrintAndLogEx(NORMAL, "\ttype: %.*s", ndef->TypeLen, ndef->Type); + PrintAndLogEx(NORMAL, "\tpayload: %.*s", ndef->PayloadLen, ndef->Payload); + break; + default: + break; + } + return 0; +} + +int ndefRecordDecodeAndPrint(uint8_t *ndefRecord, size_t ndefRecordLen) { + NDEFHeader_t NDEFHeader = {0}; + int res = ndefDecodeHeader(ndefRecord, ndefRecordLen, &NDEFHeader); + if (res) + return res; + + ndefPrintHeader(&NDEFHeader); + + if (NDEFHeader.TypeLen) { + PrintAndLogEx(INFO, "Type data:"); + dump_buffer(NDEFHeader.Type, NDEFHeader.TypeLen, stdout, 1); + } + if (NDEFHeader.IDLen) { + PrintAndLogEx(INFO, "ID data:"); + dump_buffer(NDEFHeader.ID, NDEFHeader.IDLen, stdout, 1); + } + if (NDEFHeader.PayloadLen) { + PrintAndLogEx(INFO, "Payload data:"); + dump_buffer(NDEFHeader.Payload, NDEFHeader.PayloadLen, stdout, 1); + if (NDEFHeader.TypeLen) + ndefDecodePayload(&NDEFHeader); + } + + return 0; +} + +int ndefRecordsDecodeAndPrint(uint8_t *ndefRecord, size_t ndefRecordLen) { + bool firstRec = true; + size_t len = 0; + + while (len < ndefRecordLen) { + NDEFHeader_t NDEFHeader = {0}; + int res = ndefDecodeHeader(&ndefRecord[len], ndefRecordLen - len, &NDEFHeader); + if (res) + return res; + + if (firstRec) { + if (!NDEFHeader.MessageBegin) { + PrintAndLogEx(ERR, "NDEF first record have MessageBegin=false!"); + return 1; + } + firstRec = false; + } + + if (NDEFHeader.MessageEnd && len + NDEFHeader.RecLen != ndefRecordLen) { + PrintAndLogEx(ERR, "NDEF records have wrong length. Must be %d, calculated %d", ndefRecordLen, len + NDEFHeader.RecLen); + return 1; + } + + ndefRecordDecodeAndPrint(&ndefRecord[len], NDEFHeader.RecLen); + + len += NDEFHeader.RecLen; + + if (NDEFHeader.MessageEnd) + break; + } + + return 0; +} + +int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose) { + + size_t indx = 0; + + PrintAndLogEx(INFO, "NDEF decoding:"); + while (indx < ndefLen) { + switch (ndef[indx]) { + case 0x00: { + indx++; + uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); + PrintAndLogEx(INFO, "-- NDEF NULL block."); + if (len) + PrintAndLogEx(WARNING, "NDEF NULL block size must be 0 instead of %d.", len); + indx += len; + break; + } + case 0x03: { + indx++; + uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); + PrintAndLogEx(INFO, "-- NDEF message. len: %d", len); + + int res = ndefRecordsDecodeAndPrint(&ndef[indx], len); + if (res) + return res; + + indx += len; + break; + } + case 0xfd: { + indx++; + uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); + PrintAndLogEx(INFO, "-- NDEF proprietary info. Skipped %d bytes.", len); + indx += len; + break; + } + case 0xfe: { + PrintAndLogEx(INFO, "-- NDEF Terminator. Done."); + return 0; + break; + } + default: { + PrintAndLogEx(ERR, "unknown tag 0x%02x", ndef[indx]); + return 1; + } + } + } + + return 0; +} diff --git a/client/mifare/ndef.h b/client/mifare/ndef.h new file mode 100644 index 00000000..1130ebba --- /dev/null +++ b/client/mifare/ndef.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2019 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. +//----------------------------------------------------------------------------- +// NFC Data Exchange Format (NDEF) functions +//----------------------------------------------------------------------------- + +#ifndef _NDEF_H_ +#define _NDEF_H_ + +#include +#include +#include + +typedef enum { + tnfEmptyRecord = 0x00, + tnfWellKnownRecord = 0x01, + tnfMIMEMediaRecord = 0x02, + tnfAbsoluteURIRecord = 0x03, + tnfExternalRecord = 0x04, + tnfUnknownRecord = 0x05, + tnfUnchangedRecord = 0x06 +} TypeNameFormat_t; + +typedef enum { + stNotPresent = 0x00, + stRSASSA_PSS_SHA_1 = 0x01, + stRSASSA_PKCS1_v1_5_WITH_SHA_1 = 0x02, + stDSA = 0x03, + stECDSA = 0x04, + stNA = 0x05 +} ndefSigType_t; + +typedef enum { + sfX_509 = 0x00, + sfX9_68 = 0x01, + sfNA = 0x02 +} ndefCertificateFormat_t; + +typedef struct { + bool MessageBegin; + bool MessageEnd; + bool ChunkFlag; + bool ShortRecordBit; + bool IDLenPresent; + TypeNameFormat_t TypeNameFormat; + size_t TypeLen; + size_t PayloadLen; + size_t IDLen; + size_t len; + size_t RecLen; + uint8_t *Type; + uint8_t *Payload; + uint8_t *ID; +} NDEFHeader_t; + +extern int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose); + +#endif // _NDEF_H_ diff --git a/client/mifare4.c b/client/mifare4.c deleted file mode 100644 index 419e9b23..00000000 --- a/client/mifare4.c +++ /dev/null @@ -1,311 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (C) 2018 Merlok -// Copyright (C) 2018 drHatson -// -// 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. -//----------------------------------------------------------------------------- -// iso14443-4 mifare commands -//----------------------------------------------------------------------------- - -#include "mifare4.h" -#include -#include -#include "cmdhf14a.h" -#include "util.h" -#include "ui.h" -#include "crypto/libpcrypto.h" - -AccessConditions_t MFAccessConditions[] = { - {0x00, "read AB; write AB; increment AB; decrement transfer restore AB"}, - {0x01, "read AB; decrement transfer restore AB"}, - {0x02, "read AB"}, - {0x03, "read B; write B"}, - {0x04, "read AB; writeB"}, - {0x05, "read B"}, - {0x06, "read AB; write B; increment B; decrement transfer restore AB"}, - {0x07, "none"} -}; - -AccessConditions_t MFAccessConditionsTrailer[] = { - {0x00, "read A by A; read ACCESS by A; read B by A; write B by A"}, - {0x01, "write A by A; read ACCESS by A write ACCESS by A; read B by A; write B by A"}, - {0x02, "read ACCESS by A; read B by A"}, - {0x03, "write A by B; read ACCESS by AB; write ACCESS by B; write B by B"}, - {0x04, "write A by B; read ACCESS by AB; write B by B"}, - {0x05, "read ACCESS by AB; write ACCESS by B"}, - {0x06, "read ACCESS by AB"}, - {0x07, "read ACCESS by AB"} -}; - -char *mfGetAccessConditionsDesc(uint8_t blockn, uint8_t *data) { - static char StaticNone[] = "none"; - - uint8_t data1 = ((data[1] >> 4) & 0x0f) >> blockn; - uint8_t data2 = ((data[2]) & 0x0f) >> blockn; - uint8_t data3 = ((data[2] >> 4) & 0x0f) >> blockn; - - uint8_t cond = (data1 & 0x01) << 2 | (data2 & 0x01) << 1 | (data3 & 0x01); - - if (blockn == 3) { - for (int i = 0; i < ARRAYLEN(MFAccessConditionsTrailer); i++) - if (MFAccessConditionsTrailer[i].cond == cond) { - return MFAccessConditionsTrailer[i].description; - } - } else { - for (int i = 0; i < ARRAYLEN(MFAccessConditions); i++) - if (MFAccessConditions[i].cond == cond) { - return MFAccessConditions[i].description; - } - }; - - return StaticNone; -}; - -int CalculateEncIVCommand(mf4Session *session, uint8_t *iv, bool verbose) { - memcpy(&iv[0], session->TI, 4); - memcpy(&iv[4], &session->R_Ctr, 2); - memcpy(&iv[6], &session->W_Ctr, 2); - memcpy(&iv[8], &session->R_Ctr, 2); - memcpy(&iv[10], &session->W_Ctr, 2); - memcpy(&iv[12], &session->R_Ctr, 2); - memcpy(&iv[14], &session->W_Ctr, 2); - - return 0; -} - -int CalculateEncIVResponse(mf4Session *session, uint8_t *iv, bool verbose) { - memcpy(&iv[0], &session->R_Ctr, 2); - memcpy(&iv[2], &session->W_Ctr, 2); - memcpy(&iv[4], &session->R_Ctr, 2); - memcpy(&iv[6], &session->W_Ctr, 2); - memcpy(&iv[8], &session->R_Ctr, 2); - memcpy(&iv[10], &session->W_Ctr, 2); - memcpy(&iv[12], session->TI, 4); - - return 0; -} - - -int CalculateMAC(mf4Session *session, MACType_t mtype, uint8_t blockNum, uint8_t blockCount, uint8_t *data, int datalen, uint8_t *mac, bool verbose) { - if (!session || !session->Authenticated || !mac || !data || !datalen || datalen < 1) - return 1; - - memset(mac, 0x00, 8); - - uint16_t ctr = session->R_Ctr; - switch(mtype) { - case mtypWriteCmd: - case mtypWriteResp: - ctr = session->W_Ctr; - break; - case mtypReadCmd: - case mtypReadResp: - break; - } - - uint8_t macdata[2049] = {data[0], (ctr & 0xFF), (ctr >> 8), 0}; - int macdatalen = datalen; - memcpy(&macdata[3], session->TI, 4); - - switch(mtype) { - case mtypReadCmd: - memcpy(&macdata[7], &data[1], datalen - 1); - macdatalen = datalen + 6; - break; - case mtypReadResp: - macdata[7] = blockNum; - macdata[8] = 0; - macdata[9] = blockCount; - memcpy(&macdata[10], &data[1], datalen - 1); - macdatalen = datalen + 9; - break; - case mtypWriteCmd: - memcpy(&macdata[7], &data[1], datalen - 1); - macdatalen = datalen + 6; - break; - case mtypWriteResp: - macdatalen = 1 + 6; - break; - } - - if (verbose) - PrintAndLog("MAC data[%d]: %s", macdatalen, sprint_hex(macdata, macdatalen)); - - 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) { - 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 (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); - if (res) { - PrintAndLog("ERROR exchande raw error: %d", res); - DropField(); - return 2; - } - - if (verbose) - PrintAndLog("phase2: %s", sprint_hex(cmd2, 33)); - - res = ExchangeRAW14a(cmd2, sizeof(cmd2), false, true, data, sizeof(data), &datalen); - if (res) { - PrintAndLog("ERROR exchande raw error: %d", res); - DropField(); - return 4; - } - - if (verbose) - PrintAndLog("Authenticated = true; - session->R_Ctr = 0; - session->W_Ctr = 0; - session->KeyNum = keyn[1] + (keyn[0] << 8); - memmove(session->RndA, RndA, 16); - memmove(session->RndB, RndB, 16); - memmove(session->Key, key, 16); - memmove(session->TI, raw, 4); - memmove(session->PICCap2, &raw[20], 6); - memmove(session->PCDCap2, &raw[26], 6); - memmove(session->Kenc, kenc, 16); - memmove(session->Kmac, kmac, 16); - } - - PrintAndLog("Authentication OK"); - - return 0; -} - -// Mifare Memory Structure: up to 32 Sectors with 4 blocks each (1k and 2k cards), -// plus evtl. 8 sectors with 16 blocks each (4k cards) -uint8_t mfNumBlocksPerSector(uint8_t sectorNo) { - if (sectorNo < 32) - return 4; - else - return 16; -} - -uint8_t mfFirstBlockOfSector(uint8_t sectorNo) { - if (sectorNo < 32) - return sectorNo * 4; - else - return 32 * 4 + (sectorNo - 32) * 16; -} - -uint8_t mfSectorTrailer(uint8_t blockNo) { - if (blockNo < 32*4) { - return (blockNo | 0x03); - } else { - return (blockNo | 0x0f); - } -} - -bool mfIsSectorTrailer(uint8_t blockNo) { - return (blockNo == mfSectorTrailer(blockNo)); -} - -uint8_t mfSectorNum(uint8_t blockNo) { - if (blockNo < 32 * 4) - return blockNo / 4; - else - return 32 + (blockNo - 32 * 4) / 16; - -} diff --git a/client/obj/mifare/.dummy b/client/obj/mifare/.dummy new file mode 100644 index 00000000..e69de29b diff --git a/client/scripting.c b/client/scripting.c index ed7ae007..85a788e9 100644 --- a/client/scripting.c +++ b/client/scripting.c @@ -19,7 +19,7 @@ #include "usb_cmd.h" #include "cmdmain.h" #include "util.h" -#include "mifarehost.h" +#include "mifare/mifarehost.h" #include "../common/iso15693tools.h" #include "iso14443crc.h" #include "../common/crc16.h" diff --git a/common/crc.c b/common/crc.c index 0c73474f..b6d7551e 100644 --- a/common/crc.c +++ b/common/crc.c @@ -9,6 +9,20 @@ #include #include +#define BITMASK(X) (1 << (X)) + +uint32_t reflect(uint32_t v, int b) { + uint32_t t = v; + for (int i = 0; i < b; ++i) { + if (t & 1) + v |= BITMASK((b - 1) - i); + else + v &= ~BITMASK((b - 1) - i); + t >>= 1; + } + return v; +} + void crc_init(crc_t *crc, int order, uint32_t polynom, uint32_t initial_value, uint32_t final_xor) { crc->order = order; @@ -54,3 +68,13 @@ uint32_t CRC8Maxim(uint8_t *buff, size_t size) } return crc_finish(&crc); } + +// width=8 poly=0x1d, init=0xc7 (0xe3 - WRONG! but it mentioned in MAD datasheet) refin=false refout=false xorout=0x00 name="CRC-8/MIFARE-MAD" +uint32_t CRC8Mad(uint8_t *buff, size_t size) { + crc_t crc; + crc_init(&crc, 8, reflect(0x1d, 8), reflect(0xc7, 8), 0); + for (int i = 0; i < size; ++i) + crc_update(&crc, reflect(buff[i], 8), 8); + + return reflect(crc_finish(&crc), 8); +} diff --git a/common/crc.h b/common/crc.h index 946be8a1..e2ccd34b 100644 --- a/common/crc.h +++ b/common/crc.h @@ -11,6 +11,7 @@ #include #include +#include typedef struct crc { uint32_t state; @@ -39,6 +40,10 @@ extern uint32_t crc_finish(crc_t *crc); // Calculate CRC-8/Maxim checksum uint32_t CRC8Maxim(uint8_t *buff, size_t size ); + +// Calculate CRC-8 Mifare MAD checksum +uint32_t CRC8Mad(uint8_t *buff, size_t size); + /* Static initialization of a crc structure */ #define CRC_INITIALIZER(_order, _polynom, _initial_value, _final_xor) { \ .state = ((_initial_value) & ((1L<<(_order))-1)), \ diff --git a/include/mifare.h b/include/mifare.h index bb07adcd..144094f0 100644 --- a/include/mifare.h +++ b/include/mifare.h @@ -13,6 +13,12 @@ #include "common.h" +#define MF_KEY_A 0 +#define MF_KEY_B 1 + +#define MF_MAD1_SECTOR 0x00 +#define MF_MAD2_SECTOR 0x10 + //----------------------------------------------------------------------------- // ISO 14443A //----------------------------------------------------------------------------- diff --git a/tools/mfkey/Makefile b/tools/mfkey/Makefile index 9dab37f5..5d6de924 100755 --- a/tools/mfkey/Makefile +++ b/tools/mfkey/Makefile @@ -1,4 +1,4 @@ -VPATH = ../../common ../../common/crapto1 ../../client +VPATH = ../../common ../../common/crapto1 ../../client ../../client/mifare CC = gcc LD = gcc CFLAGS += -std=c99 -D_ISOC99_SOURCE -I../../include -I../../common -I../../client -Wall -O3 diff --git a/tools/mfkey/mfkey32.c b/tools/mfkey/mfkey32.c index d84305d0..1f7747b5 100755 --- a/tools/mfkey/mfkey32.c +++ b/tools/mfkey/mfkey32.c @@ -3,7 +3,7 @@ #include #include #include "crapto1/crapto1.h" -#include "mfkey.h" +#include "mifare/mfkey.h" #include "util_posix.h" -- 2.39.5