diff --git a/client/src/cipurse/cipursecore.c b/client/src/cipurse/cipursecore.c index f36181992..016fa9c04 100644 --- a/client/src/cipurse/cipursecore.c +++ b/client/src/cipurse/cipursecore.c @@ -126,9 +126,12 @@ int CIPURSESelectFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, siz return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xa4, 0x00, 0x00, 02, fileIdBin}, true, 0, Result, MaxResultLen, ResultLen, sw); } -int CIPURSEReadFileAttributes(uint8_t *data, uint16_t *datalen) { - //CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0x82, 0x00, keyIndex, paramslen, params}, true, 0x10, Result, MaxResultLen, ResultLen, sw); - return 2; +int CIPURSESelectMFFile(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xa4, 0x00, 0x00, 0, NULL}, true, 0, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSEReadFileAttributes(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchangeEx(false, true, (sAPDU) {0x80, 0xce, 0x00, 0x00, 0, NULL}, true, 0, Result, MaxResultLen, ResultLen, sw); } int CIPURSEReadBinary(uint16_t offset, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { @@ -209,3 +212,128 @@ void CIPURSEPrintInfoFile(uint8_t *data, size_t len) { PrintAndLogEx(INFO, "CIPURSE version %d revision %d", data[0], data[1]); } +static void CIPURSEPrintFileDescriptor(uint8_t desc) { + if (desc == 0x01) + PrintAndLogEx(INFO, "Binary file"); + else if (desc == 0x11) + PrintAndLogEx(INFO, "Binary file with transactions"); + else if (desc == 0x02) + PrintAndLogEx(INFO, "Linear record file"); + else if (desc == 0x12) + PrintAndLogEx(INFO, "Linear record file with transactions"); + else if (desc == 0x06) + PrintAndLogEx(INFO, "Cyclic record file"); + else if (desc == 0x16) + PrintAndLogEx(INFO, "Cyclic record file with transactions"); + else if (desc == 0x1E) + PrintAndLogEx(INFO, "Linear value-record file"); + else if (desc == 0x1F) + PrintAndLogEx(INFO, "Linear value-record file with transactions"); + else + PrintAndLogEx(INFO, "Unknown file 0x%02x", desc); +} + +static void CIPURSEPrintKeyAttrib(uint8_t *attr) { + PrintAndLogEx(INFO, "-------- KEY ATTRIBUTES --------"); + PrintAndLogEx(INFO, "Additional info: 0x%02x", attr[0]); + PrintAndLogEx(INFO, "Key length: %d", attr[1]); + PrintAndLogEx(INFO, "Algorithm ID: 0x%02x", attr[2]); + PrintAndLogEx(INFO, "Security attr: 0x%02x", attr[3]); + PrintAndLogEx(INFO, "KVV: 0x%02x%02x%02x", attr[4], attr[5], attr[6]); + PrintAndLogEx(INFO, "-------------------------------"); +} + +void CIPURSEPrintFileAttr(uint8_t *fileAttr, size_t len) { + if (len < 7) { + PrintAndLogEx(ERR, "Attributes length " _RED_("ERROR")); + return; + } + + PrintAndLogEx(INFO, "--------- FILE ATTRIBUTES ---------"); + if (fileAttr[0] == 0x38) { + PrintAndLogEx(INFO, "Type: MF, ADF"); + if (fileAttr[1] == 0x00) { + PrintAndLogEx(INFO, "Type: MF"); + } else { + if ((fileAttr[1] & 0xe0) == 0x00) + PrintAndLogEx(INFO, "Type: Unknown"); + if ((fileAttr[1] & 0xe0) == 0x20) + PrintAndLogEx(INFO, "Type: CIPURSE L"); + if ((fileAttr[1] & 0xe0) == 0x40) + PrintAndLogEx(INFO, "Type: CIPURSE S"); + if ((fileAttr[1] & 0xe0) == 0x60) + PrintAndLogEx(INFO, "Type: CIPURSE T"); + if ((fileAttr[1] & 0x02) == 0x00) + PrintAndLogEx(INFO, "Autoselect on PxSE select OFF"); + else + PrintAndLogEx(INFO, "Autoselect on PxSE select ON"); + if ((fileAttr[1] & 0x01) == 0x00) + PrintAndLogEx(INFO, "PxSE select returns FCPTemplate OFF"); + else + PrintAndLogEx(INFO, "PxSE select returns FCPTemplate ON"); + } + + PrintAndLogEx(INFO, "File ID: 0x%02x%02x", fileAttr[2], fileAttr[3]); + + PrintAndLogEx(INFO, "Maximum number of custom EFs: %d", fileAttr[4]); + PrintAndLogEx(INFO, "Maximum number of EFs with SFID: %d", fileAttr[5]); + uint8_t keyNum = fileAttr[6]; + PrintAndLogEx(INFO, "Keys assigned: %d", keyNum); + + if (len >= 9) { + PrintAndLogEx(INFO, "SMR entries: %02x%02x", fileAttr[7], fileAttr[8]); + } + + if (len >= 10 + keyNum + 1) { + PrintAndLogEx(INFO, "ART: %s", sprint_hex(&fileAttr[9], keyNum + 1)); + } + + if (len >= 11 + keyNum + 1 + keyNum * 7) { + for (int i = 0; i < keyNum; i++) { + PrintAndLogEx(INFO, "Key %d Attributes: %s", i, sprint_hex(&fileAttr[11 + keyNum + 1 + i * 7], 7)); + CIPURSEPrintKeyAttrib(&fileAttr[11 + keyNum + 1 + i * 7]); + } + } + // MF + if (fileAttr[1] == 0x00) { + PrintAndLogEx(INFO, "Total memory size: %d", (fileAttr[len - 6] << 16) + (fileAttr[len - 1] << 5) + fileAttr[len - 4]); + PrintAndLogEx(INFO, "Free memory size: %d", (fileAttr[len - 3] << 16) + (fileAttr[len - 2] << 8) + fileAttr[len - 1]); + + } else { + int ptr = 11 + keyNum + 1 + keyNum * 7; + if (len > ptr) + PrintAndLogEx(INFO, "TLV file control: %s", sprint_hex(&fileAttr[ptr], len - ptr)); + } + } else { + PrintAndLogEx(INFO, "Type: EF"); + CIPURSEPrintFileDescriptor(fileAttr[0]); + if (fileAttr[1] == 0) + PrintAndLogEx(INFO, "SFI: not assigned"); + else + PrintAndLogEx(INFO, "SFI: 0x%02x", fileAttr[1]); + + PrintAndLogEx(INFO, "File ID: 0x%02x%02x", fileAttr[2], fileAttr[3]); + + if (fileAttr[0] == 0x01 || fileAttr[0] == 0x11) + PrintAndLogEx(INFO, "File size: %d", (fileAttr[4] << 8) + fileAttr[5]); + else + PrintAndLogEx(INFO, "Record num: %d record size: %d", fileAttr[4], fileAttr[5]); + + PrintAndLogEx(INFO, "Keys assigned: %d", fileAttr[6]); + + if (len >= 9) { + PrintAndLogEx(INFO, "SMR entries: %02x%02x", fileAttr[7], fileAttr[8]); + } + + if (len >= 10) { + PrintAndLogEx(INFO, "ART: %s", sprint_hex(&fileAttr[9], len - 9)); + if (fileAttr[6] + 1 != len - 9) + PrintAndLogEx(WARNING, "ART length is wrong"); + } + + } + + +} + + diff --git a/client/src/cipurse/cipursecore.h b/client/src/cipurse/cipursecore.h index 9ac8f4906..715ca4c25 100644 --- a/client/src/cipurse/cipursecore.h +++ b/client/src/cipurse/cipursecore.h @@ -31,12 +31,15 @@ int CIPURSEMutalAuthenticate(uint8_t keyIndex, uint8_t *params, uint8_t paramsle int CIPURSECreateFile(uint16_t fileID, uint8_t *fileAttr); int CIPURSEDeleteFile(uint16_t fileID); +int CIPURSESelectMFFile(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) ; int CIPURSESelectFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); -int CIPURSEReadFileAttributes(uint8_t *data, uint16_t *datalen); +int CIPURSEReadFileAttributes(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); int CIPURSEReadBinary(uint16_t offset, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); int CIPURSEUpdateBinary(uint16_t offset, uint8_t *data, uint16_t datalen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); bool CIPURSEChannelAuthenticate(uint8_t keyIndex, uint8_t *key, bool verbose); void CIPURSECSetActChannelSecurityLevels(CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp); +void CIPURSEPrintFileAttr(uint8_t *fileAttr, size_t len); + #endif /* __CIPURSECORE_H__ */ diff --git a/client/src/cmdhfcipurse.c b/client/src/cmdhfcipurse.c index 588ac9b3e..040e988c9 100644 --- a/client/src/cmdhfcipurse.c +++ b/client/src/cmdhfcipurse.c @@ -437,7 +437,7 @@ static int CmdHFCipurseWriteFile(const char *Cmd) { res = CIPURSEUpdateBinary(offset, hdata, hdatalen, buf, sizeof(buf), &len, &sw); if (res != 0 || sw != 0x9000) { if (verbose == false) - PrintAndLogEx(ERR, "File read " _RED_("ERROR") ". Card returns 0x%04x.", sw); + PrintAndLogEx(ERR, "File write " _RED_("ERROR") ". Card returns 0x%04x.", sw); DropField(); return PM3_ESOFT; } @@ -448,6 +448,130 @@ static int CmdHFCipurseWriteFile(const char *Cmd) { return PM3_SUCCESS; } +static int CmdHFCipurseReadFileAttr(const char *Cmd) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t key[] = CIPURSE_DEFAULT_KEY; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse aread", + "Read file attributes by file ID with key ID and key", + "hf cipurse aread -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and read file with id 2ff7\n" + "hf cipurse aread -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and read file\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "show technical data"), + arg_int0("n", "keyid", "", "key id"), + arg_str0("k", "key", "", "key for authenticate"), + arg_str0("f", "file", "", "file ID"), + arg_lit0(NULL, "noauth", "read file attributes without authentication"), + arg_str0(NULL, "sreq", "", "communication reader-PICC security level"), + arg_str0(NULL, "sresp", "", "communication PICC-reader security level"), + arg_lit0(NULL, "sel-adf","show info about ADF itself"), + arg_lit0(NULL, "sel-mf", "show info about master file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + uint8_t keyId = arg_get_int_def(ctx, 3, 1); + + CipurseChannelSecurityLevel sreq = CPSMACed; + CipurseChannelSecurityLevel sresp = CPSMACed; + int res = CLIParseKeyAndSecurityLevels(ctx, 4, 7, 8, key, &sreq, &sresp); + if (res) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + uint16_t fileId = 0x2ff7; + + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen); + if (hdatalen && hdatalen != 2) { + PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + fileId = (hdata[0] << 8) + hdata[1]; + + bool noAuth = arg_get_lit(ctx, 6); + + bool seladf = arg_get_lit(ctx, 9); + bool selmf = arg_get_lit(ctx, 10); + + SetAPDULogging(APDULogging); + + CLIParserFree(ctx); + + res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) + PrintAndLogEx(INFO, "File id: %x key id: %d key: %s", fileId, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH)); + + if (noAuth == false) { + bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose); + if (bres == false) { + if (verbose == false) + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR")); + DropField(); + return PM3_ESOFT; + } + + // set channel security levels + CIPURSECSetActChannelSecurityLevels(sreq, sresp); + } + + if (seladf == false) { + if (selmf) + res = CIPURSESelectMFFile(buf, sizeof(buf), &len, &sw); + else + res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw); + + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + } + + if (verbose) + PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId); + + res = CIPURSEReadFileAttributes(buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File read " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (len == 0) { + PrintAndLogEx(WARNING, "File id: %x attributes is empty", fileId); + DropField(); + return PM3_SUCCESS; + } + + if (verbose) + PrintAndLogEx(INFO, "File id: %x attributes[%d]: %s", fileId, len, sprint_hex(buf, len)); + + CIPURSEPrintFileAttr(buf, len); + + DropField(); + return PM3_SUCCESS; +} @@ -469,6 +593,7 @@ static command_t CommandTable[] = { {"auth", CmdHFCipurseAuth, IfPm3Iso14443a, "Authentication."}, {"read", CmdHFCipurseReadFile, IfPm3Iso14443a, "Read binary file."}, {"write", CmdHFCipurseWriteFile, IfPm3Iso14443a, "Write binary file."}, + {"aread", CmdHFCipurseReadFileAttr, IfPm3Iso14443a, "Read file attributes."}, {NULL, NULL, 0, NULL} };