diff --git a/armsrc/secc.c b/armsrc/secc.c index 5169a555e..cf0a86914 100644 --- a/armsrc/secc.c +++ b/armsrc/secc.c @@ -52,6 +52,11 @@ 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}; +// Default response for unmatched APDUs (loaded from JSON "DefaultResponse"). +// When s_default_resp_len == 0 the handler falls back to the legacy 90 00 reply. +static uint8_t s_default_resp[HID_APDU_MAX_RESP] = {0}; +static uint8_t s_default_resp_len = 0; + // SCP02 session state — updated on each INITIALIZE UPDATE. static uint16_t s_seq_counter = 0; static uint8_t s_host_challenge[8] = {0}; @@ -71,6 +76,12 @@ void hid_config_card_set_apdu_table(const hid_apdu_entry_t *table, uint8_t count memcpy(s_apdu_table, table, s_apdu_count * sizeof(hid_apdu_entry_t)); } +static void hid_config_card_set_default_resp(const uint8_t *resp, uint8_t len) { + s_default_resp_len = (len > HID_APDU_MAX_RESP) ? HID_APDU_MAX_RESP : len; + if (s_default_resp_len) + memcpy(s_default_resp, resp, s_default_resp_len); +} + static void hid_config_card_set_scp02_key(const uint8_t *key) { memcpy(s_scp02_key, key, 16); s_seq_counter = 0; @@ -227,37 +238,9 @@ bool hid_config_card_handle_iblock(const uint8_t *cmd, int len, tag_response_inf } } - // ----- SELECT AID (INS=0xA4, P1=0x04) ----- - // no-CID frame: off=1, INS at cmd[2], Lc at cmd[4+1]=cmd[5], AID[6] at cmd[1+5+6]=cmd[12] - // CID frame: off=2, INS at cmd[3], Lc at cmd[3+3]=cmd[6], AID[6] at cmd[2+5+6]=cmd[13] - if (!has_cid && len >= 17 && cmd[2] == 0xA4 && cmd[3] == 0x04 && cmd[5] == 0x0A) { - if (cmd[12] == 0x17) { - rsp[0] = 0x6A; rsp[1] = 0x82; // File Not Found - } else { - rsp[0] = 0x90; rsp[1] = 0x00; - } - ri->response_n = off + 2; - return true; - } - - if (has_cid && len >= 18 && cmd[3] == 0xA4 && cmd[4] == 0x04 && cmd[6] == 0x0A) { - if (cmd[13] == 0x17) { - rsp[0] = 0x6A; rsp[1] = 0x82; // File Not Found - } else { - rsp[0] = 0x90; rsp[1] = 0x00; - } - ri->response_n = off + 2; - return true; - } - - // ----- A0 D4 00 00 00 (HID proprietary) ----- - if (has_cid && len == 9 && - cmd[2] == 0xA0 && cmd[3] == 0xD4 && - cmd[4] == 0x00 && cmd[5] == 0x00 && cmd[6] == 0x00) { - rsp[0] = 0x00; rsp[1] = 0x00; rsp[2] = 0x90; rsp[3] = 0x00; - ri->response_n = off + 4; - return true; - } + // SELECT AID, A0 D4, and other generic APDUs are now handled exclusively + // by the JSON APDUResponses table (above) and the DefaultResponse + // fall-through (below). Only SCP02 crypto handlers stay hardcoded. // ----- INITIALIZE UPDATE (INS=0x50) ----- // CID frame: INS at cmd[3], host challenge at cmd[off+5] (after CLA INS P1 P2 Lc) @@ -308,9 +291,14 @@ bool hid_config_card_handle_iblock(const uint8_t *cmd, int len, tag_response_inf return true; } - // ----- All other APDUs: generic 90 00 ----- - rsp[0] = 0x90; rsp[1] = 0x00; - ri->response_n = off + 2; + // ----- All other APDUs: configured DefaultResponse, or 90 00 if none ----- + if (s_default_resp_len > 0) { + memcpy(rsp, s_default_resp, s_default_resp_len); + ri->response_n = off + s_default_resp_len; + } else { + rsp[0] = 0x90; rsp[1] = 0x00; + ri->response_n = off + 2; + } return true; } @@ -448,6 +436,7 @@ int hid_config_card_iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chainin void SimulateHIDConfigCard(const hid_sim_payload_t *payload) { hid_config_card_set_apdu_table(payload->apdu_table, payload->apdu_count); + hid_config_card_set_default_resp(payload->default_resp, payload->default_resp_len); hid_config_card_set_scp02_key(payload->scp02_key); // Command buffers diff --git a/armsrc/secc.h b/armsrc/secc.h index 66a7b0a2e..27d448e9d 100644 --- a/armsrc/secc.h +++ b/armsrc/secc.h @@ -26,7 +26,10 @@ // Shared payload structs (used by both ARM and client via CMD_HF_HIDCONFIG_SIM) // --------------------------------------------------------------------------- -#define HID_APDU_MAX_ENTRIES 8 +// Sized so the full hid_sim_payload_t (including default_resp[]) stays within +// PM3_CMD_DATA_SIZE (512). Adding/removing fields here requires re-checking +// sizeof(hid_sim_payload_t) against the NG transport limit. +#define HID_APDU_MAX_ENTRIES 7 #define HID_APDU_MAX_CMD 20 // max APDU command bytes to prefix-match #define HID_APDU_MAX_RESP 32 // max response bytes (without PCB/CID/CRC) #define HID_APDU_MASK_LEN 3 // ceil(HID_APDU_MAX_CMD / 8): bitmask for wildcard bytes @@ -57,6 +60,8 @@ typedef struct { uint8_t scp02_key[16]; // SCP02 master key (from JSON "SCP02Key") 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 (handler will skip the fallback) uint8_t apdu_count; hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES]; } PACKED hid_sim_payload_t; diff --git a/client/resources/hidconfig_sample.json b/client/resources/hidconfig_sample.json index 2f1fcdd73..6f33bde3c 100644 --- a/client/resources/hidconfig_sample.json +++ b/client/resources/hidconfig_sample.json @@ -3,6 +3,7 @@ "AID": "A0000003820013000101", "SCP02Key": "404142434445464748494A4B4C4D4E4F", "ATS": "1478F7B10280590180415254454346477300011B", + "DefaultResponse": "6A82", "APDUResponses": [ { "_comment": "Prefix-match SELECT by AID (A000000382001700010100) -> 9000 (ok) / 6A82 (file not found)", diff --git a/client/src/cmdhfsecc.c b/client/src/cmdhfsecc.c index b19e39afd..c85649eb0 100644 --- a/client/src/cmdhfsecc.c +++ b/client/src/cmdhfsecc.c @@ -17,7 +17,9 @@ // Must stay in sync with hid_apdu_entry_t / hid_sim_payload_t. // --------------------------------------------------------------------------- -#define HID_APDU_MAX_ENTRIES 8 +// 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) @@ -40,10 +42,16 @@ typedef struct { uint8_t scp02_key[16]; // SCP02 master key (from JSON "SCP02Key") 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 @@ -282,8 +290,9 @@ static int CmdHFHIDConfigSim(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf secc sim", "Simulate a HID iCLASS SE Config Card (JCOP / GlobalPlatform SCP02).\n" - "Responds to SELECT AID (0013/0017), A0 D4, INITIALIZE UPDATE, and EXTERNAL AUTH.\n" - "Load card parameters (UID, AID, SCP02Key) from a JSON file.", + "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"); @@ -346,6 +355,24 @@ static int CmdHFHIDConfigSim(const char *Cmd) { } 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; @@ -414,12 +441,19 @@ static int CmdHFHIDConfigSim(const char *Cmd) { 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"), - sprint_hex_inrow(uid, uidlen), aid_str, ats_len, apdu_count); + " 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_("") " to abort simulation"); hid_sim_payload_t payload; @@ -431,10 +465,13 @@ static int CmdHFHIDConfigSim(const char *Cmd) { 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.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.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();