From b314d1ef1c8ae7a5d69dbbab9247af6bcd7864a3 Mon Sep 17 00:00:00 2001 From: DidierA <1620015+DidierA@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:15:07 +0200 Subject: [PATCH] Add MIFARE Plus commands in trace list --- CHANGELOG.md | 1 + client/src/cmdhflist.c | 224 +++++++++++++++++++++++++++++++++++++++++ client/src/cmdhflist.h | 2 + client/src/cmdhfmfp.c | 2 +- client/src/cmdtrace.c | 19 ++-- doc/commands.json | 11 +- include/protocols.h | 53 ++++++++++ 7 files changed, 300 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5711c1090..292bd4c12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added `hf mfp list` - interprets MIFARE Plus commands in traces (@DidierA) - Changed `hf legic sim` - loop and return codes on deviceside updated to DEFINES (@iceman1001) - Changed `hf legic einfo` - now accepts the three different cardsizes as params (@iceman1001) - Fix `lf cotag reader -1` - now doesn't fail (@iceman1001) diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index 5ddd3f1fa..eea8378ae 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -1196,6 +1196,230 @@ void annotateMfDesfire(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) { } } } +// codes for which no data is interpreted, returns the message to print. +const char *mfpGetAnnotationForCode(uint8_t code) { + struct mfp_code_msg { + uint8_t code; + const char *annotation; + } messages[] = { + { MFP_GETVERSION, "GET VERSION"}, + { MFP_ADDITIONALFRAME, "NEXT FRAME"}, + { MFP_AUTHENTICATENONFIRST, "FOLLOWING AUTH"}, + { MFP_AUTHENTICATECONTINUE, "SECOND AUTH STEP"}, + { MFP_RESETAUTH, "RESET AUTH"}, + { MFP_COMMITPERSO, "COMMIT PERSO"}, + { MFP_VCSUPPORTLASTISOL3, "CHECK VIRTUAL CARD"}, + { MFP_ISOSELECT, "SELECT VIRTUAL CARD"}, + { MFP_SETCONFIGSL1, "SET CONFIG SL1"}, + { MFP_MF_PERSONALIZEUIDUSAGE, "PERSONALIZE UID USAGE"}, + { MFP_READ_SIG, "READ SIGNATURE"}, + { MFDES_PREPARE_PC, "PREPARE PROXIMITY CHECK"}, + { MFDES_PROXIMITY_CHECK, "PROXIMITY CHECK"}, + { MFDES_VERIFY_PC, "VERIFY PROXIMITY CHECK"}, + { 0, NULL} + } ; + + for (struct mfp_code_msg *p=messages ; p->annotation != NULL ; p++) { + if (p->code == code) { + return p->annotation ; + } + } + return NULL ; +} + +// MIFARE Plus +void annotateMfPlus(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) { + + // If we are in Mifare Classic Authenticated mode, all the work has already be done elsewhere + if ((MifareAuthState != masNone) && (MifareAuthState != masError)) { + return ; + } + + // it's basically a ISO14443a tag, so try annotation from there + if (applyIso14443a(exp, size, cmd, cmdsize, false) == PM3_SUCCESS) { + return ; + } + + // ok this part is copy paste from annotateMfDesfire, it seems to work for MIFARE Plus also + if (((cmd[0] & 0xC0) == 0x00) && (cmdsize > 2)) { + + // PCB [CID] [NAD] [INF] CRC CRC + int pos = 1; + if ((cmd[0] & 0x08) == 0x08) // cid byte following + pos++; + + if ((cmd[0] & 0x04) == 0x04) // nad byte following + pos++; + + for (uint8_t i = 0; i < 2; i++, pos++) { + bool found_annotation = true; + + uint8_t *data = cmd + pos + 1; + // if the byte prior to the command is 90 the command is wrapped, so data starts 3 bytes later + if (i > 0 && cmd[pos - 1] == 0x90) { + data += 3; + } + uint8_t data_size = 0; + if (cmdsize > (data - cmd)) { + data_size = cmdsize - (data - cmd); + } + + // Messages for commands that do not need args are treated first + const char *annotation = mfpGetAnnotationForCode(cmd[pos]) ; + if (annotation != NULL) { + snprintf(exp, size, "%s", annotation) ; + break ; + } + + switch (cmd[pos]) { + case MFP_AUTHENTICATEFIRST: + case MFP_AUTHENTICATEFIRST_VARIANT: + if (data_size > 1) { + // key : uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0); + uint16_t uKeyNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "FIRST AUTH (Keynr 0x%04X: %c sector %d)", uKeyNum, uKeyNum & 0x0001 ? 'B' : 'A', (uKeyNum - 0x4000)/2 ); + } else { + snprintf(exp, size, "FIRST AUTH") ; + } + break; + + case MFP_WRITEPERSO: + if (data_size > 1) { + uint16_t uKeyNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "WRITE PERSO (Keynr 0x%04X)", uKeyNum); + } else { + snprintf(exp, size, "WRITE PERSO"); + } + break; + + case MFP_READENCRYPTEDNOMAC_MACED: + case MFP_READENCRYPTEDMAC_MACED: + case MFP_READENCRYPTEDNOMAC_UNMACED: + case MFP_READENCRYPTEDMAC_UNMACED: + if (data_size > 2) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + uint8_t uQty = data[2] ; + if (uQty != 1) { + snprintf(exp, size, "READ ENCRYPTED(%u-%u)", uBlockNum, uBlockNum+uQty-1); + } else { + snprintf(exp, size, "READ ENCRYPTED(%u)", uBlockNum); + } + } else { + snprintf(exp, size, "READ ENCRYPTED ?"); + } + break; + + case MFP_READPLAINNOMAC_MACED: + case MFP_READPLAINMAC_MACED: + case MFP_READPLAINNOMAC_UNMACED: + case MFP_READPLAINMAC_UNMACED: + if (data_size > 2) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + uint8_t uQty = data[2] ; + if (uQty != 1) { + snprintf(exp, size, "READ PLAIN(%u-%u)", uBlockNum, uBlockNum+uQty-1); + } else { + snprintf(exp, size, "READ PLAIN(%u)", uBlockNum); + } + } else { + snprintf(exp, size, "READ PLAIN ?"); + } + break; + + case MFP_WRITEPLAINNOMAC : + case MFP_WRITEPLAINMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "WRITE PLAIN(%u)", uBlockNum); + } else { + snprintf(exp, size, "WRITE PLAIN ?"); + } + break; + + case MFP_WRITEENCRYPTEDNOMAC: + case MFP_WRITEENCRYPTEDMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "WRITE ENCRYPTED(%u)", uBlockNum); + } else { + snprintf(exp, size, "WRITE ENCRYPTED ?"); + } + break; + + case MFP_INCREMENTNOMAC : + case MFP_INCREMENTMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "INCREMENT(%u)", uBlockNum); + } else { + snprintf(exp, size, "INCREMENT ?"); + } + break; + + case MFP_DECREMENTNOMAC : + case MFP_DECREMENTMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "DECREMENT(%u)", uBlockNum); + } else { + snprintf(exp, size, "DECREMENT ?"); + } + break; + + case MFP_TRANSFERNOMAC : + case MFP_TRANSFERMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "TRANSFER(%u)", uBlockNum); + } else { + snprintf(exp, size, "TRANSFER ?"); + } + break; + + case MFP_INCREMENTTRANSFERNOMAC: + case MFP_INCREMENTTRANSFERMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "INCREMENT, TRANSFER(%u)", uBlockNum); + } else { + snprintf(exp, size, "INCREMENT, TRANSFER ?"); + } + break; + + case MFP_DECREMENTTRANSFERNOMAC: + case MFP_DECREMENTTRANSFERMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "DECREMENT, TRANSFER(%u)", uBlockNum); + } else { + snprintf(exp, size, "DECREMENT, TRANSFER ?"); + } + break; + + case MFP_RESTORENOMAC : + case MFP_RESTOREMAC : + if (data_size > 1) { + uint16_t uBlockNum = MemLeToUint2byte(data) ; + snprintf(exp, size, "RESTORE(%u)", uBlockNum); + } else { + snprintf(exp, size, "RESTORE ?"); + } + break; + + default: + found_annotation = false; + break; + } + if (found_annotation) { + break; + } + } + } else { + // anything else + snprintf(exp, size, "?"); + } +} + /** 06 00 = INITIATE diff --git a/client/src/cmdhflist.h b/client/src/cmdhflist.h index 6499938eb..7c7e09ffb 100644 --- a/client/src/cmdhflist.h +++ b/client/src/cmdhflist.h @@ -56,6 +56,8 @@ void annotateIso7816(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); void annotateIso14443b(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); void annotateIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response); void annotateMfDesfire(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); +const char *mfpGetAnnotationForCode(uint8_t code); +void annotateMfPlus(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); void annotateMifare(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, const uint8_t *parity, uint8_t paritysize, bool isResponse); void annotateLTO(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 52f3da57e..e8e2c7226 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1811,7 +1811,7 @@ static int CmdHFMFPNDEFWrite(const char *Cmd) { } static int CmdHFMFPList(const char *Cmd) { - return CmdTraceListAlias(Cmd, "hf mf", "mf"); + return CmdTraceListAlias(Cmd, "hf mfp", "mfp"); } static command_t CommandTable[] = { diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 326a646b9..0d47d2d7c 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -533,6 +533,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr crcStatus = !felica_CRC_check(frame + 2, data_len - 4); break; case PROTO_MIFARE: + case PROTO_MFPLUS: crcStatus = mifare_CRC_check(hdr->isResponse, frame, data_len); break; case ISO_14443A: @@ -603,7 +604,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr && protocol != FELICA && protocol != LTO && protocol != PROTO_CRYPTORF - && (hdr->isResponse || protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == SEOS) + && (hdr->isResponse || protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == PROTO_MFPLUS || protocol == SEOS) && (oddparity8(frame[j]) != ((parityBits >> (7 - (j & 0x0007))) & 0x01))) { snprintf(line[j / 18] + ((j % 18) * 4), 120, "%02x! ", frame[j]); @@ -701,6 +702,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr // mark short bytes (less than 8 Bit + Parity) if (protocol == ISO_14443A || protocol == PROTO_MIFARE || + protocol == PROTO_MFPLUS || protocol == THINFILM) { // approximated with 128 * (9 * data_len); @@ -747,6 +749,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr annotateIso14443a(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); break; case PROTO_MIFARE: + case PROTO_MFPLUS: annotateMifare(explanation, sizeof(explanation), frame, data_len, parityBytes, TRACELOG_PARITY_LEN(hdr), hdr->isResponse); break; case PROTO_HITAG1: @@ -766,7 +769,6 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr } if (hdr->isResponse == false) { - switch (protocol) { case LEGIC: annotateLegic(explanation, sizeof(explanation), frame, data_len); @@ -774,6 +776,9 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case MFDES: annotateMfDesfire(explanation, sizeof(explanation), frame, data_len); break; + case PROTO_MFPLUS: + annotateMfPlus(explanation, sizeof(explanation), frame, data_len); + break; case ISO_14443B: annotateIso14443b(explanation, sizeof(explanation), frame, data_len); break; @@ -901,7 +906,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr } } - if (protocol == PROTO_MIFARE) { + if (protocol == PROTO_MIFARE || protocol == PROTO_MFPLUS) { uint8_t mfData[32] = {0}; size_t mfDataLen = 0; if (DecodeMifareData(frame, data_len, parityBytes, hdr->isResponse, mfData, &mfDataLen, mfDicKeys, mfDicKeysCount)) { @@ -1200,6 +1205,7 @@ int CmdTraceList(const char *Cmd) { "trace list -t seos -> interpret as " _YELLOW_("SEOS") "\n" "trace list -t thinfilm -> interpret as " _YELLOW_("Thinfilm") "\n" "trace list -t topaz -> interpret as " _YELLOW_("Topaz") "\n" + "trace list -t mfp -> interpret as " _YELLOW_("MIFARE Plus") "\n" "\n" "trace list -t mf -f mfc_default_keys.dic -> use default dictionary file\n" "trace list -t 14a --frame -> show frame delay times\n" @@ -1266,6 +1272,7 @@ int CmdTraceList(const char *Cmd) { else if (strcmp(type, "seos") == 0) protocol = SEOS; else if (strcmp(type, "thinfilm") == 0) protocol = THINFILM; else if (strcmp(type, "topaz") == 0) protocol = TOPAZ; + else if (strcmp(type, "mfp") == 0) protocol = PROTO_MFPLUS; else if (strcmp(type, "") == 0) protocol = -1; else { PrintAndLogEx(FAILED, "Unknown protocol \"%s\"", type); @@ -1304,7 +1311,7 @@ int CmdTraceList(const char *Cmd) { PrintAndLogEx(INFO, _YELLOW_("start") " = start of start frame " _YELLOW_("end") " = end of frame. " _YELLOW_("src") " = source of transfer"); } - if (protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == MFDES || protocol == TOPAZ || protocol == LTO) { + if (protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == MFDES || protocol == PROTO_MFPLUS || protocol == TOPAZ || protocol == LTO) { if (use_us) PrintAndLogEx(INFO, _YELLOW_("ISO14443A") " - all times are in microseconds"); else @@ -1354,7 +1361,7 @@ int CmdTraceList(const char *Cmd) { uint32_t dicKeysCount = 0; bool dictionaryLoad = false; - if (protocol == PROTO_MIFARE) { + if (protocol == PROTO_MIFARE || protocol == PROTO_MFPLUS) { if (diclen > 0) { uint8_t *keyBlock = NULL; int res = loadFileDICTIONARY_safe(dictionary, (void **) &keyBlock, 6, &dicKeysCount); @@ -1387,7 +1394,7 @@ int CmdTraceList(const char *Cmd) { PrintAndLogEx(NORMAL, "------------+------------+-----+-------------------------------------------------------------------------+-----+--------------------"); // clean authentication data used with the mifare classic decrypt fct - if (protocol == ISO_14443A || protocol == PROTO_MIFARE) + if (protocol == ISO_14443A || protocol == PROTO_MIFARE || protocol == PROTO_MFPLUS) ClearAuthData(); uint32_t previous_EOT = 0; diff --git a/doc/commands.json b/doc/commands.json index 5686c7542..0618f5152 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6159,10 +6159,10 @@ }, "hf mfp list": { "command": "hf mfp list", - "description": "Alias of `trace list -t mf` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "Alias of `trace list -t mfp` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ - "hf mf list --frame -> show frame delay times", - "hf mf list -1 -> use trace buffer" + "hf mfp list --frame -> show frame delay times", + "hf mfp list -1 -> use trace buffer" ], "offline": true, "options": [ @@ -6176,7 +6176,7 @@ "or to import into Wireshark using encapsulation type \"ISO 14443\"", "-f, --file filename of dictionary" ], - "usage": "hf mf list [-h1crux] [--frame] [-f ]" + "usage": "hf mfp list [-h1crux] [--frame] [-f ]" }, "hf mfp mad": { "command": "hf mfp mad", @@ -11488,6 +11488,7 @@ "trace list -t seos -> interpret as SEOS", "trace list -t thinfilm -> interpret as Thinfilm", "trace list -t topaz -> interpret as Topaz", + "trace list -t mfp -> interpret as MIFARE Plus", "", "trace list -t mf -f mfc_default_keys.dic -> use default dictionary file", "trace list -t 14a --frame -> show frame delay times", @@ -11694,6 +11695,6 @@ "metadata": { "commands_extracted": 679, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2023-07-17T15:46:12" + "extracted_on": "2023-07-19T19:11:10" } } \ No newline at end of file diff --git a/include/protocols.h b/include/protocols.h index 721dccaf5..772105c9d 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -428,6 +428,7 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define PROTO_HITAGS 14 #define PROTO_CRYPTORF 15 #define SEOS 16 +#define PROTO_MFPLUS 17 // Picopass fuses #define FUSE_FPERS 0x80 @@ -620,6 +621,58 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MFDES_E_FILE_NOT_FOUND 0xF0 #define MFDES_E_FILE_INTEGRITY 0xF1 +// MIFARE PLus EV2 Command set +// source: https://www.nxp.com/docs/en/data-sheet/MF1P(H)x2.pdf in Look-Up Tables + +#define MFP_READ_SIG 0x3C // same as DESFIRE +#define MFP_WRITEPERSO 0xA8 +#define MFP_COMMITPERSO 0xAA + +#define MFP_AUTHENTICATEFIRST 0x70 +#define MFP_AUTHENTICATEFIRST_VARIANT 0x73 +#define MFP_AUTHENTICATENONFIRST 0x76 +#define MFP_AUTHENTICATECONTINUE 0x72 +#define MFP_AUTHENTICATESECTORSWITCH 0x7A +#define MFP_RESETAUTH 0x78 + +#define MFP_VCSUPPORTLASTISOL3 0x4B +#define MFP_ISOSELECT 0xA4 + +#define MFP_GETVERSION 0x60 // same as DESFIRE +#define MFP_ADDITIONALFRAME 0xAF +#define MFP_SETCONFIGSL1 0x44 +#define MFP_MF_PERSONALIZEUIDUSAGE 0x40 + +// read commands +#define MFP_READENCRYPTEDNOMAC_MACED 0X30 +#define MFP_READENCRYPTEDMAC_MACED 0x31 +#define MFP_READPLAINNOMAC_MACED 0x32 +#define MFP_READPLAINMAC_MACED 0x33 +#define MFP_READENCRYPTEDNOMAC_UNMACED 0x34 +#define MFP_READENCRYPTEDMAC_UNMACED 0X35 +#define MFP_READPLAINNOMAC_UNMACED 0x36 +#define MFP_READPLAINMAC_UNMACED 0x37 + +// write commands +#define MFP_WRITEENCRYPTEDNOMAC 0xA0 +#define MFP_WRITEENCRYPTEDMAC 0xA1 +#define MFP_WRITEPLAINNOMAC 0xA2 +#define MFP_WRITEPLAINMAC 0xA3 + +// value commands +#define MFP_INCREMENTNOMAC 0xB0 +#define MFP_INCREMENTMAC 0xB1 +#define MFP_DECREMENTNOMAC 0xB2 +#define MFP_DECREMENTMAC 0xB3 +#define MFP_TRANSFERNOMAC 0xB4 +#define MFP_TRANSFERMAC 0xB5 +#define MFP_INCREMENTTRANSFERNOMAC 0xB6 +#define MFP_INCREMENTTRANSFERMAC 0xB7 +#define MFP_DECREMENTTRANSFERNOMAC 0xB8 +#define MFP_DECREMENTTRANSFERMAC 0xB9 +#define MFP_RESTORENOMAC 0xC2 +#define MFP_RESTOREMAC 0xC3 + // LEGIC Commands #define LEGIC_MIM_22 0x0D