Added live fc/cn update to hf iclass tagsim

Added live fc/cn update to `hf iclass tagsim` refreshing the csn with each update
This commit is contained in:
Antiklesys
2026-03-27 11:01:07 +08:00
parent 8bb001f2fe
commit 6b7665ed59
4 changed files with 300 additions and 4 deletions

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 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)
- Added `hf iclass tagsim` command to quickly simulate an iclass card based on facility code and card number(@antiklesys)
- Added `-f` parameter to `hf iclass sam` command to use the sam to parse a card dump (@antiklesys)

View File

@@ -500,6 +500,27 @@ int do_iclass_simulation(int simulationMode, uint8_t *reader_mac_buf) {
uint32_t reader_eof_time = 0;
len = GetIso15693CommandFromReader(receivedCmd, MAX_FRAME_SIZE, &reader_eof_time);
if (len == -2) {
// USB data arrived while waiting for RF — drain all pending EML_MEMSET
// commands inline (without FpgaDownloadAndGo) so live tag updates work.
PacketCommandNG rx;
while (data_available()) {
if (receive_ng(&rx) != PM3_SUCCESS) break;
if (rx.cmd == CMD_HF_ICLASS_EML_MEMSET) {
struct p {
uint16_t offset;
uint16_t plen;
uint8_t data[];
} PACKED;
struct p *payload = (struct p *) rx.data.asBytes;
emlSet(payload->data, payload->offset, payload->plen);
} else {
exit_loop = true;
break;
}
}
continue;
}
if (len < 0) {
button_pressed = true;
exit_loop = true;
@@ -512,6 +533,45 @@ int do_iclass_simulation(int simulationMode, uint8_t *reader_mac_buf) {
block = receivedCmd[1];
if (cmd == ICLASS_CMD_ACTALL && len == 1) { // 0x0A
// Check for a live tag-identity reload requested by the client.
// The client writes a non-zero byte to emulator offset 32*8 = 256
// (one byte past the 32-block tag data) then updates blocks 0, 3, 4, 6-9
// in emulator memory. We pick it up at the start of each anti-collision
// cycle so the reader sees the new identity from the very first SELECT.
if ((simulationMode == ICLASS_SIM_MODE_FULL ||
simulationMode == ICLASS_SIM_MODE_FULL_GLITCH ||
simulationMode == ICLASS_SIM_MODE_FULL_GLITCH_KEY) &&
emulator[32 * 8] != 0) {
emulator[32 * 8] = 0; // consume the flag
// Reload CSN (block 0) and rebuild anticollision/CSN responses
memcpy(csn_data, emulator, 8);
rotateCSN(csn_data, anticoll_data);
AddCrc(anticoll_data, 8);
AddCrc(csn_data, 8);
CodeIso15693AsTag(anticoll_data, sizeof(anticoll_data));
memcpy(resp_anticoll, ts->buf, ts->max);
resp_anticoll_len = ts->max;
CodeIso15693AsTag(csn_data, sizeof(csn_data));
memcpy(resp_csn, ts->buf, ts->max);
resp_csn_len = ts->max;
// Reload KD/KC (blocks 3 & 4) and recompute cipher states
memcpy(diversified_kd, emulator + (8 * 3), 8);
memcpy(diversified_kc, emulator + (8 * 4), 8);
cipher_state_KD[0] = opt_doTagMAC_1(card_challenge_data, diversified_kd);
cipher_state_KC[0] = opt_doTagMAC_1(card_challenge_data, diversified_kc);
// Reset per-transaction auth state
kc_attempt = 0;
using_kc = false;
cipher_state = &cipher_state_KD[0];
}
// Reader in anti collision phase
modulated_response = resp_sof;
modulated_response_size = resp_sof_len;

View File

@@ -1500,8 +1500,10 @@ int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eo
int samples = 0;
bool gotFrame = false;
// the decoder data structure
DecodeReader_t *dr = (DecodeReader_t *)BigBuf_calloc(sizeof(DecodeReader_t));
// the decoder data structure — use stack, not BigBuf, to avoid a per-call
// allocation leak when the function is restarted (e.g. on data_available exit)
DecodeReader_t dr_buf = {0};
DecodeReader_t *dr = &dr_buf;
DecodeReaderInit(dr, received, max_len, 0, NULL);
// wait for last transfer to complete
@@ -1569,6 +1571,11 @@ int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eo
break;
}
if (data_available()) {
dr->byteCount = -2;
break;
}
WDT_HIT();
}

View File

@@ -18,6 +18,12 @@
#include "cmdhficlass.h"
#include <ctype.h>
#ifdef _WIN32
#include <conio.h>
#else
#include <termios.h>
#include <unistd.h>
#endif
#include "cliparser.h"
#include "cmdparser.h" // command_t
#include "commonutil.h" // ARRAYLEN
@@ -371,6 +377,109 @@ static void iclass_encrypt_block_data(uint8_t *blk_data, uint8_t *key) {
mbedtls_des3_free(&ctx);
}
// ---------------------------------------------------------------------------
// tagsim live-update helpers
// ---------------------------------------------------------------------------
// Write a single 8-byte block to emulator memory without any console output.
static void iclass_emul_write_block_silent(uint8_t blk, const uint8_t *data8) {
struct {
uint16_t offset;
uint16_t len;
uint8_t data[PICOPASS_BLOCK_SIZE];
} PACKED p;
p.offset = blk * PICOPASS_BLOCK_SIZE;
p.len = PICOPASS_BLOCK_SIZE;
memcpy(p.data, data8, PICOPASS_BLOCK_SIZE);
SendCommandNG(CMD_HF_ICLASS_EML_MEMSET, (uint8_t *)&p, sizeof(p));
}
// Write the reload flag to emulator offset 32*8 = 256 (one byte past tag data).
// The ARM simulation loop consumes this flag on the next ACTALL command.
static void iclass_emul_set_reload_flag(void) {
struct {
uint16_t offset;
uint16_t len;
uint8_t data[1];
} PACKED p;
p.offset = 32 * PICOPASS_BLOCK_SIZE;
p.len = 1;
p.data[0] = 1;
SendCommandNG(CMD_HF_ICLASS_EML_MEMSET, (uint8_t *)&p, sizeof(p));
}
// Key codes returned by tagsim_poll_key()
typedef enum {
TAGSIM_KEY_NONE = 0,
TAGSIM_KEY_ABORT,
TAGSIM_KEY_FC_INC, // arrow up
TAGSIM_KEY_FC_DEC, // arrow down
TAGSIM_KEY_CN_INC, // arrow right
TAGSIM_KEY_CN_DEC, // arrow left
} tagsim_key_t;
#ifdef _WIN32
static void tagsim_rawmode_enter(void) {}
static void tagsim_rawmode_exit(void) {}
static tagsim_key_t tagsim_poll_key(void) {
if (!_kbhit()) return TAGSIM_KEY_NONE;
int c = _getch();
if (c == '\r' || c == '\n' || c == 0x1B) return TAGSIM_KEY_ABORT;
if (c == 0 || c == 0xE0) {
c = _getch();
if (c == 72) return TAGSIM_KEY_FC_INC; // up
if (c == 80) return TAGSIM_KEY_FC_DEC; // down
if (c == 77) return TAGSIM_KEY_CN_INC; // right
if (c == 75) return TAGSIM_KEY_CN_DEC; // left
}
return TAGSIM_KEY_NONE;
}
#else // POSIX
static struct termios tagsim_saved_termios;
static bool tagsim_rawmode_active = false;
static void tagsim_rawmode_enter(void) {
if (tcgetattr(STDIN_FILENO, &tagsim_saved_termios) < 0) return;
struct termios raw = tagsim_saved_termios;
raw.c_lflag &= ~(uint32_t)(ICANON | ECHO);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
tagsim_rawmode_active = true;
}
static void tagsim_rawmode_exit(void) {
if (tagsim_rawmode_active) {
tcsetattr(STDIN_FILENO, TCSANOW, &tagsim_saved_termios);
tagsim_rawmode_active = false;
}
}
static tagsim_key_t tagsim_poll_key(void) {
char buf[8] = {0};
int n = (int)read(STDIN_FILENO, buf, sizeof(buf));
if (n <= 0) return TAGSIM_KEY_NONE;
if (n == 1) {
if (buf[0] == '\n' || buf[0] == '\r' || buf[0] == 0x1B)
return TAGSIM_KEY_ABORT;
return TAGSIM_KEY_NONE;
}
if (n >= 3 && buf[0] == '\033' && buf[1] == '[') {
if (buf[2] == 'A') return TAGSIM_KEY_FC_INC;
if (buf[2] == 'B') return TAGSIM_KEY_FC_DEC;
if (buf[2] == 'C') return TAGSIM_KEY_CN_INC;
if (buf[2] == 'D') return TAGSIM_KEY_CN_DEC;
}
return TAGSIM_KEY_NONE;
}
#endif // _WIN32
static int generate_config_card(const iclass_config_card_item_t *o, uint8_t *key, bool got_kr, uint8_t *card_key, bool got_eki, bool use_elite, bool got_mk, uint8_t *master_key) {
// generated config card header
@@ -1345,9 +1454,11 @@ static int CmdHFiClassTagSim(const char *Cmd) {
memcpy(credential + 12, &packed.Bot, sizeof(packed.Bot));
}
// Capture smart-card helper state before starting simulation (can't query mid-sim)
bool use_sc = have_enc_key ? IsCardHelperPresent(false) : false;
// Encrypt credential blocks 7, 8, 9
if (have_enc_key) {
bool use_sc = IsCardHelperPresent(false);
if (use_sc) {
Encrypt(credential + 8, credential + 8);
Encrypt(credential + 16, credential + 16);
@@ -1382,10 +1493,127 @@ static int CmdHFiClassTagSim(const char *Cmd) {
// --- start simulation
PrintAndLogEx(INFO, "Starting iCLASS full simulation");
PrintAndLogEx(INFO, "Press " _GREEN_("`pm3 button`") " to abort");
if (bin_len == 0) {
PrintAndLogEx(INFO, _GREEN_("Arrow keys") ": up/down = FC+/- right/left = CN+/- | " _GREEN_("Enter") " or " _GREEN_("`pm3 button`") " to stop");
PrintAndLogEx(INFO, "FC: " _YELLOW_("%u") " CN: " _YELLOW_("%u") " CSN: " _YELLOW_("%s"),
card.FacilityCode, card.CardNumber, sprint_hex(csn, 8));
} else {
PrintAndLogEx(INFO, "Press " _GREEN_("`pm3 button`") " to abort");
}
clearCommandBuffer();
SendCommandMIX(CMD_HF_ICLASS_SIMULATE, ICLASS_SIM_MODE_FULL, 0, 1, csn, 8);
// --- live FC/CN navigation (wiegand mode only; binary mode has no FC/CN to adjust)
if (bin_len == 0) {
int format_idx = HIDFindCardFormat(format);
tagsim_rawmode_enter();
PacketResponseNG resp;
bool running = true;
bool arm_ended = false; // true when ARM sent its own CMD_ACK (e.g. button press)
while (running) {
// A non-zero-timeout poll lets us detect when the ARM ends the sim
if (WaitForResponseTimeout(CMD_ACK, &resp, 100)) {
arm_ended = true;
running = false;
break;
}
tagsim_key_t k = tagsim_poll_key();
if (k == TAGSIM_KEY_ABORT) { running = false; break; }
if (k == TAGSIM_KEY_NONE) { continue; }
switch (k) {
case TAGSIM_KEY_FC_INC: card.FacilityCode++; break;
case TAGSIM_KEY_FC_DEC: card.FacilityCode--; break;
case TAGSIM_KEY_CN_INC: card.CardNumber++; break;
case TAGSIM_KEY_CN_DEC: card.CardNumber--; break;
case TAGSIM_KEY_ABORT: running = false; break;
case TAGSIM_KEY_NONE: break;
}
// Rebuild CSN deterministically from new FC/CN
{
uint32_t fc = card.FacilityCode;
uint32_t cn = card.CardNumber;
csn[0] = (uint8_t)((fc ^ (cn >> 8)) ^ 0xA3);
csn[1] = (uint8_t)((fc >> 4) ^ (cn & 0xFF) ^ 0x5C);
csn[2] = (uint8_t)((cn >> 16) ^ fc ^ 0x7F);
csn[3] = (uint8_t)((cn >> 8) ^ (fc << 3) ^ 0xE9);
csn[4] = 0xF7; csn[5] = 0xFF; csn[6] = 0x12; csn[7] = 0xE0;
}
// New diversified KD/KC for the new CSN
uint8_t new_kd[8], new_kc[8];
if (rawkey) {
memcpy(new_kd, kd_master, 8);
memcpy(new_kc, kc_master, 8);
} else {
HFiClassCalcDivKey(csn, kd_master, new_kd, elite);
HFiClassCalcDivKey(csn, kc_master, new_kc, elite);
}
// New credential blocks 6-9 for the new FC/CN
uint8_t new_cred[32] = {
0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xE0, 0x17,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
if (format_idx != -1) {
wiegand_message_t packed;
memset(&packed, 0, sizeof(wiegand_message_t));
if (HIDPack(format_idx, &card, &packed, false)) {
packed.Length++;
set_bit_by_position(&packed, true, 0);
#ifdef HOST_LITTLE_ENDIAN
packed.Mid = BSWAP_32(packed.Mid);
packed.Bot = BSWAP_32(packed.Bot);
#endif
memcpy(new_cred + 8, &packed.Mid, sizeof(packed.Mid));
memcpy(new_cred + 12, &packed.Bot, sizeof(packed.Bot));
}
}
if (have_enc_key) {
if (use_sc) {
Encrypt(new_cred + 8, new_cred + 8);
Encrypt(new_cred + 16, new_cred + 16);
Encrypt(new_cred + 24, new_cred + 24);
} else {
iclass_encrypt_block_data(new_cred + 8, enc_key);
iclass_encrypt_block_data(new_cred + 16, enc_key);
iclass_encrypt_block_data(new_cred + 24, enc_key);
}
}
// Push only the changed blocks to emulator memory, then set reload flag
iclass_emul_write_block_silent(0, csn); // block 0: CSN
iclass_emul_write_block_silent(3, new_kd); // block 3: KD
iclass_emul_write_block_silent(4, new_kc); // block 4: KC
for (int b = 0; b < 4; b++) {
iclass_emul_write_block_silent(6 + b, new_cred + b * 8); // blocks 6-9
}
iclass_emul_set_reload_flag(); // signal ARM to reload on next ACTALL
PrintAndLogEx(INFO, "FC: " _YELLOW_("%u") " CN: " _YELLOW_("%u") " CSN: " _YELLOW_("%s"),
card.FacilityCode, card.CardNumber, sprint_hex(csn, 8));
}
tagsim_rawmode_exit();
if (!arm_ended) {
// Client exited the loop (Enter/Esc) but the ARM is still simulating.
// Tell it to stop and consume the resulting CMD_ACK so the ARM is
// cleanly back in the main loop before we return.
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
WaitForResponseTimeout(CMD_ACK, &resp, 2000);
}
}
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass esave -h") "` to save the emulator memory to file");
return PM3_SUCCESS;
}