From 3da7e52c1a8f844a8df2ff83437cdf1ce7c8dccd Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Sun, 10 May 2026 21:17:15 +0300 Subject: [PATCH] Implement 'hf calypso list' command --- CHANGELOG.md | 1 + client/src/cmdhfcalypso.c | 15 ++++ client/src/cmdhfcalypso.h | 1 + client/src/cmdhflist.c | 120 ++++++++++++++++++++++++++++++++ client/src/cmdhflist.h | 1 + client/src/cmdtrace.c | 11 ++- client/src/pm3line_vocabulary.h | 1 + include/protocols.h | 6 +- 8 files changed, 154 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46d73a48b..d3cad5b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/client/src/cmdhfcalypso.c b/client/src/cmdhfcalypso.c index e28985899..704e61d37 100644 --- a/client/src/cmdhfcalypso.c +++ b/client/src/cmdhfcalypso.c @@ -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} }; diff --git a/client/src/cmdhfcalypso.h b/client/src/cmdhfcalypso.h index 85b2ce80f..355b77317 100644 --- a/client/src/cmdhfcalypso.h +++ b/client/src/cmdhfcalypso.h @@ -22,5 +22,6 @@ #include "common.h" int CmdHFCalypso(const char *Cmd); +const char *CalypsoGetDataTagName(uint16_t tag); #endif diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index 2265a3a08..7069ae134 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -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" @@ -1002,6 +1003,125 @@ static bool annotateIso14443_s_r_block(char *exp, size_t size, uint8_t *cmd, uin 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) { diff --git a/client/src/cmdhflist.h b/client/src/cmdhflist.h index 0f8c526b3..55f6a67ae 100644 --- a/client/src/cmdhflist.h +++ b/client/src/cmdhflist.h @@ -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); diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index e98ca1df1..d565fb2a1 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -577,7 +577,7 @@ 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: - { + 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) { @@ -644,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 @@ -819,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); @@ -1340,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" @@ -1408,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; @@ -1504,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)"); } diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index b4282c537..2514d1c33 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -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" }, diff --git a/include/protocols.h b/include/protocols.h index 58b5041c0..bebb4349f 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -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