From 2a73285573f4d02b060550f3871000648170fab3 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Tue, 16 Jan 2024 15:20:01 +0100 Subject: [PATCH 01/36] iso15sim rework: add support for lot of commands --- armsrc/Standalone/Makefile.hal | 5 +- armsrc/Standalone/Makefile.inc | 4 + armsrc/iso15693.c | 404 +++++++++++++++++++++++++++++++-- armsrc/iso15693.h | 25 ++ 4 files changed, 413 insertions(+), 25 deletions(-) diff --git a/armsrc/Standalone/Makefile.hal b/armsrc/Standalone/Makefile.hal index 62dd8813d..91d87dd14 100644 --- a/armsrc/Standalone/Makefile.hal +++ b/armsrc/Standalone/Makefile.hal @@ -80,6 +80,9 @@ define KNOWN_STANDALONE_DEFINITIONS | HF_15SNIFF | 15693 sniff to flashmem (rdv4) or ram | | | | +----------------------------------------------------------+ +| HF_15SIM | 15693 tag simulator | +| | | ++----------------------------------------------------------+ | HF_AVEFUL | Mifare ultralight read/simulation | | | - Ave Ozkal | +----------------------------------------------------------+ @@ -136,7 +139,7 @@ endef STANDALONE_MODES := LF_SKELETON STANDALONE_MODES += LF_EM4100EMUL LF_EM4100RSWB LF_EM4100RSWW LF_EM4100RWC LF_HIDBRUTE LF_HIDFCBRUTE LF_ICEHID LF_MULTIHID LF_NEDAP_SIM LF_NEXID LF_PROXBRUTE LF_PROX2BRUTE LF_SAMYRUN LF_THAREXDE -STANDALONE_MODES += HF_14ASNIFF HF_14BSNIFF HF_15SNIFF HF_AVEFUL HF_BOG HF_CARDHOPPER HF_COLIN HF_CRAFTBYTE HF_ICECLASS HF_LEGIC HF_LEGICSIM HF_MATTYRUN HF_MFCSIM HF_MSDSAL HF_REBLAY HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG +STANDALONE_MODES += HF_14ASNIFF HF_14BSNIFF HF_15SNIFF HF_15SIM HF_AVEFUL HF_BOG HF_CARDHOPPER HF_COLIN HF_CRAFTBYTE HF_ICECLASS HF_LEGIC HF_LEGICSIM HF_MATTYRUN HF_MFCSIM HF_MSDSAL HF_REBLAY HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG STANDALONE_MODES += DANKARMULTI STANDALONE_MODES_REQ_BT := HF_CARDHOPPER HF_REBLAY STANDALONE_MODES_REQ_SMARTCARD := diff --git a/armsrc/Standalone/Makefile.inc b/armsrc/Standalone/Makefile.inc index 6f9ec5465..5873c0aff 100644 --- a/armsrc/Standalone/Makefile.inc +++ b/armsrc/Standalone/Makefile.inc @@ -89,6 +89,10 @@ endif ifneq (,$(findstring WITH_STANDALONE_HF_15SNIFF,$(APP_CFLAGS))) SRC_STANDALONE = hf_15sniff.c endif +# WITH_STANDALONE_HF_15SIM +ifneq (,$(findstring WITH_STANDALONE_HF_15SIM,$(APP_CFLAGS))) + SRC_STANDALONE = hf_15sim.c +endif # WITH_STANDALONE_HF_AVEFUL ifneq (,$(findstring WITH_STANDALONE_HF_AVEFUL,$(APP_CFLAGS))) SRC_STANDALONE = hf_aveful.c diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index b9115b561..debdef445 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2111,20 +2111,51 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { // free eventually allocated BigBuf memory BigBuf_free_keep_EM(); - Iso15693InitTag(); + iso15693_tag *tag = (iso15693_tag*) BigBuf_get_EM_addr(); + if (tag == NULL) return; + + if (uid != NULL) { // new tag (need initialization) + memcpy(tag->uid, uid, 8); + tag->dsfid = 0; + tag->dsfidLock = false; + tag->afi = 0; + tag->afiLock = false; + tag->bytesPerPage = 4; + tag->pagesCount = 64; + tag->ic = 0; + memset(tag->locks, 0, sizeof(tag->locks)); + memset(tag->data, 0, sizeof(tag->data)); + } + else { // tag is already set + if (tag->pagesCount > ISO15693_TAG_MAX_PAGES || \ + tag->pagesCount * tag->bytesPerPage > ISO15693_TAG_MAX_SIZE) { + Dbprintf("Tag size error: pagesCount = %d, bytesPerPage=%d", tag->pagesCount, tag->bytesPerPage); + return; + } + } + + Iso15693InitTag(); // init simulator LED_A_ON(); - Dbprintf("ISO-15963 Simulating uid: %02X%02X%02X%02X%02X%02X%02X%02X block size %d", uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7], block_size); + Dbprintf("ISO-15963 Simulating uid: %02X%02X%02X%02X%02X%02X%02X%02X block size %d", tag->uid[0], tag->uid[1], tag->uid[2], tag->uid[3], tag->uid[4], tag->uid[5], tag->uid[6], tag->uid[7], tag->bytesPerPage); LED_C_ON(); - enum { NO_FIELD, IDLE, ACTIVATED, SELECTED, HALTED } chip_state = NO_FIELD; - bool button_pressed = false; int vHf; // in mV bool exit_loop = false; + uint8_t cmd[ISO15693_MAX_COMMAND_LENGTH] = {0}; + uint8_t recv[ISO15693_MAX_RESPONSE_LENGTH] = {0}; + uint8_t mask_len = 0; + uint8_t maskCpt = 0; + uint8_t cmdCpt = 0; + uint16_t recvLen = 0; + uint8_t error = 0; + uint8_t pageNum = 0; + uint8_t nbPages = 0; + while (exit_loop == false) { button_pressed = BUTTON_PRESS(); @@ -2134,11 +2165,11 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { WDT_HIT(); // find reader field - if (chip_state == NO_FIELD) { + if (tag->state == TAG_STATE_NO_FIELD) { vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15; if (vHf > MF_MINFIELDV) { - chip_state = IDLE; + tag->state = TAG_STATE_READY; LED_A_ON(); } else { continue; @@ -2146,7 +2177,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { } // Listen to reader - uint8_t cmd[ISO15693_MAX_COMMAND_LENGTH]; + uint32_t reader_sof_time = GetCountSspClk() & 0xfffffff8; uint32_t reader_eof_time = 0; int cmd_len = GetIso15693CommandFromReader(cmd, sizeof(cmd), &reader_eof_time); if (cmd_len < 0) { @@ -2154,6 +2185,330 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { break; } + if (cmd_len <= 3) + continue; + + LogTrace_ISO15693(cmd, cmd_len, (reader_sof_time * 4), (reader_eof_time * 4), NULL, true); + + // Shorten 0 terminated msgs + // (Some times received commands are prolonged with a random number of 0 bytes...) + while (cmd[cmd_len-1] == 0) { + cmd_len--; + if (cmd_len <= 3) + break; + } + + if (g_dbglevel >= DBG_DEBUG) { + Dbprintf("%d bytes read from reader:", cmd_len); + Dbhexdump(cmd_len, cmd, false); + } + + if (cmd_len < 3) + continue; + + // Check CRC and drop received cmd with bad CRC + uint16_t crc = CalculateCrc15(cmd, cmd_len - 2); + if ((( crc & 0xff ) != cmd[cmd_len - 2]) || (( crc >> 8 ) != cmd[cmd_len - 1])) { + crc = CalculateCrc15(cmd, ++cmd_len - 2); // if crc end with 00 + if ((( crc & 0xff ) != cmd[cmd_len - 2]) || (( crc >> 8 ) != cmd[cmd_len - 1])) { + crc = CalculateCrc15(cmd, ++cmd_len - 2); // if crc end with 00 00 + if ((( crc & 0xff ) != cmd[cmd_len - 2]) || (( crc >> 8 ) != cmd[cmd_len - 1])) { + if (g_dbglevel >= DBG_DEBUG) Dbprintf("CrcFail!"); + continue; + } + else if (g_dbglevel >= DBG_DEBUG) + Dbprintf("CrcOK"); + } + else if (g_dbglevel >= DBG_DEBUG) + Dbprintf("CrcOK"); + } + else if (g_dbglevel >= DBG_DEBUG) + Dbprintf("CrcOK"); + + cmd_len -= 2; // remove the CRC from the cmd + recvLen = 0; + + tag->expectFast = cmd[0] & ISO15_REQ_DATARATE_HIGH; + tag->expectFsk = cmd[0] & ISO15_REQ_SUBCARRIER_TWO; + + if (g_dbglevel >= DBG_DEBUG) { + if (tag->expectFsk) + Dbprintf("ISO15_REQ_SUBCARRIER_TWO support is currently experimental!"); + if (cmd[0] & ISO15_REQ_PROTOCOL_EXT) + Dbprintf("ISO15_REQ_PROTOCOL_EXT not supported!"); + if (cmd[0] & ISO15_REQ_OPTION) + Dbprintf("ISO15_REQ_OPTION not supported!"); + } + + if (cmd[0] & ISO15_REQ_INVENTORY && tag->state != TAG_STATE_SILENCED) { + // REQ_INVENTORY flaged requests are interpreted as a INVENTORY no matter + // what is the CMD (as observed from various actual tags) + + // TODO: support colision avoidances + + if (g_dbglevel >= DBG_DEBUG) { + Dbprintf("Inventory req"); + if (cmd[0] & ISO15_REQINV_SLOT1) + Dbprintf("ISO15_REQINV_SLOT1/SLOT16 not supported!"); + } + + cmdCpt = 2; + + // Check AFI + if (cmd[0] & ISO15_REQINV_AFI) { + if (cmd[cmdCpt] != tag->afi && cmd[cmdCpt] != 0) + continue; // bad AFI : drop request + cmdCpt++; + } + + // Check mask + if (cmdCpt >= cmd_len) + continue; // mask is not present : drop request + mask_len = cmd[cmdCpt++]; + + maskCpt = 0; + + while (mask_len >= 8 && cmdCpt < cmd_len && maskCpt < 8) { // Byte comparison + if (cmd[cmdCpt++] != tag->uid[maskCpt++]) { + error++; // mask don't match : drop request + break; + } + mask_len -= 8; + } + + if (mask_len > 0 && cmdCpt >= cmd_len) + continue; // mask is shorter than declared mask lenght: drop request + + while (mask_len > 0) { // Bit comparison + mask_len--; + if (((cmd[cmdCpt] >> mask_len) & 1) != ((tag->uid[maskCpt] >> mask_len) & 1)) { + error++; // mask don't match : drop request + break; + } + } + + if (error > 0) + continue; + + // No error: Answer + recv[0] = ISO15_NOERROR; + recv[1] = tag->dsfid; + memcpy(&recv[2], tag->uid, 8); + recvLen = 10; + } + else { + if (cmd[0]&ISO15_REQ_SELECT) { + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Selected Request"); + if (tag->state != TAG_STATE_SELECTED) + continue; // drop selected request if not selected + tag->state = TAG_STATE_READY; // Select flag set if already selected : unselect + } + + cmdCpt = 2; + if (cmd[0]&ISO15_REQ_ADDRESS) { + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Addressed Request"); + if (cmd_len <= cmdCpt+8) + continue; + if (memcmp(&cmd[cmdCpt], tag->uid, 8) != 0) + { + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address don't match tag uid"); + if (cmd[1] == ISO15693_SELECT) + tag->state = TAG_STATE_READY; // we are not anymore the selected TAG + continue; // drop addressed request with other uid + } + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address match tag uid"); + cmdCpt+=8; + } + else if (tag->state == TAG_STATE_SILENCED) + { + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Unaddressed request in quiet state: drop"); + continue; // drop unadressed request in quiet state + } + + switch(cmd[1]) { + case ISO15693_INVENTORY: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Inventory cmd"); + recv[0] = ISO15_NOERROR; + recv[1] = tag->dsfid; + memcpy(&recv[2], tag->uid, 8); + recvLen = 10; + break; + case ISO15693_STAYQUIET: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("StayQuiet cmd"); + tag->state = TAG_STATE_SILENCED; + break; + case ISO15693_READBLOCK: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("ReadBlock cmd"); + pageNum = cmd[cmdCpt++]; + if (pageNum >= tag->pagesCount) + error = ISO15_ERROR_BLOCK_UNAVAILABLE; + else { + recv[0] = ISO15_NOERROR; + recvLen = 1; + if ((cmd[0] & ISO15_REQ_OPTION)) { // ask for lock status + recv[1] = tag->locks[pageNum]; + recvLen++; + } + for (uint8_t i = 0 ; i < tag->bytesPerPage ; i++) + recv[recvLen+i] = tag->data[(pageNum * tag->bytesPerPage) + i]; + recvLen += tag->bytesPerPage; + } + break; + case ISO15693_WRITEBLOCK: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("WriteBlock cmd"); + pageNum = cmd[cmdCpt++]; + if (pageNum >= tag->pagesCount) + error = ISO15_ERROR_BLOCK_UNAVAILABLE; + else { + for (uint8_t i = 0 ; i < tag->bytesPerPage ; i++) + tag->data[(pageNum*tag->bytesPerPage) + i] = cmd[i + cmdCpt]; + recv[0] = ISO15_NOERROR; + recvLen = 1; + } + break; + case ISO15693_LOCKBLOCK: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("LockBlock cmd"); + pageNum = cmd[cmdCpt++]; + if (pageNum >= tag->pagesCount) + error = ISO15_ERROR_BLOCK_UNAVAILABLE; + else if (tag->locks[pageNum]) + error = ISO15_ERROR_BLOCK_LOCKED_ALREADY; + else { + tag->locks[pageNum] = 1; + recv[0] = ISO15_NOERROR; + recvLen = 1; + } + break; + case ISO15693_READ_MULTI_BLOCK: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("ReadMultiBlock cmd"); + pageNum = cmd[cmdCpt++]; + nbPages = cmd[cmdCpt++]; + if (pageNum+nbPages >= tag->pagesCount) + error = ISO15_ERROR_BLOCK_UNAVAILABLE; + else { + recv[0] = ISO15_NOERROR; + recvLen = 1; + for (uint16_t i = 0 ; i < (nbPages + 1) * tag->bytesPerPage && \ + recvLen + 3 < ISO15693_MAX_RESPONSE_LENGTH ; i++) { + if ((i % tag->bytesPerPage) == 0 && (cmd[0] & ISO15_REQ_OPTION)) + recv[recvLen++] = tag->locks[pageNum + (i / tag->bytesPerPage)]; + recv[recvLen++] = tag->data[(pageNum * tag->bytesPerPage) + i]; + } + if (recvLen + 3 > ISO15693_MAX_RESPONSE_LENGTH) // limit response size + recvLen = ISO15693_MAX_RESPONSE_LENGTH - 3; // to avoid overflow + } + break; + case ISO15693_WRITE_AFI: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("WriteAFI cmd"); + if (tag->afiLock) + error = ISO15_ERROR_BLOCK_LOCKED; + else { + tag->afi = cmd[cmdCpt++]; + recv[0] = ISO15_NOERROR; + recvLen = 1; + } + break; + case ISO15693_LOCK_AFI: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("LockAFI cmd"); + if (tag->afiLock) + error = ISO15_ERROR_BLOCK_LOCKED_ALREADY; + else { + tag->afiLock = true; + recv[0] = ISO15_NOERROR; + recvLen = 1; + } + break; + case ISO15693_WRITE_DSFID: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("WriteDSFID cmd"); + if (tag->dsfidLock) + error = ISO15_ERROR_BLOCK_LOCKED; + else { + tag->dsfid = cmd[cmdCpt++]; + recv[0] = ISO15_NOERROR; + recvLen = 1; + } + break; + case ISO15693_LOCK_DSFID: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("LockDSFID cmd"); + if (tag->dsfidLock) + error = ISO15_ERROR_BLOCK_LOCKED_ALREADY; + else { + tag->dsfidLock = true; + recv[0] = ISO15_NOERROR; + recvLen = 1; + } + break; + case ISO15693_SELECT: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Select cmd"); + tag->state = TAG_STATE_SELECTED; + recv[0] = ISO15_NOERROR; + recvLen = 1; + break; + case ISO15693_RESET_TO_READY: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("ResetToReady cmd"); + tag->state = TAG_STATE_READY; + recv[0] = ISO15_NOERROR; + recvLen = 1; + break; + case ISO15693_GET_SYSTEM_INFO: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("GetSystemInfo cmd"); + recv[0] = ISO15_NOERROR; + recv[1] = 0x0f; // sysinfo contain all info + memcpy(&recv[2], tag->uid, 8); + recv[10] = tag->dsfid; + recv[11] = tag->afi; + recv[12] = tag->pagesCount - 1; + recv[13] = tag->bytesPerPage - 1; + recv[14] = tag->ic; + recvLen = 15; + break; + case ISO15693_READ_MULTI_SECSTATUS: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("ReadMultiSecStatus cmd"); + pageNum = cmd[cmdCpt++]; + nbPages = cmd[cmdCpt++]; + if (pageNum + nbPages >= tag->pagesCount) + error = ISO15_ERROR_BLOCK_UNAVAILABLE; + else { + recv[0] = ISO15_NOERROR; + recvLen = 1; + for (uint8_t i = 0 ; i < nbPages + 1 ; i++) + recv[recvLen++] = tag->locks[pageNum + i]; + } + break; + default: + if (g_dbglevel >= DBG_DEBUG) + Dbprintf("ISO15693 CMD 0x%2X not supported", cmd[1]); + + error = ISO15_ERROR_CMD_NOT_SUP; + break; + } + + if (error != 0) { // Error happened + recv[0] = ISO15_RES_ERROR; + recv[1] = error; + recvLen = 2; + if (g_dbglevel >= DBG_DEBUG) + Dbprintf("ERROR 0x%2X in received request", error); + } + } + + if (recvLen > 0) { // We need to answer + AddCrc15(recv, recvLen); + recvLen += 2; + CodeIso15693AsTag(recv, recvLen); + tosend_t *ts = get_tosend(); + uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; + + if (tag->expectFsk) { // Not suppoted yet + if (g_dbglevel >= DBG_DEBUG) Dbprintf("%ERROR: FSK answers are not supported yet"); + //TransmitTo15693ReaderFSK(ts->buf,ts->max, &response_time, 0, !tag->expectFast); + } + else + TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, !tag->expectFast); + + LogTrace_ISO15693(recv, recvLen, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); + } +/* // TODO: check more flags if ((cmd_len >= 5) && (cmd[0] & ISO15_REQ_INVENTORY) && (cmd[1] == ISO15693_INVENTORY)) { bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); @@ -2166,14 +2521,14 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { resp_inv[1] = 0; // DSFID (data storage format identifier). 0x00 = not supported // 64-bit UID - resp_inv[2] = uid[7]; - resp_inv[3] = uid[6]; - resp_inv[4] = uid[5]; - resp_inv[5] = uid[4]; - resp_inv[6] = uid[3]; - resp_inv[7] = uid[2]; - resp_inv[8] = uid[1]; - resp_inv[9] = uid[0]; + resp_inv[2] = tag->uid[7]; + resp_inv[3] = tag->uid[6]; + resp_inv[4] = tag->uid[5]; + resp_inv[5] = tag->uid[4]; + resp_inv[6] = tag->uid[3]; + resp_inv[7] = tag->uid[2]; + resp_inv[8] = tag->uid[1]; + resp_inv[9] = tag->uid[0]; // CRC AddCrc15(resp_inv, 10); @@ -2184,7 +2539,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); LogTrace_ISO15693(resp_inv, CMD_INV_RESP, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); - chip_state = SELECTED; + tag->state = TAG_STATE_SELECTED; } // GET_SYSTEM_INFO @@ -2199,14 +2554,14 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { resp_sysinfo[1] = 0x0F; // Information flags (0x0F - DSFID, AFI, Mem size, IC) // 64-bit UID - resp_sysinfo[2] = uid[7]; - resp_sysinfo[3] = uid[6]; - resp_sysinfo[4] = uid[5]; - resp_sysinfo[5] = uid[4]; - resp_sysinfo[6] = uid[3]; - resp_sysinfo[7] = uid[2]; - resp_sysinfo[8] = uid[1]; - resp_sysinfo[9] = uid[0]; + resp_sysinfo[2] = tag->uid[7]; + resp_sysinfo[3] = tag->uid[6]; + resp_sysinfo[4] = tag->uid[5]; + resp_sysinfo[5] = tag->uid[4]; + resp_sysinfo[6] = tag->uid[3]; + resp_sysinfo[7] = tag->uid[2]; + resp_sysinfo[8] = tag->uid[1]; + resp_sysinfo[9] = tag->uid[0]; resp_sysinfo[10] = 0; // DSFID resp_sysinfo[11] = 0; // AFI @@ -2320,6 +2675,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); LogTrace_ISO15693(resp_writeblock, response_length, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); } +*/ } switch_off(); diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index a3cb5174a..31086f50d 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -33,6 +33,31 @@ #define DELAY_ISO15693_VCD_TO_VICC_READER 1056 // 1056/3,39MHz = 311.5us from end of command EOF to start of tag response #define DELAY_ISO15693_VICC_TO_VCD_READER 1024 // 1024/3.39MHz = 302.1us between end of tag response and next reader command +#define ISO15693_TAG_MAX_PAGES 64 // in page +#define ISO15693_TAG_MAX_SIZE 2048 // in byte (64 pages of 256 bits) + +typedef struct iso15693_tag { + uint8_t uid[8]; + uint8_t dsfid; + bool dsfidLock; + uint8_t afi; + bool afiLock; + uint8_t bytesPerPage; + uint8_t pagesCount; + uint8_t ic; + uint8_t locks[ISO15693_TAG_MAX_PAGES]; + uint8_t data[ISO15693_TAG_MAX_SIZE]; + enum { + TAG_STATE_NO_FIELD, + TAG_STATE_READY, + TAG_STATE_ACTIVATED, // useless ? + TAG_STATE_SELECTED, + TAG_STATE_SILENCED + } state; + bool expectFast; + bool expectFsk; +} iso15693_tag; + void Iso15693InitReader(void); void Iso15693InitTag(void); void CodeIso15693AsReader(const uint8_t *cmd, int n); From b62bedc1dc86810b5a39b8b57fcc2d37c455d752 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:06:18 +0100 Subject: [PATCH 02/36] iso15sim: add support for GET_RANDOM_NUMBER and ENABLE_PRIVACY --- armsrc/iso15693.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index debdef445..493e662b8 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2475,6 +2475,20 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { recv[recvLen++] = tag->locks[pageNum + i]; } break; + case ISO15693_GET_RANDOM_NUMBER: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("GetRandomNumber cmd"); + recv[0] = ISO15_NOERROR; + recv[1] = 0x42; // perfectly random numbers generated + recv[2] = 0x42; // using fair dice rolls + recvLen = 3; + break; + case ISO15693_ENABLE_PRIVACY: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("EnablePrivacy cmd"); + // not realy entering privacy mode + // just return NOERROR + recv[0] = ISO15_NOERROR; + recvLen = 1; + break; default: if (g_dbglevel >= DBG_DEBUG) Dbprintf("ISO15693 CMD 0x%2X not supported", cmd[1]); From f8514f48d7ab88fb5a4a1cc52b1daf66da674415 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:28:27 +0100 Subject: [PATCH 03/36] standalone: add HF_15SIM Standalone mode code source Also add it into documentation and build_all_firmwares.sh (standalone mode was added in Standalone Makefiles in a previous commit) --- armsrc/Standalone/hf_15sim.c | 203 ++++++++++++++++++ .../4_Advanced-compilation-parameters.md | 1 + tools/build_all_firmwares.sh | 2 +- 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 armsrc/Standalone/hf_15sim.c diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c new file mode 100644 index 000000000..46debe0e0 --- /dev/null +++ b/armsrc/Standalone/hf_15sim.c @@ -0,0 +1,203 @@ +//----------------------------------------------------------------------------- +// Copyright (C) lnv42 2024 +// 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. +//----------------------------------------------------------------------------- +// Main code for standalone HF/iso15693 Simulation +// This code is trying to dump an iso15 tag, then simulate it +// It doesn't support any password protected/authenticated features +//----------------------------------------------------------------------------- + +#include "standalone.h" // standalone definitions +#include "proxmark3_arm.h" +#include "fpgaloader.h" +#include "iso15693.h" +#include "iso15.h" +#include "protocols.h" +#include "iso15693tools.h" +#include "util.h" +#include "spiffs.h" +#include "appmain.h" +#include "dbprint.h" +#include "ticks.h" +#include "BigBuf.h" +#include "crc16.h" + +#define AddCrc15(data, len) compute_crc(CRC_15693, (data), (len), (data)+(len), (data)+(len)+1) +//#define CalculateCrc15(data, len) Crc16ex(CRC_15693, (data), (len) + 2); +#define CheckCrc15(data, len) check_crc(CRC_15693, (data), (len)) + +#define ISO15693_READER_TIMEOUT 330 // 330/212kHz = 1558us +#define HF_15693SIM_LOGFILE "hf_15693sim.trace" + +static void DownloadTraceInstructions(void) { + Dbprintf(""); + Dbprintf("To get the trace from flash and display it:"); + Dbprintf("1. mem spiffs dump -s "HF_15693SIM_LOGFILE" -d hf_15693sim.trace"); + Dbprintf("2. trace load -f hf_15693sim.trace"); + Dbprintf("3. trace list -t 15 -1"); +} + +void ModInfo(void) { + DbpString(" HF 15693 SIM, a ISO15693 simulator - lnv42"); + DownloadTraceInstructions(); +} + +void RunMod(void) { + StandAloneMode(); + + Dbprintf(_YELLOW_("HF 15693 SIM started")); +#ifdef WITH_FLASH + rdv40_spiffs_lazy_mount(); +#endif + + iso15693_tag *tag = (iso15693_tag*) BigBuf_get_EM_addr(); + if (tag == NULL) return; + + uint8_t cmd[8] = {0}; + int res; + uint16_t recvLen; + uint8_t recv[32]; + uint32_t eof_time = 0, start_time; + + cmd[0] = ISO15_REQ_DATARATE_HIGH; + cmd[1] = ISO15693_GET_SYSTEM_INFO; + AddCrc15(cmd, 2); + uint8_t i; + + LED_B_ON(); + + Dbprintf("Start dumping tag"); + + while (1) { + SpinDelay(200); + LED_B_OFF(); + if (BUTTON_HELD(500) > 0) + { + LEDsoff(); + return; + } + start_time = 0;//eof_time; + res = SendDataTag(cmd, 4, true, 1, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); + if (res < 0) + continue; + if (recvLen<10) // error: recv too short + continue; + if (CheckCrc15(recv,recvLen)) // error crc not valid + continue; + if (recv[0] & ISO15_RES_ERROR) // received error from tag + continue; + + memset(tag, 0, sizeof(iso15693_tag)); + memcpy(tag->uid, &recv[2], 8); + + i=10; + if (recv[1] & 0x01) + tag->dsfid = recv[i++]; + if (recv[1] & 0x02) + tag->afi = recv[i++]; + if (recv[1] & 0x04) + { + tag->pagesCount = recv[i++]+1; + tag->bytesPerPage = recv[i++]+1; + } + else + { // Set default tag values (if can't be readed in SYSINFO) + tag->bytesPerPage = 4; + tag->pagesCount = 128; + } + if (recv[1] & 0x08) + tag->ic = recv[i++]; + break; + } + + cmd[0] = ISO15_REQ_DATARATE_HIGH | ISO15_REQ_OPTION; + cmd[1] = ISO15693_READBLOCK; + + uint8_t blocknum = 0; + int retry; + + for (retry = 0; retry < 8; retry++) { + if (blocknum >= tag->pagesCount) + break; + + cmd[2] = blocknum; + AddCrc15(cmd, 3); + + start_time = eof_time; + res = SendDataTag(cmd, 5, false, 1, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); + + if (res < 0) + continue; + if (recvLen < 4 + tag->bytesPerPage) // error: recv too short + continue; + if (CheckCrc15(recv,recvLen)) // error crc not valid + continue; + if (recv[0] & ISO15_RES_ERROR) // received error from tag + continue; + + tag->locks[blocknum] = recv[1]; + memcpy(&tag->data[blocknum * tag->bytesPerPage], recv + 2, tag->bytesPerPage); + retry = 0; + blocknum++; + } + + LEDsoff(); + if (retry >= 8) + return; + + Dbprintf("Tag dumpped"); + Dbprintf("Start simulation"); + + SimTagIso15693(0, 0); + + Dbprintf("Simulation stopped"); + SpinDelay(200); + + uint32_t trace_len = BigBuf_get_traceLen(); +#ifndef WITH_FLASH + // Keep stuff in BigBuf for USB/BT dumping + if (trace_len > 0) + Dbprintf("[!] Trace length (bytes) = %u", trace_len); +#else + // Write stuff to spiffs logfile + if (trace_len > 0) { + Dbprintf("[!] Trace length (bytes) = %u", trace_len); + + uint8_t *trace_buffer = BigBuf_get_addr(); + if (!exists_in_spiffs(HF_15693SSIM_LOGFILE)) { + rdv40_spiffs_write( + HF_15693SIM_LOGFILE, trace_buffer, trace_len, RDV40_SPIFFS_SAFETY_SAFE); + Dbprintf("[!] Wrote trace to "HF_15693SIM_LOGFILE); + } else { + rdv40_spiffs_append( + HF_15693SIM_LOGFILE, trace_buffer, trace_len, RDV40_SPIFFS_SAFETY_SAFE); + Dbprintf("[!] Appended trace to "HF_15693SIM_LOGFILE); + } + } else { + Dbprintf("[!] Trace buffer is empty, nothing to write!"); + } + + LED_D_ON(); + rdv40_spiffs_lazy_unmount(); + LED_D_OFF(); + + SpinErr(LED_A, 200, 5); + SpinDelay(100); +#endif + + Dbprintf("-=[ exit ]=-"); + LEDsoff(); + DownloadTraceInstructions(); +} diff --git a/doc/md/Use_of_Proxmark/4_Advanced-compilation-parameters.md b/doc/md/Use_of_Proxmark/4_Advanced-compilation-parameters.md index 406a7bcec..3e85e1369 100644 --- a/doc/md/Use_of_Proxmark/4_Advanced-compilation-parameters.md +++ b/doc/md/Use_of_Proxmark/4_Advanced-compilation-parameters.md @@ -127,6 +127,7 @@ Here are the supported values you can assign to `STANDALONE` in `Makefile.platfo | HF_14ASNIFF | 14a sniff storing to flashmem - Micolous | HF_14BSNIFF | 14b sniff - jacopo-j | HF_15SNIFF | 15693 sniff storing to flashmem - Glaser +| HF_15SNIFF | 15693 simulator - lnv42 | HF_AVEFUL | MIFARE Ultralight read/simulation - Ave Ozkal | HF_BOG | 14a sniff with ULC/ULEV1/NTAG auth storing in flashmem - Bogito | HF_CARDHOPPER | Long distance (over IP) relay of 14a protocols - Sam Haskins diff --git a/tools/build_all_firmwares.sh b/tools/build_all_firmwares.sh index 22a3ee60f..2740d7150 100755 --- a/tools/build_all_firmwares.sh +++ b/tools/build_all_firmwares.sh @@ -32,7 +32,7 @@ mv bootrom/obj/bootrom.elf "$DEST/PM3BOOTROM.elf" # cf armsrc/Standalone/Makefile.hal STANDALONE_MODES=(LF_SKELETON) STANDALONE_MODES+=(LF_EM4100EMUL LF_EM4100RSWB LF_EM4100RSWW LF_EM4100RWC LF_HIDBRUTE LF_HIDFCBRUTE LF_ICEHID LF_MULTIHID LF_NEDAP_SIM LF_NEXID LF_PROXBRUTE LF_PROX2BRUTE LF_SAMYRUN LF_THAREXDE) -STANDALONE_MODES+=(HF_14ASNIFF HF_14BSNIFF HF_15SNIFF HF_AVEFUL HF_BOG HF_CARDHOPPER HF_COLIN HF_CRAFTBYTE HF_ICECLASS HF_LEGIC HF_LEGICSIM HF_MATTYRUN HF_MFCSIM HF_MSDSAL HF_REBLAY HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG) +STANDALONE_MODES+=(HF_14ASNIFF HF_14BSNIFF HF_15SNIFF HF_15SIM HF_AVEFUL HF_BOG HF_CARDHOPPER HF_COLIN HF_CRAFTBYTE HF_ICECLASS HF_LEGIC HF_LEGICSIM HF_MATTYRUN HF_MFCSIM HF_MSDSAL HF_REBLAY HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG) STANDALONE_MODES+=(DANKARMULTI) STANDALONE_MODES_REQ_BT=(HF_CARDHOPPER HF_REBLAY) STANDALONE_MODES_REQ_SMARTCARD=() From 3327b23edd8a521b69873986933e5e648680ab04 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:04:23 +0100 Subject: [PATCH 04/36] iso15sim fix & clean --- armsrc/iso15693.c | 171 +--------------------------------------------- 1 file changed, 2 insertions(+), 169 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 493e662b8..39c52e97b 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -107,7 +107,7 @@ /////////////////////////////////////////////////////////////////////// // buffers -#define ISO15693_MAX_RESPONSE_LENGTH 36 // allows read single block with the maximum block size of 256bits. Read multiple blocks not supported yet +#define ISO15693_MAX_RESPONSE_LENGTH 2116 // allows read multiple block with the maximum block size of 256bits and a maximum block number of 64 with REQ_OPTION (lock status for each block #define ISO15693_MAX_COMMAND_LENGTH 45 // allows write single block with the maximum block size of 256bits. Write multiple blocks not supported yet // 32 + 2 crc + 1 @@ -120,6 +120,7 @@ //#define Crc(data, len) Crc(CRC_15693, (data), (len)) #define CheckCrc15(data, len) check_crc(CRC_15693, (data), (len)) #define AddCrc15(data, len) compute_crc(CRC_15693, (data), (len), (data)+(len), (data)+(len)+1) +#define CalculateCrc15(data, len) Crc16ex(CRC_15693, (data), (len) + 2) static void BuildIdentifyRequest(uint8_t *cmd); @@ -2522,174 +2523,6 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { LogTrace_ISO15693(recv, recvLen, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); } -/* - // TODO: check more flags - if ((cmd_len >= 5) && (cmd[0] & ISO15_REQ_INVENTORY) && (cmd[1] == ISO15693_INVENTORY)) { - bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); - uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - - // Build INVENTORY command - uint8_t resp_inv[CMD_INV_RESP] = {0}; - - resp_inv[0] = 0; // No error, no protocol format extension - resp_inv[1] = 0; // DSFID (data storage format identifier). 0x00 = not supported - - // 64-bit UID - resp_inv[2] = tag->uid[7]; - resp_inv[3] = tag->uid[6]; - resp_inv[4] = tag->uid[5]; - resp_inv[5] = tag->uid[4]; - resp_inv[6] = tag->uid[3]; - resp_inv[7] = tag->uid[2]; - resp_inv[8] = tag->uid[1]; - resp_inv[9] = tag->uid[0]; - - // CRC - AddCrc15(resp_inv, 10); - CodeIso15693AsTag(resp_inv, CMD_INV_RESP); - - tosend_t *ts = get_tosend(); - - TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); - LogTrace_ISO15693(resp_inv, CMD_INV_RESP, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); - - tag->state = TAG_STATE_SELECTED; - } - - // GET_SYSTEM_INFO - if ((cmd[1] == ISO15693_GET_SYSTEM_INFO)) { - bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); - uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - - // Build GET_SYSTEM_INFO response - uint8_t resp_sysinfo[CMD_SYSINFO_RESP] = {0}; - - resp_sysinfo[0] = 0; // Response flags. - resp_sysinfo[1] = 0x0F; // Information flags (0x0F - DSFID, AFI, Mem size, IC) - - // 64-bit UID - resp_sysinfo[2] = tag->uid[7]; - resp_sysinfo[3] = tag->uid[6]; - resp_sysinfo[4] = tag->uid[5]; - resp_sysinfo[5] = tag->uid[4]; - resp_sysinfo[6] = tag->uid[3]; - resp_sysinfo[7] = tag->uid[2]; - resp_sysinfo[8] = tag->uid[1]; - resp_sysinfo[9] = tag->uid[0]; - - resp_sysinfo[10] = 0; // DSFID - resp_sysinfo[11] = 0; // AFI - - resp_sysinfo[12] = 0x1F; // Block count - resp_sysinfo[13] = block_size - 1; // Block size. - resp_sysinfo[14] = 0x01; // IC reference. - - // CRC - AddCrc15(resp_sysinfo, 15); - CodeIso15693AsTag(resp_sysinfo, CMD_SYSINFO_RESP); - - tosend_t *ts = get_tosend(); - - TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); - LogTrace_ISO15693(resp_sysinfo, CMD_SYSINFO_RESP, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); - } - - // READ_BLOCK and READ_MULTI_BLOCK - if ((cmd[1] == ISO15693_READBLOCK) || (cmd[1] == ISO15693_READ_MULTI_BLOCK)) { - bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); - bool addressed = cmd[0] & ISO15_REQ_ADDRESS; - bool option = cmd[0] & ISO15_REQ_OPTION; - uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - - uint8_t address_offset = 0; - if (addressed) { - address_offset = 8; - } - - uint8_t block_idx = cmd[2 + address_offset]; - uint8_t block_count = 1; - if (cmd[1] == ISO15693_READ_MULTI_BLOCK) { - block_count = cmd[3 + address_offset] + 1; - } - - // Build READ_(MULTI_)BLOCK response - int response_length = 3 + block_size * block_count; - int security_offset = 0; - if (option) { - response_length += block_count; - security_offset = 1; - } - uint8_t resp_readblock[response_length]; - memset(resp_readblock, 0, response_length); - - resp_readblock[0] = 0; // Response flags - for (int j = 0; j < block_count; j++) { - // where to put the data of the current block - int work_offset = 1 + j * (block_size + security_offset); - if (option) { - resp_readblock[work_offset] = 0; // Security status - } - // Block data - if (block_size * (block_idx + j + 1) <= CARD_MEMORY_SIZE) { - emlGet( - resp_readblock + (work_offset + security_offset), - block_size * (block_idx + j), - block_size - ); - } else { - memset(resp_readblock + work_offset + security_offset, 0, block_size); - } - } - - // CRC - AddCrc15(resp_readblock, response_length - 2); - CodeIso15693AsTag(resp_readblock, response_length); - - tosend_t *ts = get_tosend(); - - TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); - LogTrace_ISO15693(resp_readblock, response_length, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); - } - - // WRITE_BLOCK and WRITE_MULTI_BLOCK - if ((cmd[1] == ISO15693_WRITEBLOCK) || (cmd[1] == ISO15693_WRITE_MULTI_BLOCK)) { - bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); - bool addressed = cmd[0] & ISO15_REQ_ADDRESS; - uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - - uint8_t address_offset = 0; - if (addressed) { - address_offset = 8; - } - - uint8_t block_idx = cmd[2 + address_offset]; - uint8_t block_count = 1; - uint8_t multi_offset = 0; - if (cmd[1] == ISO15693_WRITE_MULTI_BLOCK) { - block_count = cmd[3 + address_offset] + 1; - multi_offset = 1; - } - uint8_t *data = cmd + 3 + address_offset + multi_offset; - - // write data - emlSet(data, (block_idx * block_size), (block_count * block_size)); - - // Build WRITE_(MULTI_)BLOCK response - int response_length = 3; - uint8_t resp_writeblock[response_length]; - memset(resp_writeblock, 0, response_length); - resp_writeblock[0] = 0; // Response flags - - // CRC - AddCrc15(resp_writeblock, response_length - 2); - CodeIso15693AsTag(resp_writeblock, response_length); - - tosend_t *ts = get_tosend(); - - TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); - LogTrace_ISO15693(resp_writeblock, response_length, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); - } -*/ } switch_off(); From fa3c2e386bb58dd2c7b28c879576ab884b4fe02a Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:21:21 +0100 Subject: [PATCH 05/36] improve iso15 sniff quality --- armsrc/iso15693.c | 228 +++++++++++++++++++++++++--------------------- fpga/hi_reader.v | 5 + 2 files changed, 129 insertions(+), 104 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 39c52e97b..b9264fd9f 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -107,7 +107,7 @@ /////////////////////////////////////////////////////////////////////// // buffers -#define ISO15693_MAX_RESPONSE_LENGTH 2116 // allows read multiple block with the maximum block size of 256bits and a maximum block number of 64 with REQ_OPTION (lock status for each block +#define ISO15693_MAX_RESPONSE_LENGTH 2116 // allows read multiple block with the maximum block size of 256bits and a maximum block number of 64 with REQ_OPTION (lock status for each block). #define ISO15693_MAX_COMMAND_LENGTH 45 // allows write single block with the maximum block size of 256bits. Write multiple blocks not supported yet // 32 + 2 crc + 1 @@ -1201,42 +1201,39 @@ static int RAMFUNC Handle15693SampleFromReader(bool bit, DecodeReader_t *reader) break; case STATE_READER_AWAIT_1ST_RISING_EDGE_OF_SOF: - reader->posCount++; if (bit) { // detected rising edge - if (reader->posCount < 4) { // rising edge too early (nominally expected at 5) + if (reader->posCount < 2) { // rising edge too early (nominally expected at 4) reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; } else { // SOF reader->state = STATE_READER_AWAIT_2ND_FALLING_EDGE_OF_SOF; + reader->posCount = 1; } } else { - if (reader->posCount > 5) { // stayed low for too long + reader->posCount++; + if (reader->posCount > 6) { // stayed low for too long DecodeReaderReset(reader); - } else { - // do nothing, keep waiting } } break; case STATE_READER_AWAIT_2ND_FALLING_EDGE_OF_SOF: - - reader->posCount++; - - if (bit == false) { // detected a falling edge - - if (reader->posCount < 20) { // falling edge too early (nominally expected at 21 earliest) + if (!bit) { // detected a falling edge + if (reader->posCount < 14) { // falling edge too early (nominally expected at 16 earliest) DecodeReaderReset(reader); - } else if (reader->posCount < 23) { // SOF for 1 out of 4 coding + } else if (reader->posCount <= 18) { // SOF for 1 out of 4 coding reader->Coding = CODING_1_OUT_OF_4; reader->state = STATE_READER_AWAIT_2ND_RISING_EDGE_OF_SOF; - } else if (reader->posCount < 28) { // falling edge too early (nominally expected at 29 latest) + reader->posCount = 1; + } else if (reader->posCount < 22) { // falling edge too early (nominally expected at 24 latest) DecodeReaderReset(reader); - } else { // SOF for 1 out of 256 coding + } else { // SOF for 1 out of 256 coding reader->Coding = CODING_1_OUT_OF_256; reader->state = STATE_READER_AWAIT_2ND_RISING_EDGE_OF_SOF; + reader->posCount = 1; } - } else { - if (reader->posCount > 29) { // stayed high for too long + reader->posCount++; + if (reader->posCount > 26) { // stayed high for too long reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; } else { // do nothing, keep waiting @@ -1245,60 +1242,42 @@ static int RAMFUNC Handle15693SampleFromReader(bool bit, DecodeReader_t *reader) break; case STATE_READER_AWAIT_2ND_RISING_EDGE_OF_SOF: - - reader->posCount++; - if (bit) { // detected rising edge - if (reader->Coding == CODING_1_OUT_OF_256) { - if (reader->posCount < 32) { // rising edge too early (nominally expected at 33) - reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; - } else { - reader->posCount = 1; - reader->bitCount = 0; + if (reader->posCount < 2) { // rising edge too early (nominally expected at 8) + reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; + } else { + reader->posCount = 1; + if (reader->Coding == CODING_1_OUT_OF_256) { + reader->bitCount = 1; reader->byteCount = 0; reader->sum1 = 1; - reader->state = STATE_READER_RECEIVE_DATA_1_OUT_OF_256; LED_B_ON(); - } - } else { // CODING_1_OUT_OF_4 - if (reader->posCount < 24) { // rising edge too early (nominally expected at 25) - reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; - } else { - reader->posCount = 1; + reader->state = STATE_READER_RECEIVE_DATA_1_OUT_OF_256; + } else { // CODING_1_OUT_OF_4 reader->state = STATE_READER_AWAIT_END_OF_SOF_1_OUT_OF_4; } } } else { - if (reader->Coding == CODING_1_OUT_OF_256) { - if (reader->posCount > 34) { // signal stayed low for too long - DecodeReaderReset(reader); - } else { - // do nothing, keep waiting - } - } else { // CODING_1_OUT_OF_4 - if (reader->posCount > 26) { // signal stayed low for too long - DecodeReaderReset(reader); - } else { - // do nothing, keep waiting - } + reader->posCount++; + if (reader->posCount > 6) { // signal stayed low for too long + DecodeReaderReset(reader); + } else { + // do nothing, keep waiting } } break; case STATE_READER_AWAIT_END_OF_SOF_1_OUT_OF_4: - - reader->posCount++; - if (bit) { - if (reader->posCount == 9) { - reader->posCount = 1; + reader->posCount++; + + if (reader->posCount == 8) { + reader->posCount = 0; reader->bitCount = 0; reader->byteCount = 0; - reader->sum1 = 1; + reader->sum1 = 0; reader->state = STATE_READER_RECEIVE_DATA_1_OUT_OF_4; LED_B_ON(); - } else { - // do nothing, keep waiting } } else { // unexpected falling edge DecodeReaderReset(reader); @@ -1306,62 +1285,103 @@ static int RAMFUNC Handle15693SampleFromReader(bool bit, DecodeReader_t *reader) break; case STATE_READER_RECEIVE_DATA_1_OUT_OF_4: - - reader->posCount++; - - if (reader->posCount == 1) { - - reader->sum1 = bit ? 1 : 0; - - } else if (reader->posCount <= 4) { - - if (bit) - reader->sum1++; - - } else if (reader->posCount == 5) { - - reader->sum2 = bit ? 1 : 0; - - } else { - if (bit) - reader->sum2++; - } - - if (reader->posCount == 8) { - reader->posCount = 0; - if (reader->sum1 <= 1 && reader->sum2 >= 3) { // EOF - LED_B_OFF(); // Finished receiving - DecodeReaderReset(reader); - if (reader->byteCount != 0) { - return true; - } - - } else if (reader->sum1 >= 3 && reader->sum2 <= 1) { // detected a 2bit position - reader->shiftReg >>= 2; - reader->shiftReg |= (reader->bitCount << 6); - } - - if (reader->bitCount == 15) { // we have a full byte - - reader->output[reader->byteCount++] = reader->shiftReg; - if (reader->byteCount > reader->byteCountMax) { - // buffer overflow, give up - LED_B_OFF(); + if (!bit) { + reader->sum1++; + if (reader->sum1 == 1) { // first low bit + if (reader->posCount <= 6) { // bits : 00 + reader->shiftReg >>= 2; + //reader->shiftReg |= (0 << 6); + reader->bitCount += 2; + reader->posCount = -28; + } else if (reader->posCount <= 9) { // EOF + LED_B_OFF(); // Finished receiving DecodeReaderReset(reader); + if (reader->byteCount > 0) { + return true; + } + } else if (reader->posCount <= 14) { // bits : 01 + reader->shiftReg >>= 2; + reader->shiftReg |= (1 << 6); + reader->bitCount += 2; + reader->posCount = -20; + } else if (reader->posCount < 18) { // unexpected falling edge + DecodeReaderReset(reader); + if (reader->byteCount >= 0) { + reader->output[reader->byteCount++] = reader->posCount; + reader->output[reader->byteCount++] = reader->bitCount; + reader->output[reader->byteCount++] = 0x42; + return true; + } + } else if (reader->posCount <= 22) { // bits : 10 + reader->shiftReg >>= 2; + reader->shiftReg |= (2 << 6); + reader->bitCount += 2; + reader->posCount = -12; + } else if (reader->posCount < 26) { // unexpected falling edge + DecodeReaderReset(reader); + if (reader->byteCount >= 0) { + reader->output[reader->byteCount++] = reader->posCount; + reader->output[reader->byteCount++] = reader->bitCount; + reader->output[reader->byteCount++] = 0x43; + return true; + } + } else { // bits : 11 + reader->shiftReg >>= 2; + reader->shiftReg |= (3 << 6); + reader->bitCount += 2; + reader->posCount = -4; } - reader->bitCount = 0; - reader->shiftReg = 0; - if (reader->byteCount == reader->jam_search_len) { - if (!memcmp(reader->output, reader->jam_search_string, reader->jam_search_len)) { - LED_D_ON(); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_JAM); - reader->state = STATE_READER_RECEIVE_JAMMING; + if (reader->bitCount == 8) + { + reader->output[reader->byteCount++] = reader->shiftReg; + if (reader->byteCount > reader->byteCountMax) { + // buffer overflow, give up + LED_B_OFF(); + DecodeReaderReset(reader); + } + + reader->bitCount = 0; + reader->shiftReg = 0; + if (reader->byteCount == reader->jam_search_len) { + if (!memcmp(reader->output, reader->jam_search_string, reader->jam_search_len)) { + LED_D_ON(); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_JAM); + reader->state = STATE_READER_RECEIVE_JAMMING; + } } } - - } else { - reader->bitCount++; + } else if (reader->sum1 > 6) { // too long low bit + DecodeReaderReset(reader); + if (reader->byteCount >= 0) { + reader->output[reader->byteCount++] = reader->posCount; + reader->output[reader->byteCount++] = reader->bitCount; + reader->output[reader->byteCount++] = 0x44; + return true; + } + } + } else { + reader->posCount++; + if (reader->posCount > 30) { + reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; + if (reader->byteCount >= 0) { + reader->output[reader->byteCount++] = reader->posCount; + reader->output[reader->byteCount++] = reader->bitCount; + reader->output[reader->byteCount++] = 0x45; + return true; + } + } + if (reader->sum1 == 1) { + reader->state = STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF; + if (reader->byteCount >= 0) { + reader->output[reader->byteCount++] = reader->posCount; + reader->output[reader->byteCount++] = reader->bitCount; + reader->output[reader->byteCount++] = 0x46; + return true; + } + } else if (reader->sum1 > 1) { + reader->posCount += reader->sum1; + reader->sum1 = 0; } } break; diff --git a/fpga/hi_reader.v b/fpga/hi_reader.v index 1a0b375d3..07afdc2de 100644 --- a/fpga/hi_reader.v +++ b/fpga/hi_reader.v @@ -44,8 +44,13 @@ reg after_hysteresis, after_hysteresis_prev, after_hysteresis_prev_prev; reg [11:0] has_been_low_for; always @(negedge adc_clk) begin +`ifdef WITH_HF_15_LOWSIGNAL + if (& adc_d[7:4]) after_hysteresis <= 1'b1; + else if (~(| adc_d[7:6])) after_hysteresis <= 1'b0; +`else if (& adc_d[7:0]) after_hysteresis <= 1'b1; else if (~(| adc_d[7:0])) after_hysteresis <= 1'b0; +`endif if (after_hysteresis) begin From c27cf92b764e5c0a249f8091abecc736f62bb742 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:07:02 +0100 Subject: [PATCH 06/36] iso15sniff: enable lowsignal sniffing --- fpga/Makefile | 2 +- fpga/fpga_pm3_hf_15.bit | Bin 42175 -> 42175 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/Makefile b/fpga/Makefile index 1731d4869..8a95c7638 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -67,7 +67,7 @@ TARGET1_OPTIONS = -define \{WITH_LF WITH_LF0 WITH_LF1 WITH_LF2 WITH_LF3\} # RDV40/Generic - Enable all HF modules except Felica TARGET2_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF2 WITH_HF3 WITH_HF5\} # RDV40/Generic - Enable all HF modules except Felica and ISO14443, select HF_15 instead of HF -TARGET3_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF5 WITH_HF_15\} +TARGET3_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF5 WITH_HF_15 WITH_HF_15_LOWSIGNAL\} # RDV40/Generic - Enable all HF modules except ISO14443 TARGET4_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF4 WITH_HF5\} # ICOPYX diff --git a/fpga/fpga_pm3_hf_15.bit b/fpga/fpga_pm3_hf_15.bit index c19f86bedfdf8151f9d0ee0be6e171581f44ce13..32d8344da71c7721205337098d2a49e6f8aaedef 100644 GIT binary patch literal 42175 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 Date: Thu, 18 Jan 2024 19:01:39 +0100 Subject: [PATCH 07/36] HF_15SIM: fix & add debug info --- armsrc/Standalone/hf_15sim.c | 43 +++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c index 46debe0e0..5c78f1a98 100644 --- a/armsrc/Standalone/hf_15sim.c +++ b/armsrc/Standalone/hf_15sim.c @@ -62,6 +62,8 @@ void RunMod(void) { rdv40_spiffs_lazy_mount(); #endif + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); + iso15693_tag *tag = (iso15693_tag*) BigBuf_get_EM_addr(); if (tag == NULL) return; @@ -78,7 +80,7 @@ void RunMod(void) { LED_B_ON(); - Dbprintf("Start dumping tag"); + Dbprintf("Wait for a dumpable tag"); while (1) { SpinDelay(200); @@ -86,18 +88,33 @@ void RunMod(void) { if (BUTTON_HELD(500) > 0) { LEDsoff(); + Dbprintf("Quiting"); return; } start_time = 0;//eof_time; - res = SendDataTag(cmd, 4, true, 1, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); + res = SendDataTag(cmd, 4, true, true, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); if (res < 0) + { + Dbprintf("res < 0"); continue; + } if (recvLen<10) // error: recv too short + { + Dbprintf("recvLen<10"); continue; - if (CheckCrc15(recv,recvLen)) // error crc not valid + } + if (!CheckCrc15(recv,recvLen)) // error crc not valid + { + Dbprintf("crc failed"); continue; + } if (recv[0] & ISO15_RES_ERROR) // received error from tag + { + Dbprintf("error received"); continue; + } + + Dbprintf("Start dumping tag"); memset(tag, 0, sizeof(iso15693_tag)); memcpy(tag->uid, &recv[2], 8); @@ -136,16 +153,28 @@ void RunMod(void) { AddCrc15(cmd, 3); start_time = eof_time; - res = SendDataTag(cmd, 5, false, 1, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); + res = SendDataTag(cmd, 5, false, true, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); if (res < 0) + { + Dbprintf("res < 0"); continue; + } if (recvLen < 4 + tag->bytesPerPage) // error: recv too short + { + Dbprintf("recvLen < 4 + tag->bytesPerPage"); continue; - if (CheckCrc15(recv,recvLen)) // error crc not valid + } + if (!CheckCrc15(recv,recvLen)) // error crc not valid + { + Dbprintf("crc failed"); continue; + } if (recv[0] & ISO15_RES_ERROR) // received error from tag + { + Dbprintf("error received"); continue; + } tag->locks[blocknum] = recv[1]; memcpy(&tag->data[blocknum * tag->bytesPerPage], recv + 2, tag->bytesPerPage); @@ -155,7 +184,11 @@ void RunMod(void) { LEDsoff(); if (retry >= 8) + { + Dbprintf("Max retry attemps exeeded"); + Dbprintf("-=[ exit ]=-"); return; + } Dbprintf("Tag dumpped"); Dbprintf("Start simulation"); From e6a509b8a77e8c8f57a2815ca9fb8ac173d43485 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:44:28 +0100 Subject: [PATCH 08/36] iso15sim: fix reversed UID print --- armsrc/iso15693.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index b9264fd9f..931e87c64 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2159,7 +2159,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { LED_A_ON(); - Dbprintf("ISO-15963 Simulating uid: %02X%02X%02X%02X%02X%02X%02X%02X block size %d", tag->uid[0], tag->uid[1], tag->uid[2], tag->uid[3], tag->uid[4], tag->uid[5], tag->uid[6], tag->uid[7], tag->bytesPerPage); + Dbprintf("ISO-15963 Simulating uid: %02X%02X%02X%02X%02X%02X%02X%02X, %u bytes/blocks x %u blocks", tag->uid[7], tag->uid[6], tag->uid[5], tag->uid[4], tag->uid[3], tag->uid[2], tag->uid[1], tag->uid[0], tag->bytesPerPage, tag->pagesCount); LED_C_ON(); From 4365378a2ceb888c3a1c93461855529ef55fe21a Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:52:34 +0100 Subject: [PATCH 09/36] iso15sim: safer flag ckecking --- armsrc/iso15693.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 931e87c64..af352a76d 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2234,7 +2234,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { if ((( crc & 0xff ) != cmd[cmd_len - 2]) || (( crc >> 8 ) != cmd[cmd_len - 1])) { crc = CalculateCrc15(cmd, ++cmd_len - 2); // if crc end with 00 00 if ((( crc & 0xff ) != cmd[cmd_len - 2]) || (( crc >> 8 ) != cmd[cmd_len - 1])) { - if (g_dbglevel >= DBG_DEBUG) Dbprintf("CrcFail!"); + if (g_dbglevel >= DBG_DEBUG) Dbprintf("CrcFail!, expected CRC=%02X%02X", crc & 0xff, crc >> 8); continue; } else if (g_dbglevel >= DBG_DEBUG) @@ -2249,19 +2249,19 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { cmd_len -= 2; // remove the CRC from the cmd recvLen = 0; - tag->expectFast = cmd[0] & ISO15_REQ_DATARATE_HIGH; - tag->expectFsk = cmd[0] & ISO15_REQ_SUBCARRIER_TWO; + tag->expectFast = ((cmd[0] & ISO15_REQ_DATARATE_HIGH) == ISO15_REQ_DATARATE_HIGH); + tag->expectFsk = ((cmd[0] & ISO15_REQ_SUBCARRIER_TWO) == ISO15_REQ_SUBCARRIER_TWO); if (g_dbglevel >= DBG_DEBUG) { if (tag->expectFsk) Dbprintf("ISO15_REQ_SUBCARRIER_TWO support is currently experimental!"); - if (cmd[0] & ISO15_REQ_PROTOCOL_EXT) + if ((cmd[0] & ISO15_REQ_PROTOCOL_EXT) == ISO15_REQ_PROTOCOL_EXT) Dbprintf("ISO15_REQ_PROTOCOL_EXT not supported!"); - if (cmd[0] & ISO15_REQ_OPTION) + if ((cmd[0] & ISO15_REQ_OPTION) == ISO15_REQ_OPTION) Dbprintf("ISO15_REQ_OPTION not supported!"); } - if (cmd[0] & ISO15_REQ_INVENTORY && tag->state != TAG_STATE_SILENCED) { + if (((cmd[0] & ISO15_REQ_INVENTORY) == ISO15_REQ_INVENTORY) && tag->state != TAG_STATE_SILENCED) { // REQ_INVENTORY flaged requests are interpreted as a INVENTORY no matter // what is the CMD (as observed from various actual tags) @@ -2269,14 +2269,14 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { if (g_dbglevel >= DBG_DEBUG) { Dbprintf("Inventory req"); - if (cmd[0] & ISO15_REQINV_SLOT1) + if ((cmd[0] & ISO15_REQINV_SLOT1) == ISO15_REQINV_SLOT1) Dbprintf("ISO15_REQINV_SLOT1/SLOT16 not supported!"); } cmdCpt = 2; // Check AFI - if (cmd[0] & ISO15_REQINV_AFI) { + if ((cmd[0] & ISO15_REQINV_AFI) == ISO15_REQINV_AFI) { if (cmd[cmdCpt] != tag->afi && cmd[cmdCpt] != 0) continue; // bad AFI : drop request cmdCpt++; @@ -2318,7 +2318,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { recvLen = 10; } else { - if (cmd[0]&ISO15_REQ_SELECT) { + if ((cmd[0] & ISO15_REQ_SELECT) == ISO15_REQ_SELECT) { if (g_dbglevel >= DBG_DEBUG) Dbprintf("Selected Request"); if (tag->state != TAG_STATE_SELECTED) continue; // drop selected request if not selected @@ -2326,7 +2326,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { } cmdCpt = 2; - if (cmd[0]&ISO15_REQ_ADDRESS) { + if ((cmd[0] & ISO15_REQ_ADDRESS) == ISO15_REQ_ADDRESS) { if (g_dbglevel >= DBG_DEBUG) Dbprintf("Addressed Request"); if (cmd_len <= cmdCpt+8) continue; @@ -2366,7 +2366,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { else { recv[0] = ISO15_NOERROR; recvLen = 1; - if ((cmd[0] & ISO15_REQ_OPTION)) { // ask for lock status + if ((cmd[0] & ISO15_REQ_OPTION) == ISO15_REQ_OPTION) { // ask for lock status recv[1] = tag->locks[pageNum]; recvLen++; } From 5e2ff118385c820283a9bd2db004e99e04927c5b Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:52:57 +0100 Subject: [PATCH 10/36] iso15sim: fix CRC lenght calculation du to change in Crc16ex() --- armsrc/iso15693.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index af352a76d..265628eb7 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -120,7 +120,7 @@ //#define Crc(data, len) Crc(CRC_15693, (data), (len)) #define CheckCrc15(data, len) check_crc(CRC_15693, (data), (len)) #define AddCrc15(data, len) compute_crc(CRC_15693, (data), (len), (data)+(len), (data)+(len)+1) -#define CalculateCrc15(data, len) Crc16ex(CRC_15693, (data), (len) + 2) +#define CalculateCrc15(data, len) Crc16ex(CRC_15693, (data), (len)) static void BuildIdentifyRequest(uint8_t *cmd); From 120c9ab534ada604be24282b905e154d3354e161 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:09:37 +0100 Subject: [PATCH 11/36] iso15sim: fix addressed request minimal size --- armsrc/iso15693.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 265628eb7..180478297 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2328,7 +2328,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { cmdCpt = 2; if ((cmd[0] & ISO15_REQ_ADDRESS) == ISO15_REQ_ADDRESS) { if (g_dbglevel >= DBG_DEBUG) Dbprintf("Addressed Request"); - if (cmd_len <= cmdCpt+8) + if (cmd_len < cmdCpt+8) continue; if (memcmp(&cmd[cmdCpt], tag->uid, 8) != 0) { From 9611b411daf9eceeb1ec431eaf2a6dcdf4566e9e Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:18:41 +0100 Subject: [PATCH 12/36] iso15sim: fix: remove reader command tracing from SimTagIso() It's now already included in GetIso15693CommandFromReader(). --- armsrc/iso15693.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 180478297..6a9b47b4d 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2209,8 +2209,6 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { if (cmd_len <= 3) continue; - LogTrace_ISO15693(cmd, cmd_len, (reader_sof_time * 4), (reader_eof_time * 4), NULL, true); - // Shorten 0 terminated msgs // (Some times received commands are prolonged with a random number of 0 bytes...) while (cmd[cmd_len-1] == 0) { From 40069f6fd13bfe3e3b398622e39ebe6f6da58a8c Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:03:32 +0100 Subject: [PATCH 13/36] iso15sim: fix compile issue: remove unused variable --- armsrc/iso15693.c | 1 - 1 file changed, 1 deletion(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 6a9b47b4d..ec029c3f6 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2198,7 +2198,6 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { } // Listen to reader - uint32_t reader_sof_time = GetCountSspClk() & 0xfffffff8; uint32_t reader_eof_time = 0; int cmd_len = GetIso15693CommandFromReader(cmd, sizeof(cmd), &reader_eof_time); if (cmd_len < 0) { From bad694d7799ed491c653b9132317a53dd626df46 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:37:51 +0100 Subject: [PATCH 14/36] iso15sim: unfix the random number --- armsrc/iso15693.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index ec029c3f6..b5bda6a95 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2496,8 +2496,8 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { case ISO15693_GET_RANDOM_NUMBER: if (g_dbglevel >= DBG_DEBUG) Dbprintf("GetRandomNumber cmd"); recv[0] = ISO15_NOERROR; - recv[1] = 0x42; // perfectly random numbers generated - recv[2] = 0x42; // using fair dice rolls + recv[1] = (uint8_t)(reader_eof_time >> 24) ^ 0xFF; // poor random number + recv[2] = (uint8_t)(reader_eof_time >> 16) ^ 0xFF; recvLen = 3; break; case ISO15693_ENABLE_PRIVACY: From a72e72a0f71d6d70fc28d0461df5ad70bf3870b7 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:49:02 +0100 Subject: [PATCH 15/36] standalone: hf_15sim: reduce debug --- armsrc/Standalone/hf_15sim.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c index 5c78f1a98..07587afb9 100644 --- a/armsrc/Standalone/hf_15sim.c +++ b/armsrc/Standalone/hf_15sim.c @@ -94,10 +94,7 @@ void RunMod(void) { start_time = 0;//eof_time; res = SendDataTag(cmd, 4, true, true, recv, sizeof(recv), start_time, ISO15693_READER_TIMEOUT, &eof_time, &recvLen); if (res < 0) - { - Dbprintf("res < 0"); continue; - } if (recvLen<10) // error: recv too short { Dbprintf("recvLen<10"); @@ -158,6 +155,7 @@ void RunMod(void) { if (res < 0) { Dbprintf("res < 0"); + SpinDelay(100); continue; } if (recvLen < 4 + tag->bytesPerPage) // error: recv too short From 8dc87d543269d20111c52663c2f3e49c19aa5428 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:03:57 +0100 Subject: [PATCH 16/36] iso15sim: get more variable randoms --- armsrc/iso15693.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index b5bda6a95..dfc3e6829 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2496,8 +2496,8 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { case ISO15693_GET_RANDOM_NUMBER: if (g_dbglevel >= DBG_DEBUG) Dbprintf("GetRandomNumber cmd"); recv[0] = ISO15_NOERROR; - recv[1] = (uint8_t)(reader_eof_time >> 24) ^ 0xFF; // poor random number - recv[2] = (uint8_t)(reader_eof_time >> 16) ^ 0xFF; + recv[1] = (uint8_t)(reader_eof_time) ^ 0xFF; // poor random number + recv[2] = (uint8_t)(reader_eof_time >> 8) ^ 0xFF; recvLen = 3; break; case ISO15693_ENABLE_PRIVACY: From afa821b3ec3d6604e3aacd03b22d415a9e299841 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:04:12 +0100 Subject: [PATCH 17/36] iso15sim: fix: reset error to 0 after an error append --- armsrc/iso15693.c | 1 + 1 file changed, 1 insertion(+) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index dfc3e6829..73274859f 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2519,6 +2519,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { recv[0] = ISO15_RES_ERROR; recv[1] = error; recvLen = 2; + error = 0; if (g_dbglevel >= DBG_DEBUG) Dbprintf("ERROR 0x%2X in received request", error); } From d73576ecf50e346006761f56146b27e0c9669195 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:04:58 +0100 Subject: [PATCH 18/36] standalone: hf_15sim: text/debug fix --- armsrc/Standalone/hf_15sim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c index 07587afb9..02b22a60a 100644 --- a/armsrc/Standalone/hf_15sim.c +++ b/armsrc/Standalone/hf_15sim.c @@ -154,7 +154,6 @@ void RunMod(void) { if (res < 0) { - Dbprintf("res < 0"); SpinDelay(100); continue; } @@ -188,7 +187,7 @@ void RunMod(void) { return; } - Dbprintf("Tag dumpped"); + Dbprintf("Tag dumped"); Dbprintf("Start simulation"); SimTagIso15693(0, 0); From 4a45aaf06503e78570ed82b9469d1a0a4badf92a Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:07:17 +0100 Subject: [PATCH 19/36] standalone: hf_15sim: adapt instruction depending on flash presence --- armsrc/Standalone/hf_15sim.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c index 02b22a60a..f0bb9aefb 100644 --- a/armsrc/Standalone/hf_15sim.c +++ b/armsrc/Standalone/hf_15sim.c @@ -43,10 +43,15 @@ static void DownloadTraceInstructions(void) { Dbprintf(""); +#ifndef WITH_FLASH Dbprintf("To get the trace from flash and display it:"); Dbprintf("1. mem spiffs dump -s "HF_15693SIM_LOGFILE" -d hf_15693sim.trace"); Dbprintf("2. trace load -f hf_15693sim.trace"); Dbprintf("3. trace list -t 15 -1"); +#else + Dbprintf("To get the trace from PM3 memory:"); + Dbprintf("trace list -t 15"); +#endif } void ModInfo(void) { From f71eaaec55e2b5125f6e23ca202ec9d4815c503c Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:30:11 +0100 Subject: [PATCH 20/36] iso15sim: add suppot for SET_PASSWORD --- armsrc/iso15693.c | 23 +++++++++++++++++++++-- armsrc/iso15693.h | 2 ++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 73274859f..46016f725 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2176,6 +2176,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { uint8_t error = 0; uint8_t pageNum = 0; uint8_t nbPages = 0; + uint8_t pwdId = 0; while (exit_loop == false) { @@ -2495,11 +2496,29 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { break; case ISO15693_GET_RANDOM_NUMBER: if (g_dbglevel >= DBG_DEBUG) Dbprintf("GetRandomNumber cmd"); + tag->random[0] = (uint8_t)(reader_eof_time) ^ 0xFF; // poor random number + tag->random[1] = (uint8_t)(reader_eof_time >> 8) ^ 0xFF; recv[0] = ISO15_NOERROR; - recv[1] = (uint8_t)(reader_eof_time) ^ 0xFF; // poor random number - recv[2] = (uint8_t)(reader_eof_time >> 8) ^ 0xFF; + recv[1] = tag->random[0]; // poor random number + recv[2] = tag->random[1]; recvLen = 3; break; + case ISO15693_SET_PASSWORD: + if (g_dbglevel >= DBG_DEBUG) Dbprintf("SetPassword cmd"); + if (cmd[cmdCpt++] == tag->ic) + { + pwdId = cmd[cmdCpt++]; + if (pwdId == 4) // Privacy password + { + tag->privacyPasswd[0] = cmd[cmdCpt] ^ tag->random[0]; + tag->privacyPasswd[1] = cmd[cmdCpt+1] ^ tag->random[1]; + tag->privacyPasswd[2] = cmd[cmdCpt+2] ^ tag->random[0]; + tag->privacyPasswd[3] = cmd[cmdCpt+3] ^ tag->random[1]; + } + } + recv[0] = ISO15_NOERROR; + recvLen = 1; + break; case ISO15693_ENABLE_PRIVACY: if (g_dbglevel >= DBG_DEBUG) Dbprintf("EnablePrivacy cmd"); // not realy entering privacy mode diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 31086f50d..1de06520d 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -47,6 +47,8 @@ typedef struct iso15693_tag { uint8_t ic; uint8_t locks[ISO15693_TAG_MAX_PAGES]; uint8_t data[ISO15693_TAG_MAX_SIZE]; + uint8_t random[2]; + uint8_t privacyPasswd[4]; enum { TAG_STATE_NO_FIELD, TAG_STATE_READY, From 7cd9f20efbd6b66b3aefed77472ecf419262bb4c Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:30:46 +0100 Subject: [PATCH 21/36] iso15sim: add support for addressed requests including IC number --- armsrc/iso15693.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 46016f725..7212256fa 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2330,10 +2330,14 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { continue; if (memcmp(&cmd[cmdCpt], tag->uid, 8) != 0) { - if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address don't match tag uid"); - if (cmd[1] == ISO15693_SELECT) - tag->state = TAG_STATE_READY; // we are not anymore the selected TAG - continue; // drop addressed request with other uid + if (cmd[cmdCpt] != tag->ic || cmd_len < cmdCpt+9 \ + || memcmp(&cmd[cmdCpt+1], tag->uid, 8) != 0) + { // check uid even if IC is present + if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address don't match tag uid"); + if (cmd[1] == ISO15693_SELECT) + tag->state = TAG_STATE_READY; // we are not anymore the selected TAG + continue; // drop addressed request with other uid + } } if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address match tag uid"); cmdCpt+=8; From 548b8046500cb11a42a1b7a4247f3fee9c9b1f07 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:31:11 +0100 Subject: [PATCH 22/36] standalone: hf_15sim: fix typo --- armsrc/Standalone/hf_15sim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c index f0bb9aefb..f6427686a 100644 --- a/armsrc/Standalone/hf_15sim.c +++ b/armsrc/Standalone/hf_15sim.c @@ -43,7 +43,7 @@ static void DownloadTraceInstructions(void) { Dbprintf(""); -#ifndef WITH_FLASH +#ifdef WITH_FLASH Dbprintf("To get the trace from flash and display it:"); Dbprintf("1. mem spiffs dump -s "HF_15693SIM_LOGFILE" -d hf_15693sim.trace"); Dbprintf("2. trace load -f hf_15693sim.trace"); From c8dce595ba4d81be3becc8e273eba56338c2ada3 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:16:02 +0100 Subject: [PATCH 23/36] hf15sim: fix: ignore manifacturer code is present in requests --- armsrc/iso15693.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 7212256fa..6e0e4c42a 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2330,14 +2330,14 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { continue; if (memcmp(&cmd[cmdCpt], tag->uid, 8) != 0) { - if (cmd[cmdCpt] != tag->ic || cmd_len < cmdCpt+9 \ - || memcmp(&cmd[cmdCpt+1], tag->uid, 8) != 0) - { // check uid even if IC is present + if (cmd_len < cmdCpt+9 || memcmp(&cmd[cmdCpt+1], tag->uid, 8) != 0) + { // check uid even if manifacturer byte is present if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address don't match tag uid"); if (cmd[1] == ISO15693_SELECT) tag->state = TAG_STATE_READY; // we are not anymore the selected TAG continue; // drop addressed request with other uid } + cmdCpt++; } if (g_dbglevel >= DBG_DEBUG) Dbprintf("Address match tag uid"); cmdCpt+=8; @@ -2509,7 +2509,9 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { break; case ISO15693_SET_PASSWORD: if (g_dbglevel >= DBG_DEBUG) Dbprintf("SetPassword cmd"); - if (cmd[cmdCpt++] == tag->ic) + if (cmdLen > cmdCpt+5) + cmdCpt++: // skip manifacturer code + if (cmdLen > cmdCpt+4) { pwdId = cmd[cmdCpt++]; if (pwdId == 4) // Privacy password From a9b068108b33c4a31fce2de5b045081e6c5a5566 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:54:17 +0100 Subject: [PATCH 24/36] iso15sim: fix: typo --- armsrc/iso15693.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 6e0e4c42a..dbe9288a7 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2509,9 +2509,9 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { break; case ISO15693_SET_PASSWORD: if (g_dbglevel >= DBG_DEBUG) Dbprintf("SetPassword cmd"); - if (cmdLen > cmdCpt+5) - cmdCpt++: // skip manifacturer code - if (cmdLen > cmdCpt+4) + if (cmd_len > cmdCpt+5) + cmdCpt++; // skip manifacturer code + if (cmd_len > cmdCpt+4) { pwdId = cmd[cmdCpt++]; if (pwdId == 4) // Privacy password From 05912ff130ca9a7d8e0b688d4cefd9e360d44968 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:41:33 +0100 Subject: [PATCH 25/36] iso15sim: rename, move and PACK iso15_tag struct to be usable in client --- armsrc/Standalone/hf_15sim.c | 4 ++-- armsrc/iso15693.c | 2 +- armsrc/iso15693.h | 27 --------------------------- include/iso15.h | 27 +++++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/armsrc/Standalone/hf_15sim.c b/armsrc/Standalone/hf_15sim.c index f6427686a..edea4b30f 100644 --- a/armsrc/Standalone/hf_15sim.c +++ b/armsrc/Standalone/hf_15sim.c @@ -69,7 +69,7 @@ void RunMod(void) { FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); - iso15693_tag *tag = (iso15693_tag*) BigBuf_get_EM_addr(); + iso15_tag_t *tag = (iso15_tag_t*) BigBuf_get_EM_addr(); if (tag == NULL) return; uint8_t cmd[8] = {0}; @@ -118,7 +118,7 @@ void RunMod(void) { Dbprintf("Start dumping tag"); - memset(tag, 0, sizeof(iso15693_tag)); + memset(tag, 0, sizeof(iso15_tag_t)); memcpy(tag->uid, &recv[2], 8); i=10; diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index dbe9288a7..83ad93ed0 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2132,7 +2132,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { // free eventually allocated BigBuf memory BigBuf_free_keep_EM(); - iso15693_tag *tag = (iso15693_tag*) BigBuf_get_EM_addr(); + iso15_tag_t *tag = (iso15_tag_t*) BigBuf_get_EM_addr(); if (tag == NULL) return; if (uid != NULL) { // new tag (need initialization) diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 1de06520d..a3cb5174a 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -33,33 +33,6 @@ #define DELAY_ISO15693_VCD_TO_VICC_READER 1056 // 1056/3,39MHz = 311.5us from end of command EOF to start of tag response #define DELAY_ISO15693_VICC_TO_VCD_READER 1024 // 1024/3.39MHz = 302.1us between end of tag response and next reader command -#define ISO15693_TAG_MAX_PAGES 64 // in page -#define ISO15693_TAG_MAX_SIZE 2048 // in byte (64 pages of 256 bits) - -typedef struct iso15693_tag { - uint8_t uid[8]; - uint8_t dsfid; - bool dsfidLock; - uint8_t afi; - bool afiLock; - uint8_t bytesPerPage; - uint8_t pagesCount; - uint8_t ic; - uint8_t locks[ISO15693_TAG_MAX_PAGES]; - uint8_t data[ISO15693_TAG_MAX_SIZE]; - uint8_t random[2]; - uint8_t privacyPasswd[4]; - enum { - TAG_STATE_NO_FIELD, - TAG_STATE_READY, - TAG_STATE_ACTIVATED, // useless ? - TAG_STATE_SELECTED, - TAG_STATE_SILENCED - } state; - bool expectFast; - bool expectFsk; -} iso15693_tag; - void Iso15693InitReader(void); void Iso15693InitTag(void); void CodeIso15693AsReader(const uint8_t *cmd, int n); diff --git a/include/iso15.h b/include/iso15.h index 7d2b0e12f..7f1e5b306 100644 --- a/include/iso15.h +++ b/include/iso15.h @@ -44,4 +44,31 @@ typedef struct { uint8_t raw[]; // First byte in raw, raw[0] is ISO15693 protocol flag byte } PACKED iso15_raw_cmd_t; +#define ISO15693_TAG_MAX_PAGES 64 // in page +#define ISO15693_TAG_MAX_SIZE 2048 // in byte (64 pages of 256 bits) + +typedef struct { + uint8_t uid[8]; + uint8_t dsfid; + bool dsfidLock; + uint8_t afi; + bool afiLock; + uint8_t bytesPerPage; + uint8_t pagesCount; + uint8_t ic; + uint8_t locks[ISO15693_TAG_MAX_PAGES]; + uint8_t data[ISO15693_TAG_MAX_SIZE]; + uint8_t random[2]; + uint8_t privacyPasswd[4]; + enum { + TAG_STATE_NO_FIELD, + TAG_STATE_READY, + TAG_STATE_ACTIVATED, // useless ? + TAG_STATE_SELECTED, + TAG_STATE_SILENCED + } state; + bool expectFast; + bool expectFsk; +} PACKED iso15_tag_t; + #endif // _ISO15_H_ From aabbf7d4b2874599710d21abee1bc4dd550685ad Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:47:59 +0100 Subject: [PATCH 26/36] iso15sim: enhance parameter controls and add reply when error --- armsrc/iso15693.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 83ad93ed0..03af8bcb9 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2133,8 +2133,6 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { BigBuf_free_keep_EM(); iso15_tag_t *tag = (iso15_tag_t*) BigBuf_get_EM_addr(); - if (tag == NULL) return; - if (uid != NULL) { // new tag (need initialization) memcpy(tag->uid, uid, 8); tag->dsfid = 0; @@ -2146,14 +2144,21 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { tag->ic = 0; memset(tag->locks, 0, sizeof(tag->locks)); memset(tag->data, 0, sizeof(tag->data)); + if (tag == NULL) + { + Dbprintf("Can't allocate emulator memory"); + reply_ng(CMD_HF_ISO15693_SIMULATE, PM3_EFAILED, NULL, 0); + return; } - else { // tag is already set - if (tag->pagesCount > ISO15693_TAG_MAX_PAGES || \ - tag->pagesCount * tag->bytesPerPage > ISO15693_TAG_MAX_SIZE) { - Dbprintf("Tag size error: pagesCount = %d, bytesPerPage=%d", tag->pagesCount, tag->bytesPerPage); - return; } } + if (tag->pagesCount > ISO15693_TAG_MAX_PAGES || \ + tag->pagesCount * tag->bytesPerPage > ISO15693_TAG_MAX_SIZE || + tag->pagesCount == 0 || tag->bytesPerPage == 0) { + Dbprintf("Tag size error: pagesCount = %d, bytesPerPage=%d", tag->pagesCount, tag->bytesPerPage); + reply_ng(CMD_HF_ISO15693_SIMULATE, PM3_EOPABORTED, NULL, 0); + return; + } Iso15693InitTag(); // init simulator From e5f519e085f0030fe871b0139e20cfab53ca0389 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:48:45 +0100 Subject: [PATCH 27/36] iso15sim: fix tag initialisation when full 00 uid provided --- armsrc/iso15693.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 03af8bcb9..ae1274cd4 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2133,23 +2133,26 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { BigBuf_free_keep_EM(); iso15_tag_t *tag = (iso15_tag_t*) BigBuf_get_EM_addr(); - if (uid != NULL) { // new tag (need initialization) - memcpy(tag->uid, uid, 8); - tag->dsfid = 0; - tag->dsfidLock = false; - tag->afi = 0; - tag->afiLock = false; - tag->bytesPerPage = 4; - tag->pagesCount = 64; - tag->ic = 0; - memset(tag->locks, 0, sizeof(tag->locks)); - memset(tag->data, 0, sizeof(tag->data)); if (tag == NULL) { Dbprintf("Can't allocate emulator memory"); reply_ng(CMD_HF_ISO15693_SIMULATE, PM3_EFAILED, NULL, 0); return; } + if (uid != NULL) { // new tag (need initialization) + uint8_t nullUid[8] = { 0 }; + if (memcmp(uid, nullUid, 8) != 0) + { // simulate a new tag bazed on client parameters + memcpy(tag->uid, uid, 8); + tag->dsfid = 0; + tag->dsfidLock = false; + tag->afi = 0; + tag->afiLock = false; + tag->bytesPerPage = (block_size > 0) ? block_size : 4; + tag->pagesCount = 64; + tag->ic = 0; + memset(tag->locks, 0, sizeof(tag->locks)); + memset(tag->data, 0, sizeof(tag->data)); } } if (tag->pagesCount > ISO15693_TAG_MAX_PAGES || \ @@ -2164,7 +2167,8 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { LED_A_ON(); - Dbprintf("ISO-15963 Simulating uid: %02X%02X%02X%02X%02X%02X%02X%02X, %u bytes/blocks x %u blocks", tag->uid[7], tag->uid[6], tag->uid[5], tag->uid[4], tag->uid[3], tag->uid[2], tag->uid[1], tag->uid[0], tag->bytesPerPage, tag->pagesCount); + if (g_dbglevel >= DBG_DEBUG) + Dbprintf("ISO-15963 Simulating uid: %02X%02X%02X%02X%02X%02X%02X%02X, %u bytes/blocks x %u blocks", tag->uid[7], tag->uid[6], tag->uid[5], tag->uid[4], tag->uid[3], tag->uid[2], tag->uid[1], tag->uid[0], tag->bytesPerPage, tag->pagesCount); LED_C_ON(); From ddaba93d328ad961c6538bfdf177ddeeeae72e10 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:49:53 +0100 Subject: [PATCH 28/36] client: fileutils: iso15sim: add jfs15_v4 JSON format to fit eml fmt --- client/src/fileutils.c | 63 ++++++++++++++++++++++++++++++++++++++++++ client/src/fileutils.h | 1 + 2 files changed, 64 insertions(+) diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 4f8ecda69..dc0ff8c3f 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -28,6 +28,7 @@ #include "util.h" #include "cmdhficlass.h" // pagemap #include "iclass_cmd.h" +#include "iso15.h" #ifdef _WIN32 #include "scandir.h" @@ -518,6 +519,33 @@ int saveFileJSONex(const char *preferredName, JSONFileType ftype, uint8_t *data, } break; } + // handles ISO15693 in iso15_tag_t format + case jsf15_v4: { + JsonSaveStr(root, "FileType", "15693 v4"); + iso15_tag_t *tag = (iso15_tag_t *)data; + JsonSaveBufAsHexCompact(root, "$.Card.uid", tag->uid, 8); + JsonSaveBufAsHexCompact(root, "$.Card.dsfid", &tag->dsfid, 1); + JsonSaveBufAsHexCompact(root, "$.Card.dsfidLock", (uint8_t*)&tag->dsfidLock, 1); + JsonSaveBufAsHexCompact(root, "$.Card.afi", &tag->afi, 1); + JsonSaveBufAsHexCompact(root, "$.Card.afiLock", (uint8_t*)&tag->afiLock, 1); + JsonSaveBufAsHexCompact(root, "$.Card.bytesPerPage", &tag->bytesPerPage, 1); + JsonSaveBufAsHexCompact(root, "$.Card.pagesCount", &tag->pagesCount, 1); + JsonSaveBufAsHexCompact(root, "$.Card.IC", &tag->ic, 1); + JsonSaveBufAsHexCompact(root, "$.Card.locks", tag->locks, tag->pagesCount); + JsonSaveBufAsHexCompact(root, "$.Card.random", tag->random, 2); + JsonSaveBufAsHexCompact(root, "$.Card.privacyPasswd", tag->privacyPasswd, 4); + JsonSaveBufAsHexCompact(root, "$.Card.state", (uint8_t*)&tag->state, 1); + + for (size_t i = 0 ; i < tag->pagesCount ; i++) { + if (((i+1) * tag->bytesPerPage) > ISO15693_TAG_MAX_SIZE) + break; + snprintf(path, sizeof(path), "$.blocks.%zu", i); + JsonSaveBufAsHexCompact(root, path, + &tag->data[i * tag->bytesPerPage], + tag->bytesPerPage); + } + break; + } case jsfLegic_v2: { JsonSaveStr(root, "FileType", "legic v2"); JsonSaveBufAsHexCompact(root, "$.Card.UID", data, 4); @@ -1673,6 +1701,41 @@ int loadFileJSONex(const char *preferredName, void *data, size_t maxdatalen, siz goto out; } + if (!strcmp(ctype, "15693 v4")) { + iso15_tag_t *tag = (iso15_tag_t *)udata.bytes; + JsonLoadBufAsHex(root, "$.Card.UID", tag->uid, 8, datalen); + JsonLoadBufAsHex(root, "$.Card.dsfid", &tag->dsfid, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.dsfidLock", (uint8_t*)&tag->dsfidLock, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.afi", &tag->afi, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.afiLock", (uint8_t*)&tag->afiLock, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.bytesPerPage", &tag->bytesPerPage, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.pagesCount", &tag->pagesCount, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.IC", &tag->ic, 1, datalen); + JsonLoadBufAsHex(root, "$.Card.locks", tag->locks, tag->pagesCount, datalen); + JsonLoadBufAsHex(root, "$.Card.random", tag->random, 2, datalen); + JsonLoadBufAsHex(root, "$.Card.privacyPasswd", tag->privacyPasswd, 4, datalen); + JsonLoadBufAsHex(root, "$.Card.state", (uint8_t*)&tag->state, 1, datalen); + + size_t sptr = 0; + for (int i = 0; i < tag->pagesCount ; i++) { + if (((i+1) * tag->bytesPerPage) > ISO15693_TAG_MAX_SIZE) { + PrintAndLogEx(ERR, "loadFileJSONex: maxdatalen=%zu (%04zx) block (i)=%4d (%04x) sptr=%zu (%04zx) -- exceeded maxdatalen", maxdatalen, maxdatalen, i, i, sptr, sptr); + retval = PM3_EMALLOC; + goto out; + } + + snprintf(blocks, sizeof(blocks), "$.blocks.%d", i); + JsonLoadBufAsHex(root, blocks, &tag->data[sptr], tag->bytesPerPage, &len); + if (load_file_sanity(ctype, tag->bytesPerPage, i, len) == false) { + break; + } + sptr += len; + } + + *datalen = sptr; + goto out; + } + if (!strcmp(ctype, "legic v2")) { size_t sptr = 0; for (int i = 0; i < 64; i++) { diff --git a/client/src/fileutils.h b/client/src/fileutils.h index fa36fb89e..c4dcc5a0c 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -55,6 +55,7 @@ typedef enum { jsf15, jsf15_v2, jsf15_v3, + jsf15_v4, jsfLegic, jsfLegic_v2, jsfT55x7, From 0bbb054b3356dc1067bbda313c5de715e148e2a9 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:52:21 +0100 Subject: [PATCH 29/36] cmdhf15: addapt eload/esave to new iso15sim eml format --- client/src/cmdhf15.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index 55ae1c028..5ff7623d3 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1176,7 +1176,7 @@ static int CmdHF15ELoad(const char *Cmd) { return res; } - if (bytes_read > CARD_MEMORY_SIZE) { + if (bytes_read > CARD_MEMORY_SIZE || bytes_read > sizeof(iso15_tag_t)) { PrintAndLogEx(FAILED, "Memory image too large."); free(data); return PM3_EINVARG; @@ -1232,13 +1232,10 @@ static int CmdHF15ESave(const char *Cmd) { CLIParserInit(&ctx, "hf 15 esave", "Save emulator memory into two files (bin/json) ", "hf 15 esave -f hf-15-01020304" - "hf 15 esave -b 8 -c 42 -f hf-15-01020304" ); void *argtable[] = { arg_param_begin, arg_str1("f", "file", "", "Specify a filename for dump file"), - arg_int0(NULL, "bsize", "", "block size (def 4)"), - arg_int0("c", "count", "", "number of blocks to export (def all)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -1246,20 +1243,9 @@ static int CmdHF15ESave(const char *Cmd) { int fnlen = 0; char filename[FILE_PATH_SIZE]; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); - int blocksize = arg_get_int_def(ctx, 2, 4); - int count = arg_get_int_def(ctx, 3, -1); CLIParserFree(ctx); - // sanity checks - if (blocksize < 4) { - PrintAndLogEx(WARNING, "Blocksize too small, using default 4 bytes"); - blocksize = 4; - } - - int bytes = CARD_MEMORY_SIZE; - if (count > 0 && count * blocksize <= bytes) { - bytes = count * blocksize; - } + int bytes = sizeof(iso15_tag_t); // reserve memory uint8_t *dump = calloc(bytes, sizeof(uint8_t)); @@ -1275,11 +1261,7 @@ static int CmdHF15ESave(const char *Cmd) { return PM3_ETIMEOUT; } - if (blocksize == 8) { - pm3_save_dump(filename, dump, bytes, jsf15_v3); - } else { - pm3_save_dump(filename, dump, bytes, jsf15_v2); - } + pm3_save_dump(filename, dump, bytes, jsf15_v4); free(dump); return PM3_SUCCESS; From e6bca3221d902d089cd3f59425632e8bbeb5714a Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:53:41 +0100 Subject: [PATCH 30/36] cmdhf15: addapt eview to new iso15sim eml format --- client/src/cmdhf15.c | 50 +++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index 5ff7623d3..b82767a3c 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1279,6 +1279,13 @@ static void print_blocks_15693(uint8_t *data, uint16_t bytes, int blocksize, boo PrintAndLogEx(INFO, "----------- " _CYAN_("Tag Memory") " ---------------"); PrintAndLogEx(NORMAL, ""); + + if (blocksize == 0 || bytes == 0) + { + PrintAndLogEx(INFO, "Tag is empty!"); + return; + } + print_hrule(blocksize); char spaces[] = " "; @@ -1336,38 +1343,46 @@ static void print_blocks_15693(uint8_t *data, uint16_t bytes, int blocksize, boo PrintAndLogEx(NORMAL, ""); } +static void print_emltag_info_15693(iso15_tag_t *tag) { + PrintAndLogEx(SUCCESS, " TYPE... " _YELLOW_("%s"), getTagInfo_15(tag->uid)); + PrintAndLogEx(SUCCESS, " UID... " _GREEN_("%s"), iso15693_sprintUID(NULL, tag->uid)); + PrintAndLogEx(SUCCESS, " - DSFID [0x%02X]", tag->dsfid); + PrintAndLogEx(SUCCESS, " - AFI [0x%02X]", tag->afi); + PrintAndLogEx(SUCCESS, " - IC reference [0x%02X]", tag->ic); + PrintAndLogEx(SUCCESS, " - Tag memory layout"); + PrintAndLogEx(SUCCESS, " %u bytes/blocks x %u blocks", tag->bytesPerPage, tag->pagesCount); +} + +static void print_emltag_15693(iso15_tag_t *tag, bool dense_output) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Emulator Tag Information") " ---------------------------"); + + print_emltag_info_15693(tag); + + print_blocks_15693(tag->data, (tag->pagesCount * tag->bytesPerPage), + tag->bytesPerPage, dense_output); +} + static int CmdHF15EView(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf 15 eview", "It displays emulator memory", "hf 15 eview\n" - "hf 15 eview -b 8 -c 60\n" + "hf 15 eview -z\n" ); void *argtable[] = { arg_param_begin, - arg_int0("b", "blocksize", "", "block size (def 4)"), - arg_int0("c", "count", "", "number of blocks to display (def all)"), arg_lit0("z", "dense", "dense dump output style"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - int blocksize = arg_get_int_def(ctx, 1, 4); - int count = arg_get_int_def(ctx, 2, -1); - bool dense_output = (g_session.dense_output || arg_get_lit(ctx, 3)); + bool dense_output = (g_session.dense_output || arg_get_lit(ctx, 1)); CLIParserFree(ctx); - // santity checks - if (blocksize < 4) { - PrintAndLogEx(WARNING, "Blocksize too small, using default 4 bytes"); - blocksize = 4; - } - - int bytes = CARD_MEMORY_SIZE; - if (count > 0 && count * blocksize <= bytes) { - bytes = count * blocksize; - } + int bytes = sizeof(iso15_tag_t); + // reserve memory uint8_t *dump = calloc(bytes, sizeof(uint8_t)); if (dump == NULL) { PrintAndLogEx(WARNING, "Fail, cannot allocate memory"); @@ -1381,7 +1396,8 @@ static int CmdHF15EView(const char *Cmd) { return PM3_ETIMEOUT; } - print_blocks_15693(dump, bytes, blocksize, dense_output); + print_emltag_15693((iso15_tag_t *)dump, dense_output); + free(dump); return PM3_SUCCESS; } From 7c44337941fdf8203240a115ff685b89344b17b3 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:56:15 +0100 Subject: [PATCH 31/36] cmdhf15: addapt dump to new iso15sim eml format --- client/src/cmdhf15.c | 136 ++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 53 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index b82767a3c..56e1f24b5 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1721,8 +1721,8 @@ static int CmdHF15Dump(const char *Cmd) { uint8_t arglen = arg_add_default(argtable); argtable[arglen++] = arg_str0("f", "file", "", "Specify a filename for dump file"), argtable[arglen++] = arg_int0(NULL, "bs", "", "block size (def 4)"), - argtable[arglen++] = arg_lit0(NULL, "ns", "no save to file"), - argtable[arglen++] = arg_lit0("v", "verbose", "verbose output"); + argtable[arglen++] = arg_lit0(NULL, "ns", "no save to file"), + argtable[arglen++] = arg_lit0("v", "verbose", "verbose output"); argtable[arglen++] = arg_param_end; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -1770,9 +1770,16 @@ static int CmdHF15Dump(const char *Cmd) { return PM3_EMALLOC; } + iso15_tag_t *tag = (iso15_tag_t *)calloc(1, sizeof(iso15_tag_t)); + + if (tag == NULL) { + PrintAndLogEx(FAILED, "failed to allocate memory"); + return PM3_EMALLOC; + }; + // ISO15693 Protocol params packet->raw[packet->rawlen++] = arg_get_raw_flag(uidlen, unaddressed, scan, add_option); - packet->raw[packet->rawlen++] = ISO15693_READBLOCK; + packet->raw[packet->rawlen++] = ISO15693_GET_SYSTEM_INFO; bool used_uid = false; if (unaddressed == false) { @@ -1794,9 +1801,8 @@ static int CmdHF15Dump(const char *Cmd) { PrintAndLogEx(SUCCESS, "Using unaddressed mode"); } - if (verbose) { - PrintAndLogEx(INFO, "Using block size... " _YELLOW_("%d"), blocksize); - } + AddCrc15(packet->raw, packet->rawlen); + packet->rawlen += 2; // PM3 params packet->flags = (ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_NO_DISCONNECT); @@ -1804,31 +1810,69 @@ static int CmdHF15Dump(const char *Cmd) { packet->flags |= ISO15_HIGH_SPEED; } - // add CRC length (2) to packet and blockno (1) - packet->rawlen += 3; + clearCommandBuffer(); + SendCommandNG(CMD_HF_ISO15693_COMMAND, (uint8_t *)packet, ISO15_RAW_LEN(packet->rawlen)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_ISO15693_COMMAND, &resp, 2000) == false) { + PrintAndLogEx(DEBUG, "iso15693 timeout"); + return PM3_ETIMEOUT; + } + + if (resp.status == PM3_ETEAROFF) { + return resp.status; + } + + if (resp.length < 2) { + PrintAndLogEx(WARNING, "iso15693 card doesn't answer to systeminfo command (%d)", resp.length); + PrintAndLogEx(WARNING, "%%d)", resp.length); + return PM3_EWRONGANSWER; + } + + uint8_t *d = resp.data.asBytes; + uint8_t dCpt = 10; + + ISO15_ERROR_HANDLING_CARD_RESPONSE(d, resp.length); + + memcpy(tag->uid, &d[2], 8); + + if (d[1] & 0x01) + tag->dsfid = d[dCpt++]; + if (d[1] & 0x02) + tag->afi = d[dCpt++]; + if (d[1] & 0x04) + { + tag->pagesCount = d[dCpt++]+1; + tag->bytesPerPage = d[dCpt++]+1; + } + else + { // Set tag memory layout values (if can't be readed in SYSINFO) + tag->bytesPerPage = blocksize; + tag->pagesCount = 128; + } + if (d[1] & 0x08) + tag->ic = d[dCpt++]; + + if (verbose) + { + print_emltag_info_15693(tag); + } + + // add lenght for blockno (1) + packet->rawlen++; + packet->raw[0] |= ISO15_REQ_OPTION; // Add option to dump lock status + packet->raw[1] = ISO15693_READBLOCK; + + packet->flags = (ISO15_READ_RESPONSE | ISO15_NO_DISCONNECT); + if (fast) { + packet->flags |= ISO15_HIGH_SPEED; + } PrintAndLogEx(SUCCESS, "Reading memory"); int blocknum = 0; - // memory. - t15memory_t mem[256]; - - uint8_t data[256 * 4]; - memset(data, 0, sizeof(data)); - - // keep track of which block length tag returned? - uint8_t blklen = blocksize; - - - for (int retry = 0; (retry < 2 && blocknum < 0x100); retry++) { - - if (blocknum > 0) { - packet->flags = (ISO15_READ_RESPONSE | ISO15_NO_DISCONNECT); - if (fast) { - packet->flags |= ISO15_HIGH_SPEED; - } - } + for (int retry = 0; (retry < 2 && blocknum < tag->pagesCount); retry++) { if (used_uid) { packet->raw[10] = (uint8_t)blocknum & 0xFF; AddCrc15(packet->raw, 11); @@ -1839,7 +1883,7 @@ static int CmdHF15Dump(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_HF_ISO15693_COMMAND, (uint8_t *)packet, ISO15_RAW_LEN(packet->rawlen)); - PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_ISO15693_COMMAND, &resp, 2000)) { if (resp.length < 2) { @@ -1848,7 +1892,7 @@ static int CmdHF15Dump(const char *Cmd) { continue; } - uint8_t *d = resp.data.asBytes; + d = resp.data.asBytes; if (CheckCrc15(d, resp.length) == false) { PrintAndLogEx(NORMAL, ""); @@ -1868,21 +1912,10 @@ static int CmdHF15Dump(const char *Cmd) { break; } - // is tag responding with 4 or 8 bytes? - if (resp.length > 8) { - blklen = 8; - } - - uint8_t offset = 0; - if (add_option) { - offset = 1; - } - // lock byte value - mem[blocknum].lock = d[0 + offset]; + tag->locks[blocknum] = d[1]; // copy read data - memcpy(mem[blocknum].block, d + 1 + offset, blklen); - memcpy(data + (blocknum * 4), d + 1 + offset, 4); + memcpy(&tag->data[blocknum * tag->bytesPerPage], d + 2, tag->bytesPerPage); retry = 0; blocknum++; @@ -1894,9 +1927,9 @@ static int CmdHF15Dump(const char *Cmd) { free(packet); DropField(); - if (blklen != blocksize) { + if (tag->bytesPerPage != blocksize) { PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, _YELLOW_("%u") " byte block length detected, called with " _YELLOW_("%d"), blklen, blocksize); + PrintAndLogEx(INFO, _YELLOW_("%u") " byte block length detected, called with " _YELLOW_("%d"), tag->bytesPerPage, blocksize); } PrintAndLogEx(NORMAL, ""); @@ -1908,18 +1941,18 @@ static int CmdHF15Dump(const char *Cmd) { for (int i = 0; i < blocknum; i++) { char lck[16] = {0}; - if (mem[i].lock) { - snprintf(lck, sizeof(lck), _RED_("%d"), mem[i].lock); + if (tag->locks[i]) { + snprintf(lck, sizeof(lck), _RED_("%d"), tag->locks[i]); } else { - snprintf(lck, sizeof(lck), "%d", mem[i].lock); + snprintf(lck, sizeof(lck), "%d", tag->locks[i]); } PrintAndLogEx(INFO, "%3d/0x%02X | %s| %s | %s" , i , i - , sprint_hex(mem[i].block, blklen) + , sprint_hex(&tag->data[i*tag->bytesPerPage], tag->bytesPerPage) , lck - , sprint_ascii(mem[i].block, blklen) + , sprint_ascii(&tag->data[i*tag->bytesPerPage], tag->bytesPerPage) ); } PrintAndLogEx(INFO, "---------+-------------+---+-------"); @@ -1939,11 +1972,8 @@ static int CmdHF15Dump(const char *Cmd) { FillFileNameByUID(fptr, SwapEndian64(uid, sizeof(uid), 8), "-dump", sizeof(uid)); } - if (blklen == 8) { - pm3_save_dump(filename, data, (size_t)(blocknum * blklen), jsf15_v3); - } else { - pm3_save_dump(filename, data, (size_t)(blocknum * blklen), jsf15_v2); - } + pm3_save_dump(filename, (uint8_t*)tag, sizeof(iso15_tag_t), jsf15_v4); + return PM3_SUCCESS; } From 7f666d445cb3e9d392d7577e29ea9967b037f9ff Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:56:38 +0100 Subject: [PATCH 32/36] cmd_hf_15: fix & simplify flag parsing --- client/src/cmdhf15.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index 56e1f24b5..c6eb887c5 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -532,15 +532,13 @@ static uint8_t arg_add_default(void *at[]) { } static uint16_t arg_get_raw_flag(uint8_t uidlen, bool unaddressed, bool scan, bool add_option) { uint16_t flags = 0; - if (unaddressed) { - // unaddressed mode may not be supported by all vendors - flags |= (ISO15_REQ_SUBCARRIER_SINGLE | ISO15_REQ_DATARATE_HIGH | ISO15_REQ_NONINVENTORY); +; + if (uidlen == 8 || scan || unaddressed) { + flags = (ISO15_REQ_SUBCARRIER_SINGLE | ISO15_REQ_DATARATE_HIGH | ISO15_REQ_NONINVENTORY); } - if (uidlen == 8) { - flags |= (ISO15_REQ_SUBCARRIER_SINGLE | ISO15_REQ_DATARATE_HIGH | ISO15_REQ_NONINVENTORY | ISO15_REQ_ADDRESS); - } - if (scan) { - flags |= (ISO15_REQ_SUBCARRIER_SINGLE | ISO15_REQ_DATARATE_HIGH | ISO15_REQ_NONINVENTORY | ISO15_REQ_ADDRESS); + if ((!unaddressed) || scan) + { + flags |= ISO15_REQ_ADDRESS; } if (add_option) { flags |= (ISO15_REQ_OPTION); From 524a439d6ef2b5de4157364b440fa94ff7a13706 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:57:05 +0100 Subject: [PATCH 33/36] cmdhf15: dump: fix arg parsing/sanitization --- client/src/cmdhf15.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index c6eb887c5..c7f9049ac 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1744,7 +1744,7 @@ static int CmdHF15Dump(const char *Cmd) { CLIParserFree(ctx); // sanity checks - if ((scan + unaddressed + uidlen) > 1) { + if ((scan + unaddressed + (uidlen > 0)) > 1) { PrintAndLogEx(WARNING, "Select only one option /scan/unaddress/uid"); return PM3_EINVARG; } @@ -1755,8 +1755,7 @@ static int CmdHF15Dump(const char *Cmd) { } // default fallback to scan for tag. - // overriding unaddress parameter :) - if (uidlen != HF15_UID_LENGTH) { + if (uidlen != HF15_UID_LENGTH && !unaddressed) { scan = true; } From 2aa419beba30e85be55cce91c13b4a7d0046b956 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:58:23 +0100 Subject: [PATCH 34/36] cmdhf15: addapt sim to new iso15sim eml format --- client/src/cmdhf15.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index c7f9049ac..84e677946 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1407,39 +1407,49 @@ static int CmdHF15Sim(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf 15 sim", "Simulate a ISO-15693 tag\n", + "hf 15 sim\n" "hf 15 sim -u E011223344556677"); - void *argtable[] = { arg_param_begin, - arg_str1("u", "uid", "", "UID, 8 hex bytes"), + arg_str0("u", "uid", "", "UID, 8 hex bytes"), arg_int0("b", "blocksize", "", "block size (def 4)"), arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, false); + CLIExecWithReturn(ctx, Cmd, argtable, true); struct { uint8_t uid[HF15_UID_LENGTH]; uint8_t block_size; } PACKED payload; + memset(&payload, 0, sizeof(payload)); int uidlen = 0; CLIGetHexWithReturn(ctx, 1, payload.uid, &uidlen); - if (uidlen != HF15_UID_LENGTH) { + if (uidlen != 0 && uidlen != HF15_UID_LENGTH) { PrintAndLogEx(WARNING, "UID must include 8 hex bytes"); CLIParserFree(ctx); return PM3_EINVARG; } - - payload.block_size = arg_get_int_def(ctx, 2, 4); CLIParserFree(ctx); - // santity checks - if (payload.block_size < 4) { - PrintAndLogEx(WARNING, "Blocksize too small, using default 4 bytes"); - payload.block_size = 4; - } + if (uidlen == 0) // get UID from emulator + { + // reserve memory + iso15_tag_t *tag = calloc(1, sizeof(iso15_tag_t)); + if (tag == NULL) { + PrintAndLogEx(WARNING, "Fail, cannot allocate memory"); + return PM3_EMALLOC; + } - PrintAndLogEx(SUCCESS, "Starting simulating UID " _YELLOW_("%s"), iso15693_sprintUID(NULL, payload.uid)); + if (GetFromDevice(BIG_BUF_EML, (uint8_t*)tag, sizeof(iso15_tag_t), 0, NULL, 0, NULL, 2500, false) == false) { + PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); + free(tag); + return PM3_ETIMEOUT; + } + + PrintAndLogEx(SUCCESS, "Starting simulating UID " _YELLOW_("%s"), iso15693_sprintUID(NULL, tag->uid)); + free(tag); + } PrintAndLogEx(INFO, "Press " _YELLOW_("`pm3-button`") " to abort simulation"); PacketResponseNG resp; From 6a9eb0c97d71777011f8b6c4696420a04b3e2685 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:09:00 +0100 Subject: [PATCH 35/36] add changelog entries --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e20a023f..8198cd15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ 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_15SIM` standalone mode that dump then simulate iso15 tags (@lnv42) + - Changed `iso15 simulation`, reworked, added support for lot of features (@lnv42) + - Changed `hf 15 sniff` quality while low signal (@lnv42) - Fixed `hf sniff` broken since 17ab86c52 (@nvx) - Added `--dumpmem` to proxmark3 client for memory dumping to file (@martian01010) - Changed `hw readmem` to allow larger reads, write to file and better hex viewer (@martian01010) From c1bc9f75f66fbe2fbe73a2a1da5496a6e3a68fac Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:47:39 +0100 Subject: [PATCH 36/36] iso15sim: fix type issue to make CodeQL happy --- armsrc/iso15693.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index ae1274cd4..7e2302725 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2296,7 +2296,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { maskCpt = 0; - while (mask_len >= 8 && cmdCpt < cmd_len && maskCpt < 8) { // Byte comparison + while (mask_len >= 8 && cmdCpt < (uint8_t)cmd_len && maskCpt < 8) { // Byte comparison if (cmd[cmdCpt++] != tag->uid[maskCpt++]) { error++; // mask don't match : drop request break; @@ -2420,7 +2420,7 @@ void SimTagIso15693(uint8_t *uid, uint8_t block_size) { else { recv[0] = ISO15_NOERROR; recvLen = 1; - for (uint16_t i = 0 ; i < (nbPages + 1) * tag->bytesPerPage && \ + for (int i = 0 ; i < (nbPages + 1) * tag->bytesPerPage && \ recvLen + 3 < ISO15693_MAX_RESPONSE_LENGTH ; i++) { if ((i % tag->bytesPerPage) == 0 && (cmd[0] & ISO15_REQ_OPTION)) recv[recvLen++] = tag->locks[pageNum + (i / tag->bytesPerPage)];