Merge pull request #3188 from Antiklesys/master

SE Conf Card Sim Base
This commit is contained in:
Iceman
2026-03-30 22:04:38 +07:00
committed by GitHub
10 changed files with 1017 additions and 1 deletions
+1
View File
@@ -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)
+1 -1
View File
@@ -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
+582
View File
File diff suppressed because it is too large Load Diff
+84
View File
@@ -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__
+1
View File
@@ -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
+1
View File
@@ -696,6 +696,7 @@ SRCS = mifare/aiddesfire.c \
cmdhfgst.c \
cmdhfksx6924.c \
cmdhfcipurse.c \
cmdhfsecc.c \
cmdhficlass.c \
cmdhfict.c \
cmdhflegic.c \
+23
View File
@@ -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"
}
]
}
+313
View File
@@ -0,0 +1,313 @@
#include "cmdhfsecc.h"
#include <string.h>
#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", "<fn>", "JSON file with UID, AID, SCP02Key (without .json extension)"),
arg_int0("n", "num", "<dec>", "Exit after <n> 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_("<Enter>") " 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 *)&param, sizeof(uint8_t));
if (interactive) {
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " 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);
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef CMDHFSECC_H__
#define CMDHFSECC_H__
#include "common.h"
int CmdHFHIDConfig(const char *Cmd);
#endif
+2
View File
@@ -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