proxmark3/client/src/cmdhfcryptorf.c

557 lines
17 KiB
C
Raw Normal View History

2020-04-06 12:16:32 +08:00
//-----------------------------------------------------------------------------
// Copyright (C) 2020 iceman
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// High frequency CryptoRF commands (ISO14443B)
//-----------------------------------------------------------------------------
#include "cmdhfcryptorf.h"
#include <ctype.h>
#include "fileutils.h"
#include "cmdparser.h" // command_t
#include "comms.h" // clearCommandBuffer
#include "cmdtrace.h"
#include "crc16.h"
#include "protocols.h" // definitions of ISO14B protocol
2020-10-02 20:31:11 +08:00
#include "iso14b.h"
#include "cliparser.h" // cliparsing
2020-04-06 12:16:32 +08:00
#define TIMEOUT 2000
#ifndef CRYPTORF_MEM_SIZE
# define CRYPTORF_MEM_SIZE 1024
2021-04-08 17:08:39 +08:00
#endif
2020-04-06 12:16:32 +08:00
static int CmdHelp(const char *Cmd);
static iso14b_card_select_t last_known_card;
static void set_last_known_card(iso14b_card_select_t card) {
last_known_card = card;
2020-04-06 12:54:53 +08:00
}
2020-04-06 12:16:32 +08:00
static int switch_off_field_cryptorf(void) {
SetISODEPState(ISODEP_INACTIVE);
2021-04-22 00:36:21 +08:00
iso14b_raw_cmd_t packet = {
.flags = ISO14B_DISCONNECT,
.timeout = 0,
.rawlen = 0,
};
2020-04-06 12:16:32 +08:00
clearCommandBuffer();
2021-05-06 03:04:48 +08:00
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t));
2020-04-06 12:16:32 +08:00
return PM3_SUCCESS;
}
static int CmdHFCryptoRFList(const char *Cmd) {
2021-04-20 03:43:32 +08:00
return CmdTraceListAlias(Cmd, "hf cryptorf", "cryptorf");
2020-04-06 12:16:32 +08:00
}
static int CmdHFCryptoRFSim(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf sim",
2021-04-22 00:36:21 +08:00
"Simulate a CryptoRF tag\n"
_RED_("not implemented"),
"hf cryptorf sim");
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
CLIParserFree(ctx);
2020-04-06 12:16:32 +08:00
clearCommandBuffer();
2021-04-22 00:36:21 +08:00
SendCommandNG(CMD_HF_CRYPTORF_SIM, NULL, 0);
2020-04-06 12:16:32 +08:00
return PM3_SUCCESS;
}
2020-04-06 12:54:53 +08:00
static int CmdHFCryptoRFSniff(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf sniff",
"Sniff the communication reader and tag",
"hf cryptorf sniff\n"
);
2020-04-06 12:16:32 +08:00
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
2020-04-06 12:16:32 +08:00
clearCommandBuffer();
SendCommandNG(CMD_HF_ISO14443B_SNIFF, NULL, 0);
2021-04-08 17:08:39 +08:00
PrintAndLogEx(HINT, "Try `" _YELLOW_("hf cryptorf list") "` to view captured tracelog");
PrintAndLogEx(HINT, "Try `" _YELLOW_("trace save -f hf_cryptorf_mytrace") "` to save tracelog for later analysing");
2020-04-06 12:16:32 +08:00
return PM3_SUCCESS;
}
static bool get_14b_UID(iso14b_card_select_t *card) {
2021-04-22 00:36:21 +08:00
if (card == NULL)
2020-04-06 12:16:32 +08:00
return false;
int8_t retry = 3;
while (retry--) {
2021-04-22 00:36:21 +08:00
iso14b_raw_cmd_t packet = {
.flags = (ISO14B_CONNECT | ISO14B_SELECT_STD | ISO14B_DISCONNECT),
.timeout = 0,
.rawlen = 0,
};
2020-04-06 12:16:32 +08:00
clearCommandBuffer();
2021-05-06 03:04:48 +08:00
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t));
2021-04-22 00:36:21 +08:00
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, TIMEOUT)) {
if (resp.oldarg[0] == 0) {
2020-04-06 12:16:32 +08:00
memcpy(card, (iso14b_card_select_t *)resp.data.asBytes, sizeof(iso14b_card_select_t));
return true;
}
}
} // retry
if (retry <= 0)
2021-04-22 00:36:21 +08:00
PrintAndLogEx(FAILED, "command execution timeout");
2020-04-06 12:16:32 +08:00
return false;
}
// Print extented information about tag.
static int infoHFCryptoRF(bool verbose) {
2021-04-22 00:36:21 +08:00
iso14b_raw_cmd_t packet = {
.flags = (ISO14B_CONNECT | ISO14B_SELECT_STD | ISO14B_DISCONNECT),
.timeout = 0,
.rawlen = 0,
};
// 14b get and print UID only (general info)
clearCommandBuffer();
2021-05-06 03:04:48 +08:00
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t));
PacketResponseNG resp;
2021-04-22 00:36:21 +08:00
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, TIMEOUT) == false) {
if (verbose) {
PrintAndLogEx(WARNING, "command execution timeout");
}
switch_off_field_cryptorf();
return false;
2020-04-06 12:16:32 +08:00
}
iso14b_card_select_t card;
memcpy(&card, (iso14b_card_select_t *)resp.data.asBytes, sizeof(iso14b_card_select_t));
uint64_t status = resp.oldarg[0];
switch (status) {
case 0:
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, " UID : %s", sprint_hex(card.uid, card.uidlen));
PrintAndLogEx(SUCCESS, " ATQB : %s", sprint_hex(card.atqb, sizeof(card.atqb)));
PrintAndLogEx(SUCCESS, " CHIPID : %02X", card.chipid);
2021-04-22 00:36:21 +08:00
return PM3_SUCCESS;
case 2:
if (verbose) PrintAndLogEx(FAILED, "ISO 14443-3 ATTRIB fail");
break;
case 3:
if (verbose) PrintAndLogEx(FAILED, "ISO 14443-3 CRC fail");
break;
default:
if (verbose) PrintAndLogEx(FAILED, "ISO 14443-b card select failed");
break;
}
2021-04-22 00:36:21 +08:00
return PM3_ESOFT;
}
2020-04-06 12:16:32 +08:00
static int CmdHFCryptoRFInfo(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf info",
"Act as a CryptoRF reader.",
"hf cryptorf info");
void *argtable[] = {
arg_param_begin,
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool verbose = arg_get_lit(ctx, 1);
CLIParserFree(ctx);
int res = infoHFCryptoRF(verbose);
2020-04-06 12:16:32 +08:00
if (res != PM3_SUCCESS && verbose) {
PrintAndLogEx(FAILED, "no CryptoRF / ISO14443-B tag found");
2020-04-06 12:16:32 +08:00
}
return res;
}
// get and print general info cryptoRF
int readHFCryptoRF(bool loop, bool verbose) {
2020-04-06 12:16:32 +08:00
int res = PM3_ESOFT;
2021-05-06 03:04:48 +08:00
do {
2021-04-22 00:36:21 +08:00
iso14b_raw_cmd_t packet = {
.flags = (ISO14B_CONNECT | ISO14B_SELECT_STD | ISO14B_DISCONNECT),
.timeout = 0,
.rawlen = 0,
};
clearCommandBuffer();
2021-05-06 03:04:48 +08:00
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_ACK, &resp, 2000)) {
2020-04-06 12:16:32 +08:00
uint8_t status = resp.oldarg[0] & 0xFF;
if (loop) {
if (status != 0) {
continue;
}
} else {
// when not in continuous mode
if (status != 0) {
if (verbose) PrintAndLogEx(WARNING, "cryptoRF / ISO14443-b card select failed");
res = PM3_EOPABORTED;
2020-04-06 12:16:32 +08:00
break;
}
}
iso14b_card_select_t card;
memcpy(&card, (iso14b_card_select_t *)resp.data.asBytes, sizeof(iso14b_card_select_t));
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex_inrow(card.uid, card.uidlen));
set_last_known_card(card);
2020-04-06 12:16:32 +08:00
}
} while (loop && kbd_enter_pressed() == false);
DropField();
return res;
}
static int CmdHFCryptoRFReader(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf reader",
"Act as a cryptoRF reader. Look for cryptoRF tags until Enter or the pm3 button is pressed",
"hf cryptorf reader -@ -> continuous reader mode"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("@", NULL, "optional - continuous reader mode"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool cm = arg_get_lit(ctx, 1);
CLIParserFree(ctx);
if (cm) {
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
2020-04-06 12:16:32 +08:00
}
return readHFCryptoRF(cm, false);
}
2020-04-06 12:16:32 +08:00
// need to write to file
static int CmdHFCryptoRFDump(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf dump",
2021-04-08 17:08:39 +08:00
"Dump all memory from a CryptoRF tag (512/4096 bit size)",
"hf cryptorf dump\n"
);
void *argtable[] = {
arg_param_begin,
2021-06-25 16:02:30 +08:00
arg_str0("f", "file", "<fn>", "filename to save dump to"),
arg_lit0(NULL, "64", "64byte / 512bit memory"),
arg_lit0(NULL, "512", "512byte / 4096bit memory"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
2020-04-06 12:16:32 +08:00
bool m64 = arg_get_lit(ctx, 2);
bool m512 = arg_get_lit(ctx, 3);
CLIParserFree(ctx);
if (m512 + m64 > 1) {
PrintAndLogEx(INFO, "Select only one card memory size");
return PM3_EINVARG;
2020-04-06 12:16:32 +08:00
}
uint16_t cardsize = 0;
uint8_t blocks = 0;
if (m64) {
cardsize = (512 / 8) + 4;
blocks = 0x0F;
}
if (m512) {
cardsize = (4096 / 8) + 4;
blocks = 0x7F;
2020-04-06 12:16:32 +08:00
}
iso14b_card_select_t card;
if (get_14b_UID(&card) == false) {
PrintAndLogEx(WARNING, "No tag found.");
return PM3_SUCCESS;
2020-04-06 12:16:32 +08:00
}
// detect blocksize from card :)
PrintAndLogEx(INFO, "Reading memory from tag UID " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen));
2020-04-06 12:16:32 +08:00
2021-04-22 00:36:21 +08:00
// select tag
2021-05-06 03:04:48 +08:00
iso14b_raw_cmd_t *packet = (iso14b_raw_cmd_t *)calloc(1, sizeof(iso14b_raw_cmd_t) + 2);
2021-04-22 00:36:21 +08:00
if (packet == NULL) {
PrintAndLogEx(FAILED, "failed to allocate memory");
return PM3_EMALLOC;
}
packet->flags = (ISO14B_CONNECT | ISO14B_SELECT_SR);
packet->timeout = 0;
packet->rawlen = 0;
2020-04-06 12:16:32 +08:00
clearCommandBuffer();
2021-05-06 03:04:48 +08:00
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)packet, sizeof(iso14b_raw_cmd_t));
2021-04-22 00:36:21 +08:00
PacketResponseNG resp;
2020-04-06 12:16:32 +08:00
2021-04-22 00:36:21 +08:00
// select
int status = 0;
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, 2000)) {
status = resp.oldarg[0];
if (status < 0) {
PrintAndLogEx(FAILED, "failed to select %" PRId64 "]", resp.oldarg[0]);
free(packet);
return switch_off_field_cryptorf();
2020-04-06 12:16:32 +08:00
}
}
2021-04-22 00:36:21 +08:00
PrintAndLogEx(INFO, "." NOLF);
uint8_t data[cardsize];
memset(data, 0, sizeof(data));
uint16_t blocknum = 0;
2020-04-06 12:16:32 +08:00
for (int retry = 0; retry < 5; retry++) {
2021-04-22 00:36:21 +08:00
// set up the read command
packet->flags = (ISO14B_APPEND_CRC | ISO14B_RAW);
packet->rawlen = 2;
packet->raw[0] = ISO14443B_READ_BLK;
packet->raw[1] = blocknum & 0xFF;
2020-04-06 12:16:32 +08:00
clearCommandBuffer();
2021-05-06 03:04:48 +08:00
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t) + 2);
2021-04-22 00:36:21 +08:00
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, 2000)) {
2020-04-06 12:16:32 +08:00
2021-04-22 00:36:21 +08:00
status = resp.oldarg[0];
if (status < 0) {
PrintAndLogEx(FAILED, "retrying one more time");
2020-04-06 12:16:32 +08:00
continue;
}
uint16_t len = (resp.oldarg[1] & 0xFFFF);
2021-04-22 00:36:21 +08:00
uint8_t *recv = resp.data.asBytes;
2020-04-06 12:16:32 +08:00
2021-04-22 00:36:21 +08:00
if (check_crc(CRC_14443_B, recv, len) == false) {
2020-04-06 12:16:32 +08:00
PrintAndLogEx(FAILED, "crc fail, retrying one more time");
continue;
}
memcpy(data + (blocknum * 4), resp.data.asBytes, 4);
2021-04-22 00:36:21 +08:00
// last read
2020-04-06 12:16:32 +08:00
if (blocknum == 0xFF) {
break;
}
retry = 0;
blocknum++;
if (blocknum > blocks) {
// read config block
blocknum = 0xFF;
}
2021-04-22 00:36:21 +08:00
PrintAndLogEx(NORMAL, "." NOLF);
2020-04-06 12:16:32 +08:00
fflush(stdout);
}
}
2021-04-22 00:36:21 +08:00
free(packet);
PrintAndLogEx(NORMAL, "");
2020-04-06 12:16:32 +08:00
2021-04-22 00:36:21 +08:00
if (blocknum != 0xFF) {
PrintAndLogEx(FAILED, "dump failed");
return switch_off_field_cryptorf();
2020-04-06 12:16:32 +08:00
}
PrintAndLogEx(INFO, "block# | data | ascii");
PrintAndLogEx(INFO, "---------+--------------+----------");
2020-04-06 12:16:32 +08:00
for (int i = 0; i <= blocks; i++) {
PrintAndLogEx(INFO,
2020-04-06 12:16:32 +08:00
"%3d/0x%02X | %s | %s",
i,
i,
sprint_hex(data + (i * 4), 4),
sprint_ascii(data + (i * 4), 4)
);
}
2021-04-22 00:36:21 +08:00
PrintAndLogEx(INFO, "---------+--------------+----------");
PrintAndLogEx(NORMAL, "");
2020-04-06 12:16:32 +08:00
size_t datalen = (blocks + 1) * 4;
if (fnlen < 1) {
PrintAndLogEx(INFO, "Using UID as filename");
2021-04-22 00:36:21 +08:00
char *fptr = filename;
fptr += sprintf(fptr, "hf-cryptorf-");
FillFileNameByUID(fptr, card.uid, "-dump", card.uidlen);
}
2020-04-06 12:16:32 +08:00
saveFileEML(filename, data, datalen, 4);
saveFile(filename, ".bin", data, datalen);
2021-04-22 00:36:21 +08:00
// json?
2020-04-06 12:16:32 +08:00
return switch_off_field_cryptorf();
}
2020-04-06 12:54:53 +08:00
static int CmdHFCryptoRFELoad(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf eload",
2021-04-08 17:08:39 +08:00
"Loads CryptoRF tag dump into emulator memory on device",
"hf cryptorf eload -f hf-cryptorf-0102030405-dump.bin\n"
);
2020-04-06 14:02:48 +08:00
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<filename>", "filename of dump"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
2021-03-31 23:03:20 +08:00
CLIParserFree(ctx);
2021-03-31 23:03:20 +08:00
if (fnlen == 0) {
PrintAndLogEx(ERR, "Error: Please specify a filename");
return PM3_EINVARG;
}
size_t datalen = CRYPTORF_MEM_SIZE;
2020-04-06 12:54:53 +08:00
// set up buffer
uint8_t *data = calloc(datalen, sizeof(uint8_t));
if (!data) {
PrintAndLogEx(WARNING, "Fail, cannot allocate memory");
return PM3_EMALLOC;
}
if (loadFile_safe(filename, ".bin", (void **)&data, &datalen) != PM3_SUCCESS) {
free(data);
PrintAndLogEx(WARNING, "Error, reading file");
return PM3_EFILE;
}
PrintAndLogEx(SUCCESS, "Uploading to emulator memory");
uint32_t bytes_sent = 0;
2020-04-16 15:01:14 +08:00
/*
//Send to device
uint32_t bytes_remaining = bytes_read;
while (bytes_remaining > 0) {
uint32_t bytes_in_packet = MIN(PM3_CMD_DATA_SIZE, bytes_remaining);
if (bytes_in_packet == bytes_remaining) {
// Disable fast mode on last packet
2021-08-22 02:21:08 +08:00
g_conn.block_after_ACK = false;
2020-04-06 12:54:53 +08:00
}
clearCommandBuffer();
2021-04-22 00:36:21 +08:00
SendCommandMIX(CMD_HF_CRYPTORF_EML_MEMSET, bytes_sent, bytes_in_packet, 0, data + bytes_sent, bytes_in_packet);
bytes_remaining -= bytes_in_packet;
bytes_sent += bytes_in_packet;
}
2020-04-16 15:01:14 +08:00
*/
2020-04-06 12:54:53 +08:00
free(data);
PrintAndLogEx(SUCCESS, "sent %d bytes of data to device emulator memory", bytes_sent);
2020-04-06 14:02:48 +08:00
return PM3_SUCCESS;
2020-04-06 12:54:53 +08:00
}
static int CmdHFCryptoRFESave(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf cryptorf esave",
2021-04-08 17:08:39 +08:00
"Save emulator memory to bin/eml/json file\n"
"if filename is not supplied, UID will be used.",
"hf cryptorf esave\n"
"hf cryptorf esave -f filename"
);
void *argtable[] = {
arg_param_begin,
2021-06-25 16:02:30 +08:00
arg_str0("f", "file", "<fn>", "filename of dumpfile"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int fnlen = 0;
2020-04-06 12:54:53 +08:00
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
CLIParserFree(ctx);
size_t numofbytes = CRYPTORF_MEM_SIZE;
2020-04-06 12:54:53 +08:00
// set up buffer
uint8_t *data = calloc(numofbytes, sizeof(uint8_t));
2021-04-22 00:36:21 +08:00
if (data == NULL) {
2020-04-06 12:54:53 +08:00
PrintAndLogEx(WARNING, "Fail, cannot allocate memory");
return PM3_EMALLOC;
}
// download emulator memory
PrintAndLogEx(SUCCESS, "Reading emulator memory...");
2021-04-22 00:36:21 +08:00
if (GetFromDevice(BIG_BUF_EML, data, numofbytes, 0, NULL, 0, NULL, 2500, false) == false) {
2020-04-06 12:54:53 +08:00
PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
free(data);
return PM3_ETIMEOUT;
}
// user supplied filename?
if (fnlen < 1) {
2020-04-06 12:54:53 +08:00
PrintAndLogEx(INFO, "Using UID as filename");
2021-04-22 00:36:21 +08:00
char *fptr = filename;
fptr += sprintf(fptr, "hf-cryptorf-");
FillFileNameByUID(fptr, data, "-dump", 4);
2020-04-06 12:54:53 +08:00
}
saveFile(filename, ".bin", data, numofbytes);
//needs to change
saveFileEML(filename, data, numofbytes, 8);
//needs to change
saveFileJSON(filename, jsfRaw, data, numofbytes, NULL);
2021-04-22 00:36:21 +08:00
free(data);
2020-04-06 12:54:53 +08:00
return PM3_SUCCESS;
}
2020-04-06 12:16:32 +08:00
static command_t CommandTable[] = {
2020-04-06 12:54:53 +08:00
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"dump", CmdHFCryptoRFDump, IfPm3Iso14443b, "Read all memory pages of an CryptoRF tag, save to file"},
{"info", CmdHFCryptoRFInfo, IfPm3Iso14443b, "Tag information"},
{"list", CmdHFCryptoRFList, AlwaysAvailable, "List ISO 14443B history"},
{"reader", CmdHFCryptoRFReader, IfPm3Iso14443b, "Act as a CryptoRF reader to identify a tag"},
{"sim", CmdHFCryptoRFSim, IfPm3Iso14443b, "Fake CryptoRF tag"},
{"sniff", CmdHFCryptoRFSniff, IfPm3Iso14443b, "Eavesdrop CryptoRF"},
{"eload", CmdHFCryptoRFELoad, AlwaysAvailable, "Load binary dump to emulator memory"},
{"esave", CmdHFCryptoRFESave, AlwaysAvailable, "Save emulator memory to binary file"},
2020-04-06 12:16:32 +08:00
{NULL, NULL, NULL, NULL}
};
static int CmdHelp(const char *Cmd) {
(void)Cmd; // Cmd is not used so far
CmdsHelp(CommandTable);
return PM3_SUCCESS;
}
int CmdHFCryptoRF(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}