X-Git-Url: http://git.zerfleddert.de/cgi-bin/gitweb.cgi/proxmark3-svn/blobdiff_plain/4c16ae80f0bffae2092e208c46a5005356c24ce1..f4329b17322acc5d12ff8a931478c9c7c6fd0677:/client/cmdhfmf.c diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index 3fc3f8f7..d909cd8c 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -8,90 +8,49 @@ // High frequency MIFARE commands //----------------------------------------------------------------------------- -#include #include "cmdhfmf.h" -#include "./nonce2key/nonce2key.h" + +#include +#include +#include +#include +#include +#include "proxmark3.h" +#include "cmdmain.h" +#include "cmdhfmfhard.h" +#include "util.h" +#include "util_posix.h" +#include "usb_cmd.h" +#include "ui.h" +#include "mifarehost.h" +#include "mifare.h" +#include "mfkey.h" + +#define NESTED_SECTOR_RETRY 10 // how often we try mfested() until we give up + static int CmdHelp(const char *Cmd); int CmdHF14AMifare(const char *Cmd) { - uint32_t uid = 0; - uint32_t nt = 0, nr = 0; - uint64_t par_list = 0, ks_list = 0, r_key = 0; - int16_t isOK = 0; - - UsbCommand c = {CMD_READER_MIFARE, {true, 0, 0}}; - - // message - printf("-------------------------------------------------------------------------\n"); - printf("Executing command. Expected execution time: 25sec on average :-)\n"); - printf("Press button on the proxmark3 device to abort both proxmark3 and client.\n"); - printf("-------------------------------------------------------------------------\n"); - - - start: - clearCommandBuffer(); - SendCommand(&c); - - //flush queue - while (ukbhit()) { - int c = getchar(); (void) c; - } - - // wait cycle - while (true) { - printf("."); - fflush(stdout); - if (ukbhit()) { - getchar(); - printf("\naborted via keyboard!\n"); - break; - } - - UsbCommand resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { - isOK = resp.arg[0]; - uid = (uint32_t)bytes_to_num(resp.d.asBytes + 0, 4); - nt = (uint32_t)bytes_to_num(resp.d.asBytes + 4, 4); - par_list = bytes_to_num(resp.d.asBytes + 8, 8); - ks_list = bytes_to_num(resp.d.asBytes + 16, 8); - nr = bytes_to_num(resp.d.asBytes + 24, 4); - printf("\n\n"); - switch (isOK) { - case -1 : PrintAndLog("Button pressed. Aborted.\n"); break; - case -2 : PrintAndLog("Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests).\n"); break; - case -3 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator is not predictable).\n"); break; - case -4 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown"); - PrintAndLog("generating polynomial with 16 effective bits only, but shows unexpected behaviour.\n"); break; - default: ; - } - break; - } - } - - printf("\n"); - - // error - if (isOK != 1) return 1; - - // execute original function from util nonce2key - if (nonce2key(uid, nt, nr, par_list, ks_list, &r_key)) { - isOK = 2; - PrintAndLog("Key not found (lfsr_common_prefix list is null). Nt=%08x", nt); - PrintAndLog("Failing is expected to happen in 25%% of all cases. Trying again with a different reader nonce..."); - c.arg[0] = false; - goto start; - } else { - isOK = 0; - printf("------------------------------------------------------------------\n"); - PrintAndLog("Found valid key:%012" PRIx64 " \n", r_key); + int isOK = 0; + uint64_t key = 0; + isOK = mfDarkside(&key); + switch (isOK) { + case -1 : PrintAndLog("Button pressed. Aborted."); return 1; + case -2 : PrintAndLog("Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests)."); return 1; + case -3 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator is not predictable)."); return 1; + case -4 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown"); + PrintAndLog("generating polynomial with 16 effective bits only, but shows unexpected behaviour."); return 1; + case -5 : PrintAndLog("Aborted via keyboard."); return 1; + default : PrintAndLog("Found valid key:%012" PRIx64 "\n", key); } PrintAndLog(""); return 0; } + int CmdHF14AMfWrBl(const char *Cmd) { uint8_t blockNo = 0; @@ -328,29 +287,32 @@ int CmdHF14AMfDump(const char *Cmd) PrintAndLog("|-----------------------------------------|"); PrintAndLog("|------ Reading sector access bits...-----|"); PrintAndLog("|-----------------------------------------|"); - + uint8_t tries = 0; for (sectorNo = 0; sectorNo < numSectors; sectorNo++) { - UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 0, 0}}; - memcpy(c.d.asBytes, keyA[sectorNo], 6); - SendCommand(&c); + for (tries = 0; tries < 3; tries++) { + UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 0, 0}}; + memcpy(c.d.asBytes, keyA[sectorNo], 6); + SendCommand(&c); - if (WaitForResponseTimeout(CMD_ACK,&resp,1500)) { - uint8_t isOK = resp.arg[0] & 0xff; - uint8_t *data = resp.d.asBytes; - if (isOK){ - rights[sectorNo][0] = ((data[7] & 0x10)>>2) | ((data[8] & 0x1)<<1) | ((data[8] & 0x10)>>4); // C1C2C3 for data area 0 - rights[sectorNo][1] = ((data[7] & 0x20)>>3) | ((data[8] & 0x2)<<0) | ((data[8] & 0x20)>>5); // C1C2C3 for data area 1 - rights[sectorNo][2] = ((data[7] & 0x40)>>4) | ((data[8] & 0x4)>>1) | ((data[8] & 0x40)>>6); // C1C2C3 for data area 2 - rights[sectorNo][3] = ((data[7] & 0x80)>>5) | ((data[8] & 0x8)>>2) | ((data[8] & 0x80)>>7); // C1C2C3 for sector trailer + if (WaitForResponseTimeout(CMD_ACK,&resp,1500)) { + uint8_t isOK = resp.arg[0] & 0xff; + uint8_t *data = resp.d.asBytes; + if (isOK){ + rights[sectorNo][0] = ((data[7] & 0x10)>>2) | ((data[8] & 0x1)<<1) | ((data[8] & 0x10)>>4); // C1C2C3 for data area 0 + rights[sectorNo][1] = ((data[7] & 0x20)>>3) | ((data[8] & 0x2)<<0) | ((data[8] & 0x20)>>5); // C1C2C3 for data area 1 + rights[sectorNo][2] = ((data[7] & 0x40)>>4) | ((data[8] & 0x4)>>1) | ((data[8] & 0x40)>>6); // C1C2C3 for data area 2 + rights[sectorNo][3] = ((data[7] & 0x80)>>5) | ((data[8] & 0x8)>>2) | ((data[8] & 0x80)>>7); // C1C2C3 for sector trailer + break; + } else if (tries == 2) { // on last try set defaults + PrintAndLog("Could not get access rights for sector %2d. Trying with defaults...", sectorNo); + rights[sectorNo][0] = rights[sectorNo][1] = rights[sectorNo][2] = 0x00; + rights[sectorNo][3] = 0x01; + } } else { - PrintAndLog("Could not get access rights for sector %2d. Trying with defaults...", sectorNo); + PrintAndLog("Command execute timeout when trying to read access rights for sector %2d. Trying with defaults...", sectorNo); rights[sectorNo][0] = rights[sectorNo][1] = rights[sectorNo][2] = 0x00; rights[sectorNo][3] = 0x01; } - } else { - PrintAndLog("Command execute timeout when trying to read access rights for sector %2d. Trying with defaults...", sectorNo); - rights[sectorNo][0] = rights[sectorNo][1] = rights[sectorNo][2] = 0x00; - rights[sectorNo][3] = 0x01; } } @@ -362,27 +324,33 @@ int CmdHF14AMfDump(const char *Cmd) for (sectorNo = 0; isOK && sectorNo < numSectors; sectorNo++) { for (blockNo = 0; isOK && blockNo < NumBlocksPerSector(sectorNo); blockNo++) { bool received = false; - - if (blockNo == NumBlocksPerSector(sectorNo) - 1) { // sector trailer. At least the Access Conditions can always be read with key A. - UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + blockNo, 0, 0}}; - memcpy(c.d.asBytes, keyA[sectorNo], 6); - SendCommand(&c); - received = WaitForResponseTimeout(CMD_ACK,&resp,1500); - } else { // data block. Check if it can be read with key A or key B - uint8_t data_area = sectorNo<32?blockNo:blockNo/5; - if ((rights[sectorNo][data_area] == 0x03) || (rights[sectorNo][data_area] == 0x05)) { // only key B would work - UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + blockNo, 1, 0}}; - memcpy(c.d.asBytes, keyB[sectorNo], 6); - SendCommand(&c); - received = WaitForResponseTimeout(CMD_ACK,&resp,1500); - } else if (rights[sectorNo][data_area] == 0x07) { // no key would work - isOK = false; - PrintAndLog("Access rights do not allow reading of sector %2d block %3d", sectorNo, blockNo); - } else { // key A would work + for (tries = 0; tries < 3; tries++) { + if (blockNo == NumBlocksPerSector(sectorNo) - 1) { // sector trailer. At least the Access Conditions can always be read with key A. UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + blockNo, 0, 0}}; memcpy(c.d.asBytes, keyA[sectorNo], 6); SendCommand(&c); received = WaitForResponseTimeout(CMD_ACK,&resp,1500); + } else { // data block. Check if it can be read with key A or key B + uint8_t data_area = sectorNo<32?blockNo:blockNo/5; + if ((rights[sectorNo][data_area] == 0x03) || (rights[sectorNo][data_area] == 0x05)) { // only key B would work + UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + blockNo, 1, 0}}; + memcpy(c.d.asBytes, keyB[sectorNo], 6); + SendCommand(&c); + received = WaitForResponseTimeout(CMD_ACK,&resp,1500); + } else if (rights[sectorNo][data_area] == 0x07) { // no key would work + isOK = false; + PrintAndLog("Access rights do not allow reading of sector %2d block %3d", sectorNo, blockNo); + tries = 2; + } else { // key A would work + UsbCommand c = {CMD_MIFARE_READBL, {FirstBlockOfSector(sectorNo) + blockNo, 0, 0}}; + memcpy(c.d.asBytes, keyA[sectorNo], 6); + SendCommand(&c); + received = WaitForResponseTimeout(CMD_ACK,&resp,1500); + } + } + if (received) { + isOK = resp.arg[0] & 0xff; + if (isOK) break; } } @@ -542,10 +510,17 @@ int CmdHF14AMfRestore(const char *Cmd) return 0; } + +typedef struct { + uint64_t Key[2]; + int foundKey[2]; +} sector_t; + + int CmdHF14AMfNested(const char *Cmd) { int i, j, res, iterations; - sector *e_sector = NULL; + sector_t *e_sector = NULL; uint8_t blockNo = 0; uint8_t keyType = 0; uint8_t trgBlockNo = 0; @@ -662,10 +637,10 @@ int CmdHF14AMfNested(const char *Cmd) } } else { // ------------------------------------ multiple sectors working - clock_t time1; - time1 = clock(); + uint64_t msclock1; + msclock1 = msclock(); - e_sector = calloc(SectorsCnt, sizeof(sector)); + e_sector = calloc(SectorsCnt, sizeof(sector_t)); if (e_sector == NULL) return 1; //test current key and additional standard keys first @@ -733,7 +708,7 @@ int CmdHF14AMfNested(const char *Cmd) } } - printf("Time in nested: %1.3f (%1.3f sec per key)\n\n", ((float)clock() - time1)/CLOCKS_PER_SEC, ((float)clock() - time1)/iterations/CLOCKS_PER_SEC); + printf("Time in nested: %1.3f (%1.3f sec per key)\n\n", ((float)(msclock() - msclock1))/1000.0, ((float)(msclock() - msclock1))/iterations/1000.0); PrintAndLog("-----------------------------------------------\nIterations count: %d\n\n", iterations); //print them @@ -792,6 +767,127 @@ int CmdHF14AMfNested(const char *Cmd) return 0; } + +int CmdHF14AMfNestedHard(const char *Cmd) +{ + uint8_t blockNo = 0; + uint8_t keyType = 0; + uint8_t trgBlockNo = 0; + uint8_t trgKeyType = 0; + uint8_t key[6] = {0, 0, 0, 0, 0, 0}; + uint8_t trgkey[6] = {0, 0, 0, 0, 0, 0}; + + char ctmp; + ctmp = param_getchar(Cmd, 0); + + if (ctmp != 'R' && ctmp != 'r' && ctmp != 'T' && ctmp != 't' && strlen(Cmd) < 20) { + PrintAndLog("Usage:"); + PrintAndLog(" hf mf hardnested "); + PrintAndLog(" [known target key (12 hex symbols)] [w] [s]"); + PrintAndLog(" or hf mf hardnested r [known target key]"); + PrintAndLog(" "); + PrintAndLog("Options: "); + PrintAndLog(" w: Acquire nonces and write them to binary file nonces.bin"); + PrintAndLog(" s: Slower acquisition (required by some non standard cards)"); + PrintAndLog(" r: Read nonces.bin and start attack"); + PrintAndLog(" "); + PrintAndLog(" sample1: hf mf hardnested 0 A FFFFFFFFFFFF 4 A"); + PrintAndLog(" sample2: hf mf hardnested 0 A FFFFFFFFFFFF 4 A w"); + PrintAndLog(" sample3: hf mf hardnested 0 A FFFFFFFFFFFF 4 A w s"); + PrintAndLog(" sample4: hf mf hardnested r"); + PrintAndLog(" "); + PrintAndLog("Add the known target key to check if it is present in the remaining key space:"); + PrintAndLog(" sample5: hf mf hardnested 0 A A0A1A2A3A4A5 4 A FFFFFFFFFFFF"); + return 0; + } + + bool know_target_key = false; + bool nonce_file_read = false; + bool nonce_file_write = false; + bool slow = false; + int tests = 0; + + + if (ctmp == 'R' || ctmp == 'r') { + nonce_file_read = true; + if (!param_gethex(Cmd, 1, trgkey, 12)) { + know_target_key = true; + } + } else if (ctmp == 'T' || ctmp == 't') { + tests = param_get32ex(Cmd, 1, 100, 10); + if (!param_gethex(Cmd, 2, trgkey, 12)) { + know_target_key = true; + } + } else { + blockNo = param_get8(Cmd, 0); + ctmp = param_getchar(Cmd, 1); + if (ctmp != 'a' && ctmp != 'A' && ctmp != 'b' && ctmp != 'B') { + PrintAndLog("Key type must be A or B"); + return 1; + } + if (ctmp != 'A' && ctmp != 'a') { + keyType = 1; + } + + if (param_gethex(Cmd, 2, key, 12)) { + PrintAndLog("Key must include 12 HEX symbols"); + return 1; + } + + trgBlockNo = param_get8(Cmd, 3); + ctmp = param_getchar(Cmd, 4); + if (ctmp != 'a' && ctmp != 'A' && ctmp != 'b' && ctmp != 'B') { + PrintAndLog("Target key type must be A or B"); + return 1; + } + if (ctmp != 'A' && ctmp != 'a') { + trgKeyType = 1; + } + + uint16_t i = 5; + + if (!param_gethex(Cmd, 5, trgkey, 12)) { + know_target_key = true; + i++; + } + + while ((ctmp = param_getchar(Cmd, i))) { + if (ctmp == 's' || ctmp == 'S') { + slow = true; + } else if (ctmp == 'w' || ctmp == 'W') { + nonce_file_write = true; + } else { + PrintAndLog("Possible options are w and/or s"); + return 1; + } + i++; + } + } + + PrintAndLog("--target block no:%3d, target key type:%c, known target key: 0x%02x%02x%02x%02x%02x%02x%s, file action: %s, Slow: %s, Tests: %d ", + trgBlockNo, + trgKeyType?'B':'A', + trgkey[0], trgkey[1], trgkey[2], trgkey[3], trgkey[4], trgkey[5], + know_target_key?"":" (not set)", + nonce_file_write?"write":nonce_file_read?"read":"none", + slow?"Yes":"No", + tests); + + int16_t isOK = mfnestedhard(blockNo, keyType, key, trgBlockNo, trgKeyType, know_target_key?trgkey:NULL, nonce_file_read, nonce_file_write, slow, tests); + + if (isOK) { + switch (isOK) { + case 1 : PrintAndLog("Error: No response from Proxmark.\n"); break; + case 2 : PrintAndLog("Button pressed. Aborted.\n"); break; + default : break; + } + return 2; + } + + return 0; +} + + int CmdHF14AMfChk(const char *Cmd) { if (strlen(Cmd)<3) { @@ -810,7 +906,7 @@ int CmdHF14AMfChk(const char *Cmd) char filename[FILE_PATH_SIZE]={0}; char buf[13]; uint8_t *keyBlock = NULL, *p; - uint8_t stKeyBlock = 20; + uint16_t stKeyBlock = 20; int i, res; int keycnt = 0; @@ -875,6 +971,7 @@ int CmdHF14AMfChk(const char *Cmd) break; default: PrintAndLog("Key type must be A , B or ?"); + free(keyBlock); return 1; }; @@ -1025,7 +1122,8 @@ int CmdHF14AMfChk(const char *Cmd) } void readerAttack(nonces_t ar_resp[], bool setEmulatorMem, bool doStandardAttack) { - #define ATTACK_KEY_COUNT 8 // keep same as define in iso14443a.c -> Mifare1ksim() + #define ATTACK_KEY_COUNT 7 // keep same as define in iso14443a.c -> Mifare1ksim() + // cannot be more than 7 or it will overrun c.d.asBytes(512) uint64_t key = 0; typedef struct { uint64_t keyA; @@ -1062,7 +1160,7 @@ void readerAttack(nonces_t ar_resp[], bool setEmulatorMem, bool doStandardAttack } } } - } else if (tryMfk32_moebius(ar_resp[i+ATTACK_KEY_COUNT], &key)) { + } else if (mfkey32_moebius(ar_resp[i+ATTACK_KEY_COUNT], &key)) { uint8_t sectorNum = ar_resp[i+ATTACK_KEY_COUNT].sector; uint8_t keyType = ar_resp[i+ATTACK_KEY_COUNT].keytype; @@ -1759,7 +1857,7 @@ int CmdHF14AMfCSetBlk(const char *Cmd) { uint8_t memBlock[16] = {0x00}; uint8_t blockNo = 0; - bool wipeCard = FALSE; + bool wipeCard = false; int res; if (strlen(Cmd) < 1 || param_getchar(Cmd, 0) == 'h') { @@ -2211,33 +2309,34 @@ int CmdDecryptTraceCmds(const char *Cmd){ static command_t CommandTable[] = { - {"help", CmdHelp, 1, "This help"}, - {"dbg", CmdHF14AMfDbg, 0, "Set default debug mode"}, - {"rdbl", CmdHF14AMfRdBl, 0, "Read MIFARE classic block"}, - {"rdsc", CmdHF14AMfRdSc, 0, "Read MIFARE classic sector"}, - {"dump", CmdHF14AMfDump, 0, "Dump MIFARE classic tag to binary file"}, - {"restore", CmdHF14AMfRestore, 0, "Restore MIFARE classic binary file to BLANK tag"}, - {"wrbl", CmdHF14AMfWrBl, 0, "Write MIFARE classic block"}, - {"chk", CmdHF14AMfChk, 0, "Test block keys"}, - {"mifare", CmdHF14AMifare, 0, "Read parity error messages."}, - {"nested", CmdHF14AMfNested, 0, "Test nested authentication"}, - {"sniff", CmdHF14AMfSniff, 0, "Sniff card-reader communication"}, - {"sim", CmdHF14AMf1kSim, 0, "Simulate MIFARE card"}, - {"eclr", CmdHF14AMfEClear, 0, "Clear simulator memory block"}, - {"eget", CmdHF14AMfEGet, 0, "Get simulator memory block"}, - {"eset", CmdHF14AMfESet, 0, "Set simulator memory block"}, - {"eload", CmdHF14AMfELoad, 0, "Load from file emul dump"}, - {"esave", CmdHF14AMfESave, 0, "Save to file emul dump"}, - {"ecfill", CmdHF14AMfECFill, 0, "Fill simulator memory with help of keys from simulator"}, - {"ekeyprn", CmdHF14AMfEKeyPrn, 0, "Print keys from simulator memory"}, - {"csetuid", CmdHF14AMfCSetUID, 0, "Set UID for magic Chinese card"}, - {"csetblk", CmdHF14AMfCSetBlk, 0, "Write block - Magic Chinese card"}, - {"cgetblk", CmdHF14AMfCGetBlk, 0, "Read block - Magic Chinese card"}, - {"cgetsc", CmdHF14AMfCGetSc, 0, "Read sector - Magic Chinese card"}, - {"cload", CmdHF14AMfCLoad, 0, "Load dump into magic Chinese card"}, - {"csave", CmdHF14AMfCSave, 0, "Save dump from magic Chinese card into file or emulator"}, - {"decrypt", CmdDecryptTraceCmds,1, "[nt] [ar_enc] [at_enc] [data] - to decrypt snoop or trace"}, - {NULL, NULL, 0, NULL} + {"help", CmdHelp, 1, "This help"}, + {"dbg", CmdHF14AMfDbg, 0, "Set default debug mode"}, + {"rdbl", CmdHF14AMfRdBl, 0, "Read MIFARE classic block"}, + {"rdsc", CmdHF14AMfRdSc, 0, "Read MIFARE classic sector"}, + {"dump", CmdHF14AMfDump, 0, "Dump MIFARE classic tag to binary file"}, + {"restore", CmdHF14AMfRestore, 0, "Restore MIFARE classic binary file to BLANK tag"}, + {"wrbl", CmdHF14AMfWrBl, 0, "Write MIFARE classic block"}, + {"chk", CmdHF14AMfChk, 0, "Test block keys"}, + {"mifare", CmdHF14AMifare, 0, "Read parity error messages."}, + {"hardnested", CmdHF14AMfNestedHard, 0, "Nested attack for hardened Mifare cards"}, + {"nested", CmdHF14AMfNested, 0, "Test nested authentication"}, + {"sniff", CmdHF14AMfSniff, 0, "Sniff card-reader communication"}, + {"sim", CmdHF14AMf1kSim, 0, "Simulate MIFARE card"}, + {"eclr", CmdHF14AMfEClear, 0, "Clear simulator memory block"}, + {"eget", CmdHF14AMfEGet, 0, "Get simulator memory block"}, + {"eset", CmdHF14AMfESet, 0, "Set simulator memory block"}, + {"eload", CmdHF14AMfELoad, 0, "Load from file emul dump"}, + {"esave", CmdHF14AMfESave, 0, "Save to file emul dump"}, + {"ecfill", CmdHF14AMfECFill, 0, "Fill simulator memory with help of keys from simulator"}, + {"ekeyprn", CmdHF14AMfEKeyPrn, 0, "Print keys from simulator memory"}, + {"csetuid", CmdHF14AMfCSetUID, 0, "Set UID for magic Chinese card"}, + {"csetblk", CmdHF14AMfCSetBlk, 0, "Write block - Magic Chinese card"}, + {"cgetblk", CmdHF14AMfCGetBlk, 0, "Read block - Magic Chinese card"}, + {"cgetsc", CmdHF14AMfCGetSc, 0, "Read sector - Magic Chinese card"}, + {"cload", CmdHF14AMfCLoad, 0, "Load dump into magic Chinese card"}, + {"csave", CmdHF14AMfCSave, 0, "Save dump from magic Chinese card into file or emulator"}, + {"decrypt", CmdDecryptTraceCmds, 1, "[nt] [ar_enc] [at_enc] [data] - to decrypt snoop or trace"}, + {NULL, NULL, 0, NULL} }; int CmdHFMF(const char *Cmd)