diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e00165d27..80dd566e3 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1853,7 +1853,7 @@ static void PacketReceived(PacketCommandNG *packet) { break; } case CMD_HF_HIDCONFIG_SNIFF: { - SniffHIDConfigCard(packet->data.asBytes[0]); + SniffHIDConfigCard((const hid_sniff_payload_t *)packet->data.asBytes); reply_ng(CMD_HF_HIDCONFIG_SNIFF, PM3_SUCCESS, NULL, 0); break; } diff --git a/armsrc/secc.c b/armsrc/secc.c index 60d06ce2d..7ce89ef99 100644 --- a/armsrc/secc.c +++ b/armsrc/secc.c @@ -52,6 +52,16 @@ static hid_apdu_entry_t s_apdu_table[HID_APDU_MAX_ENTRIES]; static uint8_t s_apdu_count = 0; static uint8_t s_scp02_key[16] = {0}; +// Jam config — set by SniffHIDConfigCard before entering sniff loop. +// Length 0 means "use built-in default". +static uint8_t s_jam_apdu[HID_JAM_MAX_APDU]; +static uint8_t s_jam_apdu_len = 0; +static uint8_t s_jam_resp[HID_JAM_MAX_RESP]; +static uint8_t s_jam_resp_len = 0; + +static const uint8_t s_jam_apdu_default[5] = {0xA0, 0xD4, 0x00, 0x00, 0x00}; +static const uint8_t s_jam_resp_default[4] = {0x00, 0x00, 0x90, 0x00}; + void hid_config_card_set_apdu_table(const hid_apdu_entry_t *table, uint8_t count) { s_apdu_count = (count > HID_APDU_MAX_ENTRIES) ? HID_APDU_MAX_ENTRIES : count; memcpy(s_apdu_table, table, s_apdu_count * sizeof(hid_apdu_entry_t)); @@ -226,22 +236,25 @@ bool hid_config_card_jam(const uint8_t *cmd, int len, uint8_t *dma_buf) { int off = (pcb & 0x08) ? 2 : 1; // skip CID if present - if (len < off + 7) + // Select active APDU pattern and response (custom or default) + const uint8_t *match = (s_jam_apdu_len > 0) ? s_jam_apdu : s_jam_apdu_default; + int match_len = (s_jam_apdu_len > 0) ? (int)s_jam_apdu_len : (int)sizeof(s_jam_apdu_default); + const uint8_t *resp_data = (s_jam_resp_len > 0) ? s_jam_resp : s_jam_resp_default; + int resp_data_len = (s_jam_resp_len > 0) ? (int)s_jam_resp_len : (int)sizeof(s_jam_resp_default); + + if (len < off + match_len + 2) // off + APDU pattern + 2-byte CRC return false; - if (cmd[off] != 0xA0 || cmd[off + 1] != 0xD4 || - cmd[off + 2] != 0x00 || cmd[off + 3] != 0x00 || cmd[off + 4] != 0x00) + if (memcmp(cmd + off, match, match_len) != 0) return false; - // Build jam response: PCB [CID] 00 00 90 00 CRC CRC - uint8_t resp[8]; + // Build jam response: PCB [CID] CRC CRC + uint8_t resp[2 + HID_JAM_MAX_RESP + 2]; // PCB + optional CID + payload + CRC int rlen = 0; resp[rlen++] = pcb; if (pcb & 0x08) resp[rlen++] = cmd[1]; // mirror CID - resp[rlen++] = 0x00; - resp[rlen++] = 0x00; - resp[rlen++] = 0x90; - resp[rlen++] = 0x00; + memcpy(resp + rlen, resp_data, resp_data_len); + rlen += resp_data_len; AddCrc14A(resp, rlen); rlen += 2; @@ -464,9 +477,22 @@ void SimulateHIDConfigCard(const hid_sim_payload_t *payload) { // HID Config Card sniff with optional A0 D4 jamming // --------------------------------------------------------------------------- -void SniffHIDConfigCard(uint8_t param) { - // Delegate entirely to SniffIso14443a. +void SniffHIDConfigCard(const hid_sniff_payload_t *payload) { + // Configure jam pattern and response before entering the sniff loop. + if (payload->apdu_len > 0 && payload->apdu_len <= HID_JAM_MAX_APDU) { + memcpy(s_jam_apdu, payload->apdu, payload->apdu_len); + s_jam_apdu_len = payload->apdu_len; + } else { + s_jam_apdu_len = 0; // use default A0 D4 00 00 00 + } + if (payload->resp_len > 0 && payload->resp_len <= HID_JAM_MAX_RESP) { + memcpy(s_jam_resp, payload->resp, payload->resp_len); + s_jam_resp_len = payload->resp_len; + } else { + s_jam_resp_len = 0; // use default 00 00 90 00 + } + // Delegate to SniffIso14443a. // When param bit 0x04 is set, SniffIso14443a calls hid_config_card_jam() // inline after each decoded reader frame (see iso14443a.c). - SniffIso14443a(param); + SniffIso14443a(payload->param); } diff --git a/armsrc/secc.h b/armsrc/secc.h index cead53bc3..66a7b0a2e 100644 --- a/armsrc/secc.h +++ b/armsrc/secc.h @@ -61,14 +61,33 @@ typedef struct { hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES]; } PACKED hid_sim_payload_t; +// --------------------------------------------------------------------------- +// Sniff payload (sent from client to ARM via CMD_HF_HIDCONFIG_SNIFF). +// When apdu_len == 0 the default A0 D4 00 00 00 pattern is used. +// When resp_len == 0 the default 00 00 90 00 response is used. +// --------------------------------------------------------------------------- + +#define HID_JAM_MAX_APDU 32 +#define HID_JAM_MAX_RESP 32 + +typedef struct { + uint8_t param; // sniff flags: 0x01=card-triggered, 0x02=reader-triggered, 0x04=jam + uint8_t apdu[HID_JAM_MAX_APDU]; // APDU to jam (raw bytes after PCB/CID stripped) + uint8_t apdu_len; // 0 = use default (A0 D4 00 00 00) + uint8_t resp[HID_JAM_MAX_RESP]; // jam response payload (raw APDU, without PCB/CID/CRC) + uint8_t resp_len; // 0 = use default (00 00 90 00) +} PACKED hid_sniff_payload_t; + // Load a custom APDU response table into static storage (call before sim loop). void hid_config_card_set_apdu_table(const hid_apdu_entry_t *table, uint8_t count); // Run a complete HID Config Card simulation using payload received from the client. void SimulateHIDConfigCard(const hid_sim_payload_t *payload); -// Sniff ISO 14443-A with optional jamming of A0 D4 00 00 00 (param bit 0x04). -void SniffHIDConfigCard(uint8_t param); +// Sniff ISO 14443-A with optional jamming (param bit 0x04). +// When apdu_len > 0 the supplied APDU pattern overrides the default A0 D4 00 00 00. +// When resp_len > 0 the supplied response overrides the default 00 00 90 00. +void SniffHIDConfigCard(const hid_sniff_payload_t *payload); // Handle an I-block received during HID Config Card (tagType=16) simulation. // Fills dynamic_response_info with the appropriate response payload (without CRC). diff --git a/client/resources/hidconfig_sample.json b/client/resources/hidconfig_sample.json index e10fb7119..2f1fcdd73 100644 --- a/client/resources/hidconfig_sample.json +++ b/client/resources/hidconfig_sample.json @@ -20,8 +20,8 @@ "Response": "00009000" }, { - "_comment": "Busy? -> 9000 (done) / 017B (busy)", - "APDU": "017B", + "_comment": "Busy? -> 9000 (done) / 01 (busy)", + "APDU": "01", "Response": "9000" } ] diff --git a/client/src/cmdhfsecc.c b/client/src/cmdhfsecc.c index 1507b3514..5b0c6a67c 100644 --- a/client/src/cmdhfsecc.c +++ b/client/src/cmdhfsecc.c @@ -6,11 +6,11 @@ #include "cliparser.h" // CLIParser* #include "comms.h" // SendCommandNG, WaitForResponseTimeout, clearCommandBuffer #include "ui.h" // PrintAndLogEx -#include "util.h" // kbd_enter_pressed, hex_to_bytes +#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_load_file, json_object_get, json_string_value, json_decref -#include "fileutils.h" // searchFile, RESOURCES_SUBDIR +#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 @@ -44,6 +44,18 @@ typedef struct { hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES]; } PACKED hid_sim_payload_t; +// 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; + static int CmdHelp(const char *Cmd); // --------------------------------------------------------------------------- @@ -74,30 +86,19 @@ static int CmdHFHIDConfigSim(const char *Cmd) { CLIParserFree(ctx); // Load JSON from client/resources/ - char *filepath = NULL; - if (searchFile(&filepath, RESOURCES_SUBDIR, filename, ".json", false) != PM3_SUCCESS) { - PrintAndLogEx(ERR, "JSON file '%s.json' not found in resources directory", filename); + json_t *root = NULL; + if (loadFileJSONroot(filename, (void **)&root, false) != PM3_SUCCESS) return PM3_EFILE; - } - - json_error_t jerr; - json_t *root = json_load_file(filepath, 0, &jerr); - free(filepath); - if (root == NULL) { - PrintAndLogEx(ERR, "Failed to load JSON file '%s.json': %s", filename, jerr.text); - return PM3_EFILE; - } // Parse UID uint8_t uid[10] = {0}; - int uidlen = 0; - json_t *juid = json_object_get(root, "UID"); - if (json_is_string(juid) == false) { + 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; } - uidlen = hex_to_bytes(json_string_value(juid), uid, sizeof(uid)); + 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); @@ -112,33 +113,22 @@ static int CmdHFHIDConfigSim(const char *Cmd) { // Parse SCP02Key (16 bytes) uint8_t scp02_key[16] = {0}; - json_t *jkey = json_object_get(root, "SCP02Key"); - if (json_is_string(jkey) == false) { - PrintAndLogEx(ERR, "JSON missing or invalid 'SCP02Key' field"); - json_decref(root); - return PM3_EINVARG; - } - if (hex_to_bytes(json_string_value(jkey), scp02_key, sizeof(scp02_key)) != 16) { - PrintAndLogEx(ERR, "SCP02Key must be exactly 16 bytes (32 hex chars)"); + 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 ATS (1-20 bytes, without CRC) uint8_t ats[20] = {0}; - int ats_len = 0; - json_t *jats = json_object_get(root, "ATS"); - if (json_is_string(jats) == false) { - PrintAndLogEx(ERR, "JSON missing or invalid 'ATS' field"); - json_decref(root); - return PM3_EINVARG; - } - ats_len = hex_to_bytes(json_string_value(jats), ats, sizeof(ats)); - if (ats_len <= 0 || ats_len > 20) { - PrintAndLogEx(ERR, "ATS must be 1-20 bytes (got %d)", ats_len); + 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 APDUResponses array hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES]; @@ -208,16 +198,12 @@ static int CmdHFHIDConfigSim(const char *Cmd) { uint16_t flags = 0; FLAG_SET_UID_IN_DATA(flags, uidlen); - char uid_str[21] = {0}; - for (int i = 0; i < uidlen; i++) - snprintf(uid_str + i * 2, sizeof(uid_str) - i * 2, "%02X", uid[i]); - PrintAndLogEx(INFO, "HID Config Card sim:" " UID " _YELLOW_("%s") " AID " _YELLOW_("%s") " ATS len " _YELLOW_("%d") " APDU overrides " _YELLOW_("%u"), - uid_str, aid_str, ats_len, apdu_count); + sprint_hex_inrow(uid, uidlen), aid_str, ats_len, apdu_count); PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " to abort simulation"); hid_sim_payload_t payload; @@ -239,15 +225,16 @@ static int CmdHFHIDConfigSim(const char *Cmd) { SendCommandNG(CMD_HF_HIDCONFIG_SIM, (uint8_t *)&payload, sizeof(payload)); PacketResponseNG resp = {0}; - while (true) { - if (WaitForResponseTimeout(CMD_HF_HIDCONFIG_SIM, &resp, 1500)) { - if (resp.status != PM3_SUCCESS) - break; - } - if (kbd_enter_pressed()) { - SendCommandNG(CMD_BREAK_LOOP, NULL, 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; } @@ -260,17 +247,24 @@ 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 14a list` to view collected data.", + "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 jams responses to the specified APDU.\n" + "Use -r 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 -c -r -> trigger on card or reader data"); + "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 APDU A0 D4 00 00 00, respond with 00 00 90 00"), + arg_lit0("j", "jam", "jam responses to a specific APDU (see -d/-a)"), + arg_str0("d", "apdu", "", "APDU bytes to jam (default: A0D4000000)"), + arg_str0("a", "resp", "", "response payload when jamming (default: 00009000)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -285,17 +279,66 @@ static int CmdHFHIDConfigSniff(const char *Cmd) { 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; - PrintAndLogEx(INFO, "Sniff with jam of APDU " _YELLOW_("A0 D4 00 00 00") " -> " _YELLOW_("00 00 90 00")); + // 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; - clearCommandBuffer(); - SendCommandNG(sniff_cmd, (uint8_t *)¶m, sizeof(uint8_t)); + 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_("") " to abort sniffing"); @@ -314,7 +357,7 @@ static int CmdHFHIDConfigSniff(const char *Cmd) { } PrintAndLogEx(INFO, "Done!"); - PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf 14a list") "` to view captured tracelog"); + 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");