style
[RRG-proxmark3.git] / client / src / cmdhfmfp.c
blobe6fcf55f96b5139fba8951316be4d0c0d9f9f4c4
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 if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
321 PrintAndLogEx(DEBUG, "iso14443a card select timeout");
322 DropField();
323 return false;
326 iso14a_card_select_t card;
327 memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
329 uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision
331 bool supportVersion = false;
332 bool supportSignature = false;
334 // version check
335 uint8_t version[30] = {0};
336 int version_len = sizeof(version);
337 if (get_plus_version(version, &version_len) == PM3_SUCCESS) {
338 plus_print_version(version);
339 supportVersion = true;
340 } else {
341 // info about 14a part, historical bytes.
342 infoHF14A(false, false, false);
345 // Signature originality check
346 uint8_t signature[56] = {0};
347 int signature_len = sizeof(signature);
348 if (get_plus_signature(signature, &signature_len) == PM3_SUCCESS) {
349 plus_print_signature(card.uid, card.uidlen, signature, signature_len);
350 supportSignature = true;
353 if (select_status == 1 || select_status == 2) {
355 PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint"));
357 bool isPlus = false;
359 if (supportVersion) {
361 int cardtype = getCardType(version[1], version[3], version[4]);
362 switch (cardtype) {
363 case PLUS_EV1: {
364 if (supportSignature) {
365 PrintAndLogEx(INFO, "Tech..... " _GREEN_("MIFARE Plus EV1"));
366 } else {
367 PrintAndLogEx(INFO, "Tech..... " _YELLOW_("MIFARE Plus SE/X"));
369 isPlus = true;
370 break;
372 case PLUS_EV2: {
373 if (supportSignature) {
374 PrintAndLogEx(INFO, "Tech..... " _GREEN_("MIFARE Plus EV2"));
375 } else {
376 PrintAndLogEx(INFO, "Tech..... " _YELLOW_("MIFARE Plus EV2 ???"));
378 isPlus = true;
379 break;
381 case DESFIRE_MF3ICD40:
382 case DESFIRE_EV1:
383 case DESFIRE_EV2:
384 case DESFIRE_EV2_XL:
385 case DESFIRE_EV3:
386 case DESFIRE_LIGHT: {
387 PrintAndLogEx(HINT, "Card seems to be MIFARE DESFire. Try " _YELLOW_("`hf mfdes info`"));
388 PrintAndLogEx(NORMAL, "");
389 DropField();
390 return PM3_SUCCESS;
392 default: {
393 PrintAndLogEx(INFO, "Tech..... Unknown ( " _YELLOW_("%u") " )", cardtype);
394 break;
399 // MIFARE Type Identification Procedure
400 // https://www.nxp.com/docs/en/application-note/AN10833.pdf
401 uint16_t ATQA = card.atqa[0] + (card.atqa[1] << 8);
403 if (ATQA & 0x0004) {
404 PrintAndLogEx(INFO, "Size..... " _GREEN_("2K") " (%s UID)", (ATQA & 0x0040) ? "7" : "4");
405 isPlus = true;
407 if (ATQA & 0x0002) {
408 PrintAndLogEx(INFO, "Size..... " _GREEN_("4K") " (%s UID)", (ATQA & 0x0040) ? "7" : "4");
409 isPlus = true;
412 uint8_t SLmode = 0xFF;
413 if (isPlus) {
414 if (card.sak == 0x08) {
415 PrintAndLogEx(INFO, "SAK...... " _GREEN_("2K 7b UID"));
416 if (select_status == 2) SLmode = 1;
418 if (card.sak == 0x18) {
419 PrintAndLogEx(INFO, "SAK...... " _GREEN_("4K 7b UID"));
420 if (select_status == 2) SLmode = 1;
422 if (card.sak == 0x10) {
423 PrintAndLogEx(INFO, "SAK...... " _GREEN_("2K"));
424 if (select_status == 2) SLmode = 2;
426 if (card.sak == 0x11) {
427 PrintAndLogEx(INFO, "SAK...... " _GREEN_("4K"));
428 if (select_status == 2) SLmode = 2;
432 if (card.sak == 0x20) {
433 if (card.ats_len > 0) {
434 PrintAndLogEx(INFO, "SAK...... " _GREEN_("MIFARE Plus SL0/SL3") " or " _GREEN_("MIFARE DESFire"));
435 SLmode = 3;
436 // check SL0
437 uint8_t data[128] = {0};
438 int datalen = 0;
439 // https://github.com/Proxmark/proxmark3/blob/master/client/luascripts/mifarePlus.lua#L161
440 uint8_t cmd[3 + 16] = {0xa8, 0x90, 0x90, 0x00};
441 int res = ExchangeRAW14a(cmd, sizeof(cmd), true, false, data, sizeof(data), &datalen, false);
442 if (res != PM3_SUCCESS) {
443 PrintAndLogEx(INFO, "Identification failed");
444 PrintAndLogEx(NORMAL, "");
445 DropField();
446 return PM3_SUCCESS;
448 // DESFire answers 0x1C or 67 00
449 // Plus answers 0x0B, 0x09, 0x06
450 // 6D00 is "INS code not supported" in APDU
451 if (
452 data[0] != 0x0B &&
453 data[0] != 0x09 &&
454 data[0] != 0x1C &&
455 data[0] != 0x67 &&
456 data[0] != 0x6D &&
457 data[0] != 0x6E) {
459 PrintAndLogEx(INFO, _RED_("Send copy to iceman of this command output!"));
460 PrintAndLogEx(INFO, "Data... %s", sprint_hex(data, datalen));
463 if ((memcmp(data, "\x67\x00", 2) == 0) || // wrong length
464 (memcmp(data, "\x1C\x83\x0C", 3) == 0) // desfire answers
466 PrintAndLogEx(INFO, "Result... " _RED_("MIFARE DESFire"));
467 PrintAndLogEx(NORMAL, "");
468 DropField();
469 return PM3_SUCCESS;
471 // } else if (memcmp(data, "\x68\x82", 2) == 0) { // Secure message not supported
472 } else if (memcmp(data, "\x6D\x00", 2) == 0) {
473 // } else if (memcmp(data, "\x6E\x00", 2) == 0) { // Class not supported
474 isPlus = false;
475 } else {
476 PrintAndLogEx(INFO, "Result... " _GREEN_("MIFARE Plus SL0/SL3"));
479 if ((datalen > 1) && (data[0] == 0x09)) {
480 SLmode = 0;
486 if (isPlus) {
487 // How do we detect SL0 / SL1 / SL2 / SL3 modes?!?
488 PrintAndLogEx(INFO, "--- " _CYAN_("Security Level (SL)"));
490 if (SLmode != 0xFF)
491 PrintAndLogEx(SUCCESS, "SL mode... " _YELLOW_("SL%d"), SLmode);
492 else
493 PrintAndLogEx(WARNING, "SL mode... " _YELLOW_("unknown"));
495 switch (SLmode) {
496 case 0:
497 PrintAndLogEx(INFO, "SL 0: initial delivery configuration, used for card personalization");
498 break;
499 case 1:
500 PrintAndLogEx(INFO, "SL 1: backwards functional compatibility mode (with MIFARE Classic 1K / 4K) with an optional AES authentication");
501 break;
502 case 2:
503 PrintAndLogEx(INFO, "SL 2: 3-Pass Authentication based on AES followed by MIFARE CRYPTO1 authentication, communication secured by MIFARE CRYPTO1");
504 break;
505 case 3:
506 PrintAndLogEx(INFO, "SL 3: 3-Pass authentication based on AES, data manipulation commands secured by AES encryption and an AES based MACing method.");
507 break;
508 default:
509 break;
512 } else {
513 PrintAndLogEx(INFO, "MIFARE Plus info not available");
515 PrintAndLogEx(NORMAL, "");
516 DropField();
517 return PM3_SUCCESS;
520 static int CmdHFMFPWritePerso(const char *Cmd) {
521 CLIParserContext *ctx;
522 CLIParserInit(&ctx, "hf mfp wrp",
523 "Executes Write Perso command. Can be used in SL0 mode only.",
524 "Use this command to program AES keys, as well as personalize other data on the tag.\n"
525 "You can program:\n"
526 "* Address 00 [00-FF]: Memory blocks (as well as ACLs and Crypto1 keys)\n"
527 "* Address 40 [00-40]: AES sector keys\n"
528 "* Address 90 [00-04]: AES administrative keys\n"
529 "* Address A0 [00, 01, 80, 81]: Virtual Card keys\n"
530 "* Address B0 [00-03]: Configuration data (DO NOT TOUCH B003)\n"
531 "Examples:\n"
532 "hf mfp wrp --adr 4000 --data 000102030405060708090a0b0c0d0e0f -> write key (00..0f) to key number 4000 \n"
533 "hf mfp wrp --adr 4000 -> write default key(0xff..0xff) to key number 4000\n"
534 "hf mfp wrp --adr b000 -d FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> allow 255 commands without MAC in configuration block (B000)\n"
535 "hf mfp wrp --adr 0003 -d 1234561234567F078869B0B1B2B3B4B5 -> write crypto1 keys A: 123456123456 and B: B0B1B2B3B4B5 to block 3\n");
537 void *argtable[] = {
538 arg_param_begin,
539 arg_lit0("v", "verbose", "Verbose output"),
540 arg_str1("a", "adr", "<hex>", "Address, 2 hex bytes"),
541 arg_str0("d", "data", "<hex>", "Data, 16 hex bytes"),
542 arg_param_end
544 CLIExecWithReturn(ctx, Cmd, argtable, true);
546 bool verbose = arg_get_lit(ctx, 1);
548 uint8_t addr[64] = {0};
549 int addrLen = 0;
550 CLIGetHexWithReturn(ctx, 2, addr, &addrLen);
552 uint8_t datain[64] = {0};
553 int datainLen = 0;
554 CLIGetHexWithReturn(ctx, 3, datain, &datainLen);
555 CLIParserFree(ctx);
557 mfpSetVerboseMode(verbose);
559 if (!datainLen) {
560 memmove(datain, mfp_default_key, 16);
561 datainLen = 16;
564 if (addrLen != 2) {
565 PrintAndLogEx(ERR, "Address length must be 2 bytes. Got %d", addrLen);
566 return PM3_EINVARG;
568 if (datainLen != 16) {
569 PrintAndLogEx(ERR, "Data length must be 16 bytes. Got %d", datainLen);
570 return PM3_EINVARG;
573 uint8_t data[250] = {0};
574 int datalen = 0;
576 int res = MFPWritePerso(addr, datain, true, false, data, sizeof(data), &datalen);
577 if (res) {
578 PrintAndLogEx(ERR, "Exchange error: %d", res);
579 return res;
582 if (datalen != 3) {
583 PrintAndLogEx(ERR, "Command must return 3 bytes. Got %d", datalen);
584 return PM3_ESOFT;
587 if (data[0] != 0x90) {
588 PrintAndLogEx(ERR, "Command error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
589 return PM3_ESOFT;
592 PrintAndLogEx(INFO, "Write ( " _GREEN_("ok") " )");
593 return PM3_SUCCESS;
596 static int CmdHFMFPInitPerso(const char *Cmd) {
597 CLIParserContext *ctx;
598 CLIParserInit(&ctx, "hf mfp initp",
599 "Executes Write Perso command for all card's keys. Can be used in SL0 mode only.",
600 "hf mfp initp --key 000102030405060708090a0b0c0d0e0f -> fill all the keys with key (00..0f)\n"
601 "hf mfp initp -vv -> fill all the keys with default key(0xff..0xff) and show all the data exchange");
603 void *argtable[] = {
604 arg_param_begin,
605 arg_litn("v", "verbose", 0, 2, "Verbose output"),
606 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
607 arg_param_end
609 CLIExecWithReturn(ctx, Cmd, argtable, true);
611 bool verbose = arg_get_lit(ctx, 1);
612 bool verbose2 = arg_get_lit(ctx, 1) > 1;
614 uint8_t key[256] = {0};
615 int keylen = 0;
616 CLIGetHexWithReturn(ctx, 2, key, &keylen);
617 CLIParserFree(ctx);
619 if (keylen && keylen != 16) {
620 PrintAndLogEx(FAILED, "Key length must be 16 bytes. Got %d", keylen);
621 return PM3_EINVARG;
624 if (keylen == 0) {
625 memmove(key, mfp_default_key, sizeof(mfp_default_key));
628 uint8_t keyNum[2] = {0};
629 uint8_t data[250] = {0};
630 int datalen = 0;
631 int res;
633 mfpSetVerboseMode(verbose2);
634 for (uint16_t sn = 0x4000; sn < 0x4050; sn++) {
635 keyNum[0] = sn >> 8;
636 keyNum[1] = sn & 0xff;
637 res = MFPWritePerso(keyNum, key, (sn == 0x4000), true, data, sizeof(data), &datalen);
638 if (!res && (datalen == 3) && data[0] == 0x09) {
639 PrintAndLogEx(INFO, "2K card detected.");
640 break;
642 if (res || (datalen != 3) || data[0] != 0x90) {
643 PrintAndLogEx(ERR, "Write error on address %04x", sn);
644 break;
648 mfpSetVerboseMode(verbose);
649 for (int i = 0; i < ARRAYLEN(mfp_card_adresses); i++) {
650 keyNum[0] = mfp_card_adresses[i] >> 8;
651 keyNum[1] = mfp_card_adresses[i] & 0xff;
652 res = MFPWritePerso(keyNum, key, false, true, data, sizeof(data), &datalen);
653 if (!res && (datalen == 3) && data[0] == 0x09) {
654 PrintAndLogEx(WARNING, "Skipped[%04x]...", mfp_card_adresses[i]);
655 } else {
656 if (res || (datalen != 3) || data[0] != 0x90) {
657 PrintAndLogEx(ERR, "Write error on address %04x", mfp_card_adresses[i]);
658 break;
662 DropField();
664 if (res)
665 return res;
667 PrintAndLogEx(INFO, "Done!");
668 return PM3_SUCCESS;
671 static int CmdHFMFPCommitPerso(const char *Cmd) {
672 CLIParserContext *ctx;
673 CLIParserInit(&ctx, "hf mfp commitp",
674 "Executes Commit Perso command. Can be used in SL0 mode only.\n"
675 "OBS! This command will not be executed if \n"
676 "CardConfigKey, CardMasterKey and L3SwitchKey AES keys are not written.",
677 "hf mfp commitp\n"
678 // "hf mfp commitp --sl 1"
681 void *argtable[] = {
682 arg_param_begin,
683 arg_lit0("v", "verbose", "Verbose output"),
684 // arg_int0(NULL, "sl", "<dec>", "SL mode"),
685 arg_param_end
687 CLIExecWithReturn(ctx, Cmd, argtable, true);
688 bool verbose = arg_get_lit(ctx, 1);
689 // int slmode = arg_get_int(ctx, 2);
690 CLIParserFree(ctx);
692 mfpSetVerboseMode(verbose);
694 uint8_t data[250] = {0};
695 int datalen = 0;
697 int res = MFPCommitPerso(true, false, data, sizeof(data), &datalen);
698 if (res) {
699 PrintAndLogEx(ERR, "Exchange error: %d", res);
700 return res;
703 if (datalen != 3) {
704 PrintAndLogEx(ERR, "Command must return 3 bytes. Got %d", datalen);
705 return PM3_EINVARG;
708 if (data[0] != 0x90) {
709 PrintAndLogEx(ERR, "Command error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
710 return PM3_EINVARG;
712 PrintAndLogEx(INFO, "Switched security level ( " _GREEN_("ok") " )");
713 return PM3_SUCCESS;
716 static int CmdHFMFPAuth(const char *Cmd) {
717 uint8_t keyn[250] = {0};
718 int keynlen = 0;
719 uint8_t key[250] = {0};
720 int keylen = 0;
722 CLIParserContext *ctx;
723 CLIParserInit(&ctx, "hf mfp auth",
724 "Executes AES authentication command for MIFARE Plus card",
725 "hf mfp auth --ki 4000 --key 000102030405060708090a0b0c0d0e0f -> executes authentication\n"
726 "hf mfp auth --ki 9003 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -v -> executes authentication and shows all the system data");
728 void *argtable[] = {
729 arg_param_begin,
730 arg_lit0("v", "verbose", "Verbose output"),
731 arg_str1(NULL, "ki", "<hex>", "Key number, 2 hex bytes"),
732 arg_str1(NULL, "key", "<hex>", "Key, 16 hex bytes"),
733 arg_param_end
735 CLIExecWithReturn(ctx, Cmd, argtable, true);
737 bool verbose = arg_get_lit(ctx, 1);
738 CLIGetHexWithReturn(ctx, 2, keyn, &keynlen);
739 CLIGetHexWithReturn(ctx, 3, key, &keylen);
740 CLIParserFree(ctx);
742 if (keynlen != 2) {
743 PrintAndLogEx(ERR, "ERROR: <key number> must be 2 bytes. Got %d", keynlen);
744 return PM3_EINVARG;
747 if (keylen != 16) {
748 PrintAndLogEx(ERR, "ERROR: <key> must be 16 bytes. Got %d", keylen);
749 return PM3_EINVARG;
752 return MifareAuth4(NULL, keyn, key, true, false, true, verbose, false);
754 static int data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev) {
755 uint8_t kenc[16];
756 memcpy(kenc, mf4session->Kenc, 16);
757 uint8_t ti[4];
758 memcpy(ti, mf4session->TI, 4);
759 uint8_t ctr[1];
760 uint8_t IV[16] = {0, 0, 0x00, 0x00, 0x00, 0, 0x00, 0x00, 0x00, 0};
761 if (rev) {
762 ctr[0] = (uint8_t)(mf4session->R_Ctr & 0xff);
763 for (int i = 0; i < 9; i += 4) {memcpy(&IV[i], ctr, 1);}
764 memcpy(&IV[12], ti, 4); // For reads TI is LS
765 } else {
766 ctr[0] = (uint8_t)(mf4session->W_Ctr & 0xff);
767 for (int i = 3; i < 16; i += 4) {memcpy(&IV[i], ctr, 1);}
768 memcpy(&IV[0], ti, 4); // For writes TI is MS
770 if (rev) {
771 aes_decode(IV, kenc, dati, dato, 16);
772 } else {
773 aes_encode(IV, kenc, dati, dato, 16);
775 return 0;
777 static int CmdHFMFPRdbl(const char *Cmd) {
778 CLIParserContext *ctx;
779 CLIParserInit(&ctx, "hf mfp rdbl",
780 "Reads blocks from MIFARE Plus card",
781 "hf mfp rdbl --blk 0 --key 000102030405060708090a0b0c0d0e0f -> executes authentication and read block 0 data\n"
782 "hf mfp rdbl --blk 1 -v -> executes authentication and shows sector 1 data with default key 0xFF..0xFF");
784 void *argtable[] = {
785 arg_param_begin,
786 arg_lit0("v", "verbose", "Verbose output"),
787 arg_int0("n", "count", "<dec>", "Blocks count (def: 1)"),
788 arg_lit0("b", "keyb", "Use key B (def: keyA)"),
789 arg_lit0("p", "plain", "Do not use encrypted communication mode between reader and card"),
790 arg_lit0(NULL, "nmc", "Do not append MAC to command"),
791 arg_lit0(NULL, "nmr", "Do not expect MAC in reply"),
792 arg_int1(NULL, "blk", "<0..255>", "Block number"),
793 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
794 arg_param_end
796 CLIExecWithReturn(ctx, Cmd, argtable, false);
798 bool verbose = arg_get_lit(ctx, 1);
799 int blocksCount = arg_get_int_def(ctx, 2, 1);
800 bool keyB = arg_get_lit(ctx, 3);
801 bool plain = arg_get_lit(ctx, 4);
802 bool nomaccmd = arg_get_lit(ctx, 5);
803 bool nomacres = arg_get_lit(ctx, 6);
804 uint32_t blockn = arg_get_int(ctx, 7);
806 uint8_t keyn[2] = {0};
807 uint8_t key[250] = {0};
808 int keylen = 0;
809 CLIGetHexWithReturn(ctx, 8, key, &keylen);
810 CLIParserFree(ctx);
812 mfpSetVerboseMode(verbose);
814 if (!keylen) {
815 memmove(key, mfp_default_key, 16);
816 keylen = 16;
819 if (blockn > 255) {
820 PrintAndLogEx(ERR, "<block number> must be in range [0..255]. got %d", blockn);
821 return PM3_EINVARG;
824 if (keylen != 16) {
825 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
826 return PM3_EINVARG;
829 // 3 blocks - wo iso14443-4 chaining
830 if (blocksCount > 3) {
831 PrintAndLogEx(ERR, "blocks count must be less than 3. Got %d", blocksCount);
832 return PM3_EINVARG;
835 if (blocksCount > 1 && mfIsSectorTrailer(blockn)) {
836 PrintAndLogEx(WARNING, "WARNING: trailer!");
839 uint8_t sectorNum = mfSectorNum(blockn & 0xff);
840 uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
841 keyn[0] = uKeyNum >> 8;
842 keyn[1] = uKeyNum & 0xff;
843 if (verbose)
844 PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockn, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
846 mf4Session_t mf4session;
847 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
848 if (res) {
849 PrintAndLogEx(ERR, "Authentication error: %d", res);
850 return res;
853 uint8_t data[250] = {0};
854 int datalen = 0;
855 uint8_t mac[8] = {0};
856 res = MFPReadBlock(&mf4session, plain, nomaccmd, nomacres, blockn & 0xff, blocksCount, false, false, data, sizeof(data), &datalen, mac);
857 if (res) {
858 PrintAndLogEx(ERR, "Read error: %d", res);
859 return res;
862 if (datalen && data[0] != 0x90) {
863 PrintAndLogEx(ERR, "Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
864 return PM3_ESOFT;
866 //PrintAndLogEx(INFO, "%i", 8 && (!macres || 0xff));
867 if (datalen != 1 + blocksCount * 16 + (nomacres ? 0 : 8) + 2) {
868 PrintAndLogEx(ERR, "Error return length: %d", datalen);
869 return PM3_ESOFT;
872 if (!plain) data_crypt(&mf4session, &data[1], &data[1], true);
873 uint8_t sector = mfSectorNum(blockn);
874 mf_print_sector_hdr(sector);
876 int indx = blockn;
877 for (int i = 0; i < blocksCount; i++) {
878 mf_print_block_one(indx, data + 1 + (i * MFBLOCK_SIZE), verbose);
879 indx++;
882 if (memcmp(&data[(blocksCount * 16) + 1], mac, 8) && !nomacres) {
883 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
884 PrintAndLogEx(WARNING, "MAC card... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + (blocksCount * MFBLOCK_SIZE)], 8));
885 PrintAndLogEx(WARNING, "MAC reader... " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
886 } else if (!nomacres) {
887 if (verbose) {
888 PrintAndLogEx(INFO, "MAC... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + (blocksCount * MFBLOCK_SIZE)], 8));
891 PrintAndLogEx(NORMAL, "");
892 return PM3_SUCCESS;
895 static int CmdHFMFPRdsc(const char *Cmd) {
896 CLIParserContext *ctx;
897 CLIParserInit(&ctx, "hf mfp rdsc",
898 "Reads one sector from MIFARE Plus card",
899 "hf mfp rdsc -s 0 --key 000102030405060708090a0b0c0d0e0f -> executes authentication and read sector 0 data\n"
900 "hf mfp rdsc -s 1 -v -> executes authentication and shows sector 1 data with default key");
902 void *argtable[] = {
903 arg_param_begin,
904 arg_lit0("v", "verbose", "Verbose output"),
905 arg_lit0("b", "keyb", "Use key B (def: keyA)"),
906 arg_lit0("p", "plain", "Do not use encrypted communication mode between reader and card"),
907 arg_lit0(NULL, "nmc", "Do not append MAC to command"),
908 arg_lit0(NULL, "nmr", "Do not expect MAC in reply"),
909 arg_int1("s", "sn", "<0..255>", "Sector number"),
910 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
911 arg_param_end
913 CLIExecWithReturn(ctx, Cmd, argtable, false);
915 bool verbose = arg_get_lit(ctx, 1);
916 bool keyB = arg_get_lit(ctx, 2);
917 bool plain = arg_get_lit(ctx, 3);
918 bool nomaccmd = arg_get_lit(ctx, 4);
919 bool nomacres = arg_get_lit(ctx, 5);
920 uint32_t sectorNum = arg_get_int(ctx, 6);
921 uint8_t keyn[2] = {0};
922 uint8_t key[250] = {0};
923 int keylen = 0;
924 CLIGetHexWithReturn(ctx, 7, key, &keylen);
925 CLIParserFree(ctx);
927 mfpSetVerboseMode(verbose);
929 if (!keylen) {
930 memmove(key, mfp_default_key, 16);
931 keylen = 16;
934 if (sectorNum > 39) {
935 PrintAndLogEx(ERR, "<sector number> must be in range [0..39]. Got %d", sectorNum);
936 return PM3_EINVARG;
939 if (keylen != 16) {
940 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
941 return PM3_EINVARG;
944 uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
945 keyn[0] = uKeyNum >> 8;
946 keyn[1] = uKeyNum & 0xff;
947 if (verbose)
948 PrintAndLogEx(INFO, "--sector[%u]:%02x key:%04x", mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
950 mf4Session_t mf4session;
951 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
952 if (res) {
953 PrintAndLogEx(ERR, "Authentication error: %d", res);
954 return res;
957 uint8_t data[250] = {0};
958 int datalen = 0;
959 uint8_t mac[8] = {0};
961 mf_print_sector_hdr(sectorNum);
963 for (int blockno = mfFirstBlockOfSector(sectorNum); blockno < mfFirstBlockOfSector(sectorNum) + mfNumBlocksPerSector(sectorNum); blockno++) {
965 res = MFPReadBlock(&mf4session, plain, nomaccmd, nomacres, blockno & 0xff, 1, false, true, data, sizeof(data), &datalen, mac);
966 if (res) {
967 PrintAndLogEx(ERR, "Read error: %d", res);
968 DropField();
969 return res;
972 if (datalen && data[0] != 0x90) {
973 PrintAndLogEx(ERR, "Card read error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
974 DropField();
975 return PM3_ESOFT;
978 if (datalen != 1 + MFBLOCK_SIZE + (nomacres ? 0 : 8) + 2) {
979 PrintAndLogEx(ERR, "Error return length:%d", datalen);
980 DropField();
981 return PM3_ESOFT;
983 if (!plain) data_crypt(&mf4session, &data[1], &data[1], true);
984 mf_print_block_one(blockno, data + 1, verbose);
986 if (memcmp(&data[1 + 16], mac, 8) && !nomacres) {
987 PrintAndLogEx(WARNING, "WARNING: mac on block %d not equal...", blockno);
988 PrintAndLogEx(WARNING, "MAC card... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + MFBLOCK_SIZE], 8));
989 PrintAndLogEx(WARNING, "MAC reader... " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
990 } else if (!nomacres) {
991 if (verbose) {
992 PrintAndLogEx(INFO, "MAC... " _YELLOW_("%s"), sprint_hex_inrow(&data[1 + MFBLOCK_SIZE], 8));
996 PrintAndLogEx(NORMAL, "");
997 DropField();
998 return PM3_SUCCESS;
1001 static int CmdHFMFPWrbl(const char *Cmd) {
1002 CLIParserContext *ctx;
1003 CLIParserInit(&ctx, "hf mfp wrbl",
1004 "Writes one block to MIFARE Plus card",
1005 "hf mfp wrbl --blk 1 -d ff0000000000000000000000000000ff --key 000102030405060708090a0b0c0d0e0f -> write block 1 data\n"
1006 "hf mfp wrbl --blk 2 -d ff0000000000000000000000000000ff -v -> write block 2 data with default key 0xFF..0xFF"
1009 void *argtable[] = {
1010 arg_param_begin,
1011 arg_lit0("v", "verbose", "Verbose output"),
1012 arg_lit0("b", "keyb", "Use key B (def: keyA)"),
1013 arg_int1(NULL, "blk", "<0..255>", "Block number"),
1014 arg_lit0("p", "plain", "Do not use encrypted transmission"),
1015 arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
1016 arg_str1("d", "data", "<hex>", "Data, 16 hex bytes"),
1017 arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
1018 arg_param_end
1020 CLIExecWithReturn(ctx, Cmd, argtable, false);
1022 bool verbose = arg_get_lit(ctx, 1);
1023 bool keyB = arg_get_lit(ctx, 2);
1024 uint32_t blockNum = arg_get_int(ctx, 3);
1025 bool plain = arg_get_lit(ctx, 4);
1026 bool nomacres = arg_get_lit(ctx, 5);
1028 uint8_t datain[250] = {0};
1029 int datainlen = 0;
1030 CLIGetHexWithReturn(ctx, 6, datain, &datainlen);
1032 uint8_t key[250] = {0};
1033 int keylen = 0;
1034 CLIGetHexWithReturn(ctx, 7, key, &keylen);
1035 CLIParserFree(ctx);
1037 uint8_t keyn[2] = {0};
1039 mfpSetVerboseMode(verbose);
1041 if (!keylen) {
1042 memmove(key, mfp_default_key, 16);
1043 keylen = 16;
1046 if (blockNum > 255) {
1047 PrintAndLogEx(ERR, "<block number> must be in range [0..255]. Got %d", blockNum);
1048 return PM3_EINVARG;
1051 if (keylen != 16) {
1052 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
1053 return PM3_EINVARG;
1056 if (datainlen != 16) {
1057 PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
1058 return PM3_EINVARG;
1061 uint8_t sectorNum = mfSectorNum(blockNum & 0xff);
1062 uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);
1063 keyn[0] = uKeyNum >> 8;
1064 keyn[1] = uKeyNum & 0xff;
1065 if (verbose)
1066 PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockNum & 0xff, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum);
1068 mf4Session_t mf4session;
1069 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
1070 if (res) {
1071 PrintAndLogEx(ERR, "Authentication error: %d", res);
1072 return res;
1074 if (!plain) data_crypt(&mf4session, &datain[0], &datain[0], false);
1075 uint8_t data[250] = {0};
1076 int datalen = 0;
1077 uint8_t mac[8] = {0};
1078 res = MFPWriteBlock(&mf4session, plain, nomacres, blockNum & 0xff, 0x00, datain, false, false, data, sizeof(data), &datalen, mac);
1079 if (res) {
1080 PrintAndLogEx(ERR, "Write error: %d", res);
1081 DropField();
1082 return res;
1085 if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
1086 PrintAndLogEx(ERR, "Error return length:%d", datalen);
1087 DropField();
1088 return PM3_ESOFT;
1091 if (datalen && data[0] != 0x90) {
1092 PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
1093 DropField();
1094 return PM3_ESOFT;
1097 if (memcmp(&data[1], mac, 8) && !nomacres) {
1098 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
1099 PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8));
1100 PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
1101 } else if (!nomacres) {
1102 if (verbose)
1103 PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
1106 DropField();
1107 PrintAndLogEx(INFO, "Write ( " _GREEN_("ok") " )");
1108 return PM3_SUCCESS;
1111 static int CmdHFMFPChKey(const char *Cmd) {
1112 CLIParserContext *ctx;
1113 CLIParserInit(&ctx, "hf mfp chkey",
1114 "Change the keys on a Mifare Plus tag",
1115 "This requires the key that can update the key that you are trying to update.\n"
1116 "hf mfp chkey --ki 401f -d FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF --key A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 -> Change key B for Sector 15 from MAD to default\n"
1117 "hf mfp chkey --ki 9000 -d 32F9351A1C02B35FF97E0CA943F814F6 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> Change card master key to custom from default"
1120 void *argtable[] = {
1121 arg_param_begin,
1122 arg_lit0("v", "verbose", "Verbose output"),
1123 arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
1124 arg_str1(NULL, "ki", "<hex>", "Key Index, 2 hex bytes"),
1125 arg_str0("k", "key", "<hex>", "Current sector key, 16 hex bytes"),
1126 arg_lit0("b", "typeb", "Sector key is key B"),
1127 arg_str1("d", "data", "<hex>", "New key, 16 hex bytes"),
1128 arg_param_end
1130 CLIExecWithReturn(ctx, Cmd, argtable, false);
1132 bool verbose = arg_get_lit(ctx, 1);
1133 bool nomacres = arg_get_lit(ctx, 2);
1135 uint8_t keyn[250] = {0};
1137 uint8_t ki[250] = {0};
1138 int kilen = 0;
1139 CLIGetHexWithReturn(ctx, 3, ki, &kilen);
1141 uint8_t key[250] = {0};
1142 int keylen = 0;
1143 CLIGetHexWithReturn(ctx, 4, key, &keylen);
1145 bool usekeyb = arg_get_lit(ctx, 5);
1146 uint8_t datain[250] = {0};
1147 int datainlen = 0;
1148 CLIGetHexWithReturn(ctx, 6, datain, &datainlen);
1150 CLIParserFree(ctx);
1152 mfpSetVerboseMode(verbose);
1154 if (!keylen) {
1155 memmove(key, mfp_default_key, 16);
1156 keylen = 16;
1159 if (keylen != 16) {
1160 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
1161 return PM3_EINVARG;
1164 if (datainlen != 16) {
1165 PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
1166 return PM3_EINVARG;
1168 mf4Session_t mf4session;
1169 keyn[0] = ki[0];
1170 if (ki[0] == 0x40) { // Only if we are working with sector keys
1171 if (usekeyb) {
1172 keyn[1] = (ki[1] % 2 == 0) ? ki[1] + 1 : ki[1]; // If we change using key B, check if KI is key A
1173 } else {
1174 keyn[1] = (ki[1] % 2 == 0) ? ki[1] : ki[1] - 1; // If we change using key A, check if KI is key A
1176 } else {keyn[1] = ki[1];}
1177 if (verbose) {
1178 PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
1180 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
1181 if (res) {
1182 PrintAndLogEx(ERR, "Authentication error: %d", res);
1183 return res;
1185 data_crypt(&mf4session, &datain[0], &datain[0], false);
1186 uint8_t data[250] = {0};
1187 int datalen = 0;
1188 uint8_t mac[8] = {0};
1189 res = MFPWriteBlock(&mf4session, false, nomacres, ki[1], ki[0], datain, false, false, data, sizeof(data), &datalen, mac);
1190 if (res) {
1191 PrintAndLogEx(ERR, "Write error: %d", res);
1192 DropField();
1193 return res;
1196 if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
1197 PrintAndLogEx(ERR, "Error return length:%d", datalen);
1198 DropField();
1199 return PM3_ESOFT;
1202 if (datalen && data[0] != 0x90) {
1203 PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
1204 DropField();
1205 return PM3_ESOFT;
1208 if (memcmp(&data[1], mac, 8) && !nomacres) {
1209 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
1210 PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8));
1211 PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
1212 } else if (!nomacres) {
1213 if (verbose)
1214 PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
1217 DropField();
1218 PrintAndLogEx(INFO, "Key update ( " _GREEN_("ok") " )");
1219 return PM3_SUCCESS;
1222 static int CmdHFMFPChConf(const char *Cmd) {
1223 CLIParserContext *ctx;
1224 CLIParserInit(&ctx, "hf mfp chconf",
1225 "Change the configuration on a Mifare Plus tag. DANGER!",
1226 "This requires Card Master Key (9000) or Card Configuration Key (9001).\n"
1227 "Configuration block info can be found below.\n"
1228 "* Block B000 (00; CMK): Max amount of commands without MAC (byte 0), as well as plain mode access (unknown).\n"
1229 "* Block B001 (01; CCK): Installation identifier for Virtual Card. Please consult NXP for data.\n"
1230 "* Block B002 (02; CCK): ATS data.\n"
1231 "* 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"
1232 "More configuration tips to follow. Check JMY600 Series IC Card Module.\n"
1233 "hf mfp chconf -c 00 -d 10ffffffffffffffffffffffffffffff --key A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 -> Allow 16 commands without MAC in a single transaction."
1236 void *argtable[] = {
1237 arg_param_begin,
1238 arg_lit0("v", "verbose", "Verbose output"),
1239 arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
1240 arg_int1("c", "conf", "<hex>", "Config block number, 0-3"),
1241 arg_str0("k", "key", "<hex>", "Card key, 16 hex bytes"),
1242 arg_lit0(NULL, "cck", "Auth as Card Configuration key instead of Card Master Key"),
1243 arg_str1("d", "data", "<hex>", "New configuration data, 16 hex bytes"),
1244 arg_param_end
1246 CLIExecWithReturn(ctx, Cmd, argtable, false);
1248 bool verbose = arg_get_lit(ctx, 1);
1249 bool nomacres = arg_get_lit(ctx, 2);
1251 uint8_t keyn[250] = {0};
1252 uint32_t blockNum = arg_get_int(ctx, 3);
1254 uint8_t key[250] = {0};
1255 int keylen = 0;
1256 CLIGetHexWithReturn(ctx, 4, key, &keylen);
1257 bool usecck = arg_get_lit(ctx, 5);
1259 uint8_t datain[250] = {0};
1260 int datainlen = 0;
1261 CLIGetHexWithReturn(ctx, 6, datain, &datainlen);
1263 CLIParserFree(ctx);
1265 mfpSetVerboseMode(verbose);
1267 if (!keylen) {
1268 memmove(key, mfp_default_key, 16);
1269 keylen = 16;
1272 if (keylen != 16) {
1273 PrintAndLogEx(ERR, "<key> must be 16 bytes. Got %d", keylen);
1274 return PM3_EINVARG;
1277 if (datainlen != 16) {
1278 PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
1279 return PM3_EINVARG;
1282 if (blockNum > 3) {
1283 PrintAndLogEx(ERR, "<config number> must be in range [0..3]. Got %d", blockNum);
1284 return PM3_EINVARG;
1286 mf4Session_t mf4session;
1287 keyn[0] = 0x90;
1288 keyn[1] = usecck ? 0x01 : 0x00;
1289 if (verbose) {
1290 PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
1292 int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
1293 if (res) {
1294 PrintAndLogEx(ERR, "Authentication error: %d", res);
1295 return res;
1297 data_crypt(&mf4session, &datain[0], &datain[0], false);
1298 uint8_t data[250] = {0};
1299 int datalen = 0;
1300 uint8_t mac[8] = {0};
1301 res = MFPWriteBlock(&mf4session, false, nomacres, blockNum & 0xff, 0xb0, datain, false, false, data, sizeof(data), &datalen, mac);
1302 if (res) {
1303 PrintAndLogEx(ERR, "Write error: %d", res);
1304 DropField();
1305 return res;
1308 if (datalen != 3 && (datalen != 3 + (nomacres ? 0 : 8))) {
1309 PrintAndLogEx(ERR, "Error return length:%d", datalen);
1310 DropField();
1311 return PM3_ESOFT;
1314 if (datalen && data[0] != 0x90) {
1315 PrintAndLogEx(ERR, "Card write error: %02x %s", data[0], mfpGetErrorDescription(data[0]));
1316 DropField();
1317 return PM3_ESOFT;
1320 if (memcmp(&data[1], mac, 8) && !nomacres) {
1321 PrintAndLogEx(WARNING, "WARNING: mac not equal...");
1322 PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8));
1323 PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8));
1324 } else if (!nomacres) {
1325 if (verbose)
1326 PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8));
1329 DropField();
1330 PrintAndLogEx(INFO, "Write config ( " _GREEN_("ok") " )");
1331 return PM3_SUCCESS;
1334 static int plus_key_check(uint8_t startSector, uint8_t endSector, uint8_t startKeyAB, uint8_t endKeyAB,
1335 uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], size_t keyListLen, uint8_t foundKeys[2][64][AES_KEY_LEN + 1],
1336 bool verbose) {
1337 int res;
1338 bool selectCard = true;
1339 uint8_t keyn[2] = {0};
1341 // sector number from 0
1342 for (uint8_t sector = startSector; sector <= endSector; sector++) {
1343 // 0-keyA 1-keyB
1344 for (uint8_t keyAB = startKeyAB; keyAB <= endKeyAB; keyAB++) {
1345 // main cycle with key check
1346 for (int i = 0; i < keyListLen; i++) {
1348 // allow client abort every iteration
1349 if (kbd_enter_pressed()) {
1350 PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
1351 DropField();
1352 return PM3_EOPABORTED;
1355 if (i % 10 == 0) {
1356 if (verbose == false) {
1357 PrintAndLogEx(NORMAL, "." NOLF);
1361 uint16_t uKeyNum = 0x4000 + sector * 2 + keyAB;
1362 keyn[0] = uKeyNum >> 8;
1363 keyn[1] = uKeyNum & 0xff;
1365 for (int retry = 0; retry < 4; retry++) {
1366 res = MifareAuth4(NULL, keyn, keyList[i], selectCard, true, false, false, true);
1367 if (res == PM3_SUCCESS || res == PM3_EWRONGANSWER)
1368 break;
1370 if (verbose)
1371 PrintAndLogEx(WARNING, "\nretried[%d]...", retry);
1372 else
1373 PrintAndLogEx(NORMAL, "R" NOLF);
1375 DropField();
1376 selectCard = true;
1377 msleep(100);
1380 // key for [sector,keyAB] found
1381 if (res == PM3_SUCCESS) {
1382 if (verbose)
1383 PrintAndLogEx(INFO, "\nFound key for sector %d key %s [%s]", sector, keyAB == 0 ? "A" : "B", sprint_hex_inrow(keyList[i], 16));
1384 else
1385 PrintAndLogEx(NORMAL, "+" NOLF);
1387 foundKeys[keyAB][sector][0] = 0x01;
1388 memcpy(&foundKeys[keyAB][sector][1], keyList[i], AES_KEY_LEN);
1389 DropField();
1390 selectCard = true;
1391 msleep(50);
1393 // break out from keylist check loop,
1394 break;
1397 if (verbose)
1398 PrintAndLogEx(WARNING, "\nsector %02d key %d [%s] res: %d", sector, keyAB, sprint_hex_inrow(keyList[i], 16), res);
1400 // RES can be:
1401 // PM3_ERFTRANS -7
1402 // PM3_EWRONGANSWER -16
1403 if (res == PM3_ERFTRANS) {
1404 if (verbose)
1405 PrintAndLogEx(ERR, "\nExchange error. Aborted.");
1406 else
1407 PrintAndLogEx(NORMAL, "E" NOLF);
1409 DropField();
1410 return PM3_ECARDEXCHANGE;
1413 selectCard = false;
1418 DropField();
1419 return PM3_SUCCESS;
1422 static void Fill2bPattern(uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], uint32_t *keyListLen, uint32_t *startPattern) {
1423 for (uint32_t pt = *startPattern; pt < 0x10000; pt++) {
1424 keyList[*keyListLen][0] = (pt >> 8) & 0xff;
1425 keyList[*keyListLen][1] = pt & 0xff;
1426 memcpy(&keyList[*keyListLen][2], &keyList[*keyListLen][0], 2);
1427 memcpy(&keyList[*keyListLen][4], &keyList[*keyListLen][0], 4);
1428 memcpy(&keyList[*keyListLen][8], &keyList[*keyListLen][0], 8);
1429 (*keyListLen)++;
1430 *startPattern = pt;
1431 if (*keyListLen == MAX_AES_KEYS_LIST_LEN)
1432 break;
1434 (*startPattern)++;
1437 static int CmdHFMFPChk(const char *Cmd) {
1439 CLIParserContext *ctx;
1440 CLIParserInit(&ctx, "hf mfp chk",
1441 "Checks keys on MIFARE Plus card",
1442 "hf mfp chk -k 000102030405060708090a0b0c0d0e0f -> check key on sector 0 as key A and B\n"
1443 "hf mfp chk -s 2 -a -> check default key list on sector 2, only key A\n"
1444 "hf mfp chk -d mfp_default_keys -s0 -e6 -> check keys from dictionary against sectors 0-6\n"
1445 "hf mfp chk --pattern1b --dump -> check all 1-byte keys pattern and save found keys to file\n"
1446 "hf mfp chk --pattern2b --startp2b FA00 -> check all 2-byte keys pattern. Start from key FA00FA00...FA00");
1448 void *argtable[] = {
1449 arg_param_begin,
1450 arg_lit0("a", "keya", "Check only key A (def: check all keys)"),
1451 arg_lit0("b", "keyb", "Check only key B (def: check all keys)"),
1452 arg_int0("s", "startsec", "<0..255>", "Start sector number"),
1453 arg_int0("e", "endsec", "<0..255>", "End sector number"),
1454 arg_str0("k", "key", "<hex>", "Key for checking (HEX 16 bytes)"),
1455 arg_str0("d", "dict", "<fn>", "Dictionary file with keys"),
1456 arg_lit0(NULL, "pattern1b", "Check all 1-byte combinations of key (0000...0000, 0101...0101, 0202...0202, ...)"),
1457 arg_lit0(NULL, "pattern2b", "Check all 2-byte combinations of key (0000...0000, 0001...0001, 0002...0002, ...)"),
1458 arg_str0(NULL, "startp2b", "<pattern>", "Start key (2-byte HEX) for 2-byte search (use with `--pattern2b`)"),
1459 arg_lit0(NULL, "dump", "Dump found keys to JSON file"),
1460 arg_lit0("v", "verbose", "Verbose output"),
1461 arg_param_end
1463 CLIExecWithReturn(ctx, Cmd, argtable, true);
1465 bool keyA = arg_get_lit(ctx, 1);
1466 bool keyB = arg_get_lit(ctx, 2);
1467 uint8_t startSector = arg_get_int_def(ctx, 3, 0);
1468 uint8_t endSector = arg_get_int_def(ctx, 4, 0);
1470 uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN] = {{0}};
1471 uint32_t keyListLen = 0;
1472 uint8_t foundKeys[2][64][AES_KEY_LEN + 1] = {{{0}}};
1474 uint8_t vkey[16] = {0};
1475 int vkeylen = 0;
1476 CLIGetHexWithReturn(ctx, 5, vkey, &vkeylen);
1477 if (vkeylen > 0) {
1478 if (vkeylen == 16) {
1479 memcpy(&keyList[keyListLen], vkey, 16);
1480 keyListLen++;
1481 } else {
1482 PrintAndLogEx(ERR, "Specified key must have 16 bytes. Got %d", vkeylen);
1483 CLIParserFree(ctx);
1484 return PM3_EINVARG;
1488 uint8_t dict_filename[FILE_PATH_SIZE + 2] = {0};
1489 int dict_filenamelen = 0;
1490 if (CLIParamStrToBuf(arg_get_str(ctx, 6), dict_filename, FILE_PATH_SIZE, &dict_filenamelen)) {
1491 PrintAndLogEx(FAILED, "File name too long or invalid.");
1492 CLIParserFree(ctx);
1493 return PM3_EINVARG;
1496 bool pattern1b = arg_get_lit(ctx, 7);
1497 bool pattern2b = arg_get_lit(ctx, 8);
1499 if (pattern1b && pattern2b) {
1500 PrintAndLogEx(ERR, "Pattern search mode must be 2-byte or 1-byte only.");
1501 CLIParserFree(ctx);
1502 return PM3_EINVARG;
1505 if (dict_filenamelen && (pattern1b || pattern2b)) {
1506 PrintAndLogEx(ERR, "Pattern search mode and dictionary mode can't be used in one command.");
1507 CLIParserFree(ctx);
1508 return PM3_EINVARG;
1511 uint32_t startPattern = 0x0000;
1512 uint8_t vpattern[2];
1513 int vpatternlen = 0;
1514 CLIGetHexWithReturn(ctx, 9, vpattern, &vpatternlen);
1515 if (vpatternlen > 0) {
1516 if (vpatternlen <= 2) {
1517 startPattern = (vpattern[0] << 8) + vpattern[1];
1518 } else {
1519 PrintAndLogEx(ERR, "Pattern must be 2-bytes. Got %d", vpatternlen);
1520 CLIParserFree(ctx);
1521 return PM3_EINVARG;
1523 if (!pattern2b)
1524 PrintAndLogEx(WARNING, "Pattern entered, but search mode not is 2-byte search.");
1527 bool create_dumpfile = arg_get_lit(ctx, 10);
1528 bool verbose = arg_get_lit(ctx, 11);
1529 CLIParserFree(ctx);
1531 uint8_t startKeyAB = 0;
1532 uint8_t endKeyAB = 1;
1533 if (keyA && (keyB == false))
1534 endKeyAB = 0;
1536 if ((keyA == false) && keyB)
1537 startKeyAB = 1;
1539 if (endSector < startSector)
1540 endSector = startSector;
1542 // 1-byte pattern search mode
1543 if (pattern1b) {
1544 for (int i = 0; i < 0x100; i++) {
1545 memset(keyList[i], i, 16);
1548 keyListLen = 0x100;
1551 // 2-byte pattern search mode
1552 if (pattern2b) {
1553 Fill2bPattern(keyList, &keyListLen, &startPattern);
1556 int res = PM3_SUCCESS;
1558 // dictionary mode
1559 size_t endFilePosition = 0;
1560 if (dict_filenamelen) {
1561 uint32_t keycnt = 0;
1562 res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, 0, &endFilePosition, true);
1564 if (res == PM3_SUCCESS && endFilePosition) {
1565 keyListLen = keycnt;
1566 PrintAndLogEx(SUCCESS, "First part of dictionary successfully loaded.");
1570 if (keyListLen == 0) {
1571 for (int i = 0; i < g_mifare_plus_default_keys_len; i++) {
1572 if (hex_to_bytes(g_mifare_plus_default_keys[i], keyList[keyListLen], 16) != 16) {
1573 break;
1576 keyListLen++;
1580 if (keyListLen == 0) {
1581 PrintAndLogEx(ERR, "Key list is empty. Nothing to check.");
1582 return PM3_EINVARG;
1583 } else {
1584 PrintAndLogEx(INFO, "Loaded " _YELLOW_("%"PRIu32) " keys", keyListLen);
1587 if (verbose == false) {
1588 PrintAndLogEx(INFO, "Search keys");
1591 while (true) {
1592 res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, keyList, keyListLen, foundKeys, verbose);
1593 if (res == PM3_EOPABORTED) {
1594 break;
1597 if (pattern2b && startPattern < 0x10000) {
1598 if (verbose == false) {
1599 PrintAndLogEx(NORMAL, "p" NOLF);
1602 keyListLen = 0;
1603 Fill2bPattern(keyList, &keyListLen, &startPattern);
1604 continue;
1607 if (dict_filenamelen && endFilePosition) {
1608 if (verbose == false)
1609 PrintAndLogEx(NORMAL, "d" NOLF);
1611 uint32_t keycnt = 0;
1612 res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false);
1613 if (res == PM3_SUCCESS && endFilePosition) {
1614 keyListLen = keycnt;
1617 continue;
1619 break;
1622 if (verbose == false) {
1623 PrintAndLogEx(NORMAL, "");
1626 // print result
1627 char strA[46 + 1] = {0};
1628 char strB[46 + 1] = {0};
1630 uint8_t ndef_key[] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7};
1631 bool has_ndef_key = false;
1632 bool printedHeader = false;
1633 for (uint8_t s = startSector; s <= endSector; s++) {
1635 if ((memcmp(&foundKeys[0][s][1], ndef_key, AES_KEY_LEN) == 0) ||
1636 (memcmp(&foundKeys[1][s][1], ndef_key, AES_KEY_LEN) == 0)) {
1637 has_ndef_key = true;
1640 if (printedHeader == false) {
1641 PrintAndLogEx(NORMAL, "");
1642 PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
1643 PrintAndLogEx(INFO, " Sec | key A | key B");
1644 PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------");
1645 printedHeader = true;
1648 if (foundKeys[0][s][0]) {
1649 snprintf(strA, sizeof(strA), _GREEN_("%s"), sprint_hex_inrow(&foundKeys[0][s][1], AES_KEY_LEN));
1650 } else {
1651 snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------");
1654 if (foundKeys[1][s][0]) {
1655 snprintf(strB, sizeof(strB), _GREEN_("%s"), sprint_hex_inrow(&foundKeys[1][s][1], AES_KEY_LEN));
1656 } else {
1657 snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------");
1660 PrintAndLogEx(INFO, " " _YELLOW_("%03d") " | %s | %s", s, strA, strB);
1663 if (printedHeader == false)
1664 PrintAndLogEx(INFO, "No keys found(");
1665 else
1666 PrintAndLogEx(INFO, "-----+----------------------------------+----------------------------------\n");
1668 // save keys to json
1669 if (create_dumpfile && printedHeader) {
1671 size_t keys_len = (2 * 64 * (AES_KEY_LEN + 1));
1673 uint8_t data[10 + 1 + 2 + 1 + 256 + keys_len];
1674 memset(data, 0, sizeof(data));
1676 // Mifare Plus info
1677 SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0);
1679 PacketResponseNG resp;
1680 if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
1681 PrintAndLogEx(WARNING, "timeout while waiting for reply.");
1682 return PM3_ETIMEOUT;
1685 iso14a_card_select_t card;
1686 memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
1688 uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision
1689 uint8_t atslen = 0;
1690 if (select_status == 1 || select_status == 2) {
1691 memcpy(data, card.uid, card.uidlen);
1692 data[10] = card.sak;
1693 data[11] = card.atqa[1];
1694 data[12] = card.atqa[0];
1695 atslen = card.ats_len;
1696 data[13] = atslen;
1697 memcpy(&data[14], card.ats, atslen);
1700 char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-key")) + card.uidlen * 2 + 1, sizeof(uint8_t));
1701 strcpy(fptr, "hf-mfp-");
1703 FillFileNameByUID(fptr, card.uid, "-key", card.uidlen);
1705 // length: UID(10b)+SAK(1b)+ATQA(2b)+ATSlen(1b)+ATS(atslen)+foundKeys[2][64][AES_KEY_LEN + 1]
1706 memcpy(&data[14 + atslen], foundKeys, keys_len);
1707 // 64 here is for how many "rows" there is in the data array. A bit confusing
1708 saveFileJSON(fptr, jsfMfPlusKeys, data, 64, NULL);
1709 free(fptr);
1712 // MAD detection
1713 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)) {
1714 PrintAndLogEx(HINT, "MAD key detected. Try " _YELLOW_("`hf mfp mad`") " for more details");
1717 // NDEF detection
1718 if (has_ndef_key) {
1719 PrintAndLogEx(HINT, "NDEF key detected. Try " _YELLOW_("`hf mfp ndefread -h`") " for more details");
1721 PrintAndLogEx(NORMAL, "");
1722 return PM3_SUCCESS;
1725 static int CmdHFMFPDump(const char *Cmd) {
1726 CLIParserContext *ctx;
1727 CLIParserInit(&ctx, "hf mfp dump",
1728 "Dump MIFARE Plus tag to file (bin/json)\n"
1729 "If no <name> given, UID will be used as filename",
1730 "hf mfp dump\n"
1731 "hf mfp dump --keys hf-mf-066C8B78-key.bin --> MIFARE Plus with keys from specified file\n");
1733 void *argtable[] = {
1734 arg_param_begin,
1735 arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
1736 arg_str0("k", "keys", "<fn>", "Specify a filename for keys file"),
1737 // arg_lit0(NULL, "ns", "no save to file"),
1738 // arg_lit0("v", "verbose", "Verbose output"),
1739 arg_param_end
1741 CLIExecWithReturn(ctx, Cmd, argtable, true);
1743 int datafnlen = 0;
1744 char data_fn[FILE_PATH_SIZE] = {0};
1745 CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)data_fn, FILE_PATH_SIZE, &datafnlen);
1747 int keyfnlen = 0;
1748 char key_fn[FILE_PATH_SIZE] = {0};
1749 CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)key_fn, FILE_PATH_SIZE, &keyfnlen);
1751 // bool nosave = arg_get_lit(ctx, 3);
1752 // bool verbose = arg_get_lit(ctx, 4);
1753 CLIParserFree(ctx);
1755 PrintAndLogEx(INFO, " To be implemented, feel free to contribute!");
1756 return PM3_ENOTIMPL;
1759 mfpSetVerboseMode(verbose);
1761 // read card
1762 uint8_t *mem = calloc(MIFARE_4K_MAXBLOCK * MFBLOCK_SIZE, sizeof(uint8_t));
1763 if (mem == NULL) {
1764 PrintAndLogEx(ERR, "failed to allocate memory");
1765 return PM3_EMALLOC;
1769 // iso14a_card_select_t card ;
1770 // int res = mfp_read_tag(&card, mem, key_fn);
1771 // if (res != PM3_SUCCESS) {
1772 // free(mem);
1773 // return res;
1774 // }
1777 // Skip saving card data to file
1778 if (nosave) {
1779 PrintAndLogEx(INFO, "Called with no save option");
1780 free(mem);
1781 return PM3_SUCCESS;
1784 // Save to file
1785 // if (strlen(data_fn) < 1) {
1786 // char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-dump")) + card.uidlen * 2 + 1, sizeof(uint8_t));
1787 // strcpy(fptr, "hf-mfp-");
1788 // FillFileNameByUID(fptr, card.uid, "-dump", card.uidlen);
1789 // strcpy(data_fn, fptr);
1790 // free(fptr);
1791 // }
1793 // pm3_save_mf_dump(filename, dump, MIFARE_4K_MAX_BYTES, jsfCardMemory);
1795 free(mem);
1796 return PM3_SUCCESS;
1800 static int CmdHFMFPMAD(const char *Cmd) {
1802 CLIParserContext *ctx;
1803 CLIParserInit(&ctx, "hf mfp mad",
1804 "Checks and prints MIFARE Application Directory (MAD)",
1805 "hf mfp mad\n"
1806 "hf mfp mad --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> read and print NDEF data from MAD aid");
1808 void *argtable[] = {
1809 arg_param_begin,
1810 arg_lit0("v", "verbose", "Verbose output"),
1811 arg_str0(NULL, "aid", "<hex>", "Print all sectors with aid"),
1812 arg_str0("k", "key", "<hex>", "Key for printing sectors"),
1813 arg_lit0("b", "keyb", "Use key B for access printing sectors (def: key A)"),
1814 arg_lit0(NULL, "be", "(optional: BigEndian)"),
1815 arg_lit0(NULL, "dch", "Decode Card Holder information"),
1816 arg_param_end
1818 CLIExecWithReturn(ctx, Cmd, argtable, true);
1820 bool verbose = arg_get_lit(ctx, 1);
1821 uint8_t aid[2] = {0};
1822 int aidlen;
1823 CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
1824 uint8_t key[16] = {0};
1825 int keylen;
1826 CLIGetHexWithReturn(ctx, 3, key, &keylen);
1827 bool keyB = arg_get_lit(ctx, 4);
1828 bool swapmad = arg_get_lit(ctx, 5);
1829 bool decodeholder = arg_get_lit(ctx, 6);
1831 CLIParserFree(ctx);
1833 if (aidlen != 2 && !decodeholder && keylen > 0) {
1834 PrintAndLogEx(WARNING, "Using default MAD keys instead");
1837 uint8_t sector0[16 * 4] = {0};
1838 uint8_t sector10[16 * 4] = {0};
1840 if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) {
1841 PrintAndLogEx(NORMAL, "");
1842 PrintAndLogEx(ERR, "error, read sector 0. card doesn't have MAD or doesn't have MAD on default keys");
1843 return PM3_ESOFT;
1846 MADPrintHeader();
1848 if (verbose) {
1849 PrintAndLogEx(SUCCESS, "Raw:");
1850 for (int i = 0; i < 4; i ++)
1851 PrintAndLogEx(INFO, "[%d] %s", i, sprint_hex(&sector0[i * 16], 16));
1854 bool haveMAD2 = false;
1855 MAD1DecodeAndPrint(sector0, swapmad, verbose, &haveMAD2);
1857 if (haveMAD2) {
1858 if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) {
1859 PrintAndLogEx(NORMAL, "");
1860 PrintAndLogEx(ERR, "error, read sector " _YELLOW_("0x10") ". Card doesn't have MAD or doesn't have MAD on default keys");
1861 return PM3_ESOFT;
1864 MAD2DecodeAndPrint(sector10, swapmad, verbose);
1867 if (aidlen == 2 || decodeholder) {
1868 uint16_t mad[7 + 8 + 8 + 8 + 8] = {0};
1869 size_t madlen = 0;
1870 if (MADDecode(sector0, sector10, mad, &madlen, swapmad)) {
1871 PrintAndLogEx(ERR, "can't decode MAD");
1872 return PM3_EWRONGANSWER;
1875 // copy default NDEF key
1876 uint8_t akey[16] = {0};
1877 memcpy(akey, g_mifarep_ndef_key, 16);
1879 // user specified key
1880 if (keylen == 16) {
1881 memcpy(akey, key, 16);
1884 uint16_t aaid = 0x0004;
1885 if (aidlen == 2) {
1886 aaid = (aid[0] << 8) + aid[1];
1887 PrintAndLogEx(NORMAL, "");
1888 PrintAndLogEx(INFO, "-------------- " _CYAN_("AID 0x%04x") " ---------------", aaid);
1890 for (int i = 0; i < madlen; i++) {
1891 if (aaid == mad[i]) {
1892 uint8_t vsector[16 * 4] = {0};
1893 if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector, false)) {
1894 PrintAndLogEx(NORMAL, "");
1895 PrintAndLogEx(ERR, "error, read sector %d error", i + 1);
1896 return PM3_ESOFT;
1899 for (int j = 0; j < (verbose ? 4 : 3); j ++)
1900 PrintAndLogEx(NORMAL, " [%03d] %s", (i + 1) * 4 + j, sprint_hex(&vsector[j * 16], 16));
1905 if (decodeholder) {
1907 PrintAndLogEx(NORMAL, "");
1908 PrintAndLogEx(INFO, "-------- " _CYAN_("Card Holder Info 0x%04x") " --------", aaid);
1910 uint8_t data[4096] = {0};
1911 int datalen = 0;
1913 for (int i = 0; i < madlen; i++) {
1914 if (aaid == mad[i]) {
1916 uint8_t vsector[16 * 4] = {0};
1917 if (mfReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, akey, vsector)) {
1918 PrintAndLogEx(NORMAL, "");
1919 PrintAndLogEx(ERR, "error, read sector %d", i + 1);
1920 return PM3_ESOFT;
1923 memcpy(&data[datalen], vsector, 16 * 3);
1924 datalen += 16 * 3;
1928 if (!datalen) {
1929 PrintAndLogEx(WARNING, "no Card Holder Info data");
1930 return PM3_SUCCESS;
1932 MADCardHolderInfoDecode(data, datalen, verbose);
1935 return PM3_SUCCESS;
1938 static int CmdHFMFPNDEFFormat(const char *Cmd) {
1939 CLIParserContext *ctx;
1940 CLIParserInit(&ctx, "hf mfp ndefformat",
1941 "format MIFARE Plus Tag as a NFC tag with Data Exchange Format (NDEF)\n"
1942 "If no <name> given, UID will be used as filename. \n"
1943 "It will try default keys and MAD keys to detect if tag is already formatted in order to write.\n"
1944 "\n"
1945 "If not, it will try finding a key file based on your UID. ie, if you ran autopwn before",
1946 "hf mfp ndefformat\n"
1947 "hf mfp ndefformat --keys hf-mf-01020304-key.bin --> with keys from specified file\n"
1950 void *argtable[] = {
1951 arg_param_begin,
1952 arg_str0("k", "keys", "<fn>", "filename of keys"),
1953 arg_param_end
1955 CLIExecWithReturn(ctx, Cmd, argtable, true);
1957 int keyfnlen = 0;
1958 char keyFilename[FILE_PATH_SIZE] = {0};
1959 CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)keyFilename, FILE_PATH_SIZE, &keyfnlen);
1961 CLIParserFree(ctx);
1963 PrintAndLogEx(SUCCESS, "Not implemented yet. Feel free to contribute!");
1964 PrintAndLogEx(NORMAL, "");
1965 return PM3_SUCCESS;
1968 int CmdHFMFPNDEFRead(const char *Cmd) {
1970 CLIParserContext *ctx;
1971 CLIParserInit(&ctx, "hf mfp ndefread",
1972 "Prints NFC Data Exchange Format (NDEF)",
1973 "hf mfp ndefread \n"
1974 "hf mfp ndefread -vv -> shows NDEF parsed and raw data\n"
1975 "hf mfp ndefread --aid e103 -k d3f7d3f7d3f7d3f7d3f7d3f7d3f7d3f7 -> shows NDEF data with custom AID and key\n"
1976 "hf mfp ndefread -f myfilename -> save raw NDEF to file"
1979 void *argtable[] = {
1980 arg_param_begin,
1981 arg_litn("v", "verbose", 0, 2, "verbose output"),
1982 arg_str0(NULL, "aid", "<aid>", "replace default aid for NDEF"),
1983 arg_str0("k", "key", "<key>", "replace default key for NDEF"),
1984 arg_lit0("b", "keyb", "use key B for access sectors (by default: key A)"),
1985 arg_str0("f", "file", "<fn>", "save raw NDEF to file"),
1986 arg_param_end
1988 CLIExecWithReturn(ctx, Cmd, argtable, true);
1990 bool verbose = arg_get_lit(ctx, 1);
1991 bool verbose2 = arg_get_lit(ctx, 1) > 1;
1992 uint8_t aid[2] = {0};
1993 int aidlen;
1994 CLIGetHexWithReturn(ctx, 2, aid, &aidlen);
1995 uint8_t key[16] = {0};
1996 int keylen;
1997 CLIGetHexWithReturn(ctx, 3, key, &keylen);
1998 bool keyB = arg_get_lit(ctx, 4);
2000 int fnlen = 0;
2001 char filename[FILE_PATH_SIZE] = {0};
2002 CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
2003 CLIParserFree(ctx);
2005 uint16_t ndefAID = 0xe103;
2006 if (aidlen == 2)
2007 ndefAID = (aid[0] << 8) + aid[1];
2009 uint8_t ndefkey[16] = {0};
2010 memcpy(ndefkey, g_mifarep_ndef_key, 16);
2011 if (keylen == 16) {
2012 memcpy(ndefkey, key, 16);
2015 uint8_t sector0[16 * 4] = {0};
2016 uint8_t sector10[16 * 4] = {0};
2017 uint8_t data[4096] = {0};
2018 int datalen = 0;
2020 if (verbose)
2021 PrintAndLogEx(INFO, "reading MAD v1 sector");
2023 if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) {
2024 PrintAndLogEx(ERR, "error, read sector 0. card doesn't have MAD or doesn't have MAD on default keys");
2025 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key");
2026 return PM3_ESOFT;
2029 bool haveMAD2 = false;
2030 int res = MADCheck(sector0, NULL, verbose, &haveMAD2);
2031 if (res != PM3_SUCCESS) {
2032 PrintAndLogEx(ERR, "MAD error %d", res);
2033 return res;
2036 if (haveMAD2) {
2038 if (verbose)
2039 PrintAndLogEx(INFO, "reading MAD v2 sector");
2041 if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) {
2042 PrintAndLogEx(ERR, "error, read sector 0x10. card doesn't have MAD or doesn't have MAD on default keys");
2043 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key");
2044 return PM3_ESOFT;
2048 uint16_t mad[7 + 8 + 8 + 8 + 8] = {0};
2049 size_t madlen = 0;
2050 res = MADDecode(sector0, (haveMAD2 ? sector10 : NULL), mad, &madlen, false);
2051 if (res != PM3_SUCCESS) {
2052 PrintAndLogEx(ERR, "can't decode MAD");
2053 return res;
2056 PrintAndLogEx(INFO, "reading data from tag");
2057 for (int i = 0; i < madlen; i++) {
2058 if (ndefAID == mad[i]) {
2059 uint8_t vsector[16 * 4] = {0};
2060 if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, ndefkey, vsector, false)) {
2061 PrintAndLogEx(ERR, "error, reading sector %d", i + 1);
2062 return PM3_ESOFT;
2065 memcpy(&data[datalen], vsector, 16 * 3);
2066 datalen += 16 * 3;
2068 PrintAndLogEx(INPLACE, "%d", i);
2071 PrintAndLogEx(NORMAL, "");
2073 if (datalen == 0) {
2074 PrintAndLogEx(ERR, "no NDEF data");
2075 return PM3_SUCCESS;
2078 if (verbose2) {
2079 PrintAndLogEx(NORMAL, "");
2080 PrintAndLogEx(INFO, "--- " _CYAN_("MF Plus NDEF raw") " ----------------");
2081 print_buffer(data, datalen, 1);
2084 res = NDEFDecodeAndPrint(data, datalen, verbose);
2085 if (res != PM3_SUCCESS) {
2086 PrintAndLogEx(INFO, "Trying to parse NDEF records w/o NDEF header");
2087 res = NDEFRecordsDecodeAndPrint(data, datalen, verbose);
2090 // get total NDEF length before save. If fails, we save it all
2091 size_t n = 0;
2092 if (NDEFGetTotalLength(data, datalen, &n) != PM3_SUCCESS)
2093 n = datalen;
2095 pm3_save_dump(filename, data, n, jsfNDEF);
2097 if (verbose == false) {
2098 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -v`") " for more details");
2099 } else {
2100 if (verbose2 == false) {
2101 PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -vv`") " for more details");
2104 return PM3_SUCCESS;
2107 static int CmdHFMFPNDEFWrite(const char *Cmd) {
2108 CLIParserContext *ctx;
2109 CLIParserInit(&ctx, "hf mfp ndefwrite",
2110 "Write raw NDEF hex bytes to tag. This commands assumes tag already been NFC/NDEF formatted.\n",
2111 "hf mfp ndefwrite -d 0300FE -> write empty record to tag\n"
2112 "hf mfp ndefwrite -f myfilename\n"
2113 "hf mfp ndefwrite -d 033fd1023a53709101195405656e2d55534963656d616e2054776974746572206c696e6b5101195502747769747465722e636f6d2f686572726d616e6e31303031\n"
2116 void *argtable[] = {
2117 arg_param_begin,
2118 arg_str0("d", NULL, "<hex>", "raw NDEF hex bytes"),
2119 arg_str0("f", "file", "<fn>", "write raw NDEF file to tag"),
2120 arg_lit0("p", NULL, "fix NDEF record headers / terminator block if missing"),
2121 arg_lit0("v", "verbose", "verbose output"),
2122 arg_param_end
2124 CLIExecWithReturn(ctx, Cmd, argtable, false);
2126 uint8_t raw[4096] = {0};
2127 int rawlen;
2128 CLIGetHexWithReturn(ctx, 1, raw, &rawlen);
2130 int fnlen = 0;
2131 char filename[FILE_PATH_SIZE] = {0};
2132 CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
2134 bool fix_msg = arg_get_lit(ctx, 3);
2135 bool verbose = arg_get_lit(ctx, 4);
2136 CLIParserFree(ctx);
2138 if (fix_msg) {
2139 PrintAndLogEx(NORMAL, "called with fix NDEF message param");
2142 if (verbose) {
2143 PrintAndLogEx(NORMAL, "");
2145 PrintAndLogEx(SUCCESS, "Not implemented yet. Feel free to contribute!");
2146 PrintAndLogEx(NORMAL, "");
2147 return PM3_SUCCESS;
2150 static int CmdHFMFPList(const char *Cmd) {
2151 return CmdTraceListAlias(Cmd, "hf mfp", "mfp -c");
2154 static command_t CommandTable[] = {
2155 {"help", CmdHelp, AlwaysAvailable, "This help"},
2156 {"list", CmdHFMFPList, AlwaysAvailable, "List MIFARE Plus history"},
2157 {"-----------", CmdHelp, IfPm3Iso14443a, "------------------- " _CYAN_("operations") " ---------------------"},
2158 {"auth", CmdHFMFPAuth, IfPm3Iso14443a, "Authentication"},
2159 {"chk", CmdHFMFPChk, IfPm3Iso14443a, "Check keys"},
2160 {"dump", CmdHFMFPDump, IfPm3Iso14443a, "Dump MIFARE Plus tag to binary file"},
2161 {"info", CmdHFMFPInfo, IfPm3Iso14443a, "Info about MIFARE Plus tag"},
2162 {"mad", CmdHFMFPMAD, IfPm3Iso14443a, "Check and print MAD"},
2163 {"rdbl", CmdHFMFPRdbl, IfPm3Iso14443a, "Read blocks from card"},
2164 {"rdsc", CmdHFMFPRdsc, IfPm3Iso14443a, "Read sectors from card"},
2165 {"wrbl", CmdHFMFPWrbl, IfPm3Iso14443a, "Write block to card"},
2166 {"chkey", CmdHFMFPChKey, IfPm3Iso14443a, "Change key on card"},
2167 {"chconf", CmdHFMFPChConf, IfPm3Iso14443a, "Change config on card"},
2168 {"-----------", CmdHelp, IfPm3Iso14443a, "---------------- " _CYAN_("personalization") " -------------------"},
2169 {"commitp", CmdHFMFPCommitPerso, IfPm3Iso14443a, "Configure security layer (SL1/SL3 mode)"},
2170 {"initp", CmdHFMFPInitPerso, IfPm3Iso14443a, "Fill all the card's keys in SL0 mode"},
2171 {"wrp", CmdHFMFPWritePerso, IfPm3Iso14443a, "Write Perso command"},
2172 {"-----------", CmdHelp, IfPm3Iso14443a, "---------------------- " _CYAN_("ndef") " ------------------------"},
2173 {"ndefformat", CmdHFMFPNDEFFormat, IfPm3Iso14443a, "Format MIFARE Plus Tag as NFC Tag"},
2174 {"ndefread", CmdHFMFPNDEFRead, IfPm3Iso14443a, "Read and print NDEF records from card"},
2175 {"ndefwrite", CmdHFMFPNDEFWrite, IfPm3Iso14443a, "Write NDEF records to card"},
2176 {NULL, NULL, 0, NULL}
2179 static int CmdHelp(const char *Cmd) {
2180 (void)Cmd; // Cmd is not used so far
2181 CmdsHelp(CommandTable);
2182 return PM3_SUCCESS;
2185 int CmdHFMFP(const char *Cmd) {
2186 clearCommandBuffer();
2187 return CmdsParse(CommandTable, Cmd);