mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-09-25 17:44:01 +00:00
677 lines
28 KiB
C
677 lines
28 KiB
C
|
|
#include "cmdhfsecc.h"
|
|
|
|
#include <string.h>
|
|
#include "cmdparser.h" // command_t
|
|
#include "cliparser.h" // CLIParser*
|
|
#include "comms.h" // SendCommandNG, WaitForResponseTimeout, clearCommandBuffer
|
|
#include "ui.h" // PrintAndLogEx
|
|
#include "util.h" // kbd_enter_pressed, hex_to_bytes, sprint_hex_inrow
|
|
#include "cmdhf14a.h" // IfPm3Iso14443a
|
|
#include "pm3_cmd.h" // CMD_HF_ISO14443A_SIMULATE, CMD_HF_ISO14443A_SNIFF, CMD_BREAK_LOOP, FLAG_SET_UID_IN_DATA
|
|
#include "jansson.h" // json_object_get, json_is_array, json_string_value, json_decref
|
|
#include "fileutils.h" // loadFileJSONroot, JsonLoadBufAsHex
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Payload structs shared with armsrc/secc.h
|
|
// Must stay in sync with hid_apdu_entry_t / hid_sim_payload_t.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Must stay in sync with armsrc/secc.h. Sized so hid_sim_payload_t fits in
|
|
// PM3_CMD_DATA_SIZE (512); adjust ENTRIES carefully if any field is added.
|
|
#define HID_APDU_MAX_ENTRIES 7
|
|
#define HID_APDU_MAX_CMD 20
|
|
#define HID_APDU_MAX_RESP 32
|
|
#define HID_APDU_MASK_LEN 3 // ceil(HID_APDU_MAX_CMD / 8)
|
|
|
|
typedef struct {
|
|
uint8_t apdu[HID_APDU_MAX_CMD];
|
|
uint8_t apdu_len;
|
|
uint8_t apdu_mask[HID_APDU_MASK_LEN];
|
|
uint8_t resp[HID_APDU_MAX_RESP];
|
|
uint8_t resp_len;
|
|
} PACKED hid_apdu_entry_t;
|
|
|
|
typedef struct {
|
|
uint8_t tagtype;
|
|
uint16_t flags;
|
|
uint8_t uid[10];
|
|
uint8_t exitAfter;
|
|
uint8_t atqa[2]; // big-endian: [0]=high byte, [1]=low byte
|
|
uint8_t sak;
|
|
uint8_t scp02_key[16]; // SCP02 master key (from JSON "SCP02Key")
|
|
uint8_t kdd[10]; // 10-byte Key Diversification Data (from JSON "KDD"); all-zero = no diversification
|
|
uint8_t kvn; // Key Version Number (from JSON "KVN", default 0x01)
|
|
uint8_t ats[20]; // ATS bytes without CRC (from JSON "ATS")
|
|
uint8_t ats_len; // actual number of valid bytes in ats[]
|
|
uint8_t default_resp[HID_APDU_MAX_RESP]; // fallback reply for unmatched APDUs (from JSON "DefaultResponse")
|
|
uint8_t default_resp_len; // 0 = none configured
|
|
uint8_t apdu_count;
|
|
hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES];
|
|
} PACKED hid_sim_payload_t;
|
|
|
|
// Hard guard: SendCommandNG silently drops any payload over PM3_CMD_DATA_SIZE.
|
|
_Static_assert(sizeof(hid_sim_payload_t) <= PM3_CMD_DATA_SIZE,
|
|
"hid_sim_payload_t exceeds PM3_CMD_DATA_SIZE; shrink HID_APDU_MAX_ENTRIES or HID_APDU_MAX_RESP");
|
|
|
|
// Must stay in sync with hid_sniff_payload_t in armsrc/secc.h.
|
|
#define HID_JAM_MAX_APDU 32
|
|
#define HID_JAM_MAX_RESP 32
|
|
|
|
typedef struct {
|
|
uint8_t param;
|
|
uint8_t apdu[HID_JAM_MAX_APDU]; // APDU to jam (0-length = default A0 D4 00 00 00)
|
|
uint8_t apdu_len;
|
|
uint8_t resp[HID_JAM_MAX_RESP]; // jam response payload (0-length = default 00 00 90 00)
|
|
uint8_t resp_len;
|
|
} PACKED hid_sniff_payload_t;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf secc cardinfo - BER-TLV / OID helpers (file-scope only)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Find the value of the first matching single-byte-tag TLV in buf[0..len).
|
|
// Returns a pointer to the value bytes and sets *vlen, or NULL on failure.
|
|
static const uint8_t *secc_tlv_find(const uint8_t *buf, size_t len, uint8_t tag, size_t *vlen) {
|
|
size_t i = 0;
|
|
while (i < len) {
|
|
uint8_t t = buf[i++];
|
|
if (i >= len) break;
|
|
uint8_t lb = buf[i++];
|
|
size_t l;
|
|
if (lb == 0x81) {
|
|
if (i >= len) break;
|
|
l = buf[i++];
|
|
} else if (lb == 0x82) {
|
|
if (i + 2 > len) break;
|
|
l = ((size_t)buf[i] << 8) | buf[i + 1];
|
|
i += 2;
|
|
} else {
|
|
l = lb;
|
|
}
|
|
if (i + l > len) break;
|
|
if (t == tag) {
|
|
*vlen = l;
|
|
return buf + i;
|
|
}
|
|
i += l;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Decode BER-encoded OID bytes into individual 32-bit arcs.
|
|
// Returns the number of arcs decoded (first two arcs are always decoded together).
|
|
static int secc_decode_oid(const uint8_t *p, size_t len, uint32_t *arcs, int max_arcs) {
|
|
if (len == 0 || max_arcs < 2) return 0;
|
|
arcs[0] = (uint32_t)(p[0] / 40);
|
|
arcs[1] = (uint32_t)(p[0] % 40);
|
|
int n = 2;
|
|
uint32_t acc = 0;
|
|
for (size_t i = 1; i < len; i++) {
|
|
acc = (acc << 7) | (uint32_t)(p[i] & 0x7F);
|
|
if (!(p[i] & 0x80)) {
|
|
if (n < max_arcs)
|
|
arcs[n++] = acc;
|
|
acc = 0;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Find the inner tag 0x06 (OID) within ctxbuf and decode its arcs.
|
|
static int secc_get_inner_oid(const uint8_t *ctxbuf, size_t ctxlen, uint32_t *arcs, int max_arcs) {
|
|
size_t oid_len = 0;
|
|
const uint8_t *oid = secc_tlv_find(ctxbuf, ctxlen, 0x06, &oid_len);
|
|
if (!oid || oid_len == 0) return 0;
|
|
return secc_decode_oid(oid, oid_len, arcs, max_arcs);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf secc cardinfo
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int CmdHFHIDConfigCardInfo(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf secc info",
|
|
"Read and decode Card Recognition Data from a GlobalPlatform card.\n"
|
|
"Sends GET DATA (80 CA 00 66 00) and parses the Card Recognition\n"
|
|
"Template (tag 73) to identify platform, SCP type, and chip family.",
|
|
"hf secc info");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
CLIParserFree(ctx);
|
|
|
|
// GET DATA: tag 0x0066 = Card Data (Card Recognition Template)
|
|
const uint8_t apdu[] = {0x80, 0xCA, 0x00, 0x66, 0x00};
|
|
uint8_t resp[256];
|
|
int resplen = 0;
|
|
|
|
int res = ExchangeAPDU14a(apdu, (int)sizeof(apdu), true, false, resp, (int)sizeof(resp), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to exchange APDU with card");
|
|
return res;
|
|
}
|
|
if (resplen < 2) {
|
|
PrintAndLogEx(ERR, "Response too short (%d byte(s))", resplen);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t sw1 = resp[resplen - 2];
|
|
uint8_t sw2 = resp[resplen - 1];
|
|
if (sw1 != 0x90 || sw2 != 0x00) {
|
|
PrintAndLogEx(ERR, "Card returned error SW %02X%02X", sw1, sw2);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Raw ... " _YELLOW_("%s"), sprint_hex_inrow(resp, resplen));
|
|
|
|
// Strip SW bytes before TLV parsing
|
|
size_t datalen = (size_t)resplen - 2;
|
|
const uint8_t *data = resp;
|
|
|
|
// Outer tag 0x66: Card Data
|
|
size_t tag66_len = 0;
|
|
const uint8_t *tag66 = secc_tlv_find(data, datalen, 0x66, &tag66_len);
|
|
if (!tag66) {
|
|
PrintAndLogEx(ERR, "Tag 66 (Card Data) not found in response");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// Inner tag 0x73: Card Recognition Data
|
|
size_t tag73_len = 0;
|
|
const uint8_t *tag73 = secc_tlv_find(tag66, tag66_len, 0x73, &tag73_len);
|
|
if (!tag73) {
|
|
PrintAndLogEx(ERR, "Tag 73 (Card Recognition Data) not found");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint32_t arcs[16];
|
|
int n;
|
|
char platform[80] = "Unknown";
|
|
char cardspec[80] = "Unknown";
|
|
char scp_str[80] = "Unknown";
|
|
char keystr[80] = "Unknown";
|
|
char challenge_str[80] = "Unknown";
|
|
char rmac_str[80] = "Unknown";
|
|
char chipfamily[128] = "Unknown";
|
|
|
|
// tag 0x60: Card Management Type and Version
|
|
// OID 1.2.840.114283.2.X.Y.Z -> GlobalPlatform X.Y.Z
|
|
size_t t60_len = 0;
|
|
const uint8_t *t60 = secc_tlv_find(tag73, tag73_len, 0x60, &t60_len);
|
|
if (t60) {
|
|
n = secc_get_inner_oid(t60, t60_len, arcs, 16);
|
|
if (n >= 7 && arcs[0] == 1 && arcs[1] == 2 && arcs[2] == 840 &&
|
|
arcs[3] == 114283 && arcs[4] == 2) {
|
|
if (n >= 8)
|
|
snprintf(cardspec, sizeof(cardspec), "GlobalPlatform %u.%u.%u.%u",
|
|
arcs[4], arcs[5], arcs[6], arcs[7]);
|
|
else
|
|
snprintf(cardspec, sizeof(cardspec), "GlobalPlatform %u.%u.%u",
|
|
arcs[4], arcs[5], arcs[6]);
|
|
}
|
|
}
|
|
|
|
// tag 0x64: Secure Channel Protocol
|
|
// OID 1.2.840.114283.4.SCP.i -> SCPxx, i=0xii
|
|
// SCP02 i-parameter bits (GP Card Spec):
|
|
// bit 0 (0x01): 1 = 3 Secure Channel Keys, 0 = 1 key
|
|
// bit 4 (0x10): 1 = pseudo-random card challenge, 0 = sequential counter
|
|
// bit 6 (0x40): 1 = R-MAC supported, 0 = not supported
|
|
size_t t64_len = 0;
|
|
const uint8_t *t64 = secc_tlv_find(tag73, tag73_len, 0x64, &t64_len);
|
|
if (t64) {
|
|
n = secc_get_inner_oid(t64, t64_len, arcs, 16);
|
|
if (n >= 7 && arcs[0] == 1 && arcs[1] == 2 && arcs[2] == 840 &&
|
|
arcs[3] == 114283 && arcs[4] == 4) {
|
|
uint32_t scp_type = arcs[5];
|
|
uint32_t i_param = arcs[6];
|
|
snprintf(scp_str, sizeof(scp_str), "SCP%02u, i=0x%02X", scp_type, i_param);
|
|
snprintf(keystr, sizeof(keystr), "%s",
|
|
(i_param & 0x01) ? "3 independent session keys" : "1 shared key");
|
|
snprintf(challenge_str, sizeof(challenge_str), "%s",
|
|
(i_param & 0x10) ? "Pseudo-random (RNG)" : "Sequential counter");
|
|
snprintf(rmac_str, sizeof(rmac_str), "%s",
|
|
(i_param & 0x40) ? "Supported but optional" : "Not supported");
|
|
}
|
|
}
|
|
|
|
// tag 0x65: Card Configuration Details
|
|
// OID 1.3.656.x.x -> NXP JCOP (proprietary arc under ISO identified-org)
|
|
size_t t65_len = 0;
|
|
const uint8_t *t65 = secc_tlv_find(tag73, tag73_len, 0x65, &t65_len);
|
|
if (t65) {
|
|
n = secc_get_inner_oid(t65, t65_len, arcs, 16);
|
|
if (n >= 3 && arcs[0] == 1 && arcs[1] == 3 && arcs[2] == 656)
|
|
snprintf(chipfamily, sizeof(chipfamily),
|
|
"NXP JCOP (tag 65 OID points to NXP/G+D tree)");
|
|
else if (n >= 2)
|
|
snprintf(chipfamily, sizeof(chipfamily), "Unknown (OID %u.%u...)",
|
|
arcs[0], arcs[1]);
|
|
}
|
|
|
|
// tag 0x66 (inner): Card/Chip Details
|
|
// OID 1.3.6.1.4.1.42.2.110.1.X -> Java Card Classic 2.X (Sun/Oracle OID space)
|
|
size_t t66i_len = 0;
|
|
const uint8_t *t66i = secc_tlv_find(tag73, tag73_len, 0x66, &t66i_len);
|
|
if (t66i) {
|
|
n = secc_get_inner_oid(t66i, t66i_len, arcs, 16);
|
|
if (n >= 11 && arcs[0] == 1 && arcs[1] == 3 && arcs[2] == 6 &&
|
|
arcs[3] == 1 && arcs[4] == 4 && arcs[5] == 1 &&
|
|
arcs[6] == 42 && arcs[7] == 2 && arcs[8] == 110 && arcs[9] == 1) {
|
|
snprintf(platform, sizeof(platform), "Java Card Classic 2.%u", arcs[10]);
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Card Recognition Data") " ---");
|
|
PrintAndLogEx(INFO, " %-20s %s", "Property", "Value");
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "Platform", platform);
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "Card Spec", cardspec);
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "Secure Channel", scp_str);
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "Key structure", keystr);
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "Card challenge", challenge_str);
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "R-MAC", rmac_str);
|
|
PrintAndLogEx(INFO, " %-20s " _YELLOW_("%s"), "Chip family", chipfamily);
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHelp(const char *Cmd);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf secc sim
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int CmdHFHIDConfigSim(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf secc sim",
|
|
"Simulate a HID iCLASS SE Config Card (JCOP / GlobalPlatform SCP02).\n"
|
|
"APDUs are matched against the JSON APDUResponses table; INITIALIZE UPDATE\n"
|
|
"and EXTERNAL AUTH are handled by the built-in SCP02 crypto. Anything else\n"
|
|
"falls through to the JSON DefaultResponse (or 9000 if none is set).",
|
|
"hf secc sim -f hidconfig_sample\n"
|
|
"hf secc sim -f hidconfig_sample -n 5 -> stop after 5 reader interactions");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "JSON file with UID, AID, SCP02Key, optional KDD/KVN (no .json ext)"),
|
|
arg_int0("n", "num", "<dec>", "Exit after <n> reader interactions. 0 = infinite"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
int filenamelen = sizeof(filename) - 1;
|
|
CLIGetStrWithReturn(ctx, 1, (uint8_t *)filename, &filenamelen);
|
|
uint8_t exitAfterNReads = (uint8_t)arg_get_int_def(ctx, 2, 0);
|
|
CLIParserFree(ctx);
|
|
|
|
// Load JSON from client/resources/
|
|
json_t *root = NULL;
|
|
if (loadFileJSONroot(filename, (void **)&root, false) != PM3_SUCCESS)
|
|
return PM3_EFILE;
|
|
|
|
// Parse UID
|
|
uint8_t uid[10] = {0};
|
|
size_t uidlen_sz = 0;
|
|
if (JsonLoadBufAsHex(root, "$.UID", uid, sizeof(uid), &uidlen_sz) != 0) {
|
|
PrintAndLogEx(ERR, "JSON missing or invalid 'UID' field");
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
int uidlen = (int)uidlen_sz;
|
|
if (uidlen != 4 && uidlen != 7 && uidlen != 10) {
|
|
PrintAndLogEx(ERR, "UID must be 4, 7, or 10 bytes (got %d)", uidlen);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Parse AID (informational only)
|
|
char aid_str[32] = {0};
|
|
json_t *jaid = json_object_get(root, "AID");
|
|
if (json_is_string(jaid))
|
|
snprintf(aid_str, sizeof(aid_str), "%s", json_string_value(jaid));
|
|
|
|
// Parse SCP02Key (16 bytes)
|
|
uint8_t scp02_key[16] = {0};
|
|
size_t scp02_len = 0;
|
|
if (JsonLoadBufAsHex(root, "$.SCP02Key", scp02_key, sizeof(scp02_key), &scp02_len) != 0 || scp02_len != 16) {
|
|
PrintAndLogEx(ERR, "JSON missing or invalid 'SCP02Key' field (must be 16 bytes)");
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Parse optional KDD (10 bytes). If present, ARM diversifies SCP02Key
|
|
// per-card via VISA-2 on each handshake. If absent, all-zero is sent and
|
|
// ARM uses SCP02Key directly (legacy "no diversification" mode).
|
|
uint8_t kdd[10] = {0};
|
|
if (json_object_get(root, "KDD") != NULL) {
|
|
size_t kdd_len = 0;
|
|
if (JsonLoadBufAsHex(root, "$.KDD", kdd, sizeof(kdd), &kdd_len) != 0 || kdd_len != 10) {
|
|
PrintAndLogEx(ERR, "JSON 'KDD' field invalid (must be 10 bytes)");
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
// Parse optional KVN (Key Version Number, 1 byte). Default 0x01 matches
|
|
// the GP factory key set on most JCOP-based config cards.
|
|
uint8_t kvn = 0x01;
|
|
if (json_object_get(root, "KVN") != NULL) {
|
|
uint8_t kvn_buf[1] = {0};
|
|
size_t kvn_len = 0;
|
|
if (JsonLoadBufAsHex(root, "$.KVN", kvn_buf, sizeof(kvn_buf), &kvn_len) != 0 || kvn_len != 1) {
|
|
PrintAndLogEx(ERR, "JSON 'KVN' field invalid (must be 1 byte)");
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
kvn = kvn_buf[0];
|
|
}
|
|
|
|
// Parse ATS (1-20 bytes, without CRC)
|
|
uint8_t ats[20] = {0};
|
|
size_t ats_len_sz = 0;
|
|
if (JsonLoadBufAsHex(root, "$.ATS", ats, sizeof(ats), &ats_len_sz) != 0 || ats_len_sz == 0) {
|
|
PrintAndLogEx(ERR, "JSON missing or invalid 'ATS' field (must be 1-20 bytes)");
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
int ats_len = (int)ats_len_sz;
|
|
|
|
// Parse optional DefaultResponse: fallback reply for any APDU not matched
|
|
// by the APDUResponses table or by hardcoded handlers. If absent, the
|
|
// simulator will fall back to the legacy "90 00" reply.
|
|
uint8_t default_resp[HID_APDU_MAX_RESP] = {0};
|
|
size_t default_resp_len_sz = 0;
|
|
bool has_default_resp = false;
|
|
if (json_object_get(root, "DefaultResponse") != NULL) {
|
|
if (JsonLoadBufAsHex(root, "$.DefaultResponse", default_resp,
|
|
sizeof(default_resp), &default_resp_len_sz) != 0
|
|
|| default_resp_len_sz == 0) {
|
|
PrintAndLogEx(ERR, "JSON 'DefaultResponse' field invalid (must be 1-%d hex bytes)",
|
|
HID_APDU_MAX_RESP);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
has_default_resp = true;
|
|
}
|
|
|
|
// Parse optional APDUResponses array
|
|
hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES];
|
|
uint8_t apdu_count = 0;
|
|
memset(apdu_table, 0, sizeof(apdu_table));
|
|
|
|
json_t *jresps = json_object_get(root, "APDUResponses");
|
|
if (json_is_array(jresps)) {
|
|
size_t n = json_array_size(jresps);
|
|
for (size_t i = 0; i < n && apdu_count < HID_APDU_MAX_ENTRIES; i++) {
|
|
json_t *entry = json_array_get(jresps, i);
|
|
json_t *japdu = json_object_get(entry, "APDU");
|
|
json_t *jresp = json_object_get(entry, "Response");
|
|
if (!json_is_string(japdu) || !json_is_string(jresp))
|
|
continue;
|
|
|
|
// Parse APDU hex string with optional "**" wildcard bytes.
|
|
// mask bit i=1 → exact match; bit i=0 → wildcard.
|
|
const char *apdu_str = json_string_value(japdu);
|
|
int alen = 0;
|
|
bool apdu_ok = true;
|
|
memset(apdu_table[apdu_count].apdu_mask, 0xFF, HID_APDU_MASK_LEN);
|
|
while (*apdu_str && alen < HID_APDU_MAX_CMD) {
|
|
char hi = apdu_str[0];
|
|
char lo = apdu_str[1];
|
|
if (lo == '\0') { apdu_ok = false; break; }
|
|
apdu_str += 2;
|
|
if (hi == '*' && lo == '*') {
|
|
apdu_table[apdu_count].apdu[alen] = 0x00; // ** wildcard
|
|
apdu_table[apdu_count].apdu_mask[alen / 8] &= ~(1u << (alen % 8));
|
|
} else if (hi == '#' && lo == '#') {
|
|
apdu_table[apdu_count].apdu[alen] = 0x01; // ## length-prefix skip
|
|
apdu_table[apdu_count].apdu_mask[alen / 8] &= ~(1u << (alen % 8));
|
|
} else {
|
|
uint8_t val = 0;
|
|
for (int nb = 0; nb < 2; nb++) {
|
|
char c = (nb == 0) ? hi : lo;
|
|
uint8_t nib;
|
|
if (c >= '0' && c <= '9') nib = c - '0';
|
|
else if (c >= 'A' && c <= 'F') nib = c - 'A' + 10;
|
|
else if (c >= 'a' && c <= 'f') nib = c - 'a' + 10;
|
|
else { apdu_ok = false; break; }
|
|
val = (val << 4) | nib;
|
|
}
|
|
if (!apdu_ok) break;
|
|
apdu_table[apdu_count].apdu[alen] = val;
|
|
}
|
|
alen++;
|
|
}
|
|
|
|
int rlen = hex_to_bytes(json_string_value(jresp),
|
|
apdu_table[apdu_count].resp, HID_APDU_MAX_RESP);
|
|
if (!apdu_ok || alen <= 0 || rlen <= 0) {
|
|
PrintAndLogEx(WARNING, "APDUResponses[%zu]: invalid hex, skipping", i);
|
|
continue;
|
|
}
|
|
apdu_table[apdu_count].apdu_len = (uint8_t)alen;
|
|
apdu_table[apdu_count].resp_len = (uint8_t)rlen;
|
|
PrintAndLogEx(INFO, "APDU override [%u]: %s -> %s",
|
|
apdu_count, json_string_value(japdu), json_string_value(jresp));
|
|
apdu_count++;
|
|
}
|
|
}
|
|
|
|
json_decref(root);
|
|
|
|
uint16_t flags = 0;
|
|
FLAG_SET_UID_IN_DATA(flags, uidlen);
|
|
|
|
// sprint_hex_inrow uses a single static buffer; snapshot the UID string
|
|
// before calling it again for the default response.
|
|
char uid_str[2 * sizeof(uid) + 1];
|
|
strncpy(uid_str, sprint_hex_inrow(uid, uidlen), sizeof(uid_str) - 1);
|
|
uid_str[sizeof(uid_str) - 1] = '\0';
|
|
PrintAndLogEx(INFO, "HID Config Card sim:"
|
|
" UID " _YELLOW_("%s")
|
|
" AID " _YELLOW_("%s")
|
|
" ATS len " _YELLOW_("%d")
|
|
" APDU overrides " _YELLOW_("%u")
|
|
" default resp " _YELLOW_("%s"),
|
|
uid_str, aid_str, ats_len, apdu_count,
|
|
has_default_resp ? sprint_hex_inrow(default_resp, default_resp_len_sz) : "9000 (builtin)");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to abort simulation");
|
|
|
|
hid_sim_payload_t payload;
|
|
memset(&payload, 0, sizeof(payload));
|
|
payload.tagtype = 4; // ISO14443-4 base type; ATQA/SAK/ATS overridden by ARM
|
|
payload.flags = flags;
|
|
payload.exitAfter = exitAfterNReads;
|
|
payload.atqa[0] = 0x02; // HID Config Card ATQA high byte
|
|
payload.atqa[1] = 0x00; // HID Config Card ATQA low byte
|
|
payload.sak = 0x38; // HID Config Card SAK
|
|
payload.ats_len = (uint8_t)ats_len;
|
|
payload.kvn = kvn;
|
|
payload.default_resp_len = has_default_resp ? (uint8_t)default_resp_len_sz : 0;
|
|
payload.apdu_count = apdu_count;
|
|
memcpy(payload.uid, uid, uidlen);
|
|
memcpy(payload.scp02_key, scp02_key, sizeof(scp02_key));
|
|
memcpy(payload.kdd, kdd, sizeof(kdd));
|
|
memcpy(payload.ats, ats, ats_len);
|
|
if (has_default_resp)
|
|
memcpy(payload.default_resp, default_resp, default_resp_len_sz);
|
|
memcpy(payload.apdu_table, apdu_table, apdu_count * sizeof(hid_apdu_entry_t));
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_HIDCONFIG_SIM, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
PacketResponseNG resp = {0};
|
|
bool keypress = kbd_enter_pressed();
|
|
while (keypress == false) {
|
|
keypress = kbd_enter_pressed();
|
|
// Any response means the device finished (button press or exitAfter reached).
|
|
if (WaitForResponseTimeout(CMD_HF_HIDCONFIG_SIM, &resp, 1500))
|
|
break;
|
|
}
|
|
if (keypress) {
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
WaitForResponse(CMD_HF_HIDCONFIG_SIM, &resp);
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf secc sniff
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int CmdHFHIDConfigSniff(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf secc sniff",
|
|
"Sniff the communication between a HID Config Card reader and card.\n"
|
|
"Use `hf seos list` to view collected data.\n"
|
|
"With -j and no -d, jams responses to APDU A0 D4 00 00 00.\n"
|
|
"With -j -d <hex> jams responses to the specified APDU.\n"
|
|
"Use -r <hex> to override the jam response payload (default: 00009000).",
|
|
"hf secc sniff\n"
|
|
"hf secc sniff -j -> jam A0 D4 00 00 00, respond 00 00 90 00\n"
|
|
"hf secc sniff -j -d A0D4000000 -> same, APDU specified explicitly\n"
|
|
"hf secc sniff -j -d A0D4000000 -r 9000 -> jam A0D4000000, respond 90 00\n"
|
|
"hf secc sniff -c -i -> trigger on card data, interactive");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("c", "card", "triggered by first data from card"),
|
|
arg_lit0("r", "reader", "triggered by first 7-bit request from reader (REQ, WUP)"),
|
|
arg_lit0("i", "interactive", "console will not be returned until sniff finishes or is aborted"),
|
|
arg_lit0("j", "jam", "jam responses to a specific APDU (see -d/-a)"),
|
|
arg_str0("d", "apdu", "<hex>", "APDU bytes to jam (default: A0D4000000)"),
|
|
arg_str0("a", "resp", "<hex>", "response payload when jamming (default: 00009000)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
uint8_t param = 0;
|
|
|
|
if (arg_get_lit(ctx, 1))
|
|
param |= 0x01;
|
|
|
|
if (arg_get_lit(ctx, 2))
|
|
param |= 0x02;
|
|
|
|
bool interactive = arg_get_lit(ctx, 3);
|
|
bool jam = arg_get_lit(ctx, 4);
|
|
|
|
uint8_t apdu_buf[HID_JAM_MAX_APDU] = {0};
|
|
int apdu_buf_len = 0;
|
|
if (CLIParamHexToBuf(arg_get_str(ctx, 5), apdu_buf, sizeof(apdu_buf), &apdu_buf_len)) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t resp_buf[HID_JAM_MAX_RESP] = {0};
|
|
int resp_buf_len = 0;
|
|
if (CLIParamHexToBuf(arg_get_str(ctx, 6), resp_buf, sizeof(resp_buf), &resp_buf_len)) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool has_apdu = (apdu_buf_len > 0);
|
|
bool has_resp = (resp_buf_len > 0);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
// -d and -r only make sense with -j
|
|
if ((has_apdu || has_resp) && !jam) {
|
|
PrintAndLogEx(ERR, "-d and -r require -j (jam mode)");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (jam) {
|
|
param |= 0x04;
|
|
// sprint_hex_inrow uses a single static buffer; copy the APDU string before
|
|
// calling it again for the response.
|
|
char apdu_str[HID_JAM_MAX_APDU * 2 + 1];
|
|
strncpy(apdu_str,
|
|
has_apdu ? sprint_hex_inrow(apdu_buf, apdu_buf_len) : "A0D4000000",
|
|
sizeof(apdu_str) - 1);
|
|
apdu_str[sizeof(apdu_str) - 1] = '\0';
|
|
PrintAndLogEx(INFO, "Sniff with jam of APDU " _YELLOW_("%s") " -> " _YELLOW_("%s"),
|
|
apdu_str,
|
|
has_resp ? sprint_hex_inrow(resp_buf, resp_buf_len) : "00009000");
|
|
}
|
|
|
|
uint16_t sniff_cmd = jam ? CMD_HF_HIDCONFIG_SNIFF : CMD_HF_ISO14443A_SNIFF;
|
|
|
|
if (jam) {
|
|
hid_sniff_payload_t payload;
|
|
memset(&payload, 0, sizeof(payload));
|
|
payload.param = param;
|
|
if (has_apdu) {
|
|
memcpy(payload.apdu, apdu_buf, apdu_buf_len);
|
|
payload.apdu_len = (uint8_t)apdu_buf_len;
|
|
}
|
|
if (has_resp) {
|
|
memcpy(payload.resp, resp_buf, resp_buf_len);
|
|
payload.resp_len = (uint8_t)resp_buf_len;
|
|
}
|
|
clearCommandBuffer();
|
|
SendCommandNG(sniff_cmd, (uint8_t *)&payload, sizeof(payload));
|
|
} else {
|
|
clearCommandBuffer();
|
|
SendCommandNG(sniff_cmd, (uint8_t *)¶m, sizeof(uint8_t));
|
|
}
|
|
|
|
if (interactive) {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to abort sniffing");
|
|
|
|
PacketResponseNG resp;
|
|
bool keypress = kbd_enter_pressed();
|
|
while (keypress == false) {
|
|
keypress = kbd_enter_pressed();
|
|
if (WaitForResponseTimeout(sniff_cmd, &resp, 500))
|
|
break;
|
|
}
|
|
|
|
if (keypress) {
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
WaitForResponse(sniff_cmd, &resp);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf seos list") "` to view captured tracelog");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("trace save -h") "` to save tracelog for later analysing");
|
|
} else {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort sniffing");
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Command table
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static command_t CommandTable[];
|
|
|
|
static int CmdHelp(const char *Cmd) {
|
|
(void)Cmd;
|
|
CmdsHelp(CommandTable);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static command_t CommandTable[] = {
|
|
{"--------", CmdHelp, AlwaysAvailable, "----------- " _CYAN_("HID Config Card") " -----------"},
|
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
|
{"info", CmdHFHIDConfigCardInfo, IfPm3Iso14443a, "Read and decode Card Recognition Data (GP tag 0066)"},
|
|
{"sim", CmdHFHIDConfigSim, IfPm3Iso14443a, "Simulate HID iCLASS SE Config Card"},
|
|
{"sniff", CmdHFHIDConfigSniff, IfPm3Iso14443a, "Sniff reader<->card, jam A0 D4 APDU"},
|
|
{NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
int CmdHFHIDConfig(const char *Cmd) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(CommandTable, Cmd);
|
|
}
|