From 128e4006bf6fc86058e8d436c210a0c04cb4d75c Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Mon, 30 Mar 2026 22:58:52 +0800 Subject: [PATCH] SE Conf Card Sim Base Added `hf secc` to build a base for simulating basic function of iclass SE config cards --- CHANGELOG.md | 1 + armsrc/Makefile | 2 +- armsrc/secc.c | 582 +++++++++++++++++++++++++ armsrc/secc.h | 84 ++++ client/CMakeLists.txt | 1 + client/Makefile | 1 + client/resources/hidconfig_sample.json | 23 + client/src/cmdhfsecc.c | 313 +++++++++++++ client/src/cmdhfsecc.h | 9 + include/pm3_cmd.h | 2 + 10 files changed, 1017 insertions(+), 1 deletion(-) create mode 100644 armsrc/secc.c create mode 100644 armsrc/secc.h create mode 100644 client/resources/hidconfig_sample.json create mode 100644 client/src/cmdhfsecc.c create mode 100644 client/src/cmdhfsecc.h diff --git a/CHANGELOG.md b/CHANGELOG.md index f29158a7e..2bf0dda9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added `hf secc` to build a base for simulating basic function of iclass SE config cards (@antiklesys) - Improved SIO parsing for `hf iclass view` based on Iceman's "Dismantling the SEOS Protocol" talk (@antiklesys) - Added live fc/cn update to `hf iclass tagsim` refreshing the csn with each update (@antiklesys) - Added `--live` option to `hf iclass lookup` command to perform a live recovery of the reader's key by simulating a tag and running the lookup command against both standard and elite dictionaries (@antiklesys) diff --git a/armsrc/Makefile b/armsrc/Makefile index e6f4f56e4..94f823cd8 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -37,7 +37,7 @@ APP_CFLAGS = $(PLATFORM_DEFS) \ SRC_LF = lfops.c lfsampling.c pcf7931.c lfdemod.c lfadc.c SRC_HF = hfops.c SRC_ISO15693 = iso15693.c iso15693tools.c -SRC_ISO14443a = iso14443a.c mifareutil.c mifarecmd.c epa.c mifaresim.c sam_common.c sam_mfc.c sam_seos.c +SRC_ISO14443a = iso14443a.c secc.c mifareutil.c mifarecmd.c epa.c mifaresim.c sam_common.c sam_mfc.c sam_seos.c #UNUSED: mifaresniff.c SRC_ISO14443b = iso14443b.c diff --git a/armsrc/secc.c b/armsrc/secc.c new file mode 100644 index 000000000..04e5fbae7 --- /dev/null +++ b/armsrc/secc.c @@ -0,0 +1,582 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// HID Config Card (JCOP / GlobalPlatform SCP02) simulation helpers. +// Handles I-block dispatch for tagType=16 in SimulateIso14443aTag and the +// A0 D4 jam logic for SniffIso14443a. +//----------------------------------------------------------------------------- + +#include "secc.h" + +#include +#include "proxmark3_arm.h" +#include "dbprint.h" +#include "BigBuf.h" // DMA_BUFFER_SIZE, MAX_PARITY_SIZE +#include "crc16.h" // AddCrc14A, CheckCrc14A +#include "fpgaloader.h" // FpgaWriteConfWord, FpgaSetupSscDma +#include "desfire_crypto.h" // tdes_nxp_send +#include "mbedtls/des.h" // mbedtls_des_*, mbedtls_des3_* +#include "iso14443a.h" // ReaderTransmit, ReaderReceive, iso14a_get/set_timeout, iso14_pcb_blocknum, MAX_ISO14A_TIMEOUT +#include "appmain.h" // tearoff_hook, send_wtx +#include "util.h" // data_available, BUTTON_PRESS +#include "cmd.h" // reply_ng +#include "pm3_cmd.h" // CMD_HF_HIDCONFIG_SIM, CMD_HF_HIDCONFIG_SNIFF +#include "dbprint.h" // Dbprintf, LED_* +#include "ticks.h" // WDT_HIT +#include "protocols.h" // ISO14443A_CMD_* constants + +// --------------------------------------------------------------------------- +// Internal constants +// --------------------------------------------------------------------------- + +// Fixed card challenge used in SCP02 INITIALIZE UPDATE responses. +static const uint8_t hid_cc[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + +// --------------------------------------------------------------------------- +// Custom APDU response table and SCP02 key (loaded from payload) +// --------------------------------------------------------------------------- + +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}; + +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)); +} + +static void hid_config_card_set_scp02_key(const uint8_t *key) { + memcpy(s_scp02_key, key, 16); +} + +// --------------------------------------------------------------------------- +// Internal crypto +// --------------------------------------------------------------------------- + +// Compute GlobalPlatform SCP02 card cryptogram. +// card_cryptogram = Retail_MAC(S-ENC, host_challenge || card_challenge || 0x80 || 0x00*7) +// Uses hardcoded master key 404142...4F and SN = 0x0001. +static void compute_card_cryptogram(const uint8_t *host_challenge, uint8_t *out) { + static const uint8_t SN[2] = {0x00, 0x01}; + + // Derive S-ENC: 3DES-CBC(K, zero_IV, {0x01, 0x82, SN0, SN1, 0x00*12}) + uint8_t s_enc[16]; + { + uint8_t deriv[16] = {0x01, 0x82, SN[0], SN[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t iv[8] = {0}; + tdes_nxp_send(deriv, s_enc, 16, s_scp02_key, iv, 2); + } + + // Retail MAC over HC || CC || 0x80 || 0x00*7 + mbedtls_des_context des_ctx; + mbedtls_des3_context des3_ctx; + mbedtls_des_init(&des_ctx); + mbedtls_des3_init(&des3_ctx); + mbedtls_des_setkey_enc(&des_ctx, s_enc); + mbedtls_des3_set2key_enc(&des3_ctx, s_enc); + + uint8_t x[8] = {0}; + uint8_t tmp[8]; + + // Block 1: host challenge + for (int i = 0; i < 8; i++) tmp[i] = host_challenge[i] ^ x[i]; + mbedtls_des_crypt_ecb(&des_ctx, tmp, x); + + // Block 2: card challenge + for (int i = 0; i < 8; i++) tmp[i] = hid_cc[i] ^ x[i]; + mbedtls_des_crypt_ecb(&des_ctx, tmp, x); + + // Block 3: 0x80 || 0x00*7 (ISO 9797-1 Method 2 padding) + tmp[0] = 0x80 ^ x[0]; + for (int i = 1; i < 8; i++) tmp[i] = x[i]; + mbedtls_des3_crypt_ecb(&des3_ctx, tmp, out); + + mbedtls_des_free(&des_ctx); + mbedtls_des3_free(&des3_ctx); +} + +// --------------------------------------------------------------------------- +// I-block handler (called from SimulateIso14443aTag for tagType=16) +// --------------------------------------------------------------------------- + +bool hid_config_card_handle_iblock(const uint8_t *cmd, int len, tag_response_info_t *ri) { + uint8_t pcb = cmd[0]; + bool has_cid = (pcb & 0x08) != 0; + + // Payload starts after PCB [+ CID] + int off = has_cid ? 2 : 1; // offset to APDU CLA byte + + // Echo PCB and mirror CID from the reader frame. + ri->response[0] = pcb; + if (has_cid) + ri->response[1] = cmd[1]; // mirror CID byte from reader + + // Base pointer for APDU payload in the response (after PCB [CID]) + uint8_t *rsp = &ri->response[off]; + + // ----- Check custom APDU table first (overrides hardcoded responses) ----- + // cmd[off] is the start of the APDU payload; len includes trailing CRC (2 bytes). + for (int i = 0; i < s_apdu_count; i++) { + if (s_apdu_table[i].apdu_len == 0) + continue; + if (len - off < (int)(s_apdu_table[i].apdu_len + 2)) + continue; + if (memcmp(&cmd[off], s_apdu_table[i].apdu, s_apdu_table[i].apdu_len) == 0) { + memcpy(rsp, s_apdu_table[i].resp, s_apdu_table[i].resp_len); + ri->response_n = off + s_apdu_table[i].resp_len; + return true; + } + } + + // ----- 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; + } + + // ----- INITIALIZE UPDATE (INS=0x50) ----- + // CID frame: INS at cmd[3], host challenge at cmd[off+4] = cmd[6] + if (has_cid && len >= 17 && cmd[3] == 0x50) { + uint8_t cryptogram[8]; + compute_card_cryptogram(&cmd[off + 4], cryptogram); + + memset(rsp, 0x00, 10); // key diversification data + rsp[10] = 0xFF; // key version (JCOP factory default) + rsp[11] = 0x02; // SCP02 + memcpy(rsp + 12, hid_cc, 8); // card challenge + memcpy(rsp + 20, cryptogram, 8); // card cryptogram + rsp[28] = 0x90; + rsp[29] = 0x00; + ri->response_n = off + 30; + return true; + } + + // ----- EXTERNAL AUTH and all other APDUs: generic 90 00 ----- + rsp[0] = 0x90; rsp[1] = 0x00; + ri->response_n = off + 2; + return true; +} + +// --------------------------------------------------------------------------- +// Sniff jam handler (called from SniffIso14443a when param bit 2 is set) +// --------------------------------------------------------------------------- + +bool hid_config_card_jam(const uint8_t *cmd, int len, uint8_t *dma_buf) { + uint8_t pcb = cmd[0]; + + // Only act on I-blocks (bits 7:6 == 00) + if ((pcb & 0xC0) != 0x00) + return false; + + int off = (pcb & 0x08) ? 2 : 1; // skip CID if present + + if (len < off + 7) + return false; + + if (cmd[off] != 0xA0 || cmd[off + 1] != 0xD4 || + cmd[off + 2] != 0x00 || cmd[off + 3] != 0x00 || cmd[off + 4] != 0x00) + return false; + + // Build jam response: PCB [CID] 00 00 90 00 CRC CRC + uint8_t resp[8]; + 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; + AddCrc14A(resp, rlen); + rlen += 2; + + EmSendCmdEx(resp, rlen, false); + + // Restore sniffer FPGA mode and re-arm DMA + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO14443A | FPGA_HF_ISO14443A_SNIFFER); + FpgaSetupSscDma(dma_buf, DMA_BUFFER_SIZE); + + return true; +} + +// --------------------------------------------------------------------------- +// CID-aware ISO 14443-4 APDU exchange for HID Config Card reader interaction. +// Sends I-blocks with PCB=0x0A (CID present, CID=0) as negotiated in RATS. +// Strips PCB+CID from received responses before returning. +// API mirrors iso14_apdu(). +// --------------------------------------------------------------------------- + +int hid_config_card_iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chaining, void *data, uint16_t data_len, uint8_t *res) { + uint8_t parity[MAX_PARITY_SIZE] = {0}; + uint8_t *real_cmd = BigBuf_calloc(cmd_len + 5); // PCB(1) + CID(1) + APDU + CRC(2) + + if (cmd_len) { + real_cmd[0] = 0x0A; // I-block, CID present (bit 3), block number in bit 0 + if (send_chaining) + real_cmd[0] |= 0x10; + real_cmd[0] |= iso14_pcb_blocknum; + real_cmd[1] = 0x00; // CID = 0 (as negotiated in RATS) + memcpy(real_cmd + 2, cmd, cmd_len); + } else { + real_cmd[0] = 0xAA; // R-block ACK + CID present + real_cmd[0] |= iso14_pcb_blocknum; + real_cmd[1] = 0x00; // CID = 0 + } + AddCrc14A(real_cmd, cmd_len + 2); // PCB + CID + APDU + + ReaderTransmit(real_cmd, cmd_len + 4, NULL); // PCB(1) + CID(1) + APDU + CRC(2) + + if (tearoff_hook() == PM3_ETEAROFF) { + BigBuf_free(); + return -1; + } + + size_t len = ReaderReceive(data, data_len, parity); + uint8_t *data_bytes = (uint8_t *)data; + + if (len == 0) { + BigBuf_free(); + return 0; + } + + uint32_t save_timeout = iso14a_get_timeout(); + + // S-Block WTX + while (len && ((data_bytes[0] & 0xF2) == 0xF2)) { + if (BUTTON_PRESS() || data_available()) { + BigBuf_free(); + return -3; + } + send_wtx(38); + data_bytes[1] &= 0x3F; + iso14a_set_timeout(MAX(data_bytes[1] * save_timeout, MAX_ISO14A_TIMEOUT)); + AddCrc14A(data_bytes, len - 2); + ReaderTransmit(data_bytes, len, NULL); + len = ReaderReceive(data_bytes, data_len, parity); + } + + iso14a_set_timeout(save_timeout); + + // Toggle block number on valid I-block or R(ACK) + if (len >= 3 + && ((data_bytes[0] & 0xC0) == 0 || (data_bytes[0] & 0xD0) == 0x80) + && (data_bytes[0] & 0x01) == iso14_pcb_blocknum) { + iso14_pcb_blocknum ^= 1; + } + + if (res) + *res = data_bytes[0]; + + if (len >= 3 && !CheckCrc14A(data_bytes, len)) { + BigBuf_free(); + return -1; + } + + if (len) { + // Strip PCB and CID from the front of the response + int header_len = ((data_bytes[0] & 0x08) != 0) ? 2 : 1; + len -= header_len; + memmove(data_bytes, data_bytes + header_len, len); + } + + BigBuf_free(); + return len; +} + +// --------------------------------------------------------------------------- +// Full HID Config Card simulation (own loop, no iso14443a.c hooks) +// --------------------------------------------------------------------------- + +void SimulateHIDConfigCard(const hid_sim_payload_t *payload) { + hid_config_card_set_apdu_table(payload->apdu_table, payload->apdu_count); + hid_config_card_set_scp02_key(payload->scp02_key); + + // Command buffers + uint8_t receivedCmd[MAX_FRAME_SIZE]; + uint8_t receivedCmdPar[MAX_PARITY_SIZE]; + + BigBuf_free_keep_EM(); + +#define HID_SIM_DYNAMIC_RESPONSE_SIZE 64 +#define HID_SIM_DYNAMIC_MODULATION_SIZE 512 + uint8_t *dyn_resp = BigBuf_calloc(HID_SIM_DYNAMIC_RESPONSE_SIZE); + uint8_t *dyn_mod = BigBuf_calloc(HID_SIM_DYNAMIC_MODULATION_SIZE); + if (dyn_resp == NULL || dyn_mod == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_HIDCONFIG_SIM, PM3_EMALLOC, NULL, 0); + return; + } + tag_response_info_t dynamic_response_info = { + .response = dyn_resp, + .response_n = 0, + .modulation = dyn_mod, + .modulation_n = 0 + }; + + // Build flags: use provided UID, override ATQA/SAK/ATS with HID values. + uint16_t flags = payload->flags | FLAG_ATS_IN_DATA | FLAG_ATQA_IN_DATA | FLAG_SAK_IN_DATA; + uint8_t uid[10]; + memcpy(uid, payload->uid, sizeof(uid)); + + // ATQA override: stored big-endian in payload, SimulateIso14443aInit takes uint16 + uint16_t atqa_val = ((uint16_t)payload->atqa[0] << 8) | payload->atqa[1]; + uint8_t sak_val = payload->sak; + + tag_response_info_t *responses; + uint32_t cuid; + uint8_t pages; + + if (SimulateIso14443aInit(4, flags, uid, + (uint8_t *)payload->ats, payload->ats_len, + atqa_val, sak_val, + &responses, &cuid, &pages, NULL) == false) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_HIDCONFIG_SIM, PM3_EINIT, NULL, 0); + return; + } + + iso14443a_setup(FPGA_HF_ISO14443A_TAGSIM_LISTEN); + iso14a_set_timeout(201400); + + int len = 0; + clear_trace(); + set_tracing(true); + LED_A_ON(); + + uint8_t cardINTERACTIONS = 0; + bool finished = false; + + while (finished == false) { + WDT_HIT(); + tag_response_info_t *p_response = NULL; + + if (GetIso14443aCommandFromReader(receivedCmd, sizeof(receivedCmd), receivedCmdPar, &len) == false) { + finished = true; + break; + } + + dynamic_response_info.response_n = 0; + dynamic_response_info.modulation_n = 0; + + if (receivedCmd[0] == ISO14443A_CMD_REQA && len == 1) { + p_response = &responses[RESP_INDEX_ATQA]; + } else if (receivedCmd[0] == ISO14443A_CMD_WUPA && len == 1) { + p_response = &responses[RESP_INDEX_ATQA]; + } else if (receivedCmd[1] == 0x20 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 2) { + p_response = &responses[RESP_INDEX_UIDC1]; + } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 9) { + p_response = &responses[RESP_INDEX_SAKC1]; + } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { + p_response = &responses[RESP_INDEX_ATS]; + } else if (receivedCmd[0] == ISO14443A_CMD_HALT && len == 4) { + p_response = NULL; + } else { + // ISO 14443-4 I-block dispatch — HID-specific handling + switch (receivedCmd[0]) { + case 0x02: + case 0x03: + case 0x0A: + case 0x0B: { + hid_config_card_handle_iblock(receivedCmd, len, &dynamic_response_info); + if (dynamic_response_info.response_n > 0) { + // Mirror CID from reader frame when CID bit is set in PCB + if (receivedCmd[0] & 0x08) + dynamic_response_info.response[1] = receivedCmd[1]; + AddCrc14A(dynamic_response_info.response, dynamic_response_info.response_n); + dynamic_response_info.response_n += 2; + if (prepare_tag_modulation(&dynamic_response_info, HID_SIM_DYNAMIC_MODULATION_SIZE)) + p_response = &dynamic_response_info; + } + cardINTERACTIONS++; + if (payload->exitAfter > 0 && cardINTERACTIONS >= payload->exitAfter) + finished = true; + break; + } + default: + break; + } + } + + if (p_response != NULL) + EmSendPrecompiledCmd(p_response); + } + + set_tracing(false); + LED_A_OFF(); + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_HIDCONFIG_SIM, PM3_SUCCESS, NULL, 0); +} + +// --------------------------------------------------------------------------- +// HID Config Card sniff with optional A0 D4 jamming +// --------------------------------------------------------------------------- + +void SniffHIDConfigCard(uint8_t param) { + bool do_jam = (param & 0x04) != 0; + + // For non-jam sniff, delegate entirely to the standard sniffer. + if (!do_jam) { + SniffIso14443a(param); + return; + } + + // Jam mode: own sniff loop with hid_config_card_jam() called inline. + BigBuf_free(); + BigBuf_Clear_ext(false); + + uint8_t *receivedCmd = BigBuf_calloc(MAX_FRAME_SIZE); + uint8_t *receivedCmdPar = BigBuf_calloc(MAX_PARITY_SIZE); + uint8_t *receivedResp = BigBuf_calloc(MAX_FRAME_SIZE); + uint8_t *receivedRespPar = BigBuf_calloc(MAX_PARITY_SIZE); + + Demod14aInit(receivedResp, MAX_FRAME_SIZE, receivedRespPar); + Uart14aInit(receivedCmd, MAX_FRAME_SIZE, receivedCmdPar); + + dmabuf8_t *dma = get_dma8(); + uint8_t *data = dma->buf; + + if (FpgaSetupSscDma((uint8_t *)dma->buf, DMA_BUFFER_SIZE) == false) { + BigBuf_free(); + return; + } + + bool triggered = !(param & 0x03); + uint32_t rx_samples = 0; + bool TagIsActive = false; + bool ReaderIsActive = false; + uint8_t previous_data = 0; + int maxDataLen = 0, dataLen; + uint16_t checker = 12000; + + tUart14a *uart = GetUart14a(); + tDemod14a *demod = GetDemod14a(); + + clear_trace(); + set_tracing(true); + LED_A_ON(); + + while (BUTTON_PRESS() == false) { + WDT_HIT(); + + if (checker-- == 0) { + if (data_available()) break; + checker = 12000; + } + + int readBufDataP = data - dma->buf; + int dmaBufDataP = DMA_BUFFER_SIZE - AT91C_BASE_PDC_SSC->PDC_RCR; + dataLen = (readBufDataP <= dmaBufDataP) + ? dmaBufDataP - readBufDataP + : DMA_BUFFER_SIZE - readBufDataP + dmaBufDataP; + + if (dataLen > maxDataLen) { + maxDataLen = dataLen; + if (dataLen > (9 * DMA_BUFFER_SIZE / 10)) break; + } + if (dataLen < 1) continue; + + if (AT91C_BASE_PDC_SSC->PDC_RCR == 0) { + AT91C_BASE_PDC_SSC->PDC_RPR = (uint32_t)dma->buf; + AT91C_BASE_PDC_SSC->PDC_RCR = DMA_BUFFER_SIZE; + } + if (AT91C_BASE_PDC_SSC->PDC_RNCR == 0) { + AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t)dma->buf; + AT91C_BASE_PDC_SSC->PDC_RNCR = DMA_BUFFER_SIZE; + } + + LED_A_OFF(); + + if (rx_samples & 0x01) { + if (!TagIsActive) { + uint8_t readerdata = (previous_data & 0xF0) | (*data >> 4); + if (MillerDecoding(readerdata, (rx_samples - 1) * 4)) { + LED_C_ON(); + if (!triggered && (param & 0x02) && uart->len == 1 && uart->bitCount == 7) + triggered = true; + if (triggered) { + if (!LogTrace(receivedCmd, uart->len, + uart->startTime * 16 - DELAY_READER_AIR2ARM_AS_SNIFFER, + uart->endTime * 16 - DELAY_READER_AIR2ARM_AS_SNIFFER, + uart->parity, true)) + break; + } + if (uart->len >= 8) { + if (hid_config_card_jam(receivedCmd, uart->len, (uint8_t *)dma->buf)) + data = dma->buf; + } + Uart14aReset(); + Demod14aReset(); + LED_B_OFF(); + } + ReaderIsActive = (uart->state != STATE_14A_UNSYNCD); + } + + if (!ReaderIsActive) { + uint8_t tagdata = (previous_data << 4) | (*data & 0x0F); + if (ManchesterDecoding(tagdata, 0, (rx_samples - 1) * 4)) { + LED_B_ON(); + if (!LogTrace(receivedResp, demod->len, + demod->startTime * 16 - DELAY_TAG_AIR2ARM_AS_SNIFFER, + demod->endTime * 16 - DELAY_TAG_AIR2ARM_AS_SNIFFER, + demod->parity, false)) + break; + if (!triggered && (param & 0x01)) + triggered = true; + Uart14aReset(); + Demod14aReset(); + LED_C_OFF(); + } + TagIsActive = (demod->state != DEMOD_14A_UNSYNCD); + } + } + + previous_data = *data; + rx_samples++; + if (data == dma->buf + DMA_BUFFER_SIZE) + data = dma->buf; + else + data++; + } + + FpgaDisableSscDma(); + set_tracing(false); + LEDsoff(); + BigBuf_free(); +} diff --git a/armsrc/secc.h b/armsrc/secc.h new file mode 100644 index 000000000..a4903bc3e --- /dev/null +++ b/armsrc/secc.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// HID Config Card (JCOP / GlobalPlatform SCP02) simulation helpers. +// Called from iso14443a.c; isolated here to keep HID-specific logic separate. +//----------------------------------------------------------------------------- +#ifndef SECC_H__ +#define SECC_H__ + +#include "common.h" +#include "mifare.h" // tag_response_info_t + +// --------------------------------------------------------------------------- +// Shared payload structs (used by both ARM and client via CMD_HF_HIDCONFIG_SIM) +// --------------------------------------------------------------------------- + +#define HID_APDU_MAX_ENTRIES 8 +#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) + +// One custom APDU override entry: if the incoming APDU starts with +// apdu[0..apdu_len-1], respond with resp[0..resp_len-1] (raw APDU payload). +typedef struct { + uint8_t apdu[HID_APDU_MAX_CMD]; + uint8_t apdu_len; + uint8_t resp[HID_APDU_MAX_RESP]; + uint8_t resp_len; +} PACKED hid_apdu_entry_t; + +// Full simulation payload sent from client to ARM via CMD_HF_HIDCONFIG_SIM. +// ATQA is stored big-endian: atqa[0] = high byte (used as rATQA[0] in SimulateIso14443aInit). +typedef struct { + uint8_t tagtype; + uint16_t flags; + uint8_t uid[10]; + uint8_t exitAfter; + uint8_t atqa[2]; // ATQA override (big-endian: [0]=high, [1]=low) + uint8_t sak; // SAK override + 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 apdu_count; + hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES]; +} PACKED hid_sim_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); + +// Handle an I-block received during HID Config Card (tagType=16) simulation. +// Fills dynamic_response_info with the appropriate response payload (without CRC). +// Returns true if a response was prepared, false if the command was not handled. +bool hid_config_card_handle_iblock(const uint8_t *cmd, int len, tag_response_info_t *response_info); + +// CID-aware ISO 14443-4 APDU exchange for HID Config Card reader interaction. +// Sends I-blocks with PCB=0x0A (CID present, CID=0) as negotiated in RATS. +// Strips PCB+CID from received responses. API mirrors iso14_apdu(). +int hid_config_card_iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chaining, void *data, uint16_t data_len, uint8_t *res); + +// Handle a sniff-mode jam: inspect cmd and, if it matches A0 D4 00 00 00, +// transmit the jam response and restore sniffer FPGA mode + re-arm DMA. +// dma_buf: pointer to the DMA ring buffer start (passed to FpgaSetupSscDma). +// Returns true if the frame was jammed; caller must then reset its data pointer +// back to the DMA buffer start and continue the sniff loop. +bool hid_config_card_jam(const uint8_t *cmd, int len, uint8_t *dma_buf); + +#endif // SECC_H__ diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index e1232f08d..732636089 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -390,6 +390,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfgallagher.c ${PM3_ROOT}/client/src/cmdhfgst.c ${PM3_ROOT}/client/src/cmdhfcipurse.c + ${PM3_ROOT}/client/src/cmdhfsecc.c ${PM3_ROOT}/client/src/cmdhficlass.c ${PM3_ROOT}/client/src/cmdhfict.c ${PM3_ROOT}/client/src/cmdhfjooki.c diff --git a/client/Makefile b/client/Makefile index 7381c16b7..51a957c30 100644 --- a/client/Makefile +++ b/client/Makefile @@ -696,6 +696,7 @@ SRCS = mifare/aiddesfire.c \ cmdhfgst.c \ cmdhfksx6924.c \ cmdhfcipurse.c \ + cmdhfsecc.c \ cmdhficlass.c \ cmdhfict.c \ cmdhflegic.c \ diff --git a/client/resources/hidconfig_sample.json b/client/resources/hidconfig_sample.json new file mode 100644 index 000000000..f990a1f12 --- /dev/null +++ b/client/resources/hidconfig_sample.json @@ -0,0 +1,23 @@ +{ + "UID": "8F042795", + "AID": "A0000003820013000101", + "SCP02Key": "404142434445464748494A4B4C4D4E4F", + "ATS": "1478F7B10280590180415254454346477300011B", + "APDUResponses": [ + { + "_comment": "Prefix-match SELECT by AID (17) -> 9000 (ok)", + "APDU": "00A404000AA000000382001700010100", + "Response": "9000" + }, + { + "_comment": "Prefix-match SELECT by AID (13) -> 6A82 (file not found)", + "APDU": "00A404000AA000000382001300010100", + "Response": "6A82" + }, + { + "_comment": "Check card credits: A0 D4 00 00 00 -> 00 00 90 00 (reply 0)", + "APDU": "A0D4000000", + "Response": "00009000" + } + ] +} diff --git a/client/src/cmdhfsecc.c b/client/src/cmdhfsecc.c new file mode 100644 index 000000000..b6c6c9a1e --- /dev/null +++ b/client/src/cmdhfsecc.c @@ -0,0 +1,313 @@ + +#include "cmdhfsecc.h" + +#include +#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 +#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 + +// --------------------------------------------------------------------------- +// Payload structs shared with armsrc/secc.h +// Must stay in sync with hid_apdu_entry_t / hid_sim_payload_t. +// --------------------------------------------------------------------------- + +#define HID_APDU_MAX_ENTRIES 8 +#define HID_APDU_MAX_CMD 20 +#define HID_APDU_MAX_RESP 32 + +typedef struct { + uint8_t apdu[HID_APDU_MAX_CMD]; + uint8_t apdu_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 ats[20]; // ATS bytes without CRC (from JSON "ATS") + uint8_t ats_len; // actual number of valid bytes in ats[] + uint8_t apdu_count; + hid_apdu_entry_t apdu_table[HID_APDU_MAX_ENTRIES]; +} PACKED hid_sim_payload_t; + +static int CmdHelp(const char *Cmd); + +// --------------------------------------------------------------------------- +// hf hidconfig sim +// --------------------------------------------------------------------------- + +static int CmdHFHIDConfigSim(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf hidconfig 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.", + "hf hidconfig sim -f hidconfig_sample\n" + "hf hidconfig sim -f hidconfig_sample -n 5 -> stop after 5 reader interactions"); + + void *argtable[] = { + arg_param_begin, + arg_str1("f", "file", "", "JSON file with UID, AID, SCP02Key (without .json extension)"), + arg_int0("n", "num", "", "Exit after 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/ + 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); + 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) { + 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)); + 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}; + 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)"); + 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); + json_decref(root); + return PM3_EINVARG; + } + + // 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; + int alen = hex_to_bytes(json_string_value(japdu), + apdu_table[apdu_count].apdu, HID_APDU_MAX_CMD); + int rlen = hex_to_bytes(json_string_value(jresp), + apdu_table[apdu_count].resp, HID_APDU_MAX_RESP); + if (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); + + 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); + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " 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.apdu_count = apdu_count; + memcpy(payload.uid, uid, uidlen); + memcpy(payload.scp02_key, scp02_key, sizeof(scp02_key)); + memcpy(payload.ats, ats, ats_len); + 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}; + 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); + break; + } + } + return PM3_SUCCESS; +} + +// --------------------------------------------------------------------------- +// hf hidconfig sniff +// --------------------------------------------------------------------------- + +static int CmdHFHIDConfigSniff(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf hidconfig sniff", + "Sniff the communication between a HID Config Card reader and card.\n" + "Use `hf 14a list` to view collected data.", + "hf hidconfig sniff\n" + "hf hidconfig sniff -j -> jam A0 D4 00 00 00, respond 00 00 90 00\n" + "hf hidconfig sniff -c -r -> trigger on card or reader data"); + + 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_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); + CLIParserFree(ctx); + + if (jam) { + param |= 0x04; + PrintAndLogEx(INFO, "Sniff with jam of APDU " _YELLOW_("A0 D4 00 00 00") " -> " _YELLOW_("00 00 90 00")); + } + + uint16_t sniff_cmd = jam ? CMD_HF_HIDCONFIG_SNIFF : CMD_HF_ISO14443A_SNIFF; + + clearCommandBuffer(); + SendCommandNG(sniff_cmd, (uint8_t *)¶m, sizeof(uint8_t)); + + if (interactive) { + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " 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 14a 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"}, + {"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); +} diff --git a/client/src/cmdhfsecc.h b/client/src/cmdhfsecc.h new file mode 100644 index 000000000..0142621f9 --- /dev/null +++ b/client/src/cmdhfsecc.h @@ -0,0 +1,9 @@ + +#ifndef CMDHFSECC_H__ +#define CMDHFSECC_H__ + +#include "common.h" + +int CmdHFHIDConfig(const char *Cmd); + +#endif diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 37b881be4..5d10a8b0c 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -726,6 +726,8 @@ typedef struct { #define CMD_HF_ISO14443A_SNIFF 0x0383 #define CMD_HF_ISO14443A_SIMULATE 0x0384 #define CMD_HF_ISO14443A_SIM_AID 0x1420 +#define CMD_HF_HIDCONFIG_SIM 0x1421 +#define CMD_HF_HIDCONFIG_SNIFF 0x1422 #define CMD_HF_ISO14443A_READER 0x0385 #define CMD_HF_ISO14443A_EMV_SIMULATE 0x0386