Merge pull request #3299 from kormax/calypso-list

Add `hf calypso list` command
This commit is contained in:
Iceman
2026-05-10 20:59:57 +02:00
committed by GitHub
8 changed files with 254 additions and 40 deletions
+1
View File
@@ -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)
+15
View File
@@ -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}
};
+1
View File
@@ -22,5 +22,6 @@
#include "common.h"
int CmdHFCalypso(const char *Cmd);
const char *CalypsoGetDataTagName(uint16_t tag);
#endif
+191 -32
View File
@@ -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;
+1
View File
@@ -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);
+39 -7
View File
@@ -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)");
}
+1
View File
@@ -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" },
+5 -1
View File
@@ -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