From 796ec85a6f5b886f8845fb60dbfd29cc7bf27d59 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Wed, 8 Apr 2026 16:23:50 +0800 Subject: [PATCH] Improved secc Improved hf secc to use default apdu reply from the json file and not the hardcoded one (used as fallback now). Removed old hardcoded functions no longer in use. --- armsrc/secc.c | 57 +++++++++++--------------- armsrc/secc.h | 7 +++- client/resources/hidconfig_sample.json | 1 + client/src/cmdhfsecc.c | 47 ++++++++++++++++++--- 4 files changed, 72 insertions(+), 40 deletions(-) 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();