From 1c258381ef6cffec0a922f7d50fd459dd854a2d4 Mon Sep 17 00:00:00 2001 From: suut Date: Tue, 5 May 2026 18:10:10 +0200 Subject: [PATCH 1/6] add support for hf mfdes createmfcmapping --- client/src/cmdhfmfdes.c | 197 ++++++++++++++++++++++++++ client/src/mifare/desfirecore.c | 4 + client/src/mifare/desfirecore.h | 2 + client/src/mifare/desfiresecurechan.c | 32 +++-- client/src/pm3line_vocabulary.h | 1 + include/protocols.h | 3 + 6 files changed, 229 insertions(+), 10 deletions(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 85a3e5061..e713e5f07 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -6701,6 +6701,202 @@ mfclicense_parsing_error: return PM3_EINVARG; } +static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfdes createmfcmapping", + "Create a Mifare Classic mapping on a DESFire EV3C. The MFC License MAC key (AES only) must have been set previously as PICC key number 50.", + "hf mfdes createmfcmapping --aid 123456 --fid 01 --mfc-keys FFFFFFFFFFFF111111111111222222222222 --mfc-blocks 4,5,6,8\n" + " -> Map blocks 4, 5, 6, 8 where sector 1 key A is FFFFFFFFFFFF with key B readable,\n" + " -> sector 2 keys are 111111111111 and 222222222222 with key B used for authentication\n"); + + void *argtable[] = { + arg_param_begin, + /* 1 */ arg_lit0("a", "apdu", "Show APDU requests and responses"), + /* 2 */ arg_lit0("v", "verbose", "Verbose output"), + /* 3 */ arg_int0("n", "keyno", "", "Key number"), + /* 4 */ arg_str0("t", "algo", "", "Crypt algo"), + /* 5 */ arg_str0("k", "key", "", "Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)"), + /* 6 */ arg_str0(NULL, "kdf", "", "Key Derivation Function (KDF)"), + /* 7 */ arg_str0("i", "kdfi", "", "KDF input (1-31 hex bytes)"), + /* 8 */ arg_str0("m", "cmode", "", "Communicaton mode"), + /* 9 */ arg_str0("c", "ccset", "", "Communicaton command set"), + /* 10 */ arg_str0(NULL, "schann", "", "Secure channel"), + /* 11 */ arg_str0(NULL, "aid", "", "Application ID (3 hex bytes, big endian)"), + /* 12 */ arg_str0(NULL, "isoid", "", "Application ISO ID (ISO DF ID) (2 hex bytes, big endian)"), + /* 13 */ arg_str0(NULL, "fid", "", "File ID to map (1 hex byte)"), + /* 14 */ arg_str0(NULL, "restore-fid", "", "Restore source when mapping value files to value blocks (1 hex byte)"), + /* 15 */ arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be unique (use value 'data' for all data blocks, and 'trailer' for all trailer blocks)"), + /* 16 */ arg_str0(NULL, "mfc-license", "", "The MFC license (omit to use saved license and license MAC)"), + /* 17 */ arg_str0(NULL, "mfc-license-mac", "", "The MFC license MAC (omit to use saved license and license MAC)"), + /* 18 */ arg_lit0(NULL, "key-a", "Allow updating key A from inside sector trailers mapped to DESFire files"), + /* 19 */ arg_lit0(NULL, "key-b", "Allow updating key B from inside sector trailers mapped to DESFire files"), + /* 20 */ arg_lit0(NULL, "access-conditions", "Allow updating the access conditions from inside sector trailers mapped to DESFire files"), + /* 21 */ arg_lit0(NULL, "restore-transfer", "Enable the RestoreTransfer command for the mapped value blocks"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + bool key_a = arg_get_lit(ctx, 18); + bool key_b = arg_get_lit(ctx, 19); + bool ac = arg_get_lit(ctx, 20);; + bool restore_transfer = arg_get_lit(ctx, 21); + + uint8_t license[192]; + int license_len; + uint8_t license_mac[8]; + + bool use_saved_license; + struct arg_str *blk_str_arg = arg_get_str(ctx, 15); + struct arg_str *mfc_license_arg = arg_get_str(ctx, 16); + struct arg_str *mfc_license_mac_arg = arg_get_str(ctx, 17); + + uint32_t restore_fid = -1; + uint32_t fid = -1; + + if (!arg_get_u32_hexstr_def_nlen(ctx, 13, 0, &fid, 1, false)) { + PrintAndLogEx(ERR, "--fid is required"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (!arg_get_u32_hexstr_def_nlen(ctx, 14, 0, &restore_fid, 1, false)) { + if (restore_transfer) { + PrintAndLogEx(ERR, "--restore-fid is required"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } else if (!restore_transfer) { + PrintAndLogEx(WARNING, "--restore-fid is ignored if --restore-transfer is not specified"); + } + + PrintAndLogEx(DEBUG, "FID: %02X", fid); + PrintAndLogEx(DEBUG, "Restore FID: %02X", restore_fid); + + if (mfc_license_arg->count == 0 && mfc_license_mac_arg->count == 0) { + if (!saved_mfclicense_set || !saved_mfclicense_mac_set) { + PrintAndLogEx(ERR, "No saved license, create it with hf mfdes makemfclicense --save"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + use_saved_license = true; + } else if (mfc_license_arg->count == 1 && mfc_license_mac_arg->count == 1) { + use_saved_license = false; + } else { + PrintAndLogEx(ERR, "--mfc-license and --mfc-license-mac must be given together"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + + if (use_saved_license) { + memcpy(license, saved_mfclicense, saved_mfclicense_len); + license_len = saved_mfclicense_len; + memcpy(license_mac, saved_mfclicense_mac, 8); + } else { + if (CLIParamHexToBuf(mfc_license_arg, license, sizeof (license), &license_len) != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + int license_mac_len = 0; + if (CLIParamHexToBuf(mfc_license_mac_arg, license_mac, 8, &license_mac_len) != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (license_mac_len != 8) { + PrintAndLogEx(ERR, "The license MAC must be 8 bytes"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } + + PrintAndLogEx(INFO, "License: %s", sprint_hex_inrow(license, license_len)); + PrintAndLogEx(INFO, "MAC: %s", sprint_hex_inrow(license_mac, 8)); + + if (blk_str_arg->count != 1) { + PrintAndLogEx(ERR, "--blk is mandatory"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + // parse blocks + uint8_t blocks[64]; + size_t num_blocks = 0; + if (parse_mfc_blocks(blk_str_arg->sval[0], blocks, &num_blocks, false) != PM3_SUCCESS) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + bool trailer = ((blocks[0] + 1) % 4) == 0; + + if (trailer && restore_transfer) { + PrintAndLogEx(ERR, "--restore-transfer not allowed on trailer blocks"); + CLIParserFree(ctx); + return PM3_EINVARG; + } else if (!trailer && (key_a || key_b || ac)) { + PrintAndLogEx(ERR, "--key-a, --key-b, --access-conditions not allowed on data/value blocks"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + + DesfireContext_t dctx = {0}; + int securechann = 0; + uint32_t id = 0x000000; + DesfireISOSelectWay selectway = ISW6bAID; + int res = CmdDesGetSessionParameters(ctx, &dctx, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, &securechann, DCMEncrypted, &id, &selectway); + if (res) { + CLIParserFree(ctx); + return res; + } + + CLIParserFree(ctx); + SetAPDULogging(APDULogging); + + uint8_t cmd_data[256]; + size_t cmd_data_len = 0; + + cmd_data[0] = fid & 0x1F; + cmd_data[1] = trailer ? 0x01 : 0x00; + if (key_a) { + cmd_data[1] |= 0x20; + } + if (key_b) { + cmd_data[1] |= 0x08; + } + if (restore_transfer) { + cmd_data[1] |= 0x04; + } + if (ac) { + cmd_data[1] |= 0x10; + } + cmd_data[2] = num_blocks; + cmd_data_len += 3; + memcpy(cmd_data + cmd_data_len, blocks, num_blocks); + cmd_data_len += num_blocks; + + if (restore_transfer) { + cmd_data[cmd_data_len++] = restore_fid & 0x1F; + } + + memcpy(cmd_data + cmd_data_len, license, license_len); + cmd_data_len += license_len; + + memcpy(cmd_data + cmd_data_len, license_mac, 8); + cmd_data_len += 8; + + res = DesfireSelectAndAuthenticateAppW(&dctx, securechann, selectway, id, false, verbose); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(FAILED, "Select or authentication %s " _RED_("failed") ". Result [%d] %s", DesfireWayIDStr(selectway, id), res, DesfireAuthErrorToStr(res)); + return res; + } + + res = DesfireCreateMFCMapping(&dctx, cmd_data, cmd_data_len); + if (res != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Create MFC mapping " _RED_("failed") ". Result 0x%02X"); + } + DropField(); + return res; +} + static int DesfileReadISOFileAndPrint(DesfireContext_t *dctx, bool select_current_file, uint8_t fnum, uint16_t fisoid, int filetype, @@ -9430,6 +9626,7 @@ static command_t CommandTable[] = { {"value", CmdHF14ADesValueOperations, IfPm3Iso14443a, "Operations with value file (get/credit/limited credit/debit/clear)"}, {"clearrecfile", CmdHF14ADesClearRecordFile, IfPm3Iso14443a, "Clear record File"}, {"makemfclicense", CmdHF14ADesMakeMFCLicense, AlwaysAvailable, "Generate a Mifare Classic license for DESFire EV3C"}, + {"createmfcmapping", CmdHF14ADesCreateMFCMapping, IfPm3Iso14443a, "Create a Mifare Classic mapping on a DESFire EV3C"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("DUOX") " ------------------------"}, {"verifycert", CmdHF14ADesVerifyCert, IfPm3Iso14443a, "Validate cert from file and verify key possession"}, {"intauth", CmdHF14ADesIntAuth, IfPm3Iso14443a, "ISO Internal Authenticate (ECDSA challenge-response)"}, diff --git a/client/src/mifare/desfirecore.c b/client/src/mifare/desfirecore.c index 1f8462419..d178982b5 100644 --- a/client/src/mifare/desfirecore.c +++ b/client/src/mifare/desfirecore.c @@ -2184,6 +2184,10 @@ static int DesfireCommandRxData(DesfireContext_t *dctx, uint8_t cmd, uint8_t *re return DesfireCommand(dctx, cmd, NULL, 0, resp, resplen, checklength); } +int DesfireCreateMFCMapping(DesfireContext_t *dctx, uint8_t *data, size_t datalen) { + return DesfireCommandTxData(dctx, MFDES_CREATE_MFC_MAPPING, data, datalen); +} + int DesfireFormatPICC(DesfireContext_t *dctx) { return DesfireCommandNoData(dctx, MFDES_FORMAT_PICC); } diff --git a/client/src/mifare/desfirecore.h b/client/src/mifare/desfirecore.h index 2316d78e7..41a90d33d 100644 --- a/client/src/mifare/desfirecore.h +++ b/client/src/mifare/desfirecore.h @@ -275,4 +275,6 @@ int DesfireISOUpdateBinary(DesfireContext_t *dctx, bool use_file_id, uint8_t fil int DesfireISOReadRecords(DesfireContext_t *dctx, uint8_t recordnum, bool read_all_records, uint8_t fileid, uint8_t length, uint8_t *resp, size_t *resplen); int DesfireISOAppendRecord(DesfireContext_t *dctx, uint8_t fileid, uint8_t *data, size_t datalen); +int DesfireCreateMFCMapping(DesfireContext_t *dctx, uint8_t *data, size_t datalen); + #endif // __DESFIRECORE_H diff --git a/client/src/mifare/desfiresecurechan.c b/client/src/mifare/desfiresecurechan.c index 056ec7ead..c3bbd7e02 100644 --- a/client/src/mifare/desfiresecurechan.c +++ b/client/src/mifare/desfiresecurechan.c @@ -140,6 +140,7 @@ static const AllowedChannelModes_t AllowedChannelModes[] = { {MFDES_CHANGE_FILE_SETTINGS, DACEV1, DCCNative, DCMEncrypted}, {MFDES_CREATE_TRANS_MAC_FILE, DACEV1, DCCNative, DCMEncrypted}, {MFDES_CHANGE_CONFIGURATION, DACEV1, DCCNative, DCMEncrypted}, + {MFDES_CREATE_MFC_MAPPING, DACEV1, DCCNative, DCMEncrypted}, {MFDES_CHANGE_KEY, DACEV1, DCCNative, DCMEncryptedPlain}, {MFDES_CHANGE_KEY_EV2, DACEV1, DCCNative, DCMEncryptedPlain}, @@ -186,7 +187,9 @@ static const AllowedChannelModes_t AllowedChannelModes[] = { {MFDES_CHANGE_KEY, DACLRP, DCCNative, DCMEncryptedPlain}, }; -#define CMD_HEADER_LEN_ALL 0xffff +#define CMD_HEADER_LEN_ALL 0xffff +#define CMD_HEADER_LEN_ALL_EXCEPT_MAC 0xfffe + static const CmdHeaderLengths_t CmdHeaderLengths[] = { {MFDES_CREATE_APPLICATION, CMD_HEADER_LEN_ALL}, {MFDES_CREATE_DELEGATE_APP, CMD_HEADER_LEN_ALL}, @@ -212,12 +215,19 @@ static const CmdHeaderLengths_t CmdHeaderLengths[] = { {MFDES_CREDIT, 1}, {MFDES_DEBIT, 1}, {MFDES_LIMITED_CREDIT, 1}, + {MFDES_CREATE_MFC_MAPPING, CMD_HEADER_LEN_ALL_EXCEPT_MAC}, }; -static uint8_t DesfireGetCmdHeaderLen(uint8_t cmd) { +static uint8_t DesfireGetCmdHeaderLen(uint8_t cmd, size_t datalen) { for (int i = 0; i < ARRAYLEN(CmdHeaderLengths); i++) { if (CmdHeaderLengths[i].cmd == cmd) { - return CmdHeaderLengths[i].len; + if (CmdHeaderLengths[i].len == CMD_HEADER_LEN_ALL) { + return datalen; + } else if (CmdHeaderLengths[i].len == CMD_HEADER_LEN_ALL_EXCEPT_MAC) { + return datalen >= 8 ? datalen - 8 : datalen; + } else { + return CmdHeaderLengths[i].len; + } } } return 0; @@ -300,9 +310,10 @@ static void DesfireSecureChannelEncodeD40(DesfireContext_t *ctx, uint8_t cmd, ui memcpy(dstdata, srcdata, srcdatalen); *dstdatalen = srcdatalen; - uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd); - if (srcdatalen < hdrlen) + uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd, srcdatalen); + if (srcdatalen < hdrlen) { hdrlen = srcdatalen; + } size_t rlen; @@ -376,7 +387,7 @@ static void DesfireSecureChannelEncodeEV1(DesfireContext_t *ctx, uint8_t cmd, ui memcpy(dstdata, srcdata, srcdatalen); *dstdatalen = srcdatalen; - uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd); + uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd, srcdatalen); if (srcdatalen < hdrlen) { hdrlen = srcdatalen; } @@ -443,9 +454,10 @@ static void DesfireSecureChannelEncodeEV2(DesfireContext_t *ctx, uint8_t cmd, ui memcpy(dstdata, srcdata, srcdatalen); *dstdatalen = srcdatalen; - uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd); - if (srcdatalen < hdrlen) + uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd, srcdatalen); + if (srcdatalen < hdrlen) { hdrlen = srcdatalen; + } if (ctx->commMode == DCMMACed) { uint8_t cmac[DESFIRE_MAX_CRYPTO_BLOCK_SIZE] = {0}; @@ -487,7 +499,7 @@ static void DesfireSecureChannelEncodeLRP(DesfireContext_t *ctx, uint8_t cmd, ui memcpy(dstdata, srcdata, srcdatalen); *dstdatalen = srcdatalen; - uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd); + uint8_t hdrlen = DesfireGetCmdHeaderLen(cmd, srcdatalen); if (srcdatalen < hdrlen) hdrlen = srcdatalen; @@ -522,7 +534,7 @@ static void DesfireSecureChannelEncodeLRP(DesfireContext_t *ctx, uint8_t cmd, ui void DesfireSecureChannelEncode(DesfireContext_t *ctx, uint8_t cmd, uint8_t *srcdata, size_t srcdatalen, uint8_t *dstdata, size_t *dstdatalen) { ctx->lastCommand = cmd; - ctx->lastRequestZeroLen = (srcdatalen <= DesfireGetCmdHeaderLen(cmd)); + ctx->lastRequestZeroLen = (srcdatalen <= DesfireGetCmdHeaderLen(cmd, srcdatalen)); switch (ctx->secureChannel) { case DACd40: diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 4c2624202..bfe12381d 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -528,6 +528,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mfdes value" }, { 0, "hf mfdes clearrecfile" }, { 1, "hf mfdes makemfclicense" }, + { 0, "hf mfdes createmfcmapping" }, { 0, "hf mfdes verifycert" }, { 0, "hf mfdes intauth" }, { 0, "hf mfdes vdesign" }, diff --git a/include/protocols.h b/include/protocols.h index bebb4349f..6eff4c345 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -610,9 +610,11 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MFDES_READ_RECORDS2 0xAB #define MFDES_READ_DATA2 0xAD #define MFDES_ADDITIONAL_FRAME 0xAF +#define MFDES_RESTORE_TRANSFER 0xB1 #define MFDES_UPDATE_RECORD2 0xBA #define MFDES_READ_RECORDS 0xBB #define MFDES_READ_DATA 0xBD +#define MFDES_RESTRICT_MFC_UPDATE 0xBF #define MFDES_CREATE_CYCLIC_RECORD_FILE 0xC0 #define MFDES_CREATE_LINEAR_RECORD_FILE 0xC1 #define MFDES_CHANGE_KEY 0xC4 @@ -625,6 +627,7 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MFDES_CREATE_VALUE_FILE 0xCC #define MFDES_CREATE_STD_DATA_FILE 0xCD #define MFDES_CREATE_TRANS_MAC_FILE 0xCE +#define MFDES_CREATE_MFC_MAPPING 0xCF #define MFDES_DELETE_APPLICATION 0xDA #define MFDES_UPDATE_RECORD 0xDB #define MFDES_DEBIT 0xDC From 2e3425d537002fd518cf7187432abd73c5080eed Mon Sep 17 00:00:00 2001 From: suut Date: Mon, 11 May 2026 10:54:01 +0200 Subject: [PATCH 2/6] Correct typo --- client/src/cmdhfmfdes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index e713e5f07..1249a40c9 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -6517,7 +6517,7 @@ static int CmdHF14ADesMakeMFCLicense(const char *Cmd) { int mac_key_len = 0; if (key_arg->count != 1) { - PrintAndLogEx(ERR, "At most one instance of --key is required"); + PrintAndLogEx(ERR, "Exactly one instance of --key is required"); goto mfclicense_parsing_error; } if (CLIParamHexToBuf(key_arg, mac_key, sizeof (mac_key), &mac_key_len) != 0) { From 91eb6345a9dcd9cb4863725f7550e2ee2fa3a29f Mon Sep 17 00:00:00 2001 From: suut Date: Mon, 11 May 2026 11:01:09 +0200 Subject: [PATCH 3/6] Uniformize code style --- client/src/cmdhfmfdes.c | 79 +++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 1249a40c9..23fa283e4 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -6711,27 +6711,27 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { void *argtable[] = { arg_param_begin, - /* 1 */ arg_lit0("a", "apdu", "Show APDU requests and responses"), - /* 2 */ arg_lit0("v", "verbose", "Verbose output"), - /* 3 */ arg_int0("n", "keyno", "", "Key number"), - /* 4 */ arg_str0("t", "algo", "", "Crypt algo"), - /* 5 */ arg_str0("k", "key", "", "Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)"), - /* 6 */ arg_str0(NULL, "kdf", "", "Key Derivation Function (KDF)"), - /* 7 */ arg_str0("i", "kdfi", "", "KDF input (1-31 hex bytes)"), - /* 8 */ arg_str0("m", "cmode", "", "Communicaton mode"), - /* 9 */ arg_str0("c", "ccset", "", "Communicaton command set"), - /* 10 */ arg_str0(NULL, "schann", "", "Secure channel"), - /* 11 */ arg_str0(NULL, "aid", "", "Application ID (3 hex bytes, big endian)"), - /* 12 */ arg_str0(NULL, "isoid", "", "Application ISO ID (ISO DF ID) (2 hex bytes, big endian)"), - /* 13 */ arg_str0(NULL, "fid", "", "File ID to map (1 hex byte)"), - /* 14 */ arg_str0(NULL, "restore-fid", "", "Restore source when mapping value files to value blocks (1 hex byte)"), - /* 15 */ arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be unique (use value 'data' for all data blocks, and 'trailer' for all trailer blocks)"), - /* 16 */ arg_str0(NULL, "mfc-license", "", "The MFC license (omit to use saved license and license MAC)"), - /* 17 */ arg_str0(NULL, "mfc-license-mac", "", "The MFC license MAC (omit to use saved license and license MAC)"), - /* 18 */ arg_lit0(NULL, "key-a", "Allow updating key A from inside sector trailers mapped to DESFire files"), - /* 19 */ arg_lit0(NULL, "key-b", "Allow updating key B from inside sector trailers mapped to DESFire files"), - /* 20 */ arg_lit0(NULL, "access-conditions", "Allow updating the access conditions from inside sector trailers mapped to DESFire files"), - /* 21 */ arg_lit0(NULL, "restore-transfer", "Enable the RestoreTransfer command for the mapped value blocks"), + arg_lit0("a", "apdu", "Show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose output"), + arg_int0("n", "keyno", "", "Key number"), + arg_str0("t", "algo", "", "Crypt algo"), + arg_str0("k", "key", "", "Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)"), + arg_str0(NULL, "kdf", "", "Key Derivation Function (KDF)"), + arg_str0("i", "kdfi", "", "KDF input (1-31 hex bytes)"), + arg_str0("m", "cmode", "", "Communicaton mode"), + arg_str0("c", "ccset", "", "Communicaton command set"), + arg_str0(NULL, "schann", "", "Secure channel"), + arg_str0(NULL, "aid", "", "Application ID (3 hex bytes, big endian)"), + arg_str0(NULL, "isoid", "", "Application ISO ID (ISO DF ID) (2 hex bytes, big endian)"), + arg_str0(NULL, "fid", "", "File ID to map (1 hex byte)"), + arg_str0(NULL, "restore-fid", "", "Restore source when mapping value files to value blocks (1 hex byte)"), + arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be unique (use value 'data' for all data blocks, and 'trailer' for all trailer blocks)"), + arg_str0(NULL, "mfc-license", "", "The MFC license (omit to use saved license and license MAC)"), + arg_str0(NULL, "mfc-license-mac", "", "The MFC license MAC (omit to use saved license and license MAC)"), + arg_lit0(NULL, "key-a", "Allow updating key A from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "key-b", "Allow updating key B from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "access-conditions", "Allow updating the access conditions from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "restore-transfer", "Enable the RestoreTransfer command for the mapped value blocks"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -6757,14 +6757,12 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { if (!arg_get_u32_hexstr_def_nlen(ctx, 13, 0, &fid, 1, false)) { PrintAndLogEx(ERR, "--fid is required"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } if (!arg_get_u32_hexstr_def_nlen(ctx, 14, 0, &restore_fid, 1, false)) { if (restore_transfer) { PrintAndLogEx(ERR, "--restore-fid is required"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } } else if (!restore_transfer) { PrintAndLogEx(WARNING, "--restore-fid is ignored if --restore-transfer is not specified"); @@ -6776,16 +6774,14 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { if (mfc_license_arg->count == 0 && mfc_license_mac_arg->count == 0) { if (!saved_mfclicense_set || !saved_mfclicense_mac_set) { PrintAndLogEx(ERR, "No saved license, create it with hf mfdes makemfclicense --save"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } use_saved_license = true; } else if (mfc_license_arg->count == 1 && mfc_license_mac_arg->count == 1) { use_saved_license = false; } else { PrintAndLogEx(ERR, "--mfc-license and --mfc-license-mac must be given together"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } if (use_saved_license) { @@ -6794,18 +6790,15 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { memcpy(license_mac, saved_mfclicense_mac, 8); } else { if (CLIParamHexToBuf(mfc_license_arg, license, sizeof (license), &license_len) != 0) { - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } int license_mac_len = 0; if (CLIParamHexToBuf(mfc_license_mac_arg, license_mac, 8, &license_mac_len) != 0) { - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } if (license_mac_len != 8) { PrintAndLogEx(ERR, "The license MAC must be 8 bytes"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } } @@ -6814,27 +6807,23 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { if (blk_str_arg->count != 1) { PrintAndLogEx(ERR, "--blk is mandatory"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } // parse blocks uint8_t blocks[64]; size_t num_blocks = 0; if (parse_mfc_blocks(blk_str_arg->sval[0], blocks, &num_blocks, false) != PM3_SUCCESS) { - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } bool trailer = ((blocks[0] + 1) % 4) == 0; if (trailer && restore_transfer) { PrintAndLogEx(ERR, "--restore-transfer not allowed on trailer blocks"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } else if (!trailer && (key_a || key_b || ac)) { PrintAndLogEx(ERR, "--key-a, --key-b, --access-conditions not allowed on data/value blocks"); - CLIParserFree(ctx); - return PM3_EINVARG; + goto createmfcmapping_parsing_error; } DesfireContext_t dctx = {0}; @@ -6895,6 +6884,10 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { } DropField(); return res; + +createmfcmapping_parsing_error: + CLIParserFree(ctx); + return PM3_EINVARG; } static int DesfileReadISOFileAndPrint(DesfireContext_t *dctx, From ce2e59c440118d6a499680e89dbc4f7ae9bd4b37 Mon Sep 17 00:00:00 2001 From: suut Date: Mon, 11 May 2026 11:07:30 +0200 Subject: [PATCH 4/6] Add support for the special block list values 'data' and 'trailer' when making a MFC mapping --- client/src/cmdhfmfdes.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 23fa283e4..ab33a740a 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -6725,7 +6725,7 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { arg_str0(NULL, "isoid", "", "Application ISO ID (ISO DF ID) (2 hex bytes, big endian)"), arg_str0(NULL, "fid", "", "File ID to map (1 hex byte)"), arg_str0(NULL, "restore-fid", "", "Restore source when mapping value files to value blocks (1 hex byte)"), - arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be unique (use value 'data' for all data blocks, and 'trailer' for all trailer blocks)"), + arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be unique (use -b data for all data blocks, and -b trailer for all trailer blocks)"), arg_str0(NULL, "mfc-license", "", "The MFC license (omit to use saved license and license MAC)"), arg_str0(NULL, "mfc-license-mac", "", "The MFC license MAC (omit to use saved license and license MAC)"), arg_lit0(NULL, "key-a", "Allow updating key A from inside sector trailers mapped to DESFire files"), @@ -6812,7 +6812,19 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { // parse blocks uint8_t blocks[64]; size_t num_blocks = 0; - if (parse_mfc_blocks(blk_str_arg->sval[0], blocks, &num_blocks, false) != PM3_SUCCESS) { + if (strcmp(blk_str_arg->sval[0], "data") == 0) { + for (int i = 0; i < 64; i++) { + if (((i + 1) % 4) != 0) { + blocks[num_blocks++] = i; + } + } + } else if (strcmp(blk_str_arg->sval[0], "trailer") == 0) { + for (int i = 0; i < 64; i++) { + if (((i + 1) % 4) == 0) { + blocks[num_blocks++] = i; + } + } + } else if (parse_mfc_blocks(blk_str_arg->sval[0], blocks, &num_blocks, false) != PM3_SUCCESS) { goto createmfcmapping_parsing_error; } From ac9db3314ff61f90c6d63496b4756cc432b01ddf Mon Sep 17 00:00:00 2001 From: suut Date: Mon, 11 May 2026 14:02:15 +0200 Subject: [PATCH 5/6] Add missing argument to PrintAndLogEx --- client/src/cmdhfmfdes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index ab33a740a..ff76f196f 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -6892,7 +6892,7 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { res = DesfireCreateMFCMapping(&dctx, cmd_data, cmd_data_len); if (res != PM3_SUCCESS) { - PrintAndLogEx(FAILED, "Create MFC mapping " _RED_("failed") ". Result 0x%02X"); + PrintAndLogEx(FAILED, "Create MFC mapping " _RED_("failed") ". Result 0x%02X", res); } DropField(); return res; From 5c376032ed79f9478eb750bae593d173a504c6a5 Mon Sep 17 00:00:00 2001 From: suut Date: Thu, 14 May 2026 18:39:12 +0200 Subject: [PATCH 6/6] hf mfdes createmfcmapping: review comments --- client/src/cmdhfmfdes.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index ff76f196f..0995367f2 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -6482,8 +6482,8 @@ static int CmdHF14ADesMakeMFCLicense(const char *Cmd) { arg_lit0("v", "verbose", "Show more output"), arg_str0("k", "key", "", "Key for computing the MAC, must be HEX 16(AES)"), arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be given in ascending order (use the special value 'all' for all blocks)"), - arg_lit0(NULL, "key-a", "Allow updating key A from inside sector trailers mapped to DESFire files"), - arg_lit0(NULL, "key-b", "Allow updating key B from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "ka", "Allow updating key A from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "kb", "Allow updating key B from inside sector trailers mapped to DESFire files"), arg_lit0(NULL, "restrict", "Allow the restriction of data updates by the MFC side"), arg_lit0(NULL, "map", "Allow mapping the blocks to DESFire files"), arg_lit0(NULL, "access-conditions", "Allow updating the access conditions from inside sector trailers mapped to DESFire files"), @@ -6728,8 +6728,8 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { arg_str0("b", "blk", "[,[,...]]", "The MFC blocks to map, must be unique (use -b data for all data blocks, and -b trailer for all trailer blocks)"), arg_str0(NULL, "mfc-license", "", "The MFC license (omit to use saved license and license MAC)"), arg_str0(NULL, "mfc-license-mac", "", "The MFC license MAC (omit to use saved license and license MAC)"), - arg_lit0(NULL, "key-a", "Allow updating key A from inside sector trailers mapped to DESFire files"), - arg_lit0(NULL, "key-b", "Allow updating key B from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "ka", "Allow updating key A from inside sector trailers mapped to DESFire files"), + arg_lit0(NULL, "kb", "Allow updating key B from inside sector trailers mapped to DESFire files"), arg_lit0(NULL, "access-conditions", "Allow updating the access conditions from inside sector trailers mapped to DESFire files"), arg_lit0(NULL, "restore-transfer", "Enable the RestoreTransfer command for the mapped value blocks"), arg_param_end @@ -6740,7 +6740,7 @@ static int CmdHF14ADesCreateMFCMapping(const char *Cmd) { bool verbose = arg_get_lit(ctx, 2); bool key_a = arg_get_lit(ctx, 18); bool key_b = arg_get_lit(ctx, 19); - bool ac = arg_get_lit(ctx, 20);; + bool ac = arg_get_lit(ctx, 20); bool restore_transfer = arg_get_lit(ctx, 21); uint8_t license[192];