Merge pull request #3221 from Antiklesys/master

Improved secc
This commit is contained in:
Iceman
2026-04-08 15:51:04 +07:00
committed by GitHub
4 changed files with 72 additions and 50 deletions
+23 -34
View File
@@ -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
+6 -1
View File
@@ -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;
+1 -10
View File
@@ -3,12 +3,8 @@
"AID": "A0000003820013000101",
"SCP02Key": "404142434445464748494A4B4C4D4E4F",
"ATS": "1478F7B10280590180415254454346477300011B",
"DefaultResponse": "6A82",
"APDUResponses": [
{
"_comment": "Prefix-match SELECT by AID (A000000382001700010100) -> 9000 (ok) / 6A82 (file not found)",
"APDU": "00A404000AA000000382001700010100",
"Response": "9000"
},
{
"_comment": "Prefix-match SELECT by AID (A000000382001300010100) -> 9000 (ok) / 6A82 (file not found)",
"APDU": "00A404000AA000000382001300010100",
@@ -18,11 +14,6 @@
"_comment": "Check card credits: A0 D4 00 00 00 -> 00 00 90 00 (reply 0)",
"APDU": "A0D4000000",
"Response": "00009000"
},
{
"_comment": "Busy? -> 9000 (done) / 01 (busy)",
"APDU": "01",
"Response": "9000"
}
]
}
+42 -5
View File
@@ -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_("<Enter>") " 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();