mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-15 00:55:07 +00:00
Merge branch 'RfidResearchGroup:master' into master
This commit is contained in:
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added `hf calypso list` command (@kormax)
|
||||
- Added `hf mfd verifycert` command (@kormax)
|
||||
- Added `hf calypso dump` command (@kormax)
|
||||
- Added `pm3trace_edit.py` script for editing of pm3 trace files (@iceman1001)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "cmdhf14a.h"
|
||||
#include "cmdhf14b.h"
|
||||
#include "cmdparser.h"
|
||||
#include "cmdtrace.h"
|
||||
#include "commonutil.h"
|
||||
#include "comms.h"
|
||||
#include "emv/emvcore.h"
|
||||
@@ -280,6 +281,15 @@ static const calypso_get_data_probe_t calypso_get_data_probes[] = {
|
||||
{0x5F52, "ATR historical bytes", true},
|
||||
};
|
||||
|
||||
const char *CalypsoGetDataTagName(uint16_t tag) {
|
||||
for (size_t i = 0; i < ARRAYLEN(calypso_get_data_probes); i++) {
|
||||
if (calypso_get_data_probes[i].tag == tag) {
|
||||
return calypso_get_data_probes[i].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *calypso_file_structure_desc(uint8_t subtype) {
|
||||
switch (subtype) {
|
||||
case 0x00:
|
||||
@@ -2898,6 +2908,10 @@ static int CmdHFCalypsoDump(const char *Cmd) {
|
||||
return first_error;
|
||||
}
|
||||
|
||||
static int CmdHFCalypsoList(const char *Cmd) {
|
||||
return CmdTraceListAlias(Cmd, "hf calypso", "calypso");
|
||||
}
|
||||
|
||||
static int CmdHFCalypsoInfo(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf calypso info",
|
||||
@@ -3006,6 +3020,7 @@ static command_t CommandTable[] = {
|
||||
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
||||
{"info", CmdHFCalypsoInfo, IfPm3Iso14443, "Tag information"},
|
||||
{"dump", CmdHFCalypsoDump, IfPm3Iso14443, "Dump readable files and records"},
|
||||
{"list", CmdHFCalypsoList, AlwaysAvailable, "List Calypso history"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -22,5 +22,6 @@
|
||||
#include "common.h"
|
||||
|
||||
int CmdHFCalypso(const char *Cmd);
|
||||
const char *CalypsoGetDataTagName(uint16_t tag);
|
||||
|
||||
#endif
|
||||
|
||||
+191
-32
@@ -30,6 +30,7 @@
|
||||
#include "crapto1/crapto1.h"
|
||||
#include "protocols.h"
|
||||
#include "cmdhficlass.h"
|
||||
#include "cmdhfcalypso.h"
|
||||
#include "mifare/mifaredefault.h" // mifare consts
|
||||
#include "cmdhfseos.h"
|
||||
|
||||
@@ -943,43 +944,202 @@ void annotateIso7816(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool
|
||||
}
|
||||
}
|
||||
|
||||
static bool iso14443_4_get_i_block_inf(uint8_t *cmd, uint8_t cmdsize, bool is_response, const uint8_t **inf, size_t *inf_len) {
|
||||
(void)is_response;
|
||||
size_t frame_len = cmdsize;
|
||||
if (frame_len < 1 || (cmd[0] & 0xC0) != 0x00 || (cmd[0] & 0x02) != 0x02) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pos = 1;
|
||||
if ((cmd[0] & 0x08) == 0x08) {
|
||||
pos++;
|
||||
}
|
||||
if ((cmd[0] & 0x04) == 0x04) {
|
||||
pos++;
|
||||
}
|
||||
if (pos >= frame_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*inf = cmd + pos;
|
||||
if (inf_len != NULL) {
|
||||
*inf_len = frame_len - pos;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool annotateIso14443_s_r_block(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response, bool show_block_number) {
|
||||
(void)is_response;
|
||||
size_t frame_len = cmdsize;
|
||||
if (frame_len < 1 || frame_len > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((cmd[0] & 0xC0) == 0xC0) {
|
||||
switch (cmd[0] & 0x30) {
|
||||
case 0x00:
|
||||
snprintf(exp, size, "S-block DESELECT");
|
||||
break;
|
||||
case 0x30:
|
||||
snprintf(exp, size, "S-block WTX");
|
||||
break;
|
||||
default:
|
||||
snprintf(exp, size, "S-block");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((cmd[0] & 0xD0) == 0x80) {
|
||||
if (show_block_number) {
|
||||
snprintf(exp, size, (cmd[0] & 0x10) ? "R-block NACK(%d)" : "R-block ACK(%d)", cmd[0] & 0x01);
|
||||
} else {
|
||||
snprintf(exp, size, (cmd[0] & 0x10) ? "R-block NACK" : "R-block ACK");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void calypso_sfi_file_ref(char *out, size_t out_len, uint8_t p2, uint8_t current_mask, uint8_t sfi_mask) {
|
||||
if (p2 == current_mask) {
|
||||
snprintf(out, out_len, "current EF");
|
||||
} else if ((p2 & 0x07) == sfi_mask && (p2 >> 3) != 0) {
|
||||
snprintf(out, out_len, "sfi=%u", p2 >> 3);
|
||||
} else {
|
||||
snprintf(out, out_len, "p2=%02X", p2);
|
||||
}
|
||||
}
|
||||
|
||||
static void calypso_binary_ref(char *out, size_t out_len, uint8_t ins, uint8_t p1, uint8_t p2) {
|
||||
if (ins == CALYPSO_READ_BINARY) {
|
||||
if ((p1 & 0x80) == 0x80) {
|
||||
snprintf(out, out_len, "sfi=%u, off=%u", p1 & 0x1F, p2);
|
||||
} else {
|
||||
snprintf(out, out_len, "off=%u", ((p1 & 0x7F) << 8) | p2);
|
||||
}
|
||||
} else if ((p2 & 0x80) == 0x80) {
|
||||
snprintf(out, out_len, "sfi=%u", p2 & 0x1F);
|
||||
} else if (p2 == 0x00) {
|
||||
snprintf(out, out_len, "current EF");
|
||||
} else {
|
||||
snprintf(out, out_len, "p2=%02X", p2);
|
||||
}
|
||||
}
|
||||
|
||||
static bool annotateCalypsoApdu(char *exp, size_t size, const uint8_t *apdu, size_t apdu_len) {
|
||||
if (apdu_len < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t ins = apdu[1];
|
||||
uint8_t p1 = apdu[2];
|
||||
uint8_t p2 = apdu[3];
|
||||
|
||||
switch (ins) {
|
||||
case CALYPSO_SELECT: {
|
||||
if (p1 == 0x04) {
|
||||
const char *mode = (p2 == 0x02 || p2 == 0x0E) ? "next" : "first";
|
||||
const char *fci = (p2 == 0x0C || p2 == 0x0E) ? "none" : "return";
|
||||
snprintf(exp, size, "SELECT APPLICATION (mode=%s, fci=%s)", mode, fci);
|
||||
} else if (p1 == 0x02 && (p2 == 0x00 || p2 == 0x02)) {
|
||||
snprintf(exp, size, "SELECT FILE");
|
||||
} else if (p1 == 0x09 && p2 == 0x00) {
|
||||
snprintf(exp, size, "SELECT FILE (current DF)");
|
||||
} else if (p1 == 0x00) {
|
||||
snprintf(exp, size, "SELECT FILE (by file id)");
|
||||
} else if (p1 == 0x08) {
|
||||
snprintf(exp, size, "SELECT FILE (by path)");
|
||||
} else {
|
||||
snprintf(exp, size, "SELECT");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CALYPSO_GET_DATA: {
|
||||
uint16_t tag = (p1 << 8) | p2;
|
||||
const char *name = CalypsoGetDataTagName(tag);
|
||||
if (name) {
|
||||
snprintf(exp, size, "GET DATA (tag=%04X - %s)", tag, name);
|
||||
} else {
|
||||
snprintf(exp, size, "GET DATA (tag=%04X)", tag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CALYPSO_READ_RECORD: {
|
||||
char ref[20];
|
||||
calypso_sfi_file_ref(ref, sizeof(ref), p2, 0x04, 0x04);
|
||||
snprintf(exp, size, "READ RECORD (%s, rec=%u)", ref, p1);
|
||||
return true;
|
||||
}
|
||||
case CALYPSO_READ_RECORD_MULTIPLE: {
|
||||
char ref[20];
|
||||
calypso_sfi_file_ref(ref, sizeof(ref), p2, 0x05, 0x05);
|
||||
snprintf(exp, size, "READ RECORDS (%s, rec=%u)", ref, p1);
|
||||
return true;
|
||||
}
|
||||
case CALYPSO_READ_BINARY:
|
||||
case CALYPSO_READ_BINARY_EXTENDED: {
|
||||
char ref[20];
|
||||
calypso_binary_ref(ref, sizeof(ref), ins, p1, p2);
|
||||
snprintf(exp, size, "READ BINARY (%s)", ref);
|
||||
return true;
|
||||
}
|
||||
case CALYPSO_GET_CHALLENGE:
|
||||
snprintf(exp, size, "GET CHALLENGE");
|
||||
return true;
|
||||
case CALYPSO_GET_RESPONSE:
|
||||
snprintf(exp, size, "GET RESPONSE");
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void annotateCalypso(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response) {
|
||||
if (cmdsize < 1 || is_response) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (applyIso14443a(exp, size, cmd, cmdsize, false) == PM3_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd[0] == ISO14443B_REQB || cmd[0] == ISO14443B_ATTRIB || cmd[0] == ISO14443B_HALT) {
|
||||
annotateIso14443b(exp, size, cmd, cmdsize);
|
||||
return;
|
||||
}
|
||||
|
||||
if (annotateIso14443_s_r_block(exp, size, cmd, cmdsize, false, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *inf = NULL;
|
||||
size_t inf_len = 0;
|
||||
if (iso14443_4_get_i_block_inf(cmd, cmdsize, false, &inf, &inf_len)) {
|
||||
annotateCalypsoApdu(exp, size, inf, inf_len);
|
||||
}
|
||||
}
|
||||
|
||||
// MIFARE DESFire
|
||||
void annotateMfDesfire(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) {
|
||||
|
||||
// it's basically a ISO14443a tag, so try annotation from there
|
||||
if (applyIso14443a(exp, size, cmd, cmdsize, false) != PM3_SUCCESS) {
|
||||
|
||||
// S-block 11xxx010
|
||||
if ((cmd[0] & 0xC0) && (cmdsize == 3)) {
|
||||
switch ((cmd[0] & 0x30)) {
|
||||
case 0x00:
|
||||
snprintf(exp, size, "S-block DESELECT");
|
||||
break;
|
||||
case 0x30:
|
||||
snprintf(exp, size, "S-block WTX");
|
||||
break;
|
||||
default:
|
||||
snprintf(exp, size, "S-block");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// R-block (ack) 101xx01x
|
||||
else if (((cmd[0] & 0xB0) == 0xA0) && (cmdsize > 2)) {
|
||||
if ((cmd[0] & 0x10) == 0)
|
||||
snprintf(exp, size, "R-block ACK(%d)", (cmd[0] & 0x01));
|
||||
else
|
||||
snprintf(exp, size, "R-block NACK(%d)", (cmd[0] & 0x01));
|
||||
if (annotateIso14443_s_r_block(exp, size, cmd, cmdsize, false, true)) {
|
||||
return;
|
||||
}
|
||||
// I-block 000xCN1x
|
||||
else if (((cmd[0] & 0xC0) == 0x00) && (cmdsize > 2)) {
|
||||
|
||||
// PCB [CID] [NAD] [INF] CRC CRC
|
||||
int pos = 1;
|
||||
if ((cmd[0] & 0x08) == 0x08) // cid byte following
|
||||
pos++;
|
||||
const uint8_t *inf = NULL;
|
||||
if (iso14443_4_get_i_block_inf(cmd, cmdsize, false, &inf, NULL) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cmd[0] & 0x04) == 0x04) // nad byte following
|
||||
pos++;
|
||||
int pos = inf - cmd;
|
||||
|
||||
for (uint8_t i = 0; i < 2; i++, pos++) {
|
||||
bool found_annotation = true;
|
||||
@@ -1370,13 +1530,12 @@ void annotateMfPlus(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) {
|
||||
// ok this part is copy paste from annotateMfDesfire, it seems to work for MIFARE Plus also
|
||||
if (((cmd[0] & 0xC0) == 0x00) && (cmdsize > 2)) {
|
||||
|
||||
// PCB [CID] [NAD] [INF] CRC CRC
|
||||
int pos = 1;
|
||||
if ((cmd[0] & 0x08) == 0x08) // cid byte following
|
||||
pos++;
|
||||
const uint8_t *inf = NULL;
|
||||
if (iso14443_4_get_i_block_inf(cmd, cmdsize, false, &inf, NULL) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cmd[0] & 0x04) == 0x04) // nad byte following
|
||||
pos++;
|
||||
int pos = inf - cmd;
|
||||
|
||||
for (uint8_t i = 0; i < 2; i++, pos++) {
|
||||
bool found_annotation = true;
|
||||
|
||||
@@ -56,6 +56,7 @@ void annotateTopaz(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize);
|
||||
void annotateLegic(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize);
|
||||
void annotateFelica(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize);
|
||||
void annotateIso7816(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response);
|
||||
void annotateCalypso(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response);
|
||||
void annotateIso14443b(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize);
|
||||
void annotateIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response);
|
||||
void annotateMfDesfire(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize);
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
#define MFDES_PC_MAX_ROUNDS 8U
|
||||
#define MFDES_PC_MAC_LEN 8U
|
||||
|
||||
#define MFDES_EV3C_MFC_KILL_KEY 0x31U
|
||||
#define MFDES_EV3C_ALLOWED_DATA_PERMISSIONS 0x11U
|
||||
#define MFDES_EV3C_ALLOWED_TRAILER_PERMISSIONS 0x1FU
|
||||
|
||||
// DUOX ISO Internal Authenticate
|
||||
#define DUOX_INTAUTH_CHALLENGE_LEN 16
|
||||
#define DUOX_INTAUTH_SIG_LEN 64
|
||||
@@ -90,6 +94,12 @@ static const uint8_t kDuoxVDEDefaultDFName[] = {
|
||||
};
|
||||
#define MFDES_VERIFYCERT_MAX_CAS DUOX_MAX_CERTIFICATE_ANCHORS
|
||||
|
||||
static uint8_t saved_mfclicense[192];
|
||||
static size_t saved_mfclicense_len = 0;
|
||||
static bool saved_mfclicense_set = false;
|
||||
static uint8_t saved_mfclicense_mac[8];
|
||||
static bool saved_mfclicense_mac_set = false;
|
||||
|
||||
typedef struct mfd_app_select {
|
||||
bool dfname_present;
|
||||
uint8_t dfname[16];
|
||||
@@ -6375,6 +6385,322 @@ static int CmdHF14ADesClearRecordFile(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse MFC blocks given on the command line
|
||||
*
|
||||
* If given for making the license, the blocks must be unique and in ascending order.
|
||||
*
|
||||
* If given for the CreateMFCMapping command, the blocks must be unique and either all data blocks or all trailer blocks.
|
||||
*/
|
||||
static int parse_mfc_blocks(const char *in, uint8_t blocks_out[], size_t *blocks_out_len, bool for_license) {
|
||||
if (!in || !blocks_out_len) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
*blocks_out_len = 0;
|
||||
char blk_str[512];
|
||||
if (strlen(in) + 1 > sizeof (blk_str)) {
|
||||
PrintAndLogEx(ERR, "Argument too long");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
strcpy(blk_str, in);
|
||||
|
||||
char *ptr = blk_str;
|
||||
char *saveptr = NULL;
|
||||
char *token;
|
||||
int last = -1;
|
||||
uint64_t seen_so_far = 0;
|
||||
bool has_data = false;
|
||||
bool has_trailer = false;
|
||||
for (;;) {
|
||||
token = strtok_r(ptr, ",", &saveptr);
|
||||
ptr = NULL;
|
||||
|
||||
if (!token) {
|
||||
break;
|
||||
}
|
||||
|
||||
char *endptr = NULL;
|
||||
long blk_val = strtol(token, &endptr, 0);
|
||||
if (endptr == NULL || *token == '\0' || *endptr != '\0' || blk_val < 0 || blk_val >= 64) {
|
||||
PrintAndLogEx(ERR, "Invalid block number: %s", token);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (for_license) { // check if sorted in ascending order
|
||||
if (blk_val <= last) {
|
||||
PrintAndLogEx(ERR, "Blocks must be unique and sorted in ascending order");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
} else { // check if unique and all data / all trailer
|
||||
if (seen_so_far & (1ULL << blk_val)) {
|
||||
PrintAndLogEx(ERR, "Blocks must be unique");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
seen_so_far |= (1ULL << blk_val);
|
||||
has_data |= !!((blk_val + 1) % 4 != 0);
|
||||
has_trailer |= !!((blk_val + 1) % 4 == 0);
|
||||
|
||||
if (has_data && has_trailer) {
|
||||
PrintAndLogEx(ERR, "Either data blocks or trailer blocks must be given, not both");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
}
|
||||
blocks_out[(*blocks_out_len)++] = blk_val;
|
||||
last = blk_val;
|
||||
|
||||
if (*blocks_out_len > 64) {
|
||||
PrintAndLogEx(ERR, "Too many blocks");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
}
|
||||
|
||||
if (*blocks_out_len == 0) {
|
||||
PrintAndLogEx(ERR, "At least one block must be given");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdHF14ADesMakeMFCLicense(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf mfdes makemfclicense",
|
||||
"Create a Mifare Classic license for DESFire EV3C, as well as a license MAC.\n"
|
||||
"The MFC license MAC key must have the same value as PICC level key 50.\n"
|
||||
"\n"
|
||||
"The permissions are a bitwise-or combination of:\n"
|
||||
" 0x01: allow mapping block\n"
|
||||
" 0x02: allow updating key B (only for trailers)\n"
|
||||
" 0x04: allow updating ACs (only for trailers)\n"
|
||||
" 0x08: allow updating key A (only for trailers)\n"
|
||||
" 0x10: allow the DESFire RestrictMFCUpdate command",
|
||||
"hf mfdes makemfclicense -b 4,5,6,8,9,10 --mfc-keys FFFFFFFFFFFF111111111111222222222222\n"
|
||||
" -> Create a license for blocks 4, 5, 6, 8, 9, 10 where sector 1 has key A = FFFFFFFFFFFF and key B readable,\n"
|
||||
" -> and sector 2 has key A = 111111111111, key B = 222222222222 with key B non readable");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_lit0("v", "verbose", "Show more output"),
|
||||
arg_str0("k", "key", "<hex>", "Key for computing the MAC, must be HEX 16(AES)"),
|
||||
arg_str0("b", "blk", "<num>[,<num>[,...]]", "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, "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"),
|
||||
arg_str0("r", "raw", "<hex>", "Raw license in case not all blocks should have the same permissions, of the form num_blocks||block1nr||block1perm||block2nr||block2perm||..."),
|
||||
arg_str0(NULL, "mfc-keys", "<hex>", "The concatenated MFC sector keys, one (if key B is readable) or two per sector"),
|
||||
arg_lit0("s", "save", "Save the license and license MAC for the next commands in this session"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
||||
|
||||
logLevel_t loglevel = arg_get_lit(ctx, 1) ? INFO : DEBUG;
|
||||
bool allow_key_a_update = arg_get_lit(ctx, 4);
|
||||
bool allow_key_b_update = arg_get_lit(ctx, 5);
|
||||
bool allow_restrict = arg_get_lit(ctx, 6);
|
||||
bool allow_map = arg_get_lit(ctx, 7);
|
||||
bool allow_ac = arg_get_lit(ctx, 8);
|
||||
bool save = arg_get_lit(ctx, 11);
|
||||
struct arg_str *key_arg = arg_get_str(ctx, 2);
|
||||
struct arg_str *blk_str_arg = arg_get_str(ctx, 3);
|
||||
struct arg_str *raw_arg = arg_get_str(ctx, 9);
|
||||
struct arg_str *mfc_keys_arg = arg_get_str(ctx, 10);
|
||||
|
||||
bool has_raw = raw_arg->count == 1;
|
||||
bool use_default_keys;
|
||||
uint8_t license[192];
|
||||
int license_len = 0;
|
||||
size_t num_sectors;
|
||||
uint8_t mfc_keys[192];
|
||||
int mfc_keys_len = 0;
|
||||
uint8_t mac_key[16];
|
||||
int mac_key_len = 0;
|
||||
|
||||
if (key_arg->count != 1) {
|
||||
PrintAndLogEx(ERR, "At most one instance of --key is required");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
if (CLIParamHexToBuf(key_arg, mac_key, sizeof (mac_key), &mac_key_len) != 0) {
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
if (mac_key_len != 16) {
|
||||
PrintAndLogEx(ERR, "The MFC License MAC key must be exactly 16 bytes");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
|
||||
switch (mfc_keys_arg->count) {
|
||||
case 0:
|
||||
use_default_keys = true;
|
||||
break;
|
||||
case 1:
|
||||
use_default_keys = false;
|
||||
break;
|
||||
default:
|
||||
PrintAndLogEx(ERR, "At most instance of --mfc-keys is required");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
|
||||
if (raw_arg->count + blk_str_arg->count != 1) {
|
||||
PrintAndLogEx(ERR, "Exactly one of --raw or --blk are needed");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
|
||||
if (has_raw) {
|
||||
if (CLIParamHexToBuf(raw_arg, license, sizeof (license), &license_len) != 0) {
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
if (allow_key_a_update || allow_key_b_update || allow_restrict || allow_map || allow_ac) {
|
||||
PrintAndLogEx(WARNING, "Permission flags are not used when --raw is used");
|
||||
}
|
||||
|
||||
// Verify the license
|
||||
size_t num_blocks = license[0];
|
||||
if (license_len != 2 * num_blocks + 1) {
|
||||
PrintAndLogEx(ERR, "Invalid number of blocks in the license");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
|
||||
uint8_t last_sector = license[1] / 4;
|
||||
num_sectors = 1;
|
||||
int last_block = -1;
|
||||
|
||||
for (int i = 0; i < num_blocks; i++) {
|
||||
uint8_t block_nr = license[1 + 2*i];
|
||||
uint8_t permissions = license[1 + 2*i + 1];
|
||||
|
||||
if (block_nr / 4 != last_sector) {
|
||||
num_sectors++;
|
||||
last_sector = block_nr / 4;
|
||||
}
|
||||
|
||||
if (block_nr <= last_block) {
|
||||
PrintAndLogEx(ERR, "Blocks must be unique and sorted in ascending order");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
last_block = block_nr;
|
||||
|
||||
if (block_nr >= 64) {
|
||||
PrintAndLogEx(ERR, "Invalid block number %d", block_nr);
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
|
||||
if ( (((block_nr + 1) % 4 == 0) && (permissions & ~MFDES_EV3C_ALLOWED_TRAILER_PERMISSIONS)) ||
|
||||
(((block_nr + 1) % 4 != 0) && (permissions & ~MFDES_EV3C_ALLOWED_DATA_PERMISSIONS))) {
|
||||
PrintAndLogEx(ERR, "Invalid permissions %02X", permissions);
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
|
||||
PrintAndLogEx(loglevel, "[%d] Block %d: permissions %02X", i, block_nr, permissions);
|
||||
}
|
||||
} else {
|
||||
uint8_t blocks[64];
|
||||
size_t num_blocks = 0;
|
||||
|
||||
if (strcmp("all", blk_str_arg->sval[0]) == 0) {
|
||||
PrintAndLogEx(loglevel, "Using all blocks");
|
||||
for (int i = 0; i < 64; i++) {
|
||||
blocks[i] = i;
|
||||
}
|
||||
num_blocks = 64;
|
||||
} else {
|
||||
if (parse_mfc_blocks(blk_str_arg->sval[0], blocks, &num_blocks, true) != PM3_SUCCESS) {
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t data_permissions = 0;
|
||||
uint8_t trailer_permissions = 0;
|
||||
if (allow_map) {
|
||||
data_permissions |= 0x01;
|
||||
trailer_permissions |= 0x01;
|
||||
}
|
||||
if (allow_key_b_update) {
|
||||
trailer_permissions |= 0x02;
|
||||
}
|
||||
if (allow_ac) {
|
||||
trailer_permissions |= 0x04;
|
||||
}
|
||||
if (allow_key_a_update) {
|
||||
trailer_permissions |= 0x08;
|
||||
}
|
||||
if (allow_restrict) {
|
||||
data_permissions |= 0x10;
|
||||
trailer_permissions |= 0x10;
|
||||
}
|
||||
|
||||
uint8_t last_sector = blocks[0] / 4;
|
||||
num_sectors = 1;
|
||||
license[0] = num_blocks;
|
||||
for (int i = 0; i < num_blocks; i++) {
|
||||
if (blocks[i] / 4 != last_sector) {
|
||||
num_sectors++;
|
||||
last_sector = blocks[i] / 4;
|
||||
}
|
||||
|
||||
license[1 + 2*i] = blocks[i];
|
||||
if ((blocks[i] + 1) % 4 == 0) {
|
||||
// mapping a trailer block
|
||||
license[1 + 2*i + 1] = trailer_permissions;
|
||||
} else {
|
||||
// mapping a data block
|
||||
license[1 + 2*i + 1] = data_permissions;
|
||||
}
|
||||
PrintAndLogEx(loglevel, "[%d] Block %d: permissions %02X", i, license[1 + 2*i], license[1 + 2*i + 1]);
|
||||
}
|
||||
license_len = 1 + 2*num_blocks;
|
||||
}
|
||||
|
||||
PrintAndLogEx(loglevel, "Covering %lu sector%s in total", num_sectors, num_sectors != 1 ? "s" : "");
|
||||
|
||||
if (use_default_keys) {
|
||||
// by default: all key A FFFFFFFFFFFF, all key B readable
|
||||
memset(mfc_keys, '\xFF', 6 * num_sectors);
|
||||
mfc_keys_len = 6 * num_sectors;
|
||||
} else {
|
||||
if (CLIParamHexToBuf(mfc_keys_arg, mfc_keys, sizeof (mfc_keys), &mfc_keys_len) != 0) {
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
if (mfc_keys_len % 6 != 0) {
|
||||
PrintAndLogEx(ERR, "MFC keys length must be a multiple of 6 bytes");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
// Quick sanity check: do we have more or less the right number of keys for the number of sectors?
|
||||
if (mfc_keys_len / 6 < num_sectors || (mfc_keys_len / 6) > 2 * num_sectors) {
|
||||
PrintAndLogEx(ERR, "Not enough or too many keys for the number of sectors (expecting 1 or 2 keys per sector)");
|
||||
goto mfclicense_parsing_error;
|
||||
}
|
||||
}
|
||||
|
||||
CLIParserFree(ctx);
|
||||
|
||||
// Compute the MAC
|
||||
uint8_t license_mac_data[384];
|
||||
uint8_t mac[8];
|
||||
license_mac_data[0] = 0x01;
|
||||
memcpy(license_mac_data + 1, license, license_len);
|
||||
memcpy(license_mac_data + 1 + license_len, mfc_keys, mfc_keys_len);
|
||||
|
||||
aes_cmac8(NULL, mac_key, license_mac_data, mac, 1 + license_len + mfc_keys_len);
|
||||
|
||||
PrintAndLogEx(INFO, "License: %s", sprint_hex_inrow(license, license_len));
|
||||
PrintAndLogEx(INFO, "MAC: %s", sprint_hex_inrow(mac, 8));
|
||||
|
||||
if (save) {
|
||||
memcpy(saved_mfclicense, license, license_len);
|
||||
saved_mfclicense_len = license_len;
|
||||
saved_mfclicense_set = true;
|
||||
memcpy(saved_mfclicense_mac, mac, 8);
|
||||
saved_mfclicense_mac_set = true;
|
||||
PrintAndLogEx(INFO, "Saved license and license MAC for the next commands in this session");
|
||||
}
|
||||
|
||||
return PM3_SUCCESS;
|
||||
|
||||
mfclicense_parsing_error:
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
static int DesfileReadISOFileAndPrint(DesfireContext_t *dctx,
|
||||
bool select_current_file, uint8_t fnum,
|
||||
uint16_t fisoid, int filetype,
|
||||
@@ -9103,6 +9429,7 @@ static command_t CommandTable[] = {
|
||||
{"write", CmdHF14ADesWriteData, IfPm3Iso14443a, "Write data to standard/backup/record/value file"},
|
||||
{"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"},
|
||||
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("DUOX") " ------------------------"},
|
||||
{"verifycert", CmdHF14ADesVerifyCert, IfPm3Iso14443a, "Validate cert from file and verify key possession"},
|
||||
{"intauth", CmdHF14ADesIntAuth, IfPm3Iso14443a, "ISO Internal Authenticate (ECDSA challenge-response)"},
|
||||
|
||||
+39
-7
@@ -39,6 +39,14 @@ static int CmdHelp(const char *Cmd);
|
||||
static uint8_t *gs_trace;
|
||||
static uint16_t gs_traceLen = 0;
|
||||
|
||||
typedef enum {
|
||||
TRACE_CRC_FAIL = 0,
|
||||
TRACE_CRC_OK = 1,
|
||||
TRACE_CRC_NONE = 2,
|
||||
TRACE_CRC_A_OK = 3,
|
||||
TRACE_CRC_B_OK = 4,
|
||||
} trace_crc_status_t;
|
||||
|
||||
static bool is_last_record(uint16_t tracepos, uint16_t traceLen) {
|
||||
return ((tracepos + TRACELOG_HDR_LEN) >= traceLen);
|
||||
}
|
||||
@@ -542,7 +550,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
}
|
||||
|
||||
//Check the CRC status
|
||||
uint8_t crcStatus = 2;
|
||||
trace_crc_status_t crcStatus = TRACE_CRC_NONE;
|
||||
|
||||
if (data_len > 2) {
|
||||
switch (protocol) {
|
||||
@@ -569,9 +577,18 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
crcStatus = seos_CRC_check(hdr->isResponse, frame, data_len);
|
||||
break;
|
||||
case ISO_7816_4:
|
||||
crcStatus = iso14443A_CRC_check(hdr->isResponse, frame, data_len) == 1 ? 3 : 0;
|
||||
crcStatus = iso14443B_CRC_check(frame, data_len) == 1 ? 4 : crcStatus;
|
||||
case PROTO_CALYPSO: {
|
||||
uint8_t crcA = iso14443A_CRC_check(hdr->isResponse, frame, data_len);
|
||||
uint8_t crcB = iso14443B_CRC_check(frame, data_len);
|
||||
if (crcA == TRACE_CRC_OK) {
|
||||
crcStatus = TRACE_CRC_A_OK;
|
||||
} else if (crcB == TRACE_CRC_OK) {
|
||||
crcStatus = TRACE_CRC_B_OK;
|
||||
} else {
|
||||
crcStatus = crcA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case THINFILM:
|
||||
frame[data_len - 1] ^= frame[data_len - 2];
|
||||
frame[data_len - 2] ^= frame[data_len - 1];
|
||||
@@ -627,6 +644,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
&& protocol != ISO_15693
|
||||
&& protocol != ICLASS
|
||||
&& protocol != ISO_7816_4
|
||||
&& protocol != PROTO_CALYPSO
|
||||
&& protocol != PROTO_HITAG1
|
||||
&& protocol != PROTO_HITAG2
|
||||
&& protocol != PROTO_HITAGS
|
||||
@@ -697,7 +715,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
(*(pos2 + 1)) = '\0';
|
||||
} else {
|
||||
|
||||
if (crcStatus == 0 || crcStatus == 1) {
|
||||
if (crcStatus == TRACE_CRC_FAIL || crcStatus == TRACE_CRC_OK) {
|
||||
|
||||
char *pos1 = line[(data_len - 2) / TRACE_MAX_HEX_BYTES];
|
||||
int delta = (data_len - 2) % TRACE_MAX_HEX_BYTES ? 1 : 0;
|
||||
@@ -708,7 +726,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
char *cb_str = str_dup(pos1 + delta);
|
||||
|
||||
if (g_session.supports_colors) {
|
||||
if (crcStatus == 0) {
|
||||
if (crcStatus == TRACE_CRC_FAIL) {
|
||||
snprintf(pos1, 24, AEND " " _RED_("%s"), cb_str);
|
||||
} else {
|
||||
snprintf(pos1, 24, AEND " " _GREEN_("%s"), cb_str);
|
||||
@@ -726,7 +744,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
cb_str = str_dup(pos1);
|
||||
|
||||
if (g_session.supports_colors) {
|
||||
if (crcStatus == 0) {
|
||||
if (crcStatus == TRACE_CRC_FAIL) {
|
||||
snprintf(pos1, 24, _RED_("%s"), cb_str);
|
||||
} else {
|
||||
snprintf(pos1, 24, _GREEN_("%s"), cb_str);
|
||||
@@ -742,7 +760,13 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
}
|
||||
|
||||
// Draw the CRC column
|
||||
const char *crcstrings[] = { _RED_(" !! "), _GREEN_(" ok "), " ", _GREEN_("A ok"), _GREEN_("B ok") };
|
||||
const char *crcstrings[] = {
|
||||
[TRACE_CRC_FAIL] = _RED_(" !! "),
|
||||
[TRACE_CRC_OK] = _GREEN_(" ok "),
|
||||
[TRACE_CRC_NONE] = " ",
|
||||
[TRACE_CRC_A_OK] = _GREEN_("A ok"),
|
||||
[TRACE_CRC_B_OK] = _GREEN_("B ok"),
|
||||
};
|
||||
const char *crc = crcstrings[crcStatus];
|
||||
|
||||
// mark short bytes (less than 8 Bit + Parity)
|
||||
@@ -796,6 +820,9 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
|
||||
case PROTO_FMCOS20:
|
||||
annotateIso14443a(explanation, sizeof(explanation), frame, data_len, hdr->isResponse);
|
||||
break;
|
||||
case PROTO_CALYPSO:
|
||||
annotateCalypso(explanation, sizeof(explanation), frame, data_len, hdr->isResponse);
|
||||
break;
|
||||
case PROTO_MIFARE:
|
||||
case PROTO_MFPLUS:
|
||||
annotateMifare(explanation, sizeof(explanation), frame, data_len, parityBytes, TRACELOG_PARITY_LEN(hdr), hdr->isResponse);
|
||||
@@ -1317,6 +1344,7 @@ int CmdTraceList(const char *Cmd) {
|
||||
"trace list -t 14b -> interpret as " _YELLOW_("ISO14443-B") "\n"
|
||||
"trace list -t 15 -> interpret as " _YELLOW_("ISO15693") "\n"
|
||||
"trace list -t 7816 -> interpret as " _YELLOW_("ISO7816-4") "\n"
|
||||
"trace list -t calypso -> interpret as " _YELLOW_("Calypso") "\n"
|
||||
"trace list -t cryptorf -> interpret as " _YELLOW_("CryptoRF") "\n"
|
||||
"trace list -t des -> interpret as " _YELLOW_("MIFARE DESFire") "\n"
|
||||
"trace list -t felica -> interpret as " _YELLOW_("ISO18092 / FeliCa") "\n"
|
||||
@@ -1385,6 +1413,7 @@ int CmdTraceList(const char *Cmd) {
|
||||
else if (strcmp(type, "14b") == 0) protocol = ISO_14443B;
|
||||
else if (strcmp(type, "15") == 0) protocol = ISO_15693;
|
||||
else if (strcmp(type, "7816") == 0) protocol = ISO_7816_4;
|
||||
else if (strcmp(type, "calypso") == 0) protocol = PROTO_CALYPSO;
|
||||
else if (strcmp(type, "cryptorf") == 0) protocol = PROTO_CRYPTORF;
|
||||
else if (strcmp(type, "des") == 0) protocol = MFDES;
|
||||
else if (strcmp(type, "felica") == 0) protocol = FELICA;
|
||||
@@ -1481,6 +1510,9 @@ int CmdTraceList(const char *Cmd) {
|
||||
if (protocol == ISO_7816_4)
|
||||
PrintAndLogEx(INFO, _YELLOW_("ISO7816-4 / Smartcard") " - Timings n/a");
|
||||
|
||||
if (protocol == PROTO_CALYPSO)
|
||||
PrintAndLogEx(INFO, _YELLOW_("Calypso") " - Timings n/a");
|
||||
|
||||
if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS || protocol == PROTO_HITAGU) {
|
||||
PrintAndLogEx(INFO, _YELLOW_("Hitag 1 / Hitag 2 / Hitag S / Hitag µ") " - Timings in ETU (8us)");
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ const static vocabulary_t vocabulary[] = {
|
||||
{ 1, "hf calypso help" },
|
||||
{ 0, "hf calypso info" },
|
||||
{ 0, "hf calypso dump" },
|
||||
{ 1, "hf calypso list" },
|
||||
{ 1, "hf cipurse help" },
|
||||
{ 0, "hf cipurse info" },
|
||||
{ 0, "hf cipurse select" },
|
||||
@@ -526,6 +527,7 @@ const static vocabulary_t vocabulary[] = {
|
||||
{ 0, "hf mfdes write" },
|
||||
{ 0, "hf mfdes value" },
|
||||
{ 0, "hf mfdes clearrecfile" },
|
||||
{ 1, "hf mfdes makemfclicense" },
|
||||
{ 0, "hf mfdes verifycert" },
|
||||
{ 0, "hf mfdes intauth" },
|
||||
{ 0, "hf mfdes vdesign" },
|
||||
|
||||
+5
-1
@@ -462,7 +462,8 @@ ISO 7816-4 Basic interindustry commands. For command APDU's.
|
||||
#define PROTO_TEXKOM 19
|
||||
#define PROTO_XEROX 20
|
||||
#define PROTO_FMCOS20 21
|
||||
#define COUNT_OF_PROTOCOLS 22
|
||||
#define PROTO_CALYPSO 22
|
||||
#define COUNT_OF_PROTOCOLS 23
|
||||
|
||||
// Picopass fuses
|
||||
#define FUSE_FPERS 0x80
|
||||
@@ -928,6 +929,7 @@ ISO 7816-4 Basic interindustry commands. For command APDU's.
|
||||
|
||||
// Calypso protocol
|
||||
#define CALYPSO_GET_RESPONSE 0xC0
|
||||
#define CALYPSO_GET_DATA 0xCA
|
||||
#define CALYPSO_SELECT 0xA4
|
||||
#define CALYPSO_INVALIDATE 0x04
|
||||
#define CALYPSO_REHABILITATE 0x44
|
||||
@@ -935,7 +937,9 @@ ISO 7816-4 Basic interindustry commands. For command APDU's.
|
||||
#define CALYPSO_DECREASE 0x30
|
||||
#define CALYPSO_INCREASE 0x32
|
||||
#define CALYPSO_READ_BINARY 0xB0
|
||||
#define CALYPSO_READ_BINARY_EXTENDED 0xB1
|
||||
#define CALYPSO_READ_RECORD 0xB2
|
||||
#define CALYPSO_READ_RECORD_MULTIPLE 0xB3
|
||||
#define CALYPSO_UPDATE_BINARY 0xD6
|
||||
#define CALYPSO_UPDATE_RECORD 0xDC
|
||||
#define CALYPSO_WRITE_RECORD 0xD2
|
||||
|
||||
Reference in New Issue
Block a user