mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
Merge branch 'main' of https://github.com/RfidResearchGroup/ChameleonUltra
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -142,4 +142,9 @@ GDK_SCALE=2 GDK_DPI_SCALE=0.5 /usr/local/lib/chameleonultragui/chameleonultragui
|
||||
### Key recovery from this card is not yet supported
|
||||
This error may occur when the card you are trying to recovery keys from is requiring a not yet supported recovery technology. You cannot tell the required recovery method from the outside.
|
||||
|
||||
You may be able to solve this issue by dumping the card with a Proxmark3 or Mifare Classic Tool (Mobile App) and then importing a bin file.
|
||||
You may be able to solve this issue by dumping the card with a Proxmark3 or Mifare Classic Tool (Mobile App) and then importing a bin file.
|
||||
|
||||
### Error: Invalid data length
|
||||
This error may occur on windows devices and seems to be a deeper bug in the serial library.
|
||||
|
||||
Reconnecting the device may solve it, also make sure that the App and Firmware are up to date.
|
||||
@@ -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.
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -119,6 +119,8 @@ class ChameleonCLI:
|
||||
args.prog = tree_node.fullname
|
||||
try:
|
||||
args_parse_result = args.parse_args(arg_list)
|
||||
if args.help_requested:
|
||||
return
|
||||
except chameleon_utils.ArgsParserError as e:
|
||||
args.print_help()
|
||||
print(f'{CY}'+str(e).strip()+f'{C0}', end="\n\n")
|
||||
|
||||
@@ -21,7 +21,7 @@ from chameleon_utils import CLITree
|
||||
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 @@ class HFMFWRBL(MF1AuthArgsUnit):
|
||||
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="<dec>",
|
||||
help="set value X (-2147483647 ~ 2147483647) to src block")
|
||||
operator_group.add_argument('--inc', type=int, required=False, metavar="<dec>",
|
||||
help="increment value by X (0 ~ 2147483647) from src to dst")
|
||||
operator_group.add_argument('--dec', type=int, required=False, metavar="<dec>",
|
||||
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="<dec>",
|
||||
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="<hex>", help="key of src")
|
||||
|
||||
parser.add_argument('--tblk', '--dst-block', type=int, metavar="<dec>",
|
||||
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="<hex>", 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("<iiiBBBB", resp)
|
||||
# print(f"{val1}, {val2}, {val3}, {adr1}, {adr2}, {adr3}, {adr4}")
|
||||
if (val1 != val3) or (val1 + val2 != -1):
|
||||
print(f" - {CR}Invalid value of value block: {resp.hex()}{C0}")
|
||||
return
|
||||
if (adr1 != adr3) or (adr2 != adr4) or (adr1 + adr2 != 0xFF):
|
||||
print(f" - {CR}Invalid address of value block: {resp.hex()}{C0}")
|
||||
return
|
||||
print(f" - block[{block}] = {CG}{{ value: {val1}, adr: {adr1} }}{C0}")
|
||||
|
||||
def set_value(self, block, type, key, value):
|
||||
if value < -2147483647 or value > 2147483647:
|
||||
raise ArgsParserError(f"Set value must be between -2147483647 and 2147483647. Got {value}")
|
||||
adr_inverted = 0xFF - block
|
||||
data = struct.pack("<iiiBBBB", value, -value - 1, value, block, adr_inverted, block, adr_inverted)
|
||||
resp = self.cmd.mf1_write_one_block(block, type, key, data)
|
||||
if resp:
|
||||
print(f" - {CG}Set done.{C0}")
|
||||
self.get_value(block, type, key)
|
||||
else:
|
||||
print(f" - {CR}Set fail.{C0}")
|
||||
|
||||
def inc_value(self, src_blk, src_type, src_key, value, dst_blk, dst_type, dst_key):
|
||||
if value < 0 or value > 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
|
||||
|
||||
@@ -4,9 +4,9 @@ from typing import Union
|
||||
|
||||
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 @@ class ChameleonCMD:
|
||||
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 @@ class ChameleonCMD:
|
||||
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 @@ class ChameleonCMD:
|
||||
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 ChameleonCMD:
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -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 @@ class ButtonPressFunction(enum.IntEnum):
|
||||
elif self == ButtonPressFunction.BATTERY:
|
||||
return "Show Battery Level"
|
||||
return "None"
|
||||
|
||||
@enum.unique
|
||||
class MfcValueBlockOperator(enum.IntEnum):
|
||||
DECREMENT = 0xC0
|
||||
INCREMENT = 0xC1
|
||||
RESTORE = 0xC2
|
||||
@@ -43,6 +43,7 @@ class ArgumentParserNoExit(argparse.ArgumentParser):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.add_help = False
|
||||
self.description = "Please enter correct parameters"
|
||||
self.help_requested = False
|
||||
|
||||
def exit(self, status: int = 0, message: Union[str, None] = None):
|
||||
if message:
|
||||
@@ -88,14 +89,18 @@ class ArgumentParserNoExit(argparse.ArgumentParser):
|
||||
options = lines
|
||||
lines = []
|
||||
if len(options) > 0:
|
||||
assert options[0].strip() == 'options:'
|
||||
# 2 variants depending on Python version(?)
|
||||
assert options[0].strip() in ['options:', 'optional arguments:']
|
||||
options[0] = options[0].replace('options:', f'{CG}options:{C0}')
|
||||
options[0] = options[0].replace('optional arguments:', f'{CG}optional arguments:{C0}')
|
||||
if len(options) > 1:
|
||||
options.append('')
|
||||
print('\n'.join(options))
|
||||
if len(lines) > 0:
|
||||
lines[0] = f'{CG}{lines[0]}{C0}'
|
||||
print('\n'.join(lines))
|
||||
print('')
|
||||
self.help_requested = True
|
||||
|
||||
|
||||
def expect_response(accepted_responses: Union[int, list[int]]) -> Callable[..., Any]:
|
||||
|
||||
Reference in New Issue
Block a user