fix one too small
[RRG-proxmark3.git] / client / src / cmdhfmfp.c
blob11f1ce89987642a3ac48b2ead59b7a9712aae80c
1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
16 // High frequency MIFARE Plus commands
17 //-----------------------------------------------------------------------------
19 #include "cmdhfmfp.h"
20 #include <string.h>
21 #include "cmdparser.h" // command_t
22 #include "commonutil.h" // ARRAYLEN
23 #include "comms.h"
24 #include "ui.h"
25 #include "util.h"
26 #include "cmdhf14a.h"
27 #include "mifare/mifare4.h"
28 #include "mifare/mad.h"
29 #include "nfc/ndef.h"
30 #include "cliparser.h"
31 #include "mifare/mifaredefault.h"
32 #include "util_posix.h"
33 #include "fileutils.h"
34 #include "protocols.h"
35 #include "crypto/libpcrypto.h"
36 #include "cmdhfmf.h" // printblock, header
37 #include "cmdtrace.h"
39 static const uint8_t mfp_default_key[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
40 static uint16_t mfp_card_adresses[] = {0x9000, 0x9001, 0x9002, 0x9003, 0x9004, 0x9006, 0x9007, 0xA000, 0xA001, 0xA080, 0xA081, 0xC000, 0xC001};
42 #define MFP_KEY_FILE_SIZE 14 + (2 * 64 * (AES_KEY_LEN + 1))
44 static int CmdHelp(const char *Cmd);
47 The 7 MSBits (= n) code the storage size itself based on 2^n,
48 the LSBit is set to '0' if the size is exactly 2^n
49 and set to '1' if the storage size is between 2^n and 2^(n+1).
50 For this version of DESFire the 7 MSBits are set to 0x0C (2^12 = 4096) and the LSBit is '0'.
52 static char *getCardSizeStr(uint8_t fsize) {
54 static char buf[40] = {0x00};
55 char *retStr = buf;
57 uint16_t usize = 1 << ((fsize >> 1) + 1);
58 uint16_t lsize = 1 << (fsize >> 1);
60 // is LSB set?
61 if (fsize & 1)
62 snprintf(retStr, sizeof(buf), "0x%02X ( " _GREEN_("%d - %d bytes") " )", fsize, usize, lsize);
63 else
64 snprintf(retStr, sizeof(buf), "0x%02X ( " _GREEN_("%d bytes") " )", fsize, lsize);
65 return buf;
68 static char *getProtocolStr(uint8_t id, bool hw) {
70 static char buf[50] = {0x00};
71 char *retStr = buf;
73 if (id == 0x04) {
74 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("ISO 14443-3 MIFARE, 14443-4") " )", id);
75 } else if (id == 0x05) {
76 if (hw)
77 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("ISO 14443-2, 14443-3") " )", id);
78 else
79 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("ISO 14443-3, 14443-4") " )", id);
80 } else {
81 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Unknown") " )", id);
83 return buf;
86 static char *getVersionStr(uint8_t type, uint8_t major, uint8_t minor) {
88 static char buf[40] = {0x00};
89 char *retStr = buf;
91 if (type == 0x01 && major == 0x00)
92 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire MF3ICD40") " )", major, minor);
93 else if (major == 0x10 && minor == 0x00)
94 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("NTAG413DNA") " )", major, minor);
95 else if (type == 0x01 && major == 0x01 && minor == 0x00)
96 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV1") " )", major, minor);
97 else if (type == 0x01 && major == 0x12 && minor == 0x00)
98 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV2") " )", major, minor);
99 else if (type == 0x01 && major == 0x22 && minor == 0x00)
100 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV2 XL") " )", major, minor);
101 else if (type == 0x01 && major == 0x42 && minor == 0x00)
102 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV2") " )", major, minor);
103 else if (type == 0x01 && major == 0x33 && minor == 0x00)
104 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire EV3") " )", major, minor);
105 else if (type == 0x01 && major == 0x30 && minor == 0x00)
106 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("DESFire Light") " )", major, minor);
107 else if (type == 0x02 && major == 0x11 && minor == 0x00)
108 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("Plus EV1") " )", major, minor);
109 else if (type == 0x02 && major == 0x22 && minor == 0x00)
110 snprintf(retStr, sizeof(buf), "%x.%x ( " _GREEN_("Plus EV2") " )", major, minor);
111 else
112 snprintf(retStr, sizeof(buf), "%x.%x ( " _YELLOW_("Unknown") " )", major, minor);
113 return buf;
116 static char *getTypeStr(uint8_t type) {
118 static char buf[40] = {0x00};
119 char *retStr = buf;
121 switch (type) {
122 case 0x01:
123 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("DESFire") " )", type);
124 break;
125 case 0x02:
126 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Plus") " )", type);
127 break;
128 case 0x03:
129 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Ultralight") " )", type);
130 break;
131 case 0x04:
132 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("NTAG") " )", type);
133 break;
134 case 0x81:
135 snprintf(retStr, sizeof(buf), "0x%02X ( " _YELLOW_("Smartcard") " )", type);
136 break;
137 default:
138 break;
140 return buf;
143 static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) {
145 // DESFire MF3ICD40
146 if (type == 0x01 && major == 0x00 && minor == 0x02)
147 return DESFIRE_MF3ICD40;
149 // DESFire EV1
150 if (type == 0x01 && major == 0x01 && minor == 0x00)
151 return DESFIRE_EV1;
153 // DESFire EV2
154 if (type == 0x01 && major == 0x12 && minor == 0x00)
155 return DESFIRE_EV2;
157 if (type == 0x01 && major == 0x22 && minor == 0x00)
158 return DESFIRE_EV2_XL;
160 // DESFire EV3
161 if (type == 0x01 && major == 0x33 && minor == 0x00)
162 return DESFIRE_EV3;
164 // DESFire Light
165 if (type == 0x08 && major == 0x30 && minor == 0x00)
166 return DESFIRE_LIGHT;
168 // combo card DESFire / EMV
169 if (type == 0x81 && major == 0x42 && minor == 0x00)
170 return DESFIRE_EV2;
172 // Plus EV1
173 if (type == 0x02 && major == 0x11 && minor == 0x00)
174 return PLUS_EV1;
176 // Plus Ev2
177 if (type == 0x02 && major == 0x22 && minor == 0x00)
178 return PLUS_EV2;
180 // NTAG 413 DNA
181 if (type == 0x04 && major == 0x10 && minor == 0x00)
182 return NTAG413DNA;
184 // NTAG 424
185 if (type == 0x04 && major == 0x30 && minor == 0x00)
186 return NTAG424;
188 return MFP_UNKNOWN;
191 // --- GET SIGNATURE
192 static int plus_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, int signature_len) {
194 // ref: MIFARE Plus EV1 Originality Signature Validation
195 #define PUBLIC_PLUS_ECDA_KEYLEN 57
196 const ecdsa_publickey_t nxp_plus_public_keys[] = {
197 {"MIFARE Plus EV1", "044409ADC42F91A8394066BA83D872FB1D16803734E911170412DDF8BAD1A4DADFD0416291AFE1C748253925DA39A5F39A1C557FFACD34C62E"},
198 {"MIFARE Plus Ev2", "04BB49AE4447E6B1B6D21C098C1538B594A11A4A1DBF3D5E673DEACDEB3CC512D1C08AFA1A2768CE20A200BACD2DC7804CD7523A0131ABF607"},
199 {"MIFARE Plus Troika", "040F732E0EA7DF2B38F791BF89425BF7DCDF3EE4D976669E3831F324FF15751BD52AFF1782F72FF2731EEAD5F63ABE7D126E03C856FFB942AF"}
202 uint8_t i;
203 bool is_valid = false;
205 for (i = 0; i < ARRAYLEN(nxp_plus_public_keys); i++) {
207 int dl = 0;
208 uint8_t key[PUBLIC_PLUS_ECDA_KEYLEN];
209 param_gethex_to_eol(nxp_plus_public_keys[i].value, 0, key, PUBLIC_PLUS_ECDA_KEYLEN, &dl);
211 int res = ecdsa_signature_r_s_verify(MBEDTLS_ECP_DP_SECP224R1, key, uid, uidlen, signature, signature_len, false);
212 is_valid = (res == 0);
213 if (is_valid)
214 break;
217 PrintAndLogEx(NORMAL, "");
218 PrintAndLogEx(INFO, "--- " _CYAN_("Tag Signature"));
220 if (is_valid == false || i == ARRAYLEN(nxp_plus_public_keys)) {
221 PrintAndLogEx(INFO, " Elliptic curve parameters: NID_secp224r1");
222 PrintAndLogEx(INFO, " TAG IC Signature: %s", sprint_hex_inrow(signature, 16));
223 PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 16, 16));
224 PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 32, 16));
225 PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 48, signature_len - 48));
226 PrintAndLogEx(SUCCESS, " Signature verification: " _RED_("failed"));
227 return PM3_ESOFT;
230 PrintAndLogEx(INFO, " IC signature public key name: " _GREEN_("%s"), nxp_plus_public_keys[i].desc);
231 PrintAndLogEx(INFO, "IC signature public key value: %.32s", nxp_plus_public_keys[i].value);
232 PrintAndLogEx(INFO, " : %.32s", nxp_plus_public_keys[i].value + 32);
233 PrintAndLogEx(INFO, " : %.32s", nxp_plus_public_keys[i].value + 64);
234 PrintAndLogEx(INFO, " : %.32s", nxp_plus_public_keys[i].value + 96);
235 PrintAndLogEx(INFO, " Elliptic curve parameters: NID_secp224r1");
236 PrintAndLogEx(INFO, " TAG IC Signature: %s", sprint_hex_inrow(signature, 16));
237 PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 16, 16));
238 PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 32, 16));
239 PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 48, signature_len - 48));
240 PrintAndLogEx(SUCCESS, " Signature verification: " _GREEN_("successful"));
241 return PM3_SUCCESS;
244 static int get_plus_signature(uint8_t *signature, int *signature_len) {
246 mfpSetVerboseMode(false);
248 uint8_t data[59] = {0};
249 int resplen = 0, retval = PM3_SUCCESS;
250 MFPGetSignature(true, false, data, sizeof(data), &resplen);
252 if (resplen == 59) {
253 memcpy(signature, data + 1, 56);
254 *signature_len = 56;
255 } else {
256 *signature_len = 0;
257 retval = PM3_ESOFT;
260 return retval;
263 // GET VERSION
264 static int plus_print_version(uint8_t *version) {
265 PrintAndLogEx(SUCCESS, "UID: " _GREEN_("%s"), sprint_hex(version + 14, 7));
266 PrintAndLogEx(SUCCESS, "Batch number: " _GREEN_("%s"), sprint_hex(version + 21, 5));
267 PrintAndLogEx(SUCCESS, "Production date: week " _GREEN_("%02x") " / " _GREEN_("20%02x"), version[7 + 7 + 7 + 5], version[7 + 7 + 7 + 5 + 1]);
268 PrintAndLogEx(NORMAL, "");
269 PrintAndLogEx(INFO, "--- " _CYAN_("Hardware Information"));
270 PrintAndLogEx(INFO, " Raw : %s", sprint_hex(version, 7));
271 PrintAndLogEx(INFO, " Vendor Id: " _YELLOW_("%s"), getTagInfo(version[0]));
272 PrintAndLogEx(INFO, " Type: %s", getTypeStr(version[1]));
273 PrintAndLogEx(INFO, " Subtype: " _YELLOW_("0x%02X"), version[2]);
274 PrintAndLogEx(INFO, " Version: %s", getVersionStr(version[1], version[3], version[4]));
275 PrintAndLogEx(INFO, " Storage size: %s", getCardSizeStr(version[5]));
276 PrintAndLogEx(INFO, " Protocol: %s", getProtocolStr(version[6], true));
277 PrintAndLogEx(NORMAL, "");
278 PrintAndLogEx(INFO, "--- " _CYAN_("Software Information"));
279 PrintAndLogEx(INFO, " Raw : %s", sprint_hex(version + 7, 6));
280 PrintAndLogEx(INFO, " Vendor Id: " _YELLOW_("%s"), getTagInfo(version[7]));
281 PrintAndLogEx(INFO, " Type: %s", getTypeStr(version[8]));
282 PrintAndLogEx(INFO, " Subtype: " _YELLOW_("0x%02X"), version[9]);
283 PrintAndLogEx(INFO, " Version: " _YELLOW_("%d.%d"), version[10], version[11]);
284 PrintAndLogEx(INFO, " Storage size: %s", getCardSizeStr(version[12]));
285 PrintAndLogEx(INFO, " Protocol: %s", getProtocolStr(version[13], false));
286 return PM3_SUCCESS;
289 static int get_plus_version(uint8_t *version, int *version_len) {
291 int resplen = 0, retval = PM3_SUCCESS;
292 mfpSetVerboseMode(false);
293 MFPGetVersion(true, false, version, *version_len, &resplen);
295 *version_len = resplen;
296 if (resplen != 28) {
297 retval = PM3_ESOFT;
299 return retval;
302 static int CmdHFMFPInfo(const char *Cmd) {
303 CLIParserContext *ctx;
304 CLIParserInit(&ctx, "hf mfp info",
305 "Get info from MIFARE Plus tags",
306 "hf mfp info");
308 void *argtable[] = {
309 arg_param_begin,
310 arg_param_end
312 CLIExecWithReturn(ctx, Cmd, argtable, true);
314 PrintAndLogEx(NORMAL, "");
315 PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------");
317 // Mifare Plus info
318 SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0);
319 PacketResponseNG resp;
320 WaitForResponse(CMD_ACK, &resp);
322 iso14a_card_select_t card;
323 memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
325 uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision
327 bool supportVersion = false;
328 bool supportSignature = false;
330 // version check
331 uint8_t version[30] = {0};
332 int version_len = sizeof(version);
333 if (get_plus_version(version, &version_len) == PM3_SUCCESS) {
334 plus_print_version(version);
335 supportVersion = true;
336 } else {
337 // info about 14a part, historical bytes.
338 infoHF14A(false, false, false);
341 // Signature originality check
342 uint8_t signature[56] = {0};
343 int signature_len = sizeof(signature);
344 if (get_plus_signature(signature, &signature_len) == PM3_SUCCESS) {
345 plus_print_signature(card.uid, card.uidlen, signature, signature_len);
346 supportSignature = true;
349 if (select_status == 1 || select_status == 2) {
351 PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint"));
353 bool isPlus = false;
355 if (supportVersion) {
357 int cardtype = getCardType(version[1], version[3], version[4]);
358 switch (cardtype) {
359 case PLUS_EV1: {
360 if (supportSignature) {
361 PrintAndLogEx(INFO, "Tech..... " _GREEN_("MIFARE Plus EV1"));
362 } else {
363 PrintAndLogEx(INFO, "Tech..... " _YELLOW_("MIFARE Plus SE/X"));
365 isPlus = true;
366 break;
368 case PLUS_EV2: {
369 if (supportSignature) {
370 PrintAndLogEx(INFO, "Tech..... " _GREEN_("MIFARE Plus EV2"));
371 } else {
372 PrintAndLogEx(INFO, "Tech..... " _YELLOW_("MIFARE Plus EV2 ???"));
374 isPlus = true;
375 break;
377 case DESFIRE_MF3ICD40:
378 case DESFIRE_EV1:
379 case DESFIRE_EV2:
380 case DESFIRE_EV2_XL:
381 case DESFIRE_EV3:
382 case DESFIRE_LIGHT: {
383 PrintAndLogEx(HINT, "Card seems to be MIFARE DESFire. Try " _YELLOW_("`hf mfdes info`"));
384 PrintAndLogEx(NORMAL, "");
385 DropField();
386 return PM3_SUCCESS;
388 default: {
389 PrintAndLogEx(INFO, "Tech..... Unknown ( " _YELLOW_("%u") " )", cardtype);
390 break;
395 // MIFARE Type Identification Procedure
396 // https://www.nxp.com/docs/en/application-note/AN10833.pdf
397 uint16_t ATQA = card.atqa[0] + (card.atqa[1] << 8);
399 if (ATQA & 0x0004) {
400 PrintAndLogEx(INFO, "Size..... " _GREEN_("2K") " (%s UID)", (ATQA & 0x0040) ? "7" : "4");
401 isPlus = true;
403 if (ATQA & 0x0002) {
404 PrintAndLogEx(INFO, "Size..... " _GREEN_("4K") " (%s UID)", (ATQA & 0x0040) ? "7" : "4");
405 isPlus = true;
408 uint8_t SLmode = 0xFF;
409 if (isPlus) {
410 if (card.sak == 0x08) {
411 PrintAndLogEx(INFO, "SAK...... " _GREEN_("2K 7b UID"));
412 if (select_status == 2) SLmode = 1;
414 if (card.sak == 0x18) {
415 PrintAndLogEx(INFO, "SAK...... " _GREEN_("4K 7b UID"));
416 if (select_status == 2) SLmode = 1;
418 if (card.sak == 0x10) {
419 PrintAndLogEx(INFO, "SAK...... " _GREEN_("2K"));
420 if (select_status == 2) SLmode = 2;
422 if (card.sak == 0x11) {
423 PrintAndLogEx(INFO, "SAK...... " _GREEN_("4K"));
424 if (select_status == 2) SLmode = 2;
428 if (card.sak == 0x20) {
429 if (card.ats_len > 0) {
430 PrintAndLogEx(INFO, "SAK...... " _GREEN_("MIFARE Plus SL0/SL3") " or " _GREEN_("MIFARE DESFire"));
431 SLmode = 3;
432 // check SL0
433 uint8_t data[128] = {0};
434 int datalen = 0;
435 // https://github.com/Proxmark/proxmark3/blob/master/client/luascripts/mifarePlus.lua#L161
436 uint8_t cmd[3 + 16] = {0xa8, 0x90, 0x90, 0x00};
437 int res = ExchangeRAW14a(cmd, sizeof(cmd), true, false, data, sizeof(data), &datalen, false);
438 if (res != PM3_SUCCESS) {
439 PrintAndLogEx(INFO, "Identification failed");
440 PrintAndLogEx(NORMAL, "");
441 DropField();
442 return PM3_SUCCESS;
444 // DESFire answers 0x1C or 67 00
445 // Plus answers 0x0B, 0x09, 0x06
446 // 6D00 is "INS code not supported" in APDU
447 if (
448 data[0] != 0x0B &&
449 data[0] != 0x09 &&
450 data[0] != 0x1C &&
451 data[0] != 0x67 &&
452 data[0] != 0x6D &&
453 data[0] != 0x6E) {
455 PrintAndLogEx(INFO, _RED_("Send copy to iceman of this command output!"));
456 PrintAndLogEx(INFO, "Data... %s", sprint_hex(data, datalen));
459 if ((memcmp(data, "\x67\x00", 2) == 0) || // wrong length
460 (memcmp(data, "\x1C\x83\x0C", 3) == 0) // desfire answers
462 PrintAndLogEx(INFO, "Result... " _RED_("MIFARE DESFire"));
463 PrintAndLogEx(NORMAL, "");
464 DropField();
465 return PM3_SUCCESS;
467 // } else if (memcmp(data, "\x68\x82", 2) == 0) { // Secure message not supported
468 } else if (memcmp(data, "\x6D\x00", 2) == 0) {
469 // } else if (memcmp(data, "\x6E\x00", 2) == 0) { // Class not supported
470 isPlus = false;
471 } else {
472 PrintAndLogEx(INFO, "Result... " _GREEN_("MIFARE Plus SL0/SL3"));
475 if ((datalen > 1) && (data[0] == 0x09)) {
476 SLmode = 0;
482 if (isPlus) {
483 // How do we detect SL0 / SL1 / SL2 / SL3 modes?!?
484 PrintAndLogEx(INFO, "--- " _CYAN_("Security Level (SL)"));
486 if (SLmode != 0xFF)
487 PrintAndLogEx(SUCCESS, "SL mode... " _YELLOW_("SL%d"), SLmode);
488 else
489 PrintAndLogEx(WARNING, "SL mode... " _YELLOW_("unknown"));
491 switch (SLmode) {
492 case 0:
493 PrintAndLogEx(INFO, "SL 0: initial delivery configuration, used for card personalization");
494 break;
495 case 1:
496 PrintAndLogEx(INFO, "SL 1: backwards functional compatibility mode (with MIFARE Classic 1K / 4K) with an optional AES authentication");
497 break;
498 case 2:
499 PrintAndLogEx(INFO, "SL 2: 3-Pass Authentication based on AES followed by MIFARE CRYPTO1 authentication, communication secured by MIFARE CRYPTO1");
500 break;
501 case 3:
502 PrintAndLogEx(INFO, "SL 3: 3-Pass authentication based on AES, data manipulation commands secured by AES encryption and an AES based MACing method.");
503 break;
504 default:
505 break;
508 } else {
509 PrintAndLogEx(INFO, "MIFARE Plus info not available");
511 PrintAndLogEx(NORMAL, "");
512 DropField();
513 return PM3_SUCCESS;
516 static int CmdHFMFPWritePerso(const char *Cmd) {
517 CLIParserContext *ctx;
518 CLIParserInit(&ctx, "hf mfp wrp",
519 "Executes Write Perso command. Can be used in SL0 mode only.",
520 "Use this command to program AES keys, as well as personalize other data on the tag.\n"
521 "You can program:\n"
522 "* Address 00 [00-FF]: Memory blocks (as well as ACLs and Crypto1 keys)\n"
523 "* Address 40 [00-40]: AES sector keys\n"
524 "* Address 90 [00-04]: AES administrative keys\n"
525 "* Address A0 [00, 01, 80, 81]: Virtual Card keys\n"
526 "* Address B0 [00-03]: Configuration data (DO NOT TOUCH B003)\n"
527 "Examples:\n"
528 "hf mfp wrp --adr 4000 --data 000102030405060708090a0b0c0d0e0f -> write key (00..0f) to key number 4000 \n"
529 "hf mfp wrp --adr 4000 -> write default key(0xff..0xff) to key number 4000\n"
530 "hf mfp wrp --adr b000 -d FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> allow 255 commands without MAC in configuration block (B000)\n"
531 "hf mfp wrp --adr 0003 -d 1234561234567F078869B0B1B2B3B4B5 -> write crypto1 keys A: 123456123456 and B: B0B1B2B3B4B5 to block 3\n");
533 void *argtable[] = {
534 arg_param_begin,
535 arg_lit0("v", "verbose", "Verbose output"),
536 arg_str1("a", "adr", "<hex>", "Address, 2 hex bytes"),
537 arg_str0("d", "data", "<hex>", "Data, 16 hex bytes"),
538 arg_param_end
540 CLIExecWithReturn(ctx, Cmd, argtable, true);
542 bool verbose = arg_get_lit(ctx, 1);
544 uint8_t addr[64] = {0};
545 int addrLen = 0;
546 CLIGetHexWithReturn(ctx, 2, addr, &addrLen);
548 uint8_t datain[64] = {0};
549 int datainLen = 0;
550 CLIGetHexWithReturn(ctx, 3, datain, &datainLen);
551 CLIParserFree(ctx);
553 mfpSetVerboseMode(verbose);
555 if (!datainLen) {
556 memmove(datain, mfp_default_key, 16);
557 datainLen = 16;
560 if (addrLen != 2) {
561 PrintAndLogEx(ERR, "Address length must be 2 bytes. Got %d", addrLen);
562 return PM3_EINVARG;
564 if (datainLen != 16) {
565 PrintAndLogEx(ERR, "Data length must be 16 bytes. Got %d", datainLen);
566 return PM3_EINVARG;
569 uint8_t data[250] = {0};
570 int datalen = 0;
572 int res = MFPWritePerso(addr, datain, true, false, data, sizeof(data), &datalen);
573 if (res) {
574 PrintAndLogEx(ERR, "Exchange error: %d", res);
575 return res;
578 if (datalen != 3) {
579 PrintAndLogEx(ERR, "Command must return 3 bytes. Got %d", datalen);
580 return PM3_ESOFT;
583 if (data[0] != 0x90) {
584 PrintAndLogEx(ERR, "Command error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
585 return PM3_ESOFT;
588 PrintAndLogEx(INFO, "Write ( " _GREEN_("ok") " )");
589 return PM3_SUCCESS;
592 static int CmdHFMFPInitPerso(const char *Cmd) {
593 CLIParserContext *ctx;
594 CLIParserInit(&ctx, "hf mfp initp",
595 "Executes Write Perso command for all card's keys. Can be used in SL0 mode only.",
596 "hf mfp initp --key 000102030405060708090a0b0c0d0e0f -> fill all the keys with key (00..0f)\n"
597 "hf mfp initp -vv -> fill all the keys with default key(0xff..0xff) and show all the data exchange");
599 void *argtable[] = {
600 arg_param_begin,
601 arg_litn("v", "verbose", 0, 2, "Verbose output"),
602 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
603 arg_param_end
605 CLIExecWithReturn(ctx, Cmd, argtable, true);
607 bool verbose = arg_get_lit(ctx, 1);
608 bool verbose2 = arg_get_lit(ctx, 1) > 1;
610 uint8_t key[256] = {0};
611 int keylen = 0;
612 CLIGetHexWithReturn(ctx, 2, key, &keylen);
613 CLIParserFree(ctx);
615 if (keylen && keylen != 16) {
616 PrintAndLogEx(FAILED, "Key length must be 16 bytes. Got %d", keylen);
617 return PM3_EINVARG;
620 if (keylen == 0) {
621 memmove(key, mfp_default_key, sizeof(mfp_default_key));
624 uint8_t keyNum[2] = {0};
625 uint8_t data[250] = {0};
626 int datalen = 0;
627 int res;
629 mfpSetVerboseMode(verbose2);
630 for (uint16_t sn = 0x4000; sn < 0x4050; sn++) {
631 keyNum[0] = sn >> 8;
632 keyNum[1] = sn & 0xff;
633 res = MFPWritePerso(keyNum, key, (sn == 0x4000), true, data, sizeof(data), &datalen);
634 if (!res && (datalen == 3) && data[0] == 0x09) {
635 PrintAndLogEx(INFO, "2K card detected.");
636 break;
638 if (res || (datalen != 3) || data[0] != 0x90) {
639 PrintAndLogEx(ERR, "Write error on address %04x", sn);
640 break;
644 mfpSetVerboseMode(verbose);
645 for (int i = 0; i < ARRAYLEN(mfp_card_adresses); i++) {
646 keyNum[0] = mfp_card_adresses[i] >> 8;
647 keyNum[1] = mfp_card_adresses[i] & 0xff;
648 res = MFPWritePerso(keyNum, key, false, true, data, sizeof(data), &datalen);
649 if (!res && (datalen == 3) && data[0] == 0x09) {
650 PrintAndLogEx(WARNING, "Skipped[%04x]...", mfp_card_adresses[i]);
651 } else {
652 if (res || (datalen != 3) || data[0] != 0x90) {
653 PrintAndLogEx(ERR, "Write error on address %04x", mfp_card_adresses[i]);
654 break;
658 DropField();
660 if (res)
661 return res;
663 PrintAndLogEx(INFO, "Done!");
664 return PM3_SUCCESS;
667 static int CmdHFMFPCommitPerso(const char *Cmd) {
668 CLIParserContext *ctx;
669 CLIParserInit(&ctx, "hf mfp commitp",
670 "Executes Commit Perso command. Can be used in SL0 mode only.\n"
671 "OBS! This command will not be executed if \n"
672 "CardConfigKey, CardMasterKey and L3SwitchKey AES keys are not written.",
673 "hf mfp commitp\n"
674 // "hf mfp commitp --sl 1"
677 void *argtable[] = {
678 arg_param_begin,
679 arg_lit0("v", "verbose", "Verbose output"),
680 // arg_int0(NULL, "sl", "<dec>", "SL mode"),
681 arg_param_end
683 CLIExecWithReturn(ctx, Cmd, argtable, true);
684 bool verbose = arg_get_lit(ctx, 1);
685 // int slmode = arg_get_int(ctx, 2);
686 CLIParserFree(ctx);
688 mfpSetVerboseMode(verbose);
690 uint8_t data[250] = {0};
691 int datalen = 0;
693 int res = MFPCommitPerso(true, false, data, sizeof(data), &datalen);
694 if (res) {
695 PrintAndLogEx(ERR, "Exchange error: %d", res);
696 return res;
699 if (datalen != 3) {
700 PrintAndLogEx(ERR, "Command must return 3 bytes. Got %d", datalen);
701 return PM3_EINVARG;
704 if (data[0] != 0x90) {
705 PrintAndLogEx(ERR, "Command error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
706 return PM3_EINVARG;
708 PrintAndLogEx(INFO, "Switched security level ( " _GREEN_("ok") " )");
709 return PM3_SUCCESS;
712 static int CmdHFMFPAuth(const char *Cmd) {
713 uint8_t keyn[250] = {0};
714 int keynlen = 0;
715 uint8_t key[250] = {0};
716 int keylen = 0;
718 CLIParserContext *ctx;
719 CLIParserInit(&ctx, "hf mfp auth",
720 "Executes AES authentication command for MIFARE Plus card",
721 "hf mfp auth --ki 4000 --key 000102030405060708090a0b0c0d0e0f -> executes authentication\n"
722 "hf mfp auth --ki 9003 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -v -> executes authentication and shows all the system data");
724 void *argtable[] = {
725 arg_param_begin,
726 arg_lit0("v", "verbose", "Verbose output"),
727 arg_str1(NULL, "ki", "<hex>", "Key number, 2 hex bytes"),
728 arg_str1(NULL, "key", "<hex>", "Key, 16 hex bytes"),
729 arg_param_end
731 CLIExecWithReturn(ctx, Cmd, argtable, true);
733 bool verbose = arg_get_lit(ctx, 1);
734 CLIGetHexWithReturn(ctx, 2, keyn, &keynlen);
735 CLIGetHexWithReturn(ctx, 3, key, &keylen);
736 CLIParserFree(ctx);
738 if (keynlen != 2) {
739 PrintAndLogEx(ERR, "ERROR: <key number> must be 2 bytes. Got %d", keynlen);
740 return PM3_EINVARG;
743 if (keylen != 16) {
744 PrintAndLogEx(ERR, "ERROR: <key> must be 16 bytes. Got %d", keylen);
745 return PM3_EINVARG;
748 return MifareAuth4(NULL, keyn, key, true, false, true, verbose, false);
750 static int data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev) {
751 uint8_t kenc[16];
752 memcpy(kenc, mf4session->Kenc, 16);
753 uint8_t ti[4];
754 memcpy(ti, mf4session->TI, 4);
755 uint8_t ctr[1];
756 uint8_t IV[16] = {0, 0, 0x00, 0x00, 0x00, 0, 0x00, 0x00, 0x00, 0};
757 if (rev) {
758 ctr[0] = (uint8_t)(mf4session->R_Ctr & 0xff);
759 for (int i = 0; i < 9; i += 4) {memcpy(&IV[i], ctr, 1);}
760 memcpy(&IV[12], ti, 4); // For reads TI is LS
761 } else {
762 ctr[0] = (uint8_t)(mf4session->W_Ctr & 0xff);
763 for (int i = 3; i < 16; i += 4) {memcpy(&IV[i], ctr, 1);}
764 memcpy(&IV[0], ti, 4); // For writes TI is MS
766 if (rev) {
767 aes_decode(IV, kenc, dati, dato, 16);
768 } else {
769 aes_encode(IV, kenc, dati, dato, 16);
771 return 0;
773 static int CmdHFMFPRdbl(const char *Cmd) {
774 CLIParserContext *ctx;
775 CLIParserInit(&ctx, "hf mfp rdbl",
776 "Reads blocks from MIFARE Plus card",
777 "hf mfp rdbl --blk 0 --key 000102030405060708090a0b0c0d0e0f -> executes authentication and read block 0 data\n"
778 "hf mfp rdbl --blk 1 -v -> executes authentication and shows sector 1 data with default key 0xFF..0xFF");
780 void *argtable[] = {
781 arg_param_begin,
782 arg_lit0("v", "verbose", "Verbose output"),
783 arg_int0("n", "count", "<dec>", "Blocks count (def: 1)"),
784 arg_lit0("b", "keyb", "Use key B (def: keyA)"),
785 arg_lit0("p", "plain", "Do not use encrypted communication mode between reader and card"),
786 arg_lit0(NULL, "nmc", "Do not append MAC to command"),
787 arg_lit0(NULL, "nmr", "Do not expect MAC in reply"),
788 arg_int1(NULL, "blk", "<0..255>", "Block number"),
789 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
790 arg_param_end
792 CLIExecWithReturn(ctx, Cmd, argtable, false);
794 bool verbose = arg_get_lit(ctx, 1);
795 int blocksCount = arg_get_int_def(ctx, 2, 1);
796 bool keyB = arg_get_lit(ctx, 3);
797 bool plain = arg_get_lit(ctx, 4);
798 bool nomaccmd = arg_get_lit(ctx, 5);
799 bool nomacres = arg_get_lit(ctx, 6);
800 uint32_t blockn = arg_get_int(ctx, 7);
802 uint8_t keyn[2] = {0};
803 uint8_t key[250] = {0};
804 int keylen = 0;
805 CLIGetHexWithReturn(ctx, 8, key, &keylen);
806 CLIParserFree(ctx);
808 mfpSetVerboseMode(verbose);
810 if (!keylen) {
811 memmove(key, mfp_default_key, 16);
812 keylen = 16;
815 if (blockn > 255) {
816 PrintAndLogEx(ERR, "<block number> must be in range [0..255]. got %d", blockn);
817 return PM3_EINVARG;
820 if (keylen != 16) {
821 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
822 return PM3_EINVARG;
825 // 3 blocks - wo iso14443-4 chaining
826 if (blocksCount > 3) {
827 PrintAndLogEx(ERR, "blocks count must be less than 3. Got %d", blocksCount);
828 return PM3_EINVARG;
831 if (blocksCount > 1 && mfIsSectorTrailer(blockn)) {
832 PrintAndLogEx(WARNING, "WARNING: trailer!");
835 uint8_t sectorNum = mfSectorNum(blockn & 0xff);
836 uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
837 keyn[0] = uKeyNum >> 8;
838 keyn[1] = uKeyNum & 0xff;
839 if (verbose)
840 PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockn, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
842 mf4Session_t mf4session;
843 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
844 if (res) {
845 PrintAndLogEx(ERR, "Authentication error: %d", res);
846 return res;
849 uint8_t data[250] = {0};
850 int datalen = 0;
851 uint8_t mac[8] = {0};
852 res = MFPReadBlock(&mf4session, plain, nomaccmd, nomacres, blockn & 0xff, blocksCount, false, false, data, sizeof(data), &datalen, mac);
853 if (res) {
854 PrintAndLogEx(ERR, "Read error: %d", res);
855 return res;
858 if (datalen && data[0] != 0x90) {
859 PrintAndLogEx(ERR, "Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
860 return PM3_ESOFT;
862 //PrintAndLogEx(INFO, "%i", 8 && (!macres || 0xff));
863 if (datalen != 1 + blocksCount * 16 + (nomacres ? 0 : 8) + 2) {
864 PrintAndLogEx(ERR, "Error return length: %d", datalen);
865 return PM3_ESOFT;
868 if (!plain) data_crypt(&mf4session, &data[1], &data[1], true);
869 uint8_t sector = mfSectorNum(blockn);
870 mf_print_sector_hdr(sector);
872 int indx = blockn;
873 for (int i = 0; i < blocksCount; i++) {
874 mf_print_block_one(indx, data + 1 + (i * MFBLOCK_SIZE), verbose);
875 indx++;
878 if (memcmp(&data[(blocksCount * 16) + 1], mac, 8) && !nomacres) {
879 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
880 PrintAndLogEx(WARNING, "MAC card... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + (blocksCount * MFBLOCK_SIZE)], 8));
881 PrintAndLogEx(WARNING, "MAC reader... " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
882 } else if (!nomacres) {
883 if (verbose) {
884 PrintAndLogEx(INFO, "MAC... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + (blocksCount * MFBLOCK_SIZE)], 8));
887 PrintAndLogEx(NORMAL, "");
888 return PM3_SUCCESS;
891 static int CmdHFMFPRdsc(const char *Cmd) {
892 CLIParserContext *ctx;
893 CLIParserInit(&ctx, "hf mfp rdsc",
894 "Reads one sector from MIFARE Plus card",
895 "hf mfp rdsc -s 0 --key 000102030405060708090a0b0c0d0e0f -> executes authentication and read sector 0 data\n"
896 "hf mfp rdsc -s 1 -v -> executes authentication and shows sector 1 data with default key");
898 void *argtable[] = {
899 arg_param_begin,
900 arg_lit0("v", "verbose", "Verbose output"),
901 arg_lit0("b", "keyb", "Use key B (def: keyA)"),
902 arg_lit0("p", "plain", "Do not use encrypted communication mode between reader and card"),
903 arg_lit0(NULL, "nmc", "Do not append MAC to command"),
904 arg_lit0(NULL, "nmr", "Do not expect MAC in reply"),
905 arg_int1("s", "sn", "<0..255>", "Sector number"),
906 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
907 arg_param_end
909 CLIExecWithReturn(ctx, Cmd, argtable, false);
911 bool verbose = arg_get_lit(ctx, 1);
912 bool keyB = arg_get_lit(ctx, 2);
913 bool plain = arg_get_lit(ctx, 3);
914 bool nomaccmd = arg_get_lit(ctx, 4);
915 bool nomacres = arg_get_lit(ctx, 5);
916 uint32_t sectorNum = arg_get_int(ctx, 6);
917 uint8_t keyn[2] = {0};
918 uint8_t key[250] = {0};
919 int keylen = 0;
920 CLIGetHexWithReturn(ctx, 7, key, &keylen);
921 CLIParserFree(ctx);
923 mfpSetVerboseMode(verbose);
925 if (!keylen) {
926 memmove(key, mfp_default_key, 16);
927 keylen = 16;
930 if (sectorNum > 39) {
931 PrintAndLogEx(ERR, "<sector number> must be in range [0..39]. Got %d", sectorNum);
932 return PM3_EINVARG;
935 if (keylen != 16) {
936 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
937 return PM3_EINVARG;
940 uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
941 keyn[0] = uKeyNum >> 8;
942 keyn[1] = uKeyNum & 0xff;
943 if (verbose)
944 PrintAndLogEx(INFO, "--sector[%u]:%02x key:%04x", mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
946 mf4Session_t mf4session;
947 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
948 if (res) {
949 PrintAndLogEx(ERR, "Authentication error: %d", res);
950 return res;
953 uint8_t data[250] = {0};
954 int datalen = 0;
955 uint8_t mac[8] = {0};
957 mf_print_sector_hdr(sectorNum);
959 for (int blockno = mfFirstBlockOfSector(sectorNum); blockno < mfFirstBlockOfSector(sectorNum) + mfNumBlocksPerSector(sectorNum); blockno++) {
961 res = MFPReadBlock(&mf4session, plain, nomaccmd, nomacres, blockno & 0xff, 1, false, true, data, sizeof(data), &datalen, mac);
962 if (res) {
963 PrintAndLogEx(ERR, "Read error: %d", res);
964 DropField();
965 return res;
968 if (datalen && data[0] != 0x90) {
969 PrintAndLogEx(ERR, "Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
970 DropField();
971 return PM3_ESOFT;
974 if (datalen != 1 + MFBLOCK_SIZE + (nomacres ? 0 : 8) + 2) {
975 PrintAndLogEx(ERR, "Error return length:%d", datalen);
976 DropField();
977 return PM3_ESOFT;
979 if (!plain) data_crypt(&mf4session, &data[1], &data[1], true);
980 mf_print_block_one(blockno, data + 1, verbose);
982 if (memcmp(&data[1 + 16], mac, 8) && !nomacres) {
983 PrintAndLogEx(WARNING, "WARNING: mac on block %d not equal...", blockno);
984 PrintAndLogEx(WARNING, "MAC card... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + MFBLOCK_SIZE], 8));
985 PrintAndLogEx(WARNING, "MAC reader... " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
986 } else if (!nomacres) {
987 if (verbose) {
988 PrintAndLogEx(INFO, "MAC... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + MFBLOCK_SIZE], 8));
992 PrintAndLogEx(NORMAL, "");
993 DropField();
994 return PM3_SUCCESS;
997 static int CmdHFMFPWrbl(const char *Cmd) {
998 CLIParserContext *ctx;
999 CLIParserInit(&ctx, "hf mfp wrbl",
1000 "Writes one block to MIFARE Plus card",
1001 "hf mfp wrbl --blk 1 -d ff0000000000000000000000000000ff --key 000102030405060708090a0b0c0d0e0f -> write block 1 data\n"
1002 "hf mfp wrbl --blk 2 -d ff0000000000000000000000000000ff -v -> write block 2 data with default key 0xFF..0xFF"
1005 void *argtable[] = {
1006 arg_param_begin,
1007 arg_lit0("v", "verbose", "Verbose output"),
1008 arg_lit0("b", "keyb", "Use key B (def: keyA)"),
1009 arg_int1(NULL, "blk", "<0..255>", "Block number"),
1010 arg_lit0("p", "plain", "Do not use encrypted transmission"),
1011 arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
1012 arg_str1("d", "data", "<hex>", "Data, 16 hex bytes"),
1013 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
1014 arg_param_end
1016 CLIExecWithReturn(ctx, Cmd, argtable, false);
1018 bool verbose = arg_get_lit(ctx, 1);
1019 bool keyB = arg_get_lit(ctx, 2);
1020 uint32_t blockNum = arg_get_int(ctx, 3);
1021 bool plain = arg_get_lit(ctx, 4);
1022 bool nomacres = arg_get_lit(ctx, 5);
1024 uint8_t datain[250] = {0};
1025 int datainlen = 0;
1026 CLIGetHexWithReturn(ctx, 6, datain, &datainlen);
1028 uint8_t key[250] = {0};
1029 int keylen = 0;
1030 CLIGetHexWithReturn(ctx, 7, key, &keylen);
1031 CLIParserFree(ctx);
1033 uint8_t keyn[2] = {0};
1035 mfpSetVerboseMode(verbose);
1037 if (!keylen) {
1038 memmove(key, mfp_default_key, 16);
1039 keylen = 16;
1042 if (blockNum > 255) {
1043 PrintAndLogEx(ERR, "<block number> must be in range [0..255]. Got %d", blockNum);
1044 return PM3_EINVARG;
1047 if (keylen != 16) {
1048 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
1049 return PM3_EINVARG;
1052 if (datainlen != 16) {
1053 PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
1054 return PM3_EINVARG;
1057 uint8_t sectorNum = mfSectorNum(blockNum & 0xff);
1058 uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
1059 keyn[0] = uKeyNum >> 8;
1060 keyn[1] = uKeyNum & 0xff;
1061 if (verbose)
1062 PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockNum & 0xff, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
1064 mf4Session_t mf4session;
1065 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
1066 if (res) {
1067 PrintAndLogEx(ERR, "Authentication error: %d", res);
1068 return res;
1070 if (!plain) data_crypt(&mf4session, &datain[0], &datain[0], false);
1071 uint8_t data[250] = {0};
1072 int datalen = 0;
1073 uint8_t mac[8] = {0};
1074 res = MFPWriteBlock(&mf4session, plain, nomacres, blockNum & 0xff, 0x00, datain, false, false, data, sizeof(data), &datalen, mac);
1075 if (res) {
1076 PrintAndLogEx(ERR, "Write error: %d", res);
1077 DropField();
1078 return res;
1081 if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
1082 PrintAndLogEx(ERR, "Error return length:%d", datalen);
1083 DropField();
1084 return PM3_ESOFT;
1087 if (datalen && data[0] != 0x90) {
1088 PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
1089 DropField();
1090 return PM3_ESOFT;
1093 if (memcmp(&data[1], mac, 8) && !nomacres) {
1094 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
1095 PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8));
1096 PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
1097 } else if (!nomacres) {
1098 if (verbose)
1099 PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
1102 DropField();
1103 PrintAndLogEx(INFO, "Write ( " _GREEN_("ok") " )");
1104 return PM3_SUCCESS;
1107 static int CmdHFMFPChKey(const char *Cmd) {
1108 CLIParserContext *ctx;
1109 CLIParserInit(&ctx, "hf mfp chkey",
1110 "Change the keys on a Mifare Plus tag",
1111 "This requires the key that can update the key that you are trying to update.\n"
1112 "hf mfp chkey --ki 401f -d FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF --key A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 -> Change key B for Sector 15 from MAD to default\n"
1113 "hf mfp chkey --ki 9000 -d 32F9351A1C02B35FF97E0CA943F814F6 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> Change card master key to custom from default"
1116 void *argtable[] = {
1117 arg_param_begin,
1118 arg_lit0("v", "verbose", "Verbose output"),
1119 arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
1120 arg_str1(NULL, "ki", "<hex>", "Key Index, 2 hex bytes"),
1121 arg_str0("k", "key", "<hex>", "Current sector key, 16 hex bytes"),
1122 arg_lit0("b", "typeb", "Sector key is key B"),
1123 arg_str1("d", "data", "<hex>", "New key, 16 hex bytes"),
1124 arg_param_end
1126 CLIExecWithReturn(ctx, Cmd, argtable, false);
1128 bool verbose = arg_get_lit(ctx, 1);
1129 bool nomacres = arg_get_lit(ctx, 2);
1131 uint8_t keyn[250] = {0};
1133 uint8_t ki[250] = {0};
1134 int kilen = 0;
1135 CLIGetHexWithReturn(ctx, 3, ki, &kilen);
1137 uint8_t key[250] = {0};
1138 int keylen = 0;
1139 CLIGetHexWithReturn(ctx, 4, key, &keylen);
1141 bool usekeyb = arg_get_lit(ctx, 5);
1142 uint8_t datain[250] = {0};
1143 int datainlen = 0;
1144 CLIGetHexWithReturn(ctx, 6, datain, &datainlen);
1146 CLIParserFree(ctx);
1148 mfpSetVerboseMode(verbose);
1150 if (!keylen) {
1151 memmove(key, mfp_default_key, 16);
1152 keylen = 16;
1155 if (keylen != 16) {
1156 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
1157 return PM3_EINVARG;
1160 if (datainlen != 16) {
1161 PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
1162 return PM3_EINVARG;
1164 mf4Session_t mf4session;
1165 keyn[0] = ki[0];
1166 if (ki[0] == 0x40) { // Only if we are working with sector keys
1167 if (usekeyb) {
1168 keyn[1] = (ki[1] % 2 == 0) ? ki[1] + 1 : ki[1]; // If we change using key B, check if KI is key A
1169 } else {
1170 keyn[1] = (ki[1] % 2 == 0) ? ki[1] : ki[1] - 1; // If we change using key A, check if KI is key A
1172 } else {keyn[1] = ki[1];}
1173 if (verbose) {
1174 PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
1176 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
1177 if (res) {
1178 PrintAndLogEx(ERR, "Authentication error: %d", res);
1179 return res;
1181 data_crypt(&mf4session, &datain[0], &datain[0], false);
1182 uint8_t data[250] = {0};
1183 int datalen = 0;
1184 uint8_t mac[8] = {0};
1185 res = MFPWriteBlock(&mf4session, false, nomacres, ki[1], ki[0], datain, false, false, data, sizeof(data), &datalen, mac);
1186 if (res) {
1187 PrintAndLogEx(ERR, "Write error: %d", res);
1188 DropField();
1189 return res;
1192 if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
1193 PrintAndLogEx(ERR, "Error return length:%d", datalen);
1194 DropField();
1195 return PM3_ESOFT;
1198 if (datalen && data[0] != 0x90) {
1199 PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
1200 DropField();
1201 return PM3_ESOFT;
1204 if (memcmp(&data[1], mac, 8) && !nomacres) {
1205 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
1206 PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8));
1207 PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
1208 } else if (!nomacres) {
1209 if (verbose)
1210 PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
1213 DropField();
1214 PrintAndLogEx(INFO, "Key update ( " _GREEN_("ok") " )");
1215 return PM3_SUCCESS;
1218 static int CmdHFMFPChConf(const char *Cmd) {
1219 CLIParserContext *ctx;
1220 CLIParserInit(&ctx, "hf mfp chconf",
1221 "Change the configuration on a Mifare Plus tag. DANGER!",
1222 "This requires Card Master Key (9000) or Card Configuration Key (9001).\n"
1223 "Configuration block info can be found below.\n"
1224 "* Block B000 (00; CMK): Max amount of commands without MAC (byte 0), as well as plain mode access (unknown).\n"
1225 "* Block B001 (01; CCK): Installation identifier for Virtual Card. Please consult NXP for data.\n"
1226 "* Block B002 (02; CCK): ATS data.\n"
1227 "* Block B003 (03; CCK): Use Random ID in SL3, decide whether proximity check is mandatory.\n * DO NOT WRITE THIS BLOCK UNDER ANY CIRCUMSTANCES! Risk of bricking.\n"
1228 "More configuration tips to follow. Check JMY600 Series IC Card Module.\n"
1229 "hf mfp chconf -c 00 -d 10ffffffffffffffffffffffffffffff --key A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 -> Allow 16 commands without MAC in a single transaction."
1232 void *argtable[] = {
1233 arg_param_begin,
1234 arg_lit0("v", "verbose", "Verbose output"),
1235 arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
1236 arg_int1("c", "conf", "<hex>", "Config block number, 0-3"),
1237 arg_str0("k", "key", "<hex>", "Card key, 16 hex bytes"),
1238 arg_lit0(NULL, "cck", "Auth as Card Configuration key instead of Card Master Key"),
1239 arg_str1("d", "data", "<hex>", "New configuration data, 16 hex bytes"),
1240 arg_param_end
1242 CLIExecWithReturn(ctx, Cmd, argtable, false);
1244 bool verbose = arg_get_lit(ctx, 1);
1245 bool nomacres = arg_get_lit(ctx, 2);
1247 uint8_t keyn[250] = {0};
1248 uint32_t blockNum = arg_get_int(ctx, 3);
1250 uint8_t key[250] = {0};
1251 int keylen = 0;
1252 CLIGetHexWithReturn(ctx, 4, key, &keylen);
1253 bool usecck = arg_get_lit(ctx, 5);
1255 uint8_t datain[250] = {0};
1256 int datainlen = 0;
1257 CLIGetHexWithReturn(ctx, 6, datain, &datainlen);
1259 CLIParserFree(ctx);
1261 mfpSetVerboseMode(verbose);
1263 if (!keylen) {
1264 memmove(key, mfp_default_key, 16);
1265 keylen = 16;
1268 if (keylen != 16) {
1269 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
1270 return PM3_EINVARG;
1273 if (datainlen != 16) {
1274 PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
1275 return PM3_EINVARG;
1278 if (blockNum > 3) {
1279 PrintAndLogEx(ERR, "<config number> must be in range [0..3]. Got %d", blockNum);
1280 return PM3_EINVARG;
1282 mf4Session_t mf4session;
1283 keyn[0] = 0x90;
1284 keyn[1] = usecck ? 0x01 : 0x00;
1285 if (verbose) {
1286 PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
1288 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
1289 if (res) {
1290 PrintAndLogEx(ERR, "Authentication error: %d", res);
1291 return res;
1293 data_crypt(&mf4session, &datain[0], &datain[0], false);
1294 uint8_t data[250] = {0};
1295 int datalen = 0;
1296 uint8_t mac[8] = {0};
1297 res = MFPWriteBlock(&mf4session, false, nomacres, blockNum & 0xff, 0xb0, datain, false, false, data, sizeof(data), &datalen, mac);
1298 if (res) {
1299 PrintAndLogEx(ERR, "Write error: %d", res);
1300 DropField();
1301 return res;
1304 if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
1305 PrintAndLogEx(ERR, "Error return length:%d", datalen);
1306 DropField();
1307 return PM3_ESOFT;
1310 if (datalen && data[0] != 0x90) {
1311 PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
1312 DropField();
1313 return PM3_ESOFT;
1316 if (memcmp(&data[1], mac, 8) && !nomacres) {
1317 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
1318 PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8));
1319 PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
1320 } else if (!nomacres) {
1321 if (verbose)
1322 PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
1325 DropField();
1326 PrintAndLogEx(INFO, "Write config ( " _GREEN_("ok") " )");
1327 return PM3_SUCCESS;
1330 static int plus_key_check(uint8_t startSector, uint8_t endSector, uint8_t startKeyAB, uint8_t endKeyAB,
1331 uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], size_t keyListLen, uint8_t foundKeys[2][64][AES_KEY_LEN + 1],
1332 bool verbose) {
1333 int res;
1334 bool selectCard = true;
1335 uint8_t keyn[2] = {0};
1337 // sector number from 0
1338 for (uint8_t sector = startSector; sector <= endSector; sector++) {
1339 // 0-keyA 1-keyB
1340 for (uint8_t keyAB = startKeyAB; keyAB <= endKeyAB; keyAB++) {
1341 // main cycle with key check
1342 for (int i = 0; i < keyListLen; i++) {
1344 // allow client abort every iteration
1345 if (kbd_enter_pressed()) {
1346 PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
1347 DropField();
1348 return PM3_EOPABORTED;
1351 if (i % 10 == 0) {
1352 if (verbose == false) {
1353 PrintAndLogEx(NORMAL, "." NOLF);
1357 uint16_t uKeyNum = 0x4000 + sector * 2 + keyAB;
1358 keyn[0] = uKeyNum >> 8;
1359 keyn[1] = uKeyNum & 0xff;
1361 for (int retry = 0; retry < 4; retry++) {
1362 res = MifareAuth4(NULL, keyn, keyList[i], selectCard, true, false, false, true);
1363 if (res == PM3_SUCCESS || res == PM3_EWRONGANSWER)
1364 break;
1366 if (verbose)
1367 PrintAndLogEx(WARNING, "\nretried[%d]...", retry);
1368 else
1369 PrintAndLogEx(NORMAL, "R" NOLF);
1371 DropField();
1372 selectCard = true;
1373 msleep(100);
1376 // key for [sector,keyAB] found
1377 if (res == PM3_SUCCESS) {
1378 if (verbose)
1379 PrintAndLogEx(INFO, "\nFound key for sector %d key %s [%s]", sector, keyAB == 0 ? "A" : "B", sprint_hex_inrow(keyList[i], 16));
1380 else
1381 PrintAndLogEx(NORMAL, "+" NOLF);
1383 foundKeys[keyAB][sector][0] = 0x01;
1384 memcpy(&foundKeys[keyAB][sector][1], keyList[i], AES_KEY_LEN);
1385 DropField();
1386 selectCard = true;
1387 msleep(50);
1389 // break out from keylist check loop,
1390 break;
1393 if (verbose)
1394 PrintAndLogEx(WARNING, "\nsector %02d key %d [%s] res: %d", sector, keyAB, sprint_hex_inrow(keyList[i], 16), res);
1396 // RES can be:
1397 // PM3_ERFTRANS -7
1398 // PM3_EWRONGANSWER -16
1399 if (res == PM3_ERFTRANS) {
1400 if (verbose)
1401 PrintAndLogEx(ERR, "\nExchange error. Aborted.");
1402 else
1403 PrintAndLogEx(NORMAL, "E" NOLF);
1405 DropField();
1406 return PM3_ECARDEXCHANGE;
1409 selectCard = false;
1414 DropField();
1415 return PM3_SUCCESS;
1418 static void Fill2bPattern(uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], uint32_t *keyListLen, uint32_t *startPattern) {
1419 for (uint32_t pt = *startPattern; pt < 0x10000; pt++) {
1420 keyList[*keyListLen][0] = (pt >> 8) & 0xff;
1421 keyList[*keyListLen][1] = pt & 0xff;
1422 memcpy(&keyList[*keyListLen][2], &keyList[*keyListLen][0], 2);
1423 memcpy(&keyList[*keyListLen][4], &keyList[*keyListLen][0], 4);
1424 memcpy(&keyList[*keyListLen][8], &keyList[*keyListLen][0], 8);
1425 (*keyListLen)++;
1426 *startPattern = pt;
1427 if (*keyListLen == MAX_AES_KEYS_LIST_LEN)
1428 break;
1430 (*startPattern)++;
1433 static int CmdHFMFPChk(const char *Cmd) {
1435 CLIParserContext *ctx;
1436 CLIParserInit(&ctx, "hf mfp chk",
1437 "Checks keys on MIFARE Plus card",
1438 "hf mfp chk -k 000102030405060708090a0b0c0d0e0f -> check key on sector 0 as key A and B\n"
1439 "hf mfp chk -s 2 -a -> check default key list on sector 2, only key A\n"
1440 "hf mfp chk -d mfp_default_keys -s0 -e6 -> check keys from dictionary against sectors 0-6\n"
1441 "hf mfp chk --pattern1b --dump -> check all 1-byte keys pattern and save found keys to file\n"
1442 "hf mfp chk --pattern2b --startp2b FA00 -> check all 2-byte keys pattern. Start from key FA00FA00...FA00");
1444 void *argtable[] = {
1445 arg_param_begin,
1446 arg_lit0("a", "keya", "Check only key A (def: check all keys)"),
1447 arg_lit0("b", "keyb", "Check only key B (def: check all keys)"),
1448 arg_int0("s", "startsec", "<0..255>", "Start sector number"),
1449 arg_int0("e", "endsec", "<0..255>", "End sector number"),
1450 arg_str0("k", "key", "<hex>", "Key for checking (HEX 16 bytes)"),
1451 arg_str0("d", "dict", "<fn>", "Dictionary file with keys"),
1452 arg_lit0(NULL, "pattern1b", "Check all 1-byte combinations of key (0000...0000, 0101...0101, 0202...0202, ...)"),
1453 arg_lit0(NULL, "pattern2b", "Check all 2-byte combinations of key (0000...0000, 0001...0001, 0002...0002, ...)"),
1454 arg_str0(NULL, "startp2b", "<pattern>", "Start key (2-byte HEX) for 2-byte search (use with `--pattern2b`)"),
1455 arg_lit0(NULL, "dump", "Dump found keys to JSON file"),
1456 arg_lit0("v", "verbose", "Verbose output"),
1457 arg_param_end
1459 CLIExecWithReturn(ctx, Cmd, argtable, true);
1461 bool keyA = arg_get_lit(ctx, 1);
1462 bool keyB = arg_get_lit(ctx, 2);
1463 uint8_t startSector = arg_get_int_def(ctx, 3, 0);
1464 uint8_t endSector = arg_get_int_def(ctx, 4, 0);
1466 uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN] = {{0}};
1467 uint32_t keyListLen = 0;
1468 uint8_t foundKeys[2][64][AES_KEY_LEN + 1] = {{{0}}};
1470 uint8_t vkey[16] = {0};
1471 int vkeylen = 0;
1472 CLIGetHexWithReturn(ctx, 5, vkey, &vkeylen);
1473 if (vkeylen > 0) {
1474 if (vkeylen == 16) {
1475 memcpy(&keyList[keyListLen], vkey, 16);
1476 keyListLen++;
1477 } else {
1478 PrintAndLogEx(ERR, "Specified key must have 16 bytes. Got %d", vkeylen);
1479 CLIParserFree(ctx);
1480 return PM3_EINVARG;
1484 uint8_t dict_filename[FILE_PATH_SIZE + 2] = {0};
1485 int dict_filenamelen = 0;
1486 if (CLIParamStrToBuf(arg_get_str(ctx, 6), dict_filename, FILE_PATH_SIZE, &dict_filenamelen)) {
1487 PrintAndLogEx(FAILED, "File name too long or invalid.");
1488 CLIParserFree(ctx);
1489 return PM3_EINVARG;
1492 bool pattern1b = arg_get_lit(ctx, 7);
1493 bool pattern2b = arg_get_lit(ctx, 8);
1495 if (pattern1b && pattern2b) {
1496 PrintAndLogEx(ERR, "Pattern search mode must be 2-byte or 1-byte only.");
1497 CLIParserFree(ctx);
1498 return PM3_EINVARG;
1501 if (dict_filenamelen && (pattern1b || pattern2b)) {
1502 PrintAndLogEx(ERR, "Pattern search mode and dictionary mode can't be used in one command.");
1503 CLIParserFree(ctx);
1504 return PM3_EINVARG;
1507 uint32_t startPattern = 0x0000;
1508 uint8_t vpattern[2];
1509 int vpatternlen = 0;
1510 CLIGetHexWithReturn(ctx, 9, vpattern, &vpatternlen);
1511 if (vpatternlen > 0) {
1512 if (vpatternlen <= 2) {
1513 startPattern = (vpattern[0] << 8) + vpattern[1];
1514 } else {
1515 PrintAndLogEx(ERR, "Pattern must be 2-bytes. Got %d", vpatternlen);
1516 CLIParserFree(ctx);
1517 return PM3_EINVARG;
1519 if (!pattern2b)
1520 PrintAndLogEx(WARNING, "Pattern entered, but search mode not is 2-byte search.");
1523 bool create_dumpfile = arg_get_lit(ctx, 10);
1524 bool verbose = arg_get_lit(ctx, 11);
1525 CLIParserFree(ctx);
1527 uint8_t startKeyAB = 0;
1528 uint8_t endKeyAB = 1;
1529 if (keyA && (keyB == false))
1530 endKeyAB = 0;
1532 if ((keyA == false) && keyB)
1533 startKeyAB = 1;
1535 if (endSector < startSector)
1536 endSector = startSector;
1538 // 1-byte pattern search mode
1539 if (pattern1b) {
1540 for (int i = 0; i < 0x100; i++) {
1541 memset(keyList[i], i, 16);
1544 keyListLen = 0x100;
1547 // 2-byte pattern search mode
1548 if (pattern2b) {
1549 Fill2bPattern(keyList, &keyListLen, &startPattern);
1552 int res = PM3_SUCCESS;
1554 // dictionary mode
1555 size_t endFilePosition = 0;
1556 if (dict_filenamelen) {
1557 uint32_t keycnt = 0;
1558 res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, 0, &endFilePosition, true);
1560 if (res == PM3_SUCCESS && endFilePosition) {
1561 keyListLen = keycnt;
1562 PrintAndLogEx(SUCCESS, "First part of dictionary successfully loaded.");
1566 if (keyListLen == 0) {
1567 for (int i = 0; i < g_mifare_plus_default_keys_len; i++) {
1568 if (hex_to_bytes(g_mifare_plus_default_keys[i], keyList[keyListLen], 16) != 16) {
1569 break;
1572 keyListLen++;
1576 if (keyListLen == 0) {
1577 PrintAndLogEx(ERR, "Key list is empty. Nothing to check.");
1578 return PM3_EINVARG;
1579 } else {
1580 PrintAndLogEx(INFO, "Loaded " _YELLOW_("%"PRIu32) " keys", keyListLen);
1583 if (verbose == false) {
1584 PrintAndLogEx(INFO, "Search keys");
1587 while (true) {
1588 res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, keyList, keyListLen, foundKeys, verbose);
1589 if (res == PM3_EOPABORTED) {
1590 break;
1593 if (pattern2b && startPattern < 0x10000) {
1594 if (verbose == false) {
1595 PrintAndLogEx(NORMAL, "p" NOLF);
1598 keyListLen = 0;
1599 Fill2bPattern(keyList, &keyListLen, &startPattern);
1600 continue;
1603 if (dict_filenamelen && endFilePosition) {
1604 if (verbose == false)
1605 PrintAndLogEx(NORMAL, "d" NOLF);
1607 uint32_t keycnt = 0;
1608 res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false);
1609 if (res == PM3_SUCCESS && endFilePosition) {
1610 keyListLen = keycnt;
1613 continue;
1615 break;
1618 if (verbose == false) {
1619 PrintAndLogEx(NORMAL, "");
1622 // print result
1623 char strA[46 + 1] = {0};
1624 char strB[46 + 1] = {0};
1626 uint8_t ndef_key[] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7};
1627 bool has_ndef_key = false;
1628 bool printedHeader = false;
1629 for (uint8_t s = startSector; s <= endSector; s++) {
1631 if ((memcmp(&foundKeys[0][s][1], ndef_key, AES_KEY_LEN) == 0) ||
1632 (memcmp(&foundKeys[1][s][1], ndef_key, AES_KEY_LEN) == 0)) {
1633 has_ndef_key = true;
1636 if (printedHeader == false) {
1637 PrintAndLogEx(NORMAL, "");
1638 PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
1639 PrintAndLogEx(INFO, " Sec | key A | key B");
1640 PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
1641 printedHeader = true;
1644 if (foundKeys[0][s][0]) {
1645 snprintf(strA, sizeof(strA), _GREEN_("%s"), sprint_hex_inrow(&foundKeys[0][s][1], AES_KEY_LEN));
1646 } else {
1647 snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------");
1650 if (foundKeys[1][s][0]) {
1651 snprintf(strB, sizeof(strB), _GREEN_("%s"), sprint_hex_inrow(&foundKeys[1][s][1], AES_KEY_LEN));
1652 } else {
1653 snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------");
1656 PrintAndLogEx(INFO, " " _YELLOW_("%03d") " | %s | %s", s, strA, strB);
1659 if (printedHeader == false)
1660 PrintAndLogEx(INFO, "No keys found(");
1661 else
1662 PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------\n");
1664 // save keys to json
1665 if (create_dumpfile && printedHeader) {
1667 size_t keys_len = (2 * 64 * (AES_KEY_LEN + 1));
1669 uint8_t data[10 + 1 + 2 + 1 + 256 + keys_len];
1670 memset(data, 0, sizeof(data));
1672 // Mifare Plus info
1673 SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0);
1675 PacketResponseNG resp;
1676 WaitForResponse(CMD_ACK, &resp);
1678 iso14a_card_select_t card;
1679 memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
1681 uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision
1682 uint8_t atslen = 0;
1683 if (select_status == 1 || select_status == 2) {
1684 memcpy(data, card.uid, card.uidlen);
1685 data[10] = card.sak;
1686 data[11] = card.atqa[1];
1687 data[12] = card.atqa[0];
1688 atslen = card.ats_len;
1689 data[13] = atslen;
1690 memcpy(&data[14], card.ats, atslen);
1693 char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-key")) + card.uidlen * 2 + 1, sizeof(uint8_t));
1694 strcpy(fptr, "hf-mfp-");
1696 FillFileNameByUID(fptr, card.uid, "-key", card.uidlen);
1698 // length: UID(10b)+SAK(1b)+ATQA(2b)+ATSlen(1b)+ATS(atslen)+foundKeys[2][64][AES_KEY_LEN + 1]
1699 memcpy(&data[14 + atslen], foundKeys, keys_len);
1700 // 64 here is for how many "rows" there is in the data array. A bit confusing
1701 saveFileJSON(fptr, jsfMfPlusKeys, data, 64, NULL);
1702 free(fptr);
1705 // MAD detection
1706 if ((memcmp(&foundKeys[0][0][1], "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7", AES_KEY_LEN) == 0)) {
1707 PrintAndLogEx(HINT, "MAD key detected. Try " _YELLOW_("`hf mfp mad`") " for more details");
1710 // NDEF detection
1711 if (has_ndef_key) {
1712 PrintAndLogEx(HINT, "NDEF key detected. Try " _YELLOW_("`hf mfp ndefread -h`") " for more details");
1714 PrintAndLogEx(NORMAL, "");
1715 return PM3_SUCCESS;
1718 static int CmdHFMFPDump(const char *Cmd) {
1719 CLIParserContext *ctx;
1720 CLIParserInit(&ctx, "hf mfp dump",
1721 "Dump MIFARE Plus tag to file (bin/json)\n"
1722 "If no <name> given, UID will be used as filename",
1723 "hf mfp dump\n"
1724 "hf mfp dump --keys hf-mf-066C8B78-key.bin --> MIFARE Plus with keys from specified file\n");
1726 void *argtable[] = {
1727 arg_param_begin,
1728 arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
1729 arg_str0("k", "keys", "<fn>", "Specify a filename for keys file"),
1730 // arg_lit0(NULL, "ns", "no save to file"),
1731 // arg_lit0("v", "verbose", "Verbose output"),
1732 arg_param_end
1734 CLIExecWithReturn(ctx, Cmd, argtable, true);
1736 int datafnlen = 0;
1737 char data_fn[FILE_PATH_SIZE] = {0};
1738 CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)data_fn, FILE_PATH_SIZE, &datafnlen);
1740 int keyfnlen = 0;
1741 char key_fn[FILE_PATH_SIZE] = {0};
1742 CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)key_fn, FILE_PATH_SIZE, &keyfnlen);
1744 // bool nosave = arg_get_lit(ctx, 3);
1745 // bool verbose = arg_get_lit(ctx, 4);
1746 CLIParserFree(ctx);
1748 PrintAndLogEx(INFO, " To be implemented, feel free to contribute!");
1749 return PM3_ENOTIMPL;
1752 mfpSetVerboseMode(verbose);
1754 // read card
1755 uint8_t *mem = calloc(MIFARE_4K_MAXBLOCK * MFBLOCK_SIZE, sizeof(uint8_t));
1756 if (mem == NULL) {
1757 PrintAndLogEx(ERR, "failed to allocate memory");
1758 return PM3_EMALLOC;
1762 // iso14a_card_select_t card ;
1763 // int res = mfp_read_tag(&card, mem, key_fn);
1764 // if (res != PM3_SUCCESS) {
1765 // free(mem);
1766 // return res;
1767 // }
1770 // Skip saving card data to file
1771 if (nosave) {
1772 PrintAndLogEx(INFO, "Called with no save option");
1773 free(mem);
1774 return PM3_SUCCESS;
1777 // Save to file
1778 // if (strlen(data_fn) < 1) {
1779 // char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-dump")) + card.uidlen * 2 + 1, sizeof(uint8_t));
1780 // strcpy(fptr, "hf-mfp-");
1781 // FillFileNameByUID(fptr, card.uid, "-dump", card.uidlen);
1782 // strcpy(data_fn, fptr);
1783 // free(fptr);
1784 // }
1786 // pm3_save_mf_dump(filename, dump, MIFARE_4K_MAX_BYTES, jsfCardMemory);
1788 free(mem);
1789 return PM3_SUCCESS;
1793 static int CmdHFMFPMAD(const char *Cmd) {
1795 CLIParserContext *ctx;
1796 CLIParserInit(&ctx, "hf mfp mad",
1797 "Checks and prints MIFARE Application Directory (MAD)",
1798 "hf mfp mad\n"
1799 "hf mfp mad --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> read and print NDEF data from MAD aid");
1801 void *argtable[] = {
1802 arg_param_begin,
1803 arg_lit0("v", "verbose", "Verbose output"),
1804 arg_str0(NULL, "aid", "<hex>", "Print all sectors with aid"),
1805 arg_str0("k", "key", "<hex>", "Key for printing sectors"),
1806 arg_lit0("b", "keyb", "Use key B for access printing sectors (def: key A)"),
1807 arg_lit0(NULL, "be", "(optional: BigEndian)"),
1808 arg_lit0(NULL, "dch", "Decode Card Holder information"),
1809 arg_param_end
1811 CLIExecWithReturn(ctx, Cmd, argtable, true);
1813 bool verbose = arg_get_lit(ctx, 1);
1814 uint8_t aid[2] = {0};
1815 int aidlen;
1816 CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
1817 uint8_t key[16] = {0};
1818 int keylen;
1819 CLIGetHexWithReturn(ctx, 3, key, &keylen);
1820 bool keyB = arg_get_lit(ctx, 4);
1821 bool swapmad = arg_get_lit(ctx, 5);
1822 bool decodeholder = arg_get_lit(ctx, 6);
1824 CLIParserFree(ctx);
1826 if (aidlen != 2 && !decodeholder && keylen > 0) {
1827 PrintAndLogEx(WARNING, "Using default MAD keys instead");
1830 uint8_t sector0[16 * 4] = {0};
1831 uint8_t sector10[16 * 4] = {0};
1833 if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) {
1834 PrintAndLogEx(NORMAL, "");
1835 PrintAndLogEx(ERR, "error, read sector 0. card doesn't have MAD or doesn't have MAD on default keys");
1836 return PM3_ESOFT;
1839 MADPrintHeader();
1841 if (verbose) {
1842 PrintAndLogEx(SUCCESS, "Raw:");
1843 for (int i = 0; i < 4; i ++)
1844 PrintAndLogEx(INFO, "[%d] %s", i, sprint_hex(&sector0[i * 16], 16));
1847 bool haveMAD2 = false;
1848 MAD1DecodeAndPrint(sector0, swapmad, verbose, &haveMAD2);
1850 if (haveMAD2) {
1851 if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) {
1852 PrintAndLogEx(NORMAL, "");
1853 PrintAndLogEx(ERR, "error, read sector " _YELLOW_("0x10") ". Card doesn't have MAD or doesn't have MAD on default keys");
1854 return PM3_ESOFT;
1857 MAD2DecodeAndPrint(sector10, swapmad, verbose);
1860 if (aidlen == 2 || decodeholder) {
1861 uint16_t mad[7 + 8 + 8 + 8 + 8] = {0};
1862 size_t madlen = 0;
1863 if (MADDecode(sector0, sector10, mad, &madlen, swapmad)) {
1864 PrintAndLogEx(ERR, "can't decode MAD");
1865 return PM3_EWRONGANSWER;
1868 // copy default NDEF key
1869 uint8_t akey[16] = {0};
1870 memcpy(akey, g_mifarep_ndef_key, 16);
1872 // user specified key
1873 if (keylen == 16) {
1874 memcpy(akey, key, 16);
1877 uint16_t aaid = 0x0004;
1878 if (aidlen == 2) {
1879 aaid = (aid[0] << 8) + aid[1];
1880 PrintAndLogEx(NORMAL, "");
1881 PrintAndLogEx(INFO, "-------------- " _CYAN_("AID 0x%04x") " ---------------", aaid);
1883 for (int i = 0; i < madlen; i++) {
1884 if (aaid == mad[i]) {
1885 uint8_t vsector[16 * 4] = {0};
1886 if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector, false)) {
1887 PrintAndLogEx(NORMAL, "");
1888 PrintAndLogEx(ERR, "error, read sector %d error", i + 1);
1889 return PM3_ESOFT;
1892 for (int j = 0; j < (verbose ? 4 : 3); j ++)
1893 PrintAndLogEx(NORMAL, " [%03d] %s", (i + 1) * 4 + j, sprint_hex(&vsector[j * 16], 16));
1898 if (decodeholder) {
1900 PrintAndLogEx(NORMAL, "");
1901 PrintAndLogEx(INFO, "-------- " _CYAN_("Card Holder Info 0x%04x") " --------", aaid);
1903 uint8_t data[4096] = {0};
1904 int datalen = 0;
1906 for (int i = 0; i < madlen; i++) {
1907 if (aaid == mad[i]) {
1909 uint8_t vsector[16 * 4] = {0};
1910 if (mfReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector)) {
1911 PrintAndLogEx(NORMAL, "");
1912 PrintAndLogEx(ERR, "error, read sector %d", i + 1);
1913 return PM3_ESOFT;
1916 memcpy(&data[datalen], vsector, 16 * 3);
1917 datalen += 16 * 3;
1921 if (!datalen) {
1922 PrintAndLogEx(WARNING, "no Card Holder Info data");
1923 return PM3_SUCCESS;
1925 MADCardHolderInfoDecode(data, datalen, verbose);
1928 return PM3_SUCCESS;
1931 static int CmdHFMFPNDEFFormat(const char *Cmd) {
1932 CLIParserContext *ctx;
1933 CLIParserInit(&ctx, "hf mfp ndefformat",
1934 "format MIFARE Plus Tag as a NFC tag with Data Exchange Format (NDEF)\n"
1935 "If no <name> given, UID will be used as filename. \n"
1936 "It will try default keys and MAD keys to detect if tag is already formatted in order to write.\n"
1937 "\n"
1938 "If not, it will try finding a key file based on your UID. ie, if you ran autopwn before",
1939 "hf mfp ndefformat\n"
1940 "hf mfp ndefformat --keys hf-mf-01020304-key.bin --> with keys from specified file\n"
1943 void *argtable[] = {
1944 arg_param_begin,
1945 arg_str0("k", "keys", "<fn>", "filename of keys"),
1946 arg_param_end
1948 CLIExecWithReturn(ctx, Cmd, argtable, true);
1950 int keyfnlen = 0;
1951 char keyFilename[FILE_PATH_SIZE] = {0};
1952 CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)keyFilename, FILE_PATH_SIZE, &keyfnlen);
1954 CLIParserFree(ctx);
1956 PrintAndLogEx(SUCCESS, "Not implemented yet. Feel free to contribute!");
1957 PrintAndLogEx(NORMAL, "");
1958 return PM3_SUCCESS;
1961 int CmdHFMFPNDEFRead(const char *Cmd) {
1963 CLIParserContext *ctx;
1964 CLIParserInit(&ctx, "hf mfp ndefread",
1965 "Prints NFC Data Exchange Format (NDEF)",
1966 "hf mfp ndefread \n"
1967 "hf mfp ndefread -vv -> shows NDEF parsed and raw data\n"
1968 "hf mfp ndefread --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> shows NDEF data with custom AID and key\n"
1969 "hf mfp ndefread -f myfilename -> save raw NDEF to file"
1972 void *argtable[] = {
1973 arg_param_begin,
1974 arg_litn("v", "verbose", 0, 2, "verbose output"),
1975 arg_str0(NULL, "aid", "<aid>", "replace default aid for NDEF"),
1976 arg_str0("k", "key", "<key>", "replace default key for NDEF"),
1977 arg_lit0("b", "keyb", "use key B for access sectors (by default: key A)"),
1978 arg_str0("f", "file", "<fn>", "save raw NDEF to file"),
1979 arg_param_end
1981 CLIExecWithReturn(ctx, Cmd, argtable, true);
1983 bool verbose = arg_get_lit(ctx, 1);
1984 bool verbose2 = arg_get_lit(ctx, 1) > 1;
1985 uint8_t aid[2] = {0};
1986 int aidlen;
1987 CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
1988 uint8_t key[16] = {0};
1989 int keylen;
1990 CLIGetHexWithReturn(ctx, 3, key, &keylen);
1991 bool keyB = arg_get_lit(ctx, 4);
1993 int fnlen = 0;
1994 char filename[FILE_PATH_SIZE] = {0};
1995 CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
1996 CLIParserFree(ctx);
1998 uint16_t ndefAID = 0xe103;
1999 if (aidlen == 2)
2000 ndefAID = (aid[0] << 8) + aid[1];
2002 uint8_t ndefkey[16] = {0};
2003 memcpy(ndefkey, g_mifarep_ndef_key, 16);
2004 if (keylen == 16) {
2005 memcpy(ndefkey, key, 16);
2008 uint8_t sector0[16 * 4] = {0};
2009 uint8_t sector10[16 * 4] = {0};
2010 uint8_t data[4096] = {0};
2011 int datalen = 0;
2013 if (verbose)
2014 PrintAndLogEx(INFO, "reading MAD v1 sector");
2016 if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) {
2017 PrintAndLogEx(ERR, "error, read sector 0. card doesn't have MAD or doesn't have MAD on default keys");
2018 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key");
2019 return PM3_ESOFT;
2022 bool haveMAD2 = false;
2023 int res = MADCheck(sector0, NULL, verbose, &haveMAD2);
2024 if (res != PM3_SUCCESS) {
2025 PrintAndLogEx(ERR, "MAD error %d", res);
2026 return res;
2029 if (haveMAD2) {
2031 if (verbose)
2032 PrintAndLogEx(INFO, "reading MAD v2 sector");
2034 if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) {
2035 PrintAndLogEx(ERR, "error, read sector 0x10. card doesn't have MAD or doesn't have MAD on default keys");
2036 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key");
2037 return PM3_ESOFT;
2041 uint16_t mad[7 + 8 + 8 + 8 + 8] = {0};
2042 size_t madlen = 0;
2043 res = MADDecode(sector0, (haveMAD2 ? sector10 : NULL), mad, &madlen, false);
2044 if (res != PM3_SUCCESS) {
2045 PrintAndLogEx(ERR, "can't decode MAD");
2046 return res;
2049 PrintAndLogEx(INFO, "reading data from tag");
2050 for (int i = 0; i < madlen; i++) {
2051 if (ndefAID == mad[i]) {
2052 uint8_t vsector[16 * 4] = {0};
2053 if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, ndefkey, vsector, false)) {
2054 PrintAndLogEx(ERR, "error, reading sector %d", i + 1);
2055 return PM3_ESOFT;
2058 memcpy(&data[datalen], vsector, 16 * 3);
2059 datalen += 16 * 3;
2061 PrintAndLogEx(INPLACE, "%d", i);
2064 PrintAndLogEx(NORMAL, "");
2066 if (datalen == 0) {
2067 PrintAndLogEx(ERR, "no NDEF data");
2068 return PM3_SUCCESS;
2071 if (verbose2) {
2072 PrintAndLogEx(NORMAL, "");
2073 PrintAndLogEx(INFO, "--- " _CYAN_("MF Plus NDEF raw") " ----------------");
2074 print_buffer(data, datalen, 1);
2077 res = NDEFDecodeAndPrint(data, datalen, verbose);
2078 if (res != PM3_SUCCESS) {
2079 PrintAndLogEx(INFO, "Trying to parse NDEF records w/o NDEF header");
2080 res = NDEFRecordsDecodeAndPrint(data, datalen, verbose);
2083 // get total NDEF length before save. If fails, we save it all
2084 size_t n = 0;
2085 if (NDEFGetTotalLength(data, datalen, &n) != PM3_SUCCESS)
2086 n = datalen;
2088 pm3_save_dump(filename, data, n, jsfNDEF);
2090 if (verbose == false) {
2091 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -v`") " for more details");
2092 } else {
2093 if (verbose2 == false) {
2094 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -vv`") " for more details");
2097 return PM3_SUCCESS;
2100 static int CmdHFMFPNDEFWrite(const char *Cmd) {
2101 CLIParserContext *ctx;
2102 CLIParserInit(&ctx, "hf mfp ndefwrite",
2103 "Write raw NDEF hex bytes to tag. This commands assumes tag already been NFC/NDEF formatted.\n",
2104 "hf mfp ndefwrite -d 0300FE -> write empty record to tag\n"
2105 "hf mfp ndefwrite -f myfilename\n"
2106 "hf mfp ndefwrite -d 033fd1023a53709101195405656e2d55534963656d616e2054776974746572206c696e6b5101195502747769747465722e636f6d2f686572726d616e6e31303031\n"
2109 void *argtable[] = {
2110 arg_param_begin,
2111 arg_str0("d", NULL, "<hex>", "raw NDEF hex bytes"),
2112 arg_str0("f", "file", "<fn>", "write raw NDEF file to tag"),
2113 arg_lit0("p", NULL, "fix NDEF record headers / terminator block if missing"),
2114 arg_lit0("v", "verbose", "verbose output"),
2115 arg_param_end
2117 CLIExecWithReturn(ctx, Cmd, argtable, false);
2119 uint8_t raw[4096] = {0};
2120 int rawlen;
2121 CLIGetHexWithReturn(ctx, 1, raw, &rawlen);
2123 int fnlen = 0;
2124 char filename[FILE_PATH_SIZE] = {0};
2125 CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
2127 bool fix_msg = arg_get_lit(ctx, 3);
2128 bool verbose = arg_get_lit(ctx, 4);
2129 CLIParserFree(ctx);
2131 if (fix_msg) {
2132 PrintAndLogEx(NORMAL, "called with fix NDEF message param");
2135 if (verbose) {
2136 PrintAndLogEx(NORMAL, "");
2138 PrintAndLogEx(SUCCESS, "Not implemented yet. Feel free to contribute!");
2139 PrintAndLogEx(NORMAL, "");
2140 return PM3_SUCCESS;
2143 static int CmdHFMFPList(const char *Cmd) {
2144 return CmdTraceListAlias(Cmd, "hf mfp", "mfp -c");
2147 static command_t CommandTable[] = {
2148 {"help", CmdHelp, AlwaysAvailable, "This help"},
2149 {"list", CmdHFMFPList, AlwaysAvailable, "List MIFARE Plus history"},
2150 {"-----------", CmdHelp, IfPm3Iso14443a, "------------------- " _CYAN_("operations") " ---------------------"},
2151 {"auth", CmdHFMFPAuth, IfPm3Iso14443a, "Authentication"},
2152 {"chk", CmdHFMFPChk, IfPm3Iso14443a, "Check keys"},
2153 {"dump", CmdHFMFPDump, IfPm3Iso14443a, "Dump MIFARE Plus tag to binary file"},
2154 {"info", CmdHFMFPInfo, IfPm3Iso14443a, "Info about MIFARE Plus tag"},
2155 {"mad", CmdHFMFPMAD, IfPm3Iso14443a, "Check and print MAD"},
2156 {"rdbl", CmdHFMFPRdbl, IfPm3Iso14443a, "Read blocks from card"},
2157 {"rdsc", CmdHFMFPRdsc, IfPm3Iso14443a, "Read sectors from card"},
2158 {"wrbl", CmdHFMFPWrbl, IfPm3Iso14443a, "Write block to card"},
2159 {"chkey", CmdHFMFPChKey, IfPm3Iso14443a, "Change key on card"},
2160 {"chconf", CmdHFMFPChConf, IfPm3Iso14443a, "Change config on card"},
2161 {"-----------", CmdHelp, IfPm3Iso14443a, "---------------- " _CYAN_("personalization") " -------------------"},
2162 {"commitp", CmdHFMFPCommitPerso, IfPm3Iso14443a, "Configure security layer (SL1/SL3 mode)"},
2163 {"initp", CmdHFMFPInitPerso, IfPm3Iso14443a, "Fill all the card's keys in SL0 mode"},
2164 {"wrp", CmdHFMFPWritePerso, IfPm3Iso14443a, "Write Perso command"},
2165 {"-----------", CmdHelp, IfPm3Iso14443a, "---------------------- " _CYAN_("ndef") " ------------------------"},
2166 {"ndefformat", CmdHFMFPNDEFFormat, IfPm3Iso14443a, "Format MIFARE Plus Tag as NFC Tag"},
2167 {"ndefread", CmdHFMFPNDEFRead, IfPm3Iso14443a, "Read and print NDEF records from card"},
2168 {"ndefwrite", CmdHFMFPNDEFWrite, IfPm3Iso14443a, "Write NDEF records to card"},
2169 {NULL, NULL, 0, NULL}
2172 static int CmdHelp(const char *Cmd) {
2173 (void)Cmd; // Cmd is not used so far
2174 CmdsHelp(CommandTable);
2175 return PM3_SUCCESS;
2178 int CmdHFMFP(const char *Cmd) {
2179 clearCommandBuffer();
2180 return CmdsParse(CommandTable, Cmd);