diff --git a/Firmware/Chameleon-Mini/.gitignore b/Firmware/Chameleon-Mini/.gitignore index 11e4750f..138cd0e2 100644 --- a/Firmware/Chameleon-Mini/.gitignore +++ b/Firmware/Chameleon-Mini/.gitignore @@ -1,8 +1,10 @@ -/Chameleon-Mini.eep -/Chameleon-Mini.hex -/Chameleon-Mini.elf -/Chameleon-Mini.map -/Chameleon-Mini.bin -/Chameleon-Mini.lss -/Chameleon-Mini.sym +/Chameleon-*.eep +/Chameleon-*.hex +/Chameleon-*.elf +/Chameleon-*.map +/Chameleon-*.bin +/Chameleon-*.lss +/Chameleon-*.sym /Bin/ +dfu-programmer +dfu-programmer.exe diff --git a/Firmware/Chameleon-Mini/Application/Application.h b/Firmware/Chameleon-Mini/Application/Application.h index 53caa39b..37e4537d 100644 --- a/Firmware/Chameleon-Mini/Application/Application.h +++ b/Firmware/Chameleon-Mini/Application/Application.h @@ -23,6 +23,7 @@ #include "EM4233.h" #include "NTAG215.h" #include "Sniff15693.h" +#include "IClass.h" /* Function wrappers */ INLINE void ApplicationInit(void) { diff --git a/Firmware/Chameleon-Mini/Application/IClass.c b/Firmware/Chameleon-Mini/Application/IClass.c new file mode 100644 index 00000000..51e82d02 --- /dev/null +++ b/Firmware/Chameleon-Mini/Application/IClass.c @@ -0,0 +1,571 @@ +/* + * IClass.c + * + * Created on: 17-05-2020 + * Author: NVX + */ + +#include "ISO15693-A.h" +#include "IClass.h" + +#define ICLASS_BLOCK_CSN 0 // CSN +#define ICLASS_BLOCK_CFG 1 // Configuration block +#define ICLASS_BLOCK_EPURSE 2 // e-purse / Card Challenge +#define ICLASS_BLOCK_KD 3 // Kd (Debit Key) +#define ICLASS_BLOCK_KC 4 // Kc (Credit Key) +#define ICLASS_BLOCK_AIA 5 // Application Issuer Area +#define ICLASS_BLOCK_APP1 6 // Start of Application 1 (HID) +#define ICLASS_BLOCK_APP2 19 // Start of Applicaiton 2 (User) + +#define ICLASS_FUSE_PERS 0x80 // Personalization Mode +#define ICLASS_FUSE_CRYPT1 0x10 // Crypt1 // 1+1 (crypt1+crypt0) means secured and keys changable +#define ICLASS_FUSE_CRTPT0 0x08 // Crypt0 // 1+0 means secure and keys locked, 0+1 means not secured, 0+0 means disable auth entirely +#define ICLASS_FUSE_CRYPT10 ( ICLASS_FUSE_CRYPT1 | ICLASS_FUSE_CRTPT0 ) +#define ICLASS_FUSE_RA 0x01 // Read Access, 1 meanns anonymous read enabled, 0 means must auth to read applicaion + +// Low nibble used for command +// High nibble used for options and checksum (MSB) +// The only option we care about in 15693 mode is the key +// which is only used by READCHECK, so for simplicity we +// don't bother breaking down the command and flags into parts +#define ICLASS_CMD_READ_OR_IDENTIFY 0x0C // READ: ADDRESS(1) CRC16(2) -> DATA(8) CRC16(2) _OR_ IDENTIFY: No args -> ASNB(8) CRC16(2) +#define ICLASS_CMD_READ4 0x06 // ADDRESS(1) CRC16(2) -> DATA(32) CRC16(2) +#define ICLASS_CMD_UPDATE 0x87 // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2) -> DATA(8) CRC16(2) +#define ICLASS_CMD_READCHECK_KD 0x88 // ADDRESS(1) -> DATA(8) +#define ICLASS_CMD_READCHECK_KC 0x18 // ADDRESS(1) -> DATA(8) +#define ICLASS_CMD_CHECK 0x05 // CHALLENGE(4) READERSIGNATURE(4) -> CHIPRESPONSE(4) +#define ICLASS_CMD_ACTALL 0x0A // No args -> SOF +#define ICLASS_CMD_ACT 0x8E // No args -> SOF +#define ICLASS_CMD_SELECT 0x81 // ASNB(8)|SERIALNB(8) -> SERIALNB(8) CRC16(2) +#define ICLASS_CMD_DETECT 0x0F // No args -> SERIALNB(8) CRC16(2) +#define ICLASS_CMD_HALT 0x00 // No args -> SOF +#define ICLASS_CMD_PAGESEL 0x84 // PAGE(1) CRC16(2) -> BLOCK1(8) CRC16(2) + +#define HAS_MASK(x,b) ( (x&b) == b ) + +static enum { + STATE_HALT, + STATE_IDLE, + STATE_ACTIVE, + STATE_SELECTED +} State; + +const uint8_t ffBlock[ICLASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +/* + * Definition 1 (Cipher state). A cipher state of iClass s is an element of F 40/2 + * consisting of the following four components: + * 1. the left register l = (l 0 . . . l 7 ) ∈ F 8/2 ; + * 2. the right register r = (r 0 . . . r 7 ) ∈ F 8/2 ; + * 3. the top register t = (t 0 . . . t 15 ) ∈ F 16/2 . + * 4. the bottom register b = (b 0 . . . b 7 ) ∈ F 8/2 . + */ +typedef struct { + uint8_t l; + uint8_t r; + uint8_t b; + uint16_t t; +} CipherState_t; + +uint8_t CurrentCSN[ICLASS_CSN_SIZE]; +uint8_t CurrentKeyBlockNum; // Used as current csn in loclass reader attack mode +uint8_t CurrentKey[ICLASS_BLOCK_SIZE]; +CipherState_t CipherState; + +#define NUM_CSNS 9 +// CSNs from Proxmark3 repo +static const uint8_t loclassCSNs[ICLASS_CSN_SIZE * NUM_CSNS] PROGMEM = { + 0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0, + 0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0, + 0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0, + 0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0, + 0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0, + 0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0, + 0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0, + 0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0, + 0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0 +}; + +static void loclassSetCSN(void) { + memcpy_P(CurrentCSN, loclassCSNs + (CurrentKeyBlockNum * ICLASS_CSN_SIZE), ICLASS_CSN_SIZE); + MemoryWriteBlock(CurrentCSN, ICLASS_BLOCK_CSN * ICLASS_BLOCK_SIZE, ICLASS_CSN_SIZE); +} + +// Crypto borrowed from Proxmark3 implementation and tweaked to run fast enough +// https://github.com/RfidResearchGroup/proxmark3/blob/e550f8ccc85b745e3961a096b5e3a602adaa5034/armsrc/optimized_cipher.c +static const uint8_t opt_select_LUT[256] = { + 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, + 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, + 06, 05, 04, 07, 04, 05, 06, 07, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, + 05, 06, 07, 04, 06, 07, 04, 05, 05, 06, 06, 05, 06, 07, 05, 04, + 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 02, 01, 00, 03, 00, 01, 02, 03, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, + 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00 +}; + +__attribute__((optimize("-O3"))) INLINE void opt_successor(const uint8_t *k, CipherState_t *s, uint8_t y) { + uint16_t Tt = s->t & 0xc533; + Tt = Tt ^ (Tt >> 1); + Tt = Tt ^ (Tt >> 4); + Tt = Tt ^ (Tt >> 10); + Tt = Tt ^ (Tt >> 8); + + s->t = (s->t >> 1); + s->t |= (Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15; + + uint8_t opt_B = s->b; + opt_B ^= s->b >> 6; + opt_B ^= s->b >> 5; + opt_B ^= s->b >> 4; + + s->b = s->b >> 1; + s->b |= (opt_B ^ s->r) << 7; + + uint8_t opt_select = opt_select_LUT[s->r] & 0x04; + opt_select |= (opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; + opt_select |= (opt_select_LUT[s->r] ^ Tt) & 0x01; + + uint8_t r = s->r; + s->r = (k[opt_select] ^ s->b) + s->l; + s->l = s->r + r; +} + +__attribute__((optimize("-O3"))) INLINE void opt_suc(const uint8_t *k, CipherState_t *s, const uint8_t *in) { + for (uint8_t i = 0; i < 8; i++) { + uint8_t head; + head = in[i]; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + } +} + +__attribute__((optimize("-O3"))) INLINE void opt_output(const uint8_t *k, CipherState_t *s, uint8_t *buffer) { + for (uint8_t times = 0; times < 4; times++) { + uint8_t bout = 0; + bout |= (s->r & 0x4) >> 2; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) >> 1; + opt_successor(k, s, 0); + bout |= (s->r & 0x4); + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 1; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 2; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 3; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 4; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 5; + opt_successor(k, s, 0); + buffer[times] = bout; + } +} + +/* + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +static CipherState_t IClassDoTagMAC1(uint8_t *cc_p, const uint8_t *div_key_p) { + CipherState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + opt_suc(div_key_p, &_init, cc_p); + return _init; +} + +/* + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +__attribute__((optimize("-O3"))) static void IClassDoTagMAC2(CipherState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + opt_suc(div_key_p, &_init, nr); + opt_output(div_key_p, &_init, mac); +} + +// 0x06 input should return 0x4556 +static uint16_t iClassCRC16(void *buf, uint16_t size) { + uint16_t reg = 0xE012; + uint8_t i, j; + + uint8_t *DataPtr = (uint8_t *)buf; + + for (i = 0; i < size; i++) { + reg = reg ^ *DataPtr++; + for (j = 0; j < 8; j++) { + if (reg & 0x0001) { + reg = (reg >> 1) ^ ISO15693_CRC16_POLYNORMAL; + } else { + reg = (reg >> 1); + } + } + } + + return reg; +} + +static void iClassAppendCRC(uint8_t *buf, uint16_t size) { + uint16_t crc = iClassCRC16(buf, size); + + buf[size] = crc & 0xFF; + buf[size + 1] = crc >> 8; +} + +// Borrowed from Proxmark3 repo +static void makeAntiCollCsn(const uint8_t *original_csn, uint8_t *rotated_csn) { + for (uint8_t i = 0; i < ICLASS_BLOCK_SIZE; i++) { + rotated_csn[i] = (original_csn[i] >> 3) | (original_csn[(i + 1) % 8] << 5); + } +} + +static void initCipherState(void) { + uint8_t ePurse[ICLASS_BLOCK_SIZE]; + MemoryReadBlock(ePurse, ICLASS_BLOCK_EPURSE * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + MemoryReadBlock(CurrentKey, CurrentKeyBlockNum * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + + CipherState = IClassDoTagMAC1(ePurse, CurrentKey); +} + +void IClassAppInit(void) { + MemoryReadBlock(CurrentCSN, ICLASS_BLOCK_CSN * ICLASS_BLOCK_SIZE, ICLASS_CSN_SIZE); + DetectionMode = false; + CurrentKeyBlockNum = 0; // Force IClassAppReset to re-init cipher state + IClassAppReset(); +} + +void IClassDetectionInit(void) { + State = STATE_IDLE; + + DetectionMode = true; + CurrentKeyBlockNum = 0; + + // Setup card + + // block 0 + loclassSetCSN(); + + // block 1 + const uint8_t confBlock[ICLASS_BLOCK_SIZE] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C}; + MemoryWriteBlock(confBlock, ICLASS_BLOCK_CFG * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + + // block 2 + const uint8_t ePurse[ICLASS_BLOCK_SIZE] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + MemoryWriteBlock(ePurse, ICLASS_BLOCK_EPURSE * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + + // block 3 is Kd and block 4 is Kc, neither are relevant in this mode + + // block 5 + MemoryWriteBlock(ffBlock, ICLASS_BLOCK_AIA * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + + DetectionLogToFlash(LOG_INFO_SYSTEM_BOOT, NULL, 0); +} + +void IClassAppReset(void) { + State = STATE_IDLE; + + if (DetectionMode == false && CurrentKeyBlockNum != ICLASS_BLOCK_KD) { + CurrentKeyBlockNum = ICLASS_BLOCK_KD; + initCipherState(); + } +} + +uint16_t IClassAppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { + uint8_t keyBlockNum = ICLASS_BLOCK_KD; + + switch (FrameBuf[0]) { + case ICLASS_CMD_ACTALL: // No args + if (FrameBytes != 1) { + return ISO15693_APP_NO_RESPONSE; + } + + if (State != STATE_HALT) { + State = STATE_ACTIVE; + } + return ISO15693_APP_SOF_ONLY; + case ICLASS_CMD_ACT: // No args + if (FrameBytes != 1 || State != STATE_ACTIVE) { + return ISO15693_APP_NO_RESPONSE; + } + + return ISO15693_APP_SOF_ONLY; + case ICLASS_CMD_HALT: // No args + if (FrameBytes != 1 || State != STATE_SELECTED) { + return ISO15693_APP_NO_RESPONSE; + } + + State = STATE_HALT; + return ISO15693_APP_SOF_ONLY; + case ICLASS_CMD_READ_OR_IDENTIFY: + if (FrameBytes == 1 && State == STATE_ACTIVE) { // ICLASS_CMD_IDENTIFY + // ASNB(8) CRC16(2) + makeAntiCollCsn(CurrentCSN, FrameBuf); + iClassAppendCRC(FrameBuf, ICLASS_CSN_SIZE); + return ICLASS_CSN_SIZE+2; + } else if (FrameBytes == 4 && State == STATE_SELECTED) { // ICLASS_CMD_READ ADDRESS(1) CRC16(2) + if (FrameBuf[1] >= ICLASS_BLOCK_NUM) { + return ISO15693_APP_NO_RESPONSE; + } + + // TODO: Check CRC? + // TODO: Check auth? + + // DATA(8) CRC16(2) + if (FrameBuf[1] == ICLASS_BLOCK_KD || FrameBuf[1] == ICLASS_BLOCK_KD) { + // Reading Kd or Kc blocks always returns FF's + memcpy(FrameBuf, ffBlock, ICLASS_BLOCK_SIZE); + } else { + MemoryReadBlock(FrameBuf, FrameBuf[1] * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + } + iClassAppendCRC(FrameBuf, ICLASS_BLOCK_SIZE); + return ICLASS_BLOCK_SIZE+2; + } + + return ISO15693_APP_NO_RESPONSE; + case ICLASS_CMD_READ4: // ADDRESS(1) CRC16(2) + if (FrameBytes != 4 || State != STATE_SELECTED || FrameBuf[1]+4 >= ICLASS_BLOCK_NUM) { + return ISO15693_APP_NO_RESPONSE; + } + + // TODO: Check CRC? + // TODO: Check auth? + + uint8_t blockNum = FrameBuf[1]; + + // DATA(32) CRC16(2) + MemoryReadBlock(FrameBuf, blockNum * ICLASS_BLOCK_SIZE, ICLASS_READ4_SIZE); + if (blockNum == 4) { + // Kc is block 4, so just redact first block of response + memcpy(FrameBuf, ffBlock, ICLASS_BLOCK_SIZE); + } else if (blockNum < 4) { + // Kd is block 3 + uint8_t *kdOffset = FrameBuf+((3-blockNum)*ICLASS_BLOCK_SIZE); + memcpy(kdOffset, ffBlock, ICLASS_BLOCK_SIZE); + if (blockNum != 0) { + // Redact Kc + memcpy(kdOffset+ICLASS_BLOCK_SIZE, ffBlock, ICLASS_BLOCK_SIZE); + } + } + iClassAppendCRC(FrameBuf, ICLASS_READ4_SIZE); + return ICLASS_READ4_SIZE + 2; + case ICLASS_CMD_SELECT: // ASNB(8)|SERIALNB(8) + if (FrameBytes != 9) { + return ISO15693_APP_NO_RESPONSE; + } + + uint8_t selectCsn[ICLASS_CSN_SIZE]; + if (State == STATE_HALT || State == STATE_IDLE) { + memcpy(selectCsn, CurrentCSN, ICLASS_CSN_SIZE); + } else { + makeAntiCollCsn(CurrentCSN, selectCsn); + } + + if (memcmp(FrameBuf+1, selectCsn, ICLASS_CSN_SIZE)) { + if (State == STATE_ACTIVE) { + State = STATE_IDLE; + } else if (State == STATE_SELECTED) { + State = STATE_HALT; + } + + return ISO15693_APP_NO_RESPONSE; + } + + State = STATE_SELECTED; + + // SERIALNB(8) CRC16(2) + memcpy(FrameBuf, CurrentCSN, ICLASS_CSN_SIZE); + iClassAppendCRC(FrameBuf, ICLASS_CSN_SIZE); + + return ICLASS_CSN_SIZE+2; + case ICLASS_CMD_READCHECK_KC: // ADDRESS(1) + keyBlockNum = ICLASS_BLOCK_KC; + // fallthrough + case ICLASS_CMD_READCHECK_KD: // ADDRESS(1) + if (FrameBytes != 2 || FrameBuf[1] != ICLASS_BLOCK_EPURSE || State != STATE_SELECTED) { + return ISO15693_APP_NO_RESPONSE; + } + + if (CurrentKeyBlockNum != keyBlockNum && !DetectionMode) { + CurrentKeyBlockNum = keyBlockNum; + initCipherState(); + } + + // DATA(8) + MemoryReadBlock(FrameBuf, FrameBuf[1] * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + return ICLASS_BLOCK_SIZE; + case ICLASS_CMD_CHECK: // CHALLENGE(4) READERSIGNATURE(4) + if (FrameBytes != 9 || State != STATE_SELECTED) { + return ISO15693_APP_NO_RESPONSE; + } + + if (DetectionMode) { + // LOCLASS Reader attack mode + // Save + uint8_t loclassLog[24]; + MemoryReadBlock(loclassLog, ICLASS_BLOCK_CSN * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + MemoryReadBlock(loclassLog + ICLASS_BLOCK_SIZE, ICLASS_BLOCK_EPURSE * ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + memcpy(loclassLog + (ICLASS_BLOCK_SIZE * 2), FrameBuf + 1, 8); // Copy CHALLENGE (nr) and READERSIGNATURE (mac) + DetectionLogToFlash(LOG_INFO_APP_AUTHING, loclassLog, sizeof(loclassLog)); + + // Rotate to the next CSN + CurrentKeyBlockNum = (CurrentKeyBlockNum + 1) % NUM_CSNS; + loclassSetCSN(); + + State = STATE_IDLE; + + return ISO15693_APP_NO_RESPONSE; + } + + // TODO: Validate READERSIGNATURE? + + // CHIPRESPONSE(4) + ISO15693StartEarlySend(false, 4); + // for speed reasons, IClassDoTagMAC2 requires 4 bytes of trailing 00's after the NR + memset(FrameBuf + 5, 0, 4); + IClassDoTagMAC2(CipherState, FrameBuf + 1, FrameBuf, CurrentKey); + return ISO15693_APP_EARLY_SEND; + case ICLASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2) + if ((FrameBytes != 11 && FrameBytes != 13) || State != STATE_SELECTED) { + return ISO15693_APP_NO_RESPONSE; + } + + if (FrameBuf[1] >= ICLASS_BLOCK_NUM) { + return ISO15693_APP_NO_RESPONSE; + } + + uint8_t cfgBlock[ICLASS_BLOCK_SIZE]; + MemoryReadBlock(cfgBlock, ICLASS_BLOCK_CFG*ICLASS_BLOCK_SIZE, ICLASS_BLOCK_SIZE); + bool persMode = HAS_MASK(cfgBlock[7], ICLASS_FUSE_PERS); + + if ((FrameBuf[1] == ICLASS_BLOCK_CSN) // CSN is always read only + || (!persMode && !HAS_MASK(cfgBlock[3], 0x80)) // Chip is in RO mode, no updated possible (even ePurse) + || (!persMode && FrameBuf[1] == ICLASS_BLOCK_AIA) // AIA can only be set in personalisation mode + || (!persMode && (FrameBuf[1] == ICLASS_BLOCK_KD || FrameBuf[1] == ICLASS_BLOCK_KC) && (!HAS_MASK(cfgBlock[7], ICLASS_FUSE_CRYPT10))) + ) { + return ISO15693_APP_NO_RESPONSE; // TODO: Is this the right response? + } + + if (FrameBuf[1] >= 6 && FrameBuf[1] <= 12) { + if (!HAS_MASK(cfgBlock[3], 1 << (FrameBuf[1] - 6))) { // bit0 is block6, up to bit6 being block12 + // Block is marked as read-only, deny writing + return ISO15693_APP_NO_RESPONSE; // TODO: Is this the right response? + } + } + + // TODO: Check CRC/SIGN depending on if in secure mode + // Check correct key + // -> Kd only allows decrementing e-Purse + // -> per-app controlled by key access config + //bool keyAccess = HAS_MASK(cfgBlock[5], 0x01); + // -> must auth with that key to change it + + uint8_t blockOffset = FrameBuf[1] * ICLASS_BLOCK_SIZE; + uint8_t block[ICLASS_BLOCK_SIZE]; + if (!persMode && (FrameBuf[1] == ICLASS_BLOCK_KD || FrameBuf[1] == ICLASS_BLOCK_KD)) { + MemoryReadBlock(block, blockOffset, ICLASS_BLOCK_SIZE); + for (uint8_t i = 0; i < sizeof(ICLASS_BLOCK_SIZE); i++) + block[i] ^= FrameBuf[i+2]; + } else if (FrameBuf[1] == ICLASS_BLOCK_CFG) { + block[0] = cfgBlock[0]; // Applications Limit + block[1] = cfgBlock[1] & FrameBuf[3]; // OTP + block[2] = cfgBlock[2] & FrameBuf[4]; // OTP + block[3] = cfgBlock[3] & FrameBuf[5];// Block Write Lock + block[4] = cfgBlock[4]; // Chip Config + block[5] = cfgBlock[5]; // Memory Config + block[6] = FrameBuf[8]; // EAS + block[7] = cfgBlock[7]; // Fuses + + // Some parts allow w (but not e) if in persMode + if (persMode) { + block[0] &= FrameBuf[2]; // Applications Limit + block[4] &= FrameBuf[6]; // Chip Config + block[5] &= FrameBuf[7]; // Memory Config + block[7] &= FrameBuf[9]; // Fuses + } else { + // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode + block[7] &= FrameBuf[9] | ~ICLASS_FUSE_CRYPT10; + } + } else if (FrameBuf[1] == ICLASS_BLOCK_EPURSE) { + // ePurse updates swap first and second half of the block each update + memcpy(block+4, FrameBuf+2, 4); + memcpy(block, FrameBuf+6, 4); + } else { + memcpy(block, FrameBuf+2, ICLASS_BLOCK_SIZE); + } + + // TODO: Epurse swapping first/second half + + MemoryWriteBlock(block, blockOffset, ICLASS_BLOCK_SIZE); + + if ((FrameBuf[1] == CurrentKeyBlockNum || FrameBuf[1] == ICLASS_BLOCK_EPURSE) && !DetectionMode) + initCipherState(); + + // DATA(8) CRC16(2) + if (FrameBuf[1] == ICLASS_BLOCK_KD || FrameBuf[1] == ICLASS_BLOCK_KD) { + // Key updates always return FF's + memcpy(FrameBuf, ffBlock, ICLASS_BLOCK_SIZE); + } else { + memcpy(FrameBuf, block, ICLASS_BLOCK_SIZE); + } + iClassAppendCRC(FrameBuf, ICLASS_BLOCK_SIZE); + return ICLASS_BLOCK_SIZE+2; + case ICLASS_CMD_PAGESEL: // PAGE(1) CRC16(2) + // Chips with a single page do not answer to this command + // BLOCK1(8) CRC16(2) + return ISO15693_APP_NO_RESPONSE; + case ICLASS_CMD_DETECT: + // TODO + return ISO15693_APP_NO_RESPONSE; + default: + return ISO15693_APP_NO_RESPONSE; + } +} + +void IClassGetCsn(ConfigurationUidType uid) { + MemoryReadBlock(uid, ICLASS_BLOCK_CSN * ICLASS_BLOCK_SIZE, ICLASS_CSN_SIZE); +} + +void IClassSetCsn(const ConfigurationUidType uid) { + memcpy(CurrentCSN, uid, ICLASS_CSN_SIZE); + MemoryWriteBlock(uid, ICLASS_BLOCK_CSN * ICLASS_BLOCK_SIZE, ICLASS_CSN_SIZE); +} diff --git a/Firmware/Chameleon-Mini/Application/IClass.h b/Firmware/Chameleon-Mini/Application/IClass.h new file mode 100644 index 00000000..faa2ff88 --- /dev/null +++ b/Firmware/Chameleon-Mini/Application/IClass.h @@ -0,0 +1,26 @@ +/* + * IClass.c + * + * Created on: 17-05-2020 + * Author: NVX + */ + +#ifndef ICLASS_H_ +#define ICLASS_H_ + +#include "Application.h" + +#define ICLASS_BLOCK_SIZE 8 +#define ICLASS_CSN_SIZE ICLASS_BLOCK_SIZE +#define ICLASS_BLOCK_NUM 32 +#define ICLASS_MEM_SIZE ( ICLASS_BLOCK_SIZE * ICLASS_BLOCK_NUM ) +#define ICLASS_READ4_SIZE ( ICLASS_BLOCK_SIZE * 4 ) + +void IClassAppInit(void); +void IClassAppReset(void); +void IClassDetectionInit(void); +uint16_t IClassAppProcess(uint8_t *FrameBuf, uint16_t FrameBytes); +void IClassGetCsn(ConfigurationUidType Uid); +void IClassSetCsn(const ConfigurationUidType Uid); + +#endif /* ICLASS_H_ */ diff --git a/Firmware/Chameleon-Mini/Application/MifareClassic.c b/Firmware/Chameleon-Mini/Application/MifareClassic.c index c979cc59..ac1c6616 100644 --- a/Firmware/Chameleon-Mini/Application/MifareClassic.c +++ b/Firmware/Chameleon-Mini/Application/MifareClassic.c @@ -292,7 +292,7 @@ static uint8_t AccessAddress; static uint16_t CardATQAValue; static uint8_t CardSAKValue; static bool FromHalt = false; -static bool DetectionMode = false; +bool DetectionMode = false; #define BYTE_SWAP(x) (((uint8_t)(x)>>4)|((uint8_t)(x)<<4)) #define NO_ACCESS 0x07 @@ -401,7 +401,7 @@ extern uint8_t bUidMode; // Magic card mode switch static uint16_t DetectionLogPtr = FRAM_DETECTION_DATA_ADDR; static uint8_t EEMEM LogDetectionValid = false; -static void DetectionLogToFlash(uint8_t Entry, const void *Data, uint8_t Length) { +void DetectionLogToFlash(uint8_t Entry, const void *Data, uint8_t Length) { if (!DetectionMode) return; @@ -1025,7 +1025,7 @@ uint16_t MifareClassicAppProcess(uint8_t *Buffer, uint16_t BitCount) { State = STATE_HALT; Buffer[0] = NAK_NOT_AUTHED ^ Crypto1Nibble(); return ACK_NAK_FRAME_SIZE; - } + } else { DetectionLogToFlash(LOG_INFO_APP_CMD_WRITE, Buffer, 2); /* Write command. Store the address and prepare for the upcoming data. @@ -1313,4 +1313,4 @@ void MifareClassicGetSak(uint8_t * Sak) { void MifareClassicSetSak(uint8_t Sak) { CardSAKValue = Sak; -} \ No newline at end of file +} diff --git a/Firmware/Chameleon-Mini/Application/MifareClassic.h b/Firmware/Chameleon-Mini/Application/MifareClassic.h index feab77aa..0274b014 100644 --- a/Firmware/Chameleon-Mini/Application/MifareClassic.h +++ b/Firmware/Chameleon-Mini/Application/MifareClassic.h @@ -16,6 +16,9 @@ #define MIFARE_CLASSIC_1K_MEM_SIZE 1024 #define MIFARE_CLASSIC_4K_MEM_SIZE 4096 +extern bool DetectionMode; +void DetectionLogToFlash(uint8_t Entry, const void *Data, uint8_t Length); + void DetectionInit(void); void DetectionLogClear(void); void MifareDetectionInit1K(void); diff --git a/Firmware/Chameleon-Mini/Codec/ISO15693.c b/Firmware/Chameleon-Mini/Codec/ISO15693.c index 02214847..9763f8a6 100644 --- a/Firmware/Chameleon-Mini/Codec/ISO15693.c +++ b/Firmware/Chameleon-Mini/Codec/ISO15693.c @@ -121,7 +121,7 @@ ISR_SHARED isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT(void) { */ INLINE void ISO15693_EOC(void) { /* Set bitrate required by the reader on SOF for our following response */ - if (CodecBuffer[0] & REQ_DATARATE_HIGH) { + if (CodecBuffer[0] & REQ_DATARATE_HIGH || ActiveConfiguration.TagFamily == TAG_FAMILY_ISO15693_ICLASS) { BitRate1 = 256; BitRate2 = 252; /* If single subcarrier mode is requested, BitRate2 is useless, but setting it nevertheless was still faster than checking */ } else { @@ -321,8 +321,12 @@ ISR_SHARED isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT(void) { if ((BitSent % 8) == 0) { /* Last SOF bit has been put out. Start sending out data */ - StateRegister = LOADMOD_BIT0_SINGLE; - ShiftRegister = (*CodecBufferPtr++); + if (ByteCount == 0) { + StateRegister = LOADMOD_FINISHED; + } else { + StateRegister = LOADMOD_BIT0_SINGLE; + ShiftRegister = (*CodecBufferPtr++); + } } else { StateRegister = LOADMOD_SOF_SINGLE; } @@ -403,8 +407,12 @@ ISR_SHARED isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT(void) { if ((BitSent % 8) == 0) { /* Last SOF bit has been put out. Start sending out data */ - StateRegister = LOADMOD_BIT0_DUAL; - ShiftRegister = (*CodecBufferPtr++); + if (ByteCount == 0) { + StateRegister = LOADMOD_FINISHED; + } else { + StateRegister = LOADMOD_BIT0_DUAL; + ShiftRegister = (*CodecBufferPtr++); + } } else { StateRegister = LOADMOD_SOF_DUAL; } @@ -618,6 +626,23 @@ void ISO15693CodecDeInit(void) { CodecSetLoadmodState(false); } +void ISO15693StartEarlySend(bool bDualSubcarrier, uint16_t byteCount) { + // Buy a little more time to ready the responce (400 cycles == 14.7uS) before sending the SOF + CODEC_TIMER_LOADMOD.PER = ISO15693_T1_TIME + 200; + + ByteCount = byteCount; + CodecBufferPtr = CodecBuffer; + + /* Start loadmodulating */ + if (bDualSubcarrier) { + CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OOK, SUBCARRIER_2); + StateRegister = LOADMOD_START_DUAL; + } else { + CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OOK, SUBCARRIER_1); + StateRegister = LOADMOD_START_SINGLE; + } +} + void ISO15693CodecTask(void) { if (Flags.DemodFinished) { Flags.DemodFinished = 0; @@ -630,14 +655,19 @@ void ISO15693CodecTask(void) { LogEntry(LOG_INFO_CODEC_RX_DATA, CodecBuffer, DemodByteCount); LEDHook(LED_CODEC_RX, LED_PULSE); - if (CodecBuffer[0] & REQ_SUBCARRIER_DUAL) { + if (CodecBuffer[0] & REQ_SUBCARRIER_DUAL && ActiveConfiguration.TagFamily != TAG_FAMILY_ISO15693_ICLASS) { bDualSubcarrier = true; } + LogEntry(LOG_INFO_GENERIC, "Test1", 5); AppReceivedByteCount = ApplicationProcess(CodecBuffer, DemodByteCount); } /* This is only reached when we've received a valid frame */ - if (AppReceivedByteCount != ISO15693_APP_NO_RESPONSE) { + if (AppReceivedByteCount != ISO15693_APP_NO_RESPONSE && AppReceivedByteCount != ISO15693_APP_EARLY_SEND) { + if (AppReceivedByteCount == ISO15693_APP_SOF_ONLY) { + AppReceivedByteCount = 0; + } + if (AppReceivedByteCount > CODEC_BUFFER_SIZE - ISO15693_CRC16_SIZE) { /* CRC would be written outside codec buffer */ CodecBuffer[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; CodecBuffer[ISO15693_RES_ADDR_PARAM] = ISO15693_RES_ERR_NOT_SUPP; @@ -660,11 +690,14 @@ void ISO15693CodecTask(void) { } /* Calculate the CRC while modulation is already ongoing */ - ISO15693AppendCRC(CodecBuffer, AppReceivedByteCount); - ByteCount += ISO15693_CRC16_SIZE; /* Increase this variable as it will be read by the codec during loadmodulation */ - LogEntry(LOG_INFO_CODEC_TX_DATA, CodecBuffer, AppReceivedByteCount + ISO15693_CRC16_SIZE); - - } else { + if (ActiveConfiguration.TagFamily != TAG_FAMILY_ISO15693_ICLASS && AppReceivedByteCount != 0) { + ISO15693AppendCRC(CodecBuffer, AppReceivedByteCount); + ByteCount += ISO15693_CRC16_SIZE; /* Increase this variable as it will be read by the codec during loadmodulation */ + LogEntry(LOG_INFO_CODEC_TX_DATA, CodecBuffer, AppReceivedByteCount + ISO15693_CRC16_SIZE); + } else { + LogEntry(LOG_INFO_CODEC_TX_DATA, CodecBuffer, AppReceivedByteCount); + } + } else if (AppReceivedByteCount != ISO15693_APP_EARLY_SEND) { /* Overwrite the PERBUF register, which was configured in ISO15693_EOC, with the new appropriate value. * This is expecially needed because, since load modulation has not been performed, we're jumping here straight after * ISO15693_EOC. This implies that the data stored in the PERBUF register by ISO15693_EOC still has to be copied diff --git a/Firmware/Chameleon-Mini/Codec/ISO15693.h b/Firmware/Chameleon-Mini/Codec/ISO15693.h index 0362fdcc..ae163d33 100644 --- a/Firmware/Chameleon-Mini/Codec/ISO15693.h +++ b/Firmware/Chameleon-Mini/Codec/ISO15693.h @@ -8,7 +8,12 @@ #ifndef ISO15693_H_ #define ISO15693_H_ +#include +#include + #define ISO15693_APP_NO_RESPONSE 0x0000 +#define ISO15693_APP_SOF_ONLY 0xFFFF +#define ISO15693_APP_EARLY_SEND 0xFFFE /* Codec Interface */ void ISO15693CodecInit(void); @@ -18,5 +23,6 @@ void ISO15693CodecTask(void); /* Application Interface */ void ISO15693CodecStart(void); void ISO15693CodecReset(void); +void ISO15693StartEarlySend(bool bDualSubcarrier, uint16_t byteCount); #endif /* ISO15693_H_ */ diff --git a/Firmware/Chameleon-Mini/Configuration.c b/Firmware/Chameleon-Mini/Configuration.c index e8a3ff83..ccd18e12 100644 --- a/Firmware/Chameleon-Mini/Configuration.c +++ b/Firmware/Chameleon-Mini/Configuration.c @@ -69,6 +69,10 @@ static const MapEntryType PROGMEM ConfigurationMap[] = { #ifdef CONFIG_EM4233_SUPPORT { .Id = CONFIG_EM4233, .Text = "EM4233" }, #endif +#ifdef CONFIG_ICLASS_SUPPORT + { .Id = CONFIG_ICLASS, .Text = "ICLASS" }, + { .Id = CONFIG_ICLASS_DETECTION, .Text = "ICLASS_DETECTION" }, +#endif }; /* Include all Codecs and Applications */ @@ -499,6 +503,40 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .TagFamily = TAG_FAMILY_ISO14443A }, #endif +#ifdef CONFIG_ICLASS_SUPPORT + [CONFIG_ICLASS] = { + .CodecInitFunc = ISO15693CodecInit, + .CodecDeInitFunc = ISO15693CodecDeInit, + .CodecTaskFunc = ISO15693CodecTask, + .ApplicationInitFunc = IClassAppInit, + .ApplicationResetFunc = IClassAppReset, + .ApplicationTaskFunc = ApplicationTaskDummy, + .ApplicationTickFunc = ApplicationTickDummy, + .ApplicationProcessFunc = IClassAppProcess, + .ApplicationGetUidFunc = IClassGetCsn, + .ApplicationSetUidFunc = IClassSetCsn, + .UidSize = ICLASS_CSN_SIZE, + .MemorySize = ICLASS_MEM_SIZE, + .ReadOnly = false, + .TagFamily = TAG_FAMILY_ISO15693_ICLASS + }, + [CONFIG_ICLASS_DETECTION] = { + .CodecInitFunc = ISO15693CodecInit, + .CodecDeInitFunc = ISO15693CodecDeInit, + .CodecTaskFunc = ISO15693CodecTask, + .ApplicationInitFunc = IClassDetectionInit, + .ApplicationResetFunc = IClassAppReset, + .ApplicationTaskFunc = ApplicationTaskDummy, + .ApplicationTickFunc = ApplicationTickDummy, + .ApplicationProcessFunc = IClassAppProcess, + .ApplicationGetUidFunc = IClassGetCsn, + .ApplicationSetUidFunc = ApplicationSetUidDummy, + .UidSize = ICLASS_CSN_SIZE, + .MemorySize = ICLASS_MEM_SIZE, + .ReadOnly = true, + .TagFamily = TAG_FAMILY_ISO15693_ICLASS + }, +#endif }; ConfigurationType ActiveConfiguration; diff --git a/Firmware/Chameleon-Mini/Configuration.h b/Firmware/Chameleon-Mini/Configuration.h index 0cfd2648..ee2c52bd 100644 --- a/Firmware/Chameleon-Mini/Configuration.h +++ b/Firmware/Chameleon-Mini/Configuration.h @@ -74,16 +74,21 @@ typedef enum { #endif #ifdef CONFIG_EM4233_SUPPORT CONFIG_EM4233, +#endif +#ifdef CONFIG_ICLASS_SUPPORT + CONFIG_ICLASS, + CONFIG_ICLASS_DETECTION, #endif /* This HAS to be the last element */ CONFIG_COUNT } ConfigurationEnum; /** Tag Family definitions **/ -#define TAG_FAMILY_NONE 0 -#define TAG_FAMILY_ISO14443A 1 -#define TAG_FAMILY_ISO14443B 2 -#define TAG_FAMILY_ISO15693 5 +#define TAG_FAMILY_NONE 0 +#define TAG_FAMILY_ISO14443A 1 +#define TAG_FAMILY_ISO14443B 2 +#define TAG_FAMILY_ISO15693 5 +#define TAG_FAMILY_ISO15693_ICLASS 6 // Disables CRC and dual subcarrier /** With this `struct` the behavior of a configuration is defined. */ diff --git a/Firmware/Chameleon-Mini/Makefile b/Firmware/Chameleon-Mini/Makefile index 91903503..a556fb3d 100644 --- a/Firmware/Chameleon-Mini/Makefile +++ b/Firmware/Chameleon-Mini/Makefile @@ -23,6 +23,7 @@ SETTINGS += -DCONFIG_NTAG215_SUPPORT # SETTINGS += -DCONFIG_TITAGITSTANDARD_SUPPORT # SETTINGS += -DCONFIG_EM4233_SUPPORT SETTINGS += -DCONFIG_ISO15693_SNIFF_SUPPORT +#SETTINGS += -DCONFIG_ICLASS_SUPPORT #Support magic mode on mifare classic configuration SETTINGS += -DSUPPORT_MF_CLASSIC_MAGIC_MODE @@ -71,7 +72,7 @@ SETTINGS += -DLED_SETTING_GLOBAL #Default logging mode SETTINGS += -DDEFAULT_LOG_MODE=LOG_MODE_OFF #SETTINGS += -DDEFAULT_LOG_MODE=LOG_MODE_MEMORY -#SETTINGS += -DDEFAULT_LOG_MODE=LOG_MODE_TERMINAL +#SETTINGS += -DDEFAULT_LOG_MODE=LOG_MODE_LIVE #Define if log settings should be global SETTINGS += -DLOG_SETTING_GLOBAL @@ -114,7 +115,7 @@ SRC += Terminal/Terminal.c Terminal/Commands.c Terminal/XModem.c Termina SRC += Codec/Codec.c Codec/ISO14443-2A.c Codec/Reader14443-2A.c Codec/SniffISO14443-2A.c Codec/Reader14443-ISR.S SRC += Application/MifareUltralight.c Application/MifareClassic.c Application/ISO14443-3A.c Application/Crypto1.c Application/Reader14443A.c Application/Sniff14443A.c Application/CryptoTDEA.S Application/NTAG215.c SRC += Codec/ISO15693.c Codec/SniffISO15693.c -SRC += Application/Vicinity.c Application/Sl2s2002.c Application/TITagitstandard.c Application/ISO15693-A.c Application/EM4233.c Application/Sniff15693.c +SRC += Application/Vicinity.c Application/Sl2s2002.c Application/TITagitstandard.c Application/ISO15693-A.c Application/EM4233.c Application/Sniff15693.c Application/IClass.c SRC += $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS) LUFA_PATH = ../LUFA CC_FLAGS = -flto -DUSE_LUFA_CONFIG_HEADER -DFLASH_DATA_ADDR=$(FLASH_DATA_ADDR) -DFLASH_DATA_SIZE=$(FLASH_DATA_SIZE) -DSPM_HELPER_ADDR=$(SPM_HELPER_ADDR) -DBUILD_DATE=$(BUILD_DATE) -DCOMMIT_ID=\"$(COMMIT_ID)\" $(SETTINGS) diff --git a/Firmware/Chameleon-Mini/Memory.c b/Firmware/Chameleon-Mini/Memory.c index c950e839..a5e5e777 100644 --- a/Firmware/Chameleon-Mini/Memory.c +++ b/Firmware/Chameleon-Mini/Memory.c @@ -172,15 +172,16 @@ INLINE void FRAMWrite(const void *Buffer, uint16_t Address, uint16_t ByteCount) SPIWriteBlock(Buffer, ByteCount); FRAM_PORT.OUTSET = FRAM_CS; + if (0 == Address #ifdef CONFIG_ISO14443A_READER_SUPPORT - if (0 == Address && GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ISO14443A_READER) { - ConfigurationSetById(GlobalSettings.ActiveSettingPtr->Configuration); - } -#else - if (0 == Address) { + && GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ISO14443A_READER +#endif +#ifdef CONFIG_ICLASS_SUPPORT + && GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ICLASS_DETECTION +#endif + ) { ConfigurationSetById(GlobalSettings.ActiveSettingPtr->Configuration); } -#endif } INLINE void FlashRead(void *Buffer, uint32_t Address, uint16_t ByteCount) {