From e79568298fa70dab18d3a347c7985566186cbe08 Mon Sep 17 00:00:00 2001 From: taichunmin Date: Tue, 24 Oct 2023 00:48:39 +0800 Subject: [PATCH] Added support for mifare classic value block operations --- CHANGELOG.md | 1 + docs/protocol.md | 4 + firmware/application/src/app_cmd.c | 53 ++++ firmware/application/src/data_cmd.h | 1 + .../application/src/rfid/reader/hf/rc522.c | 281 +++++++++++++++--- .../application/src/rfid/reader/hf/rc522.h | 15 + software/script/chameleon_cli_unit.py | 140 ++++++++- software/script/chameleon_cmd.py | 35 ++- software/script/chameleon_enum.py | 7 + 9 files changed, 494 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 564401de..962ac59f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added support for mifare classic value block operations (@taichunmin) - Added regression tests (@doegox) - Changed git version to version tag instead of dev tag (@taichunmin) - Fixed 14A emulate bug and MF1 emulate bug (@spp2000 and @xianglin1998) diff --git a/docs/protocol.md b/docs/protocol.md index 0be18720..8905e583 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -290,6 +290,10 @@ Notes: * `reserved`:2 * Response: data sent by the card * CLI: cf `hf 14a raw` +### 2011: MF1_MANIPULATE_VALUE_BLOCK +* Command: 21 bytes: `src_type|src_block|src_key[6]|operator|operand[4]|dst_type|dst_block|dst_key[6]`. Key as 6 bytes. Type=`0x60` for key A, `0x61` for key B. Operator=`0xC0` for decrement, `0xC1` for increment, `0xC2` for restore. Operand as I32 in Network byte order. +* Response: no data +* CLI: cf `hf mf value` ### 3000: EM410X_SCAN * Command: no data * Response: 5 bytes. `id[5]`. ID as 5 bytes. diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 00e3f54e..45b833a8 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -447,6 +447,58 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u return data_frame_make(cmd, status, resp_length, resp); } +static data_frame_tx_t *cmd_processor_mf1_manipulate_value_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t src_type; + uint8_t src_block; + uint8_t src_key[6]; + uint8_t operator; + uint32_t operand; + uint8_t dst_type; + uint8_t dst_block; + uint8_t dst_key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + payload_t *payload = (payload_t *)data; + + // scan tag + picc_14a_tag_t taginfo; + if (pcd_14a_reader_scan_auto(&taginfo) != STATUS_HF_TAG_OK) { + return data_frame_make(cmd, STATUS_HF_TAG_NO, 0, NULL); + } + + // auth src + status = pcd_14a_reader_mf1_auth(&taginfo, payload->src_type, payload->src_block, payload->src_key); + if (status != STATUS_HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + + // value block operation + status = pcd_14a_reader_mf1_manipulate_value_block(payload->operator, payload->src_block, (int32_t) U32NTOHL(payload->operand)); + if (status != STATUS_HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + + // auth dst if needed + if (payload->src_block != payload->dst_block || payload->src_type != payload->dst_type) { + status = pcd_14a_reader_mf1_auth(&taginfo, payload->dst_type, payload->dst_block, payload->dst_key); + if (status != STATUS_HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + } + + // transfer value block + status = pcd_14a_reader_mf1_transfer_value_block(payload->dst_block); + if (status != STATUS_HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + + return data_frame_make(cmd, status, 0, NULL); +} + static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t id_buffer[5] = { 0x00 }; status = PcdScanEM410X(id_buffer); @@ -1015,6 +1067,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, { DATA_CMD_HF14A_RAW, before_reader_run, cmd_processor_hf14a_raw, NULL }, + { DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK, before_hf_reader_run, cmd_processor_mf1_manipulate_value_block, after_hf_reader_run }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index b0ad7511..e605ff97 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -66,6 +66,7 @@ #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) #define DATA_CMD_HF14A_RAW (2010) +#define DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK (2011) // // ****************************************************************** diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 7fc615f4..d08a01e3 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -256,15 +256,13 @@ uint16_t pcd_14a_reader_timeout_get() { * POUTLENBIT: Bit the length of the data * @retval : Status value mi_ok, successful */ -uint8_t pcd_14a_reader_bytes_transfer(uint8_t Command, uint8_t *pIn, uint8_t InLenByte, uint8_t *pOut, uint16_t *pOutLenBit, uint16_t maxOutLenBit) { +uint8_t pcd_14a_reader_bytes_transfer(uint8_t Command, uint8_t *pIn, uint8_t InLenByte, uint8_t *pOut, uint16_t *pOutLenBit, uint16_t maxOutLenBit) { uint8_t status = STATUS_HF_ERR_STAT; uint8_t waitFor = 0x00; uint8_t lastBits = 0; uint8_t n = 0; uint8_t pcd_err_val = 0; uint8_t not_timeout = 0; - // Reset the length of the received data - *pOutLenBit = 0; switch (Command) { case PCD_AUTHENT: // MiFare certification @@ -292,6 +290,8 @@ uint8_t pcd_14a_reader_bytes_transfer(uint8_t Command, uint8_t *pIn, uint8_t In while ((read_register_single(Status2Reg) & 0x07) == 0x03); return STATUS_HF_TAG_OK; } + // Reset the length of the received data + *pOutLenBit = 0; bsp_set_timer(g_timeout_auto_timer, 0); // Before starting the operation, return to zero over time counting @@ -482,69 +482,189 @@ uint8_t pcd_14a_reader_bits_transfer(uint8_t *pTx, uint16_t szTxBits, uint8_t * return STATUS_HF_TAG_OK; } +/** +* @brief : Through RC522 and ISO14443 cartoon communication +* @param : Command: RC522 command word +* PIN: Data sent to the card through RC522 +* Inlenbyte: The byte length of sending the data +* POUT: The receiving card returns the data +* POUTLENBIT: Bit the length of the data +* FLAGS: +* - PCD_TRANSMIT_FLAG_NO_RESET_MF_CRYPTO1_ON: Do not reset MFCrypto1On +* @retval : Status value mi_ok, successful +*/ +uint8_t pcd_14a_reader_bytes_transfer_flags(uint8_t Command, uint8_t *pIn, uint8_t InLenByte, uint8_t *pOut, uint16_t *pOutLenBit, uint16_t maxOutLenBit, uint32_t flags) { + uint8_t status = STATUS_HF_ERR_STAT; + uint8_t waitFor = 0x00; + uint8_t lastBits = 0; + uint8_t n = 0; + uint8_t pcd_err_val = 0; + uint8_t not_timeout = 0; + + switch (Command) { + case PCD_AUTHENT: // MiFare certification + waitFor = 0x10; // Query the free interrupt logo when the certification card is waiting + break; + + case PCD_TRANSCEIVE: + waitFor = 0x30; // Inquiry the receiving interrupt logo position and Leisure interrupt logo + break; + } + + write_register_single(CommandReg, PCD_IDLE); // Flushbuffer clearing the internal FIFO read and writing pointer and ErRreg's Bufferovfl logo position is cleared + clear_register_mask(ComIrqReg, 0x80); // When Set1 is cleared, the shielding position of commonricqreg is clear zero + set_register_mask(FIFOLevelReg, 0x80); // Write an empty order + + write_register_buffer(FIFODataReg, pIn, InLenByte); // Write data into FIFODATA + write_register_single(CommandReg, Command); // Write command + + if (Command == PCD_TRANSCEIVE) { + set_register_mask(BitFramingReg, 0x80); // StartSend places to start the data to send this bit and send and receive commands when it is valid + } + + if (pOut == NULL) { + // If the developer does not need to receive data, then return directly after the sending! + while ((read_register_single(Status2Reg) & 0x07) == 0x03); + return STATUS_HF_TAG_OK; + } + // Reset the length of the received data + *pOutLenBit = 0; + + bsp_set_timer(g_timeout_auto_timer, 0); // Before starting the operation, return to zero over time counting + + do { + n = read_register_single(ComIrqReg); // Read the communication interrupt register to determine whether the current IO task is completed! + not_timeout = NO_TIMEOUT_1MS(g_timeout_auto_timer, g_com_timeout_ms); + } while (not_timeout && (!(n & waitFor))); // Exit conditions: timeout interruption, interrupt with empty command commands + // NRF_LOG_INFO("N = %02x\n", n); + + if (Command == PCD_TRANSCEIVE) { + clear_register_mask(BitFramingReg, 0x80); // Clean up allows the startsend bit and the bit length position + } + + // Whether to receive timeout + if (not_timeout) { + // First determine whether there is a place where there is an error register + if (n & 0x02) { + // Error occur + // Read an error logo register BufferOfI CollErr ParityErr ProtocolErr + pcd_err_val = read_register_single(ErrorReg); + // Detect whether there are abnormalities + if (pcd_err_val & 0x01) { // ProtocolErr Error only appears in the following two cases: + if (Command == PCD_AUTHENT) { // During the execution of the MFAUTHENT command, if the number of bytes received by a data stream, the position of the place + // Therefore, we need to deal with it well, assuming that there are problems during the verification process, then we need to think that this is normal + status = STATUS_MF_ERR_AUTH; + } else { // If the SOF is wrong, the position is set up and the receiver is automatically cleared during the start -up stage, which is effective at the rate of 106kbd + NRF_LOG_INFO("Protocol error\n"); + status = STATUS_HF_ERR_STAT; + } + } else if (pcd_err_val & 0x02) { + // Detecting whether there are even strange errors + NRF_LOG_INFO("Parity error\n"); + status = STATUS_HF_ERR_PARITY; + } else if (pcd_err_val & 0x04) { // Detect whether there are CRC errors + NRF_LOG_INFO("CRC error\n"); + status = STATUS_HF_ERR_CRC; + } else if (pcd_err_val & 0x08) { // There is a conflict to detect the label + NRF_LOG_INFO("Collision tag\n"); + status = STATUS_HF_COLLISION; + } else { // There are other unrepaired abnormalities + NRF_LOG_INFO("HF error: 0x%0x2\n", pcd_err_val); + status = STATUS_HF_ERR_STAT; + } + } else { + // Occasionally occur + // NRF_LOG_INFO("COM OK\n"); + if (Command == PCD_TRANSCEIVE) { + n = read_register_single(FIFOLevelReg); // Read the number of bytes saved in FIFO + if (n == 0) { n = 1; } + + lastBits = read_register_single(Control522Reg) & 0x07; // Finally receive the validity of the byte + + if (lastBits) { *pOutLenBit = (n - 1) * 8 + lastBits; } // N -byte number minus 1 (last byte)+ the number of bits of the last bit The total number of data readings read + else { *pOutLenBit = n * 8; } // Finally received the entire bytes received by the byte valid + + if (*pOutLenBit <= maxOutLenBit) { + // Read all the data in FIFO + read_register_buffer(FIFODataReg, pOut, n); + // Transmission instructions can be considered success when reading normal data! + status = STATUS_HF_TAG_OK; + } else { + NRF_LOG_INFO("pcd_14a_reader_bytes_transfer receive response overflow: %d, max = %d\n", *pOutLenBit, maxOutLenBit); + // We can't pass the problem with problems, which is meaningless for the time being + *pOutLenBit = 0; + // Since there is a problem with the data, let's notify the upper layer and inform me + status = STATUS_HF_ERR_STAT; + } + } else { + // Non -transmitted instructions, the execution is completed without errors and considered success! + status = STATUS_HF_TAG_OK; + } + } + } else { + status = STATUS_HF_TAG_NO; + // NRF_LOG_INFO("Tag lost(timeout).\n"); + } + + if ((flags & PCD_TRANSMIT_FLAG_NO_RESET_MF_CRYPTO1_ON) == 0 && status != STATUS_HF_TAG_OK) { + // If there are certain operations, + // We may need to remove MFCrypto1On This register logo, + // Because it may be because of the error encryption communication caused by verification + clear_register_mask(Status2Reg, 0x08); + } + + // NRF_LOG_INFO("Com status: %d\n", status); + return status; +} + /** * @brief : ISO14443-A Fast Select * @param :tag: tag info buffer * @retval : if return STATUS_HF_TAG_OK, the tag is selected. */ -uint8_t pcd_14a_reader_fast_select(picc_14a_tag_t *tag) { - uint8_t resp[5] = {0}; // theoretically. A usual RATS will be much smaller - uint8_t uid_resp[4] = {0}; - uint8_t sak = 0x04; // cascade uid +uint8_t pcd_14a_reader_fast_select(picc_14a_tag_t *tag) { + uint8_t dat_buff[9] = { 0x00 }; uint8_t status = STATUS_HF_TAG_OK; uint8_t cascade_level = 0; - uint16_t len; + uint16_t dat_len; // Wakeup - if (pcd_14a_reader_atqa_request(resp, NULL, U8ARR_BIT_LEN(resp)) != STATUS_HF_TAG_OK) { + if (pcd_14a_reader_atqa_request(dat_buff, NULL, U8ARR_BIT_LEN(dat_buff)) != STATUS_HF_TAG_OK) { return STATUS_HF_TAG_NO; } // OK we will select at least at cascade 1, lets see if first byte of UID was 0x88 in // which case we need to make a cascade 2 request and select - this is a long UID // While the UID is not complete, the 3nd bit (from the right) is set in the SAK. - for (; sak & 0x04; cascade_level++) { - // uint8_t sel_all[] = { PICC_ANTICOLL1, 0x20 }; - uint8_t sel_uid[] = { PICC_ANTICOLL1, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + for (; cascade_level < tag->cascade; cascade_level++) { + // Construct SELECT UID command (1 Byte cmd, 1 Byte NVB, 4 Byte UID, 1 Byte BCC, 2 Bytes CRC) // SELECT_* (L1: 0x93, L2: 0x95, L3: 0x97) - sel_uid[0] = /*sel_all[0] = */ 0x93 + cascade_level * 2; + dat_buff[0] = PICC_ANTICOLL1 + cascade_level * 2; + dat_buff[1] = 0x70; // transmitting a full UID + // Copy the UID information of the tag to the dat_buff buffer if (cascade_level < tag->cascade - 1) { - uid_resp[0] = 0x88; - memcpy(uid_resp + 1, tag->uid + cascade_level * 3, 3); + dat_buff[2] = 0x88; + memcpy(dat_buff + 3, tag->uid + cascade_level * 3, 3); } else { - memcpy(uid_resp, tag->uid + cascade_level * 3, 4); + memcpy(dat_buff + 2, tag->uid + cascade_level * 3, 4); } - // Construct SELECT UID command - //sel_uid[1] = 0x70; // transmitting a full UID (1 Byte cmd, 1 Byte NVB, 4 Byte UID, 1 Byte BCC, 2 Bytes CRC) - memcpy(sel_uid + 2, uid_resp, 4); // the UID received during anticollision, or the provided UID - sel_uid[6] = sel_uid[2] ^ sel_uid[3] ^ sel_uid[4] ^ sel_uid[5]; // calculate and add BCC - crc_14a_append(sel_uid, 7); // calculate and add CRC - status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, sel_uid, sizeof(sel_uid), resp, &len, U8ARR_BIT_LEN(resp)); + dat_buff[6] = dat_buff[2] ^ dat_buff[3] ^ dat_buff[4] ^ dat_buff[5]; // calculate BCC + crc_14a_append(dat_buff, 7); // calculate and add CRC + status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, sizeof(dat_buff), dat_buff, &dat_len, U8ARR_BIT_LEN(dat_buff)); // Receive the SAK - if (status != STATUS_HF_TAG_OK || !len) { - // printf("SAK Err: %d, %d\r\n", status, recv_len); + if (status != STATUS_HF_TAG_OK || !dat_len) { + // printf("SAK Err: %d, %d\r\n", status, dat_len); return STATUS_HF_TAG_NO; } - - sak = resp[0]; - - // Test if more parts of the uid are coming - if ((sak & 0x04) /* && uid_resp[0] == 0x88 */) { - // Remove first byte, 0x88 is not an UID byte, it CT, see page 3 of: - // http://www.nxp.com/documents/application_note/AN10927.pdf - uid_resp[0] = uid_resp[1]; - uid_resp[1] = uid_resp[2]; - uid_resp[2] = uid_resp[3]; - } } return STATUS_HF_TAG_OK; } /** * @brief : ISO14443-A Find a card, only execute once! -* @param :tag: Buffer that stores card information +* @param : tag: Buffer that stores card information * @retval : Status value hf_tag_ok, success */ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { @@ -978,6 +1098,95 @@ uint8_t pcd_14a_reader_mf1_write(uint8_t addr, uint8_t *p) { return pcd_14a_reader_mf1_write_by_cmd(PICC_WRITE, addr, p); } +/** + * @brief : Increment: increments the contents of a block and stores the result in the internal Transfer Buffer + * Decrement: decrements the contents of a block and stores the result in the internal Transfer Buffer + * Restore: reads the contents of a block into the internal Transfer Buffer + * @param : Operator: Increment, Decrement, Restore + * Addr: block address + * Operand: The written data, I32 + * @retval : Status value hf_tag_ok, success + */ +uint8_t pcd_14a_reader_mf1_manipulate_value_block(uint8_t operator, uint8_t addr, int32_t operand) { + // operator can only be PICC_DECREMENT, PICC_INCREMENT, PICC_RESTORE + if (operator != PICC_DECREMENT && operator != PICC_INCREMENT && operator != PICC_RESTORE) { + return STATUS_PAR_ERR; + } + + uint8_t status; + uint16_t dat_len; + + // Prepare the cmd data to manipulate the value block + uint8_t dat_buff[6] = { operator, addr }; + crc_14a_append(dat_buff, 2); + + // NRF_LOG_INFO("0 pcd_14a_reader_mf1_manipulate_value_block addr = %d\r\n", addr); + + // Request to manipulate block, at this time, the card should reply to ACK + status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, 4, dat_buff, &dat_len, U8ARR_BIT_LEN(dat_buff)); + // The communication fails, the reason is returned directly + if (status != STATUS_HF_TAG_OK) { + return status; + } + // The communication was successful, but the operation was rejected by the card! + if ((dat_len != 4) || ((dat_buff[0] & 0x0F) != 0x0A)) { + // NRF_LOG_INFO("1 status = %d, datalen = %d, data = %02x\n", status, dat_len, dat_buff[0]); + return STATUS_HF_ERR_STAT; + } + + // The communication was successful, the card accepted the card value block operand + // 1. Copy data and calculate CRC + memcpy(dat_buff, &operand, 4); + crc_14a_append(dat_buff, 4); + + // NRF_LOG_INFO_hex("Will send: ", (uint8_t *)dat_buff, 6); + // NRF_LOG_INFO("\n"); + + // 2. Transfer the operand to complete the value block manipulation + status = pcd_14a_reader_bytes_transfer_flags( + PCD_TRANSCEIVE, + dat_buff, + 6, + dat_buff, + &dat_len, + U8ARR_BIT_LEN(dat_buff), + PCD_TRANSMIT_FLAG_NO_RESET_MF_CRYPTO1_ON); + + // Operand Part of Increment/Decrement/Restore does not acknowledge, so Timeout means success + if (status != STATUS_HF_TAG_NO || dat_len != 0) { + return status == STATUS_HF_TAG_OK ? STATUS_HF_ERR_STAT : status; + } + + return STATUS_HF_TAG_OK; +} + +/** +* @brief : Writes the contents of the internal Transfer Buffer to a block +* @param : cmd : Transfer instruction +* addr: block address +* @retval : Status value hf_tag_ok, success +*/ +uint8_t pcd_14a_reader_mf1_transfer_value_block(uint8_t addr) { + uint8_t status; + uint16_t dat_len; + uint8_t dat_buff[4] = { PICC_TRANSFER, addr }; + + // Short data directly MCU calculate + crc_14a_append(dat_buff, 2); + // Then initiate communication + status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, 4, dat_buff, &dat_len, U8ARR_BIT_LEN(dat_buff)); + // The communication fails, the reason is returned directly + if (status != STATUS_HF_TAG_OK) { + return status; + } + // The communication was successful, but the operation was rejected by the card! + if ((dat_len != 4) || ((dat_buff[0] & 0x0F) != 0x0A)) { + // NRF_LOG_INFO("1 status = %d, datalen = %d, data = %02x\n", status, dat_len, dat_buff[0]); + return STATUS_HF_ERR_STAT; + } + return STATUS_HF_TAG_OK; +} + /** * @brief : Let the card enter the dormant mode * @param :none diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 96d6652c..2c42d1b9 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -19,6 +19,11 @@ #define PCD_RESET 0x0F //Restoration #define PCD_CALCCRC 0x03 //CRC calculation +/** +* flags for pcd_14a_reader_bytes_transfer_flags +*/ +#define PCD_TRANSMIT_FLAG_NO_RESET_MF_CRYPTO1_ON 0x01 // do not clear MFCrypto1On when status != STATUS_HF_TAG_OK + /* * isO14443ACommandWord */ @@ -190,6 +195,13 @@ uint8_t pcd_14a_reader_bytes_transfer(uint8_t Command, uint8_t *pOut, uint16_t *pOutLenBit, uint16_t maxOutLenBit); +uint8_t pcd_14a_reader_bytes_transfer_flags(uint8_t Command, + uint8_t *pIn, + uint8_t InLenByte, + uint8_t *pOut, + uint16_t *pOutLenBit, + uint16_t maxOutLenBit, + uint32_t flags); uint8_t pcd_14a_reader_bits_transfer(uint8_t *pTx, uint16_t szTxBits, uint8_t *pTxPar, @@ -217,6 +229,9 @@ uint8_t pcd_14a_reader_mf1_write(uint8_t addr, uint8_t *pData); // cardReadingOperation uint8_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p); uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); +// value block operation +uint8_t pcd_14a_reader_mf1_manipulate_value_block(uint8_t operator, uint8_t addr, int32_t operand); +uint8_t pcd_14a_reader_mf1_transfer_value_block(uint8_t addr); // Formation card operation uint8_t pcd_14a_reader_halt_tag(void); void pcd_14a_reader_fast_halt_tag(void); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 1e6b4732..b551bb9c 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -21,7 +21,7 @@ from chameleon_utils import CR, CG, CB, CC, CY, C0 from chameleon_enum import Command, Status, SlotNumber, TagSenseType, TagSpecificType from chameleon_enum import MifareClassicWriteMode, MifareClassicPrngType, MifareClassicDarksideStatus, MfcKeyType -from chameleon_enum import AnimationMode, ButtonType, ButtonPressFunction +from chameleon_enum import AnimationMode, ButtonPressFunction, ButtonType, MfcValueBlockOperator # NXP IDs based on https://www.nxp.com/docs/en/application-note/AN10833.pdf type_id_SAK_dict = {0x00: "MIFARE Ultralight Classic/C/EV1/Nano | NTAG 2xx", @@ -884,6 +884,144 @@ def on_exec(self, args: argparse.Namespace): print(f" - {CR}Write fail.{C0}") +@hf_mf.command('value') +class HFMFVALUE(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Classic value block commands' + + operator_group = parser.add_mutually_exclusive_group() + operator_group.add_argument('--get', action='store_true', help="get value from src block") + operator_group.add_argument('--set', type=int, required=False, metavar="", + help="set value X (-2147483647 ~ 2147483647) to src block") + operator_group.add_argument('--inc', type=int, required=False, metavar="", + help="increment value by X (0 ~ 2147483647) from src to dst") + operator_group.add_argument('--dec', type=int, required=False, metavar="", + help="decrement value by X (0 ~ 2147483647) from src to dst") + operator_group.add_argument('--res', '--cp', action='store_true', help="copy value from src to dst (Restore and Transfer)") + + parser.add_argument('--blk', '--src-block', type=int, required=True, metavar="", + help="block number of src") + srctype_group = parser.add_mutually_exclusive_group() + srctype_group.add_argument('-a', '-A', action='store_true', help="key of src is A key (default)") + srctype_group.add_argument('-b', '-B', action='store_true', help="key of src is B key") + parser.add_argument('-k', '--src-key', type=str, required=True, metavar="", help="key of src") + + parser.add_argument('--tblk', '--dst-block', type=int, metavar="", + help="block number of dst (default to src)") + dsttype_group = parser.add_mutually_exclusive_group() + dsttype_group.add_argument('--ta', '--tA', action='store_true', help="key of dst is A key (default to src)") + dsttype_group.add_argument('--tb', '--tB', action='store_true', help="key of dst is B key (default to src)") + parser.add_argument('--tkey', '--dst-key', type=str, metavar="", help="key of dst (default to src)") + + return parser + + def on_exec(self, args: argparse.Namespace): + # print(args) + # src + src_blk = args.blk + src_type = MfcKeyType.B if args.b is not False else MfcKeyType.A + src_key = args.src_key + if not re.match(r"^[a-fA-F0-9]{12}$", src_key): + print("src_key must include 12 HEX symbols") + return + src_key = bytearray.fromhex(src_key) + # print(src_blk, src_type, src_key) + + if args.get is not False: + self.get_value(src_blk, src_type, src_key) + return + elif args.set is not None: + self.set_value(src_blk, src_type, src_key, args.set) + return + + # dst + dst_blk = args.tblk if args.tblk is not None else src_blk + dst_type = MfcKeyType.A if args.ta is not False else (MfcKeyType.B if args.tb is not False else src_type) + dst_key = args.tkey if args.tkey is not None else args.src_key + if not re.match(r"^[a-fA-F0-9]{12}$", dst_key): + print("dst_key must include 12 HEX symbols") + return + dst_key = bytearray.fromhex(dst_key) + # print(dst_blk, dst_type, dst_key) + + if args.inc is not None: + self.inc_value(src_blk, src_type, src_key, args.inc, dst_blk, dst_type, dst_key) + return + elif args.dec is not None: + self.dec_value(src_blk, src_type, src_key, args.dec, dst_blk, dst_type, dst_key) + return + elif args.res is not False: + self.res_value(src_blk, src_type, src_key, dst_blk, dst_type, dst_key) + return + else: + raise ArgsParserError("Please specify a value command") + + def get_value(self, block, type, key): + resp = self.cmd.mf1_read_one_block(block, type, key) + val1, val2, val3, adr1, adr2, adr3, adr4 = struct.unpack(" 2147483647: + raise ArgsParserError(f"Set value must be between -2147483647 and 2147483647. Got {value}") + adr_inverted = 0xFF - block + data = struct.pack(" 2147483647: + raise ArgsParserError(f"Increment value must be between 0 and 2147483647. Got {value}") + resp = self.cmd.mf1_manipulate_value_block( + src_blk, src_type, src_key, + MfcValueBlockOperator.INCREMENT, value, + dst_blk, dst_type, dst_key + ) + if resp: + print(f" - {CG}Increment done.{C0}") + self.get_value(dst_blk, dst_type, dst_key) + else: + print(f" - {CR}Increment fail.{C0}") + + def dec_value(self, src_blk, src_type, src_key, value, dst_blk, dst_type, dst_key): + if value < 0 or value > 2147483647: + raise ArgsParserError(f"Decrement value must be between 0 and 2147483647. Got {value}") + resp = self.cmd.mf1_manipulate_value_block( + src_blk, src_type, src_key, + MfcValueBlockOperator.DECREMENT, value, + dst_blk, dst_type, dst_key + ) + if resp: + print(f" - {CG}Decrement done.{C0}") + self.get_value(dst_blk, dst_type, dst_key) + else: + print(f" - {CR}Decrement fail.{C0}") + + def res_value(self, src_blk, src_type, src_key, dst_blk, dst_type, dst_key): + resp = self.cmd.mf1_manipulate_value_block( + src_blk, src_type, src_key, + MfcValueBlockOperator.RESTORE, 0, + dst_blk, dst_type, dst_key + ) + if resp: + print(f" - {CG}Restore done.{C0}") + self.get_value(dst_blk, dst_type, dst_key) + else: + print(f" - {CR}Restore fail.{C0}") + + @hf_mf.command('elog') class HFMFELog(DeviceRequiredUnit): detection_log_size = 18 diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 15356feb..12c11cc1 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -4,9 +4,9 @@ import chameleon_com from chameleon_utils import expect_response -from chameleon_enum import Command, Status, SlotNumber, TagSenseType, TagSpecificType -from chameleon_enum import MifareClassicDarksideStatus -from chameleon_enum import ButtonType, ButtonPressFunction +from chameleon_enum import Command, SlotNumber, Status, TagSenseType, TagSpecificType +from chameleon_enum import ButtonPressFunction, ButtonType, MifareClassicDarksideStatus +from chameleon_enum import MfcKeyType, MfcValueBlockOperator CURRENT_VERSION_SETTINGS = 5 @@ -187,7 +187,7 @@ def mf1_darkside_acquire(self, block_target, type_target, first_recover: Union[i return resp @expect_response([Status.HF_TAG_OK, Status.MF_ERR_AUTH]) - def mf1_auth_one_key_block(self, block, type_value, key): + def mf1_auth_one_key_block(self, block, type_value: MfcKeyType, key): """ Verify the mf1 key, only verify the specified type of key for a single sector. @@ -202,7 +202,7 @@ def mf1_auth_one_key_block(self, block, type_value, key): return resp @expect_response(Status.HF_TAG_OK) - def mf1_read_one_block(self, block, type_value, key): + def mf1_read_one_block(self, block, type_value: MfcKeyType, key): """ Read one mf1 block. @@ -217,7 +217,7 @@ def mf1_read_one_block(self, block, type_value, key): return resp @expect_response(Status.HF_TAG_OK) - def mf1_write_one_block(self, block, type_value, key, block_data): + def mf1_write_one_block(self, block, type_value: MfcKeyType, key, block_data): """ Write mf1 single block. @@ -277,6 +277,29 @@ class CStruct(ctypes.BigEndianStructure): resp.parsed = resp.data return resp + @expect_response(Status.HF_TAG_OK) + def mf1_manipulate_value_block(self, src_block, src_type: MfcKeyType, src_key, operator: MfcValueBlockOperator, operand, dst_block, dst_type: MfcKeyType, dst_key): + """ + 1. Increment: increments value from source block and write to dest block + 2. Decrement: decrements value from source block and write to dest block + 3. Restore: copy value from source block and write to dest block + + + :param src_block: + :param src_type: + :param src_key: + :param operator: + :param operand: + :param dst_block: + :param dst_type: + :param dst_key: + :return: + """ + data = struct.pack('!BB6sBiBB6s', src_type, src_block, src_key, operator, operand, dst_type, dst_block, dst_key) + resp = self.device.send_cmd_sync(Command.MF1_MANIPULATE_VALUE_BLOCK, data) + resp.parsed = resp.status == Status.HF_TAG_OK + return resp + @expect_response(Status.HF_TAG_OK) def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index b0e8d53b..58d63595 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -67,6 +67,7 @@ class Command(enum.IntEnum): MF1_READ_ONE_BLOCK = 2008 MF1_WRITE_ONE_BLOCK = 2009 HF14A_RAW = 2010 + MF1_MANIPULATE_VALUE_BLOCK = 2011 EM410X_SCAN = 3000 EM410X_WRITE_TO_T55XX = 3001 @@ -431,3 +432,9 @@ def __str__(self): elif self == ButtonPressFunction.BATTERY: return "Show Battery Level" return "None" + +@enum.unique +class MfcValueBlockOperator(enum.IntEnum): + DECREMENT = 0xC0 + INCREMENT = 0xC1 + RESTORE = 0xC2 \ No newline at end of file