From 151f412490929f29fe4d11c24a6ea92f45aafbcf Mon Sep 17 00:00:00 2001 From: Foxushka <135865149+Foxushka@users.noreply.github.com> Date: Sat, 19 Aug 2023 03:00:01 +0300 Subject: [PATCH] Implement new Mifare Classic emulator configuration commands, improve CLI Slot Won't work like this More randomness on nested auth (for hardnested recovery) and change default GCC location Nobody will have GCC in this default location, /usr/bin/ will target way more users Get enabled slots command hw slot list show disabled slots Improve python code quality Show Mifare Classic emulator settings in hw slot list Implement hf mf settings to change Mifare Classic emulator settings Update --- firmware/application/src/app_cmd.c | 131 +++++++++++- firmware/application/src/data_cmd.h | 12 ++ .../application/src/rfid/nfctag/hf/nfc_mf1.c | 83 ++++++-- .../application/src/rfid/nfctag/hf/nfc_mf1.h | 23 +- .../components/toolchain/gcc/Makefile.posix | 2 +- software/script/chameleon_cli_main.py | 32 ++- software/script/chameleon_cli_unit.py | 197 ++++++++++------- software/script/chameleon_cmd.py | 198 ++++++++++++------ software/script/chameleon_com.py | 41 ++-- software/script/chameleon_cstruct.py | 33 +-- software/script/chameleon_status.py | 99 +++++---- 11 files changed, 564 insertions(+), 287 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 2bbbdbd..ac22dd8 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -463,7 +463,7 @@ data_frame_tx_t* cmd_processor_set_mf1_anti_collision_res(uint16_t cmd, uint16_t } else { uint8_t uid_length = length - 3; if (is_valid_uid_size(uid_length)) { - nfc_tag_14a_coll_res_referen_t* info = get_miafre_coll_res(); + nfc_tag_14a_coll_res_referen_t* info = get_mifare_coll_res(); // copy sak info->sak[0] = data[0]; // copy atqa @@ -528,6 +528,124 @@ data_frame_tx_t* cmd_processor_get_slot_tag_nick_name(uint16_t cmd, uint16_t sta return data_frame_make(cmd, status, length, data); } +data_frame_tx_t* cmd_processor_get_mf1_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mf1_info[5] = {}; + mf1_info[0] = nfc_tag_mf1_is_detection_enable(); + mf1_info[1] = nfc_tag_mf1_is_gen1a_magic_mode(); + mf1_info[2] = nfc_tag_mf1_is_gen2_magic_mode(); + mf1_info[3] = nfc_tag_mf1_is_use_mf1_coll_res(); + nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); + if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { + mf1_info[4] = 0; + } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { + mf1_info[4] = 1; + } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { + mf1_info[4] = 2; + } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { + mf1_info[4] = 3; + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 5, mf1_info); +} + +data_frame_tx_t* cmd_processor_get_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (nfc_tag_mf1_is_gen1a_magic_mode()) { + status = 1; + } else { + status = 0; + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t*)&status); +} + +data_frame_tx_t* cmd_processor_set_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 1 && (data[0] == 0 || data[0] == 1)) { + nfc_tag_mf1_set_gen1a_magic_mode(data[0]); + status = STATUS_DEVICE_SUCCESS; + } else { + status = STATUS_PAR_ERR; + } + return data_frame_make(cmd, status, 0, NULL); +} + +data_frame_tx_t* cmd_processor_get_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (nfc_tag_mf1_is_gen2_magic_mode()) { + status = 1; + } else { + status = 0; + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t*)&status); +} + +data_frame_tx_t* cmd_processor_set_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 1 && (data[0] == 0 || data[0] == 1)) { + nfc_tag_mf1_set_gen2_magic_mode(data[0]); + status = STATUS_DEVICE_SUCCESS; + } else { + status = STATUS_PAR_ERR; + } + return data_frame_make(cmd, status, 0, NULL); +} + +data_frame_tx_t* cmd_processor_get_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (nfc_tag_mf1_is_use_mf1_coll_res()) { + status = 1; + } else { + status = 0; + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t*)&status); +} + +data_frame_tx_t* cmd_processor_set_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 1 && (data[0] == 0 || data[0] == 1)) { + nfc_tag_mf1_set_use_mf1_coll_res(data[0]); + status = STATUS_DEVICE_SUCCESS; + } else { + status = STATUS_PAR_ERR; + } + return data_frame_make(cmd, status, 0, NULL); +} + +data_frame_tx_t* cmd_processor_get_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); + if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { + status = 0; + } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { + status = 1; + } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { + status = 2; + } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { + status = 3; + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t*)&status); +} + +data_frame_tx_t* cmd_processor_set_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 1 && (data[0] >= 0 || data[0] <= 3)) { + uint8_t mode = data[0]; + if (mode == 0) { + nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_NORMAL); + } else if (mode == 1) { + nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DENIED); + } else if (mode == 2) { + nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DECEIVE); + } else if (mode == 3) { + nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_SHADOW); + } + status = STATUS_DEVICE_SUCCESS; + } else { + status = STATUS_PAR_ERR; + } + return data_frame_make(cmd, status, 0, NULL); +} + +data_frame_tx_t* cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t slot_info[8] = {}; + for (uint8_t slot = 0; slot < 8; slot++) { + slot_info[slot] = tag_emulation_slot_is_enable(slot); + } + + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, slot_info); +} + #if defined(PROJECT_CHAMELEON_ULTRA) @@ -614,6 +732,8 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_activated_slot, NULL }, { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, + { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, + { DATA_CMD_SET_EM410X_EMU_ID, NULL, cmd_processor_set_em410x_emu_id, NULL }, @@ -624,6 +744,15 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_GET_MF1_DETECTION_RESULT, NULL, cmd_processor_get_mf1_detection_log, NULL }, { DATA_CMD_LOAD_MF1_BLOCK_DATA, NULL, cmd_processor_set_mf1_emulator_block, NULL }, { DATA_CMD_SET_MF1_ANTI_COLLISION_RES, NULL, cmd_processor_set_mf1_anti_collision_res, NULL }, + { DATA_CMD_GET_MF1_EMULATOR_CONFIG, NULL, cmd_processor_get_mf1_info, NULL }, + { DATA_CMD_GET_MF1_GEN1A_MODE, NULL, cmd_processor_get_mf1_gen1a_magic_mode, NULL }, + { DATA_CMD_SET_MF1_GEN1A_MODE, NULL, cmd_processor_set_mf1_gen1a_magic_mode, NULL }, + { DATA_CMD_GET_MF1_GEN2_MODE, NULL, cmd_processor_get_mf1_gen2_magic_mode, NULL }, + { DATA_CMD_SET_MF1_GEN2_MODE, NULL, cmd_processor_set_mf1_gen2_magic_mode, NULL }, + { DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_get_mf1_use_coll_res, NULL }, + { DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_set_mf1_use_coll_res, NULL }, + { DATA_CMD_GET_MF1_WRITE_MODE, NULL, cmd_processor_get_mf1_write_mode, NULL }, + { DATA_CMD_SET_MF1_WRITE_MODE, NULL, cmd_processor_set_mf1_write_mode, NULL }, { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick_name, NULL }, { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick_name, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 2050fa8..ce00e3a 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -28,6 +28,7 @@ #define DATA_CMD_GET_ACTIVE_SLOT (1018) #define DATA_CMD_GET_SLOT_INFO (1019) #define DATA_CMD_WIPE_FDS (1020) +#define DATA_CMD_GET_ENABLED_SLOTS (1023) // // ****************************************************************** @@ -69,6 +70,17 @@ // #define DATA_CMD_LOAD_MF1_BLOCK_DATA (4000) #define DATA_CMD_SET_MF1_ANTI_COLLISION_RES (4001) + +#define DATA_CMD_GET_MF1_EMULATOR_CONFIG (4009) +#define DATA_CMD_GET_MF1_GEN1A_MODE (4010) +#define DATA_CMD_SET_MF1_GEN1A_MODE (4011) +#define DATA_CMD_GET_MF1_GEN2_MODE (4012) +#define DATA_CMD_SET_MF1_GEN2_MODE (4013) +#define DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL (4014) +#define DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL (4015) +#define DATA_CMD_GET_MF1_WRITE_MODE (4016) +#define DATA_CMD_SET_MF1_WRITE_MODE (4017) + // // ****************************************************************** diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c index 6402327..920511f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -38,14 +38,14 @@ NRF_LOG_MODULE_REGISTER(); #define CMD_AUTH_A 0x60 #define CMD_AUTH_B 0x61 #define CMD_AUTH_FRAME_SIZE 2 /* Bytes without CRCA */ -#define CMD_AUTH_RB_FRAME_SIZE 4 /* Bytes */ -#define CMD_AUTH_AB_FRAME_SIZE 8 /* Bytes */ -#define CMD_AUTH_BA_FRAME_SIZE 4 /* Bytes */ +#define CMD_AUTH_RB_FRAME_SIZE 4 /* Bytes */ +#define CMD_AUTH_AB_FRAME_SIZE 8 /* Bytes */ +#define CMD_AUTH_BA_FRAME_SIZE 4 /* Bytes */ #define CMD_HALT 0x50 -#define CMD_HALT_FRAME_SIZE 2 /* Bytes without CRCA */ +#define CMD_HALT_FRAME_SIZE 2 /* Bytes without CRCA */ #define CMD_READ 0x30 #define CMD_READ_FRAME_SIZE 2 /* Bytes without CRCA */ -#define CMD_READ_RESPONSE_FRAME_SIZE 16 /* Bytes without CRCA */ +#define CMD_READ_RESPONSE_FRAME_SIZE 16 /* Bytes without CRCA */ #define CMD_WRITE 0xA0 #define CMD_WRITE_FRAME_SIZE 2 /* Bytes without CRCA */ #define CMD_DECREMENT 0xC0 @@ -54,8 +54,6 @@ NRF_LOG_MODULE_REGISTER(); #define CMD_INCREMENT_FRAME_SIZE 2 /* Bytes without CRCA */ #define CMD_RESTORE 0xC2 #define CMD_RESTORE_FRAME_SIZE 2 /* Bytes without CRCA */ -#define CMD_SIG_READ 0xC2 -#define CMD_SIG_READ_FRAME_SIZE 1 /* Bytes without CRCA */ #define CMD_TRANSFER 0xB0 #define CMD_TRANSFER_FRAME_SIZE 2 /* Bytes without CRCA */ @@ -311,9 +309,18 @@ void ValueToBlock(uint8_t *Block, uint32_t Value) { /** @brief mf1获取一个随机数 * @param nonce 随机数的Buffer */ -void nfc_tag_mf1_random_nonce(uint8_t nonce[4]) { +void nfc_tag_mf1_random_nonce(uint8_t nonce[4], bool isNested) { // 使用rand进行快速产生随机数,性能损耗较小 - num_to_bytes(rand(), 4, nonce); + // isNested provides more randomness for hardnested attack + if (isNested) { + nonce[0] = rand() & 0xff; + nonce[1] = rand() & 0xff; + nonce[2] = rand() & 0xff; + nonce[3] = rand() & 0xff; + } else { + // fast for most readers + num_to_bytes(rand(), 4, nonce); + } } /** @@ -494,7 +501,7 @@ void nfc_tag_mf1_state_handler(uint8_t* p_data, uint16_t szDataBits) { m_tag_trailer_info = (nfc_tag_mf1_trailer_info_t*)m_tag_information->memory[BlockEnd]; // 生成随机数 - nfc_tag_mf1_random_nonce(CardNonce); + nfc_tag_mf1_random_nonce(CardNonce, false); // 根据卡随机数预先计算读卡器应答 for (uint8_t i = 0; i < sizeof(ReaderResponse); i++) { @@ -674,10 +681,10 @@ void nfc_tag_mf1_state_handler(uint8_t* p_data, uint16_t szDataBits) { case CMD_READ: { // 保存当前操作的块地址 CurrentAddress = p_data[1]; + // 生成访问控制,用于下面的数据访问控制 + uint8_t Acc = abTrailorAccessConditions[ GetAccessCondition(CurrentAddress) ][ KeyInUse ]; // 读取命令。从内存中读取数据并附加CRCA。注意:读取操作受到控制位的限制,但是目前我们只限制控制位的读取 - if ((p_data[1] < 128 && (p_data[1] & 3) == 3) || ((p_data[1] & 15) == 15)) { - // 生成访问控制,用于下面的数据访问控制 - uint8_t Acc = abTrailorAccessConditions[ GetAccessCondition(CurrentAddress) ][ KeyInUse ]; + if ((CurrentAddress < 128 && (CurrentAddress & 3) == 3) || ((CurrentAddress & 15) == 15)) { // 清空一下buffer,避免缓存的数据影响到后续操作 memset(m_tag_tx_buffer.tx_raw_buffer, 0x00, sizeof(m_tag_tx_buffer.tx_raw_buffer)); // 让这块数据区域变成我们需要的尾部块类型 @@ -717,7 +724,7 @@ void nfc_tag_mf1_state_handler(uint8_t* p_data, uint16_t szDataBits) { } case CMD_WRITE: { // 正常的卡不允许写block0,不然会被CUID防火墙识别到 - if (p_data[1] == 0x00) { + if (p_data[1] == 0x00 && !m_tag_information->config.mode_gen2_magic) { // 直接重置14a的状态机,让标签休眠 nfc_tag_14a_set_state(NFC_TAG_STATE_14A_HALTED); // 告知一下读头此操作不被允许 @@ -823,7 +830,7 @@ void nfc_tag_mf1_state_handler(uint8_t* p_data, uint16_t szDataBits) { m_tag_trailer_info = (nfc_tag_mf1_trailer_info_t*)m_tag_information->memory[BlockEnd]; // 生成随机数 - nfc_tag_mf1_random_nonce(CardNonce); + nfc_tag_mf1_random_nonce(CardNonce, true); // 根据卡随机数预先计算读卡器响应 for (uint8_t i = 0; i < sizeof(ReaderResponse); i++) { @@ -1041,7 +1048,7 @@ void nfc_tag_mf1_state_handler(uint8_t* p_data, uint16_t szDataBits) { /** * @brief 提供mifare标签必要的防冲突资源(仅提供指针) */ -nfc_tag_14a_coll_res_referen_t* get_miafre_coll_res() { +nfc_tag_14a_coll_res_referen_t* get_mifare_coll_res() { // 根据当前的互通配置,选择性的返回其中配置的数据,假设开启了数据互通,那么我们还需要确保当前模拟的卡是4BYTE的 if (m_tag_information->config.use_mf1_coll_res && m_tag_information->res_coll.size == NFC_TAG_14A_UID_SINGLE_SIZE) { // 获得数据区域的厂商信息 @@ -1119,7 +1126,7 @@ int nfc_tag_mf1_data_loadcb(tag_specific_type_t type, tag_data_buffer_t* buffer) m_tag_type = type; // 注册14a通信管理接口 nfc_tag_14a_handler_t handler_for_14a = { - .get_coll_res = get_miafre_coll_res, + .get_coll_res = get_mifare_coll_res, .cb_state = nfc_tag_mf1_state_handler, .cb_reset = nfc_tag_mf1_reset_handler, }; @@ -1166,6 +1173,7 @@ bool nfc_tag_mf1_data_factory(uint8_t slot, tag_specific_type_t tag_type) { // default mf1 config p_mf1_information->config.mode_gen1a_magic = false; + p_mf1_information->config.mode_gen2_magic = false; p_mf1_information->config.use_mf1_coll_res = false; p_mf1_information->config.mode_block_write = NFC_TAG_MF1_WRITE_NORMAL; p_mf1_information->config.detection_enable = false; @@ -1204,3 +1212,44 @@ void nfc_tag_mf1_detection_log_clear(void) { uint32_t nfc_tag_mf1_detection_log_count(void) { return m_auth_log.count; } + +// Set gen1a magic mode +void nfc_tag_mf1_set_gen1a_magic_mode(bool enable) { + m_tag_information->config.mode_gen1a_magic = enable; +} + +// Is in gen1a magic mode? +bool nfc_tag_mf1_is_gen1a_magic_mode(void) { + return m_tag_information->config.mode_gen1a_magic; +} + +// Set gen2 magic mode +void nfc_tag_mf1_set_gen2_magic_mode(bool enable) { + m_tag_information->config.mode_gen2_magic = enable; +} + +// Is in gen2 magic mode? +bool nfc_tag_mf1_is_gen2_magic_mode(void) { + return m_tag_information->config.mode_gen2_magic; +} + +// Set anti collision data from block 0 +void nfc_tag_mf1_set_use_mf1_coll_res(bool enable) { + m_tag_information->config.use_mf1_coll_res = enable; +} + +// Get is anti collision data from block 0 +bool nfc_tag_mf1_is_use_mf1_coll_res(void) { + return m_tag_information->config.use_mf1_coll_res; +} + +// Set write mode +void nfc_tag_mf1_set_write_mode(nfc_tag_mf1_write_mode_t write_mode) { + m_tag_information->config.mode_block_write = write_mode; +} + +// Get write mode +nfc_tag_mf1_write_mode_t nfc_tag_mf1_get_write_mode(void) { + return m_tag_information->config.mode_block_write; +} + diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h index 2a8a228..ea35b25 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h @@ -44,10 +44,10 @@ typedef enum { // mf1配置 typedef struct { /** - * 正常写入模式(根据当前的状态去正常写入,受控制位和后门卡影响) - * 拒绝写入模式(类似控制位锁死,直接拒绝任何写入,返回nack) - * 欺诈写入模式(表面上返回ack表示写入成功,其实连RAM都不写入) - * 影子写入模式(写入到RAM里面,并且返回ack表示成功,但是不保存到flash里面) + * Normal write mode (write normally according to the current state, affected by the control bit and the back door card) + * Deny write mode (similar to control bit lock, directly reject any write, return nack) + * Fraudulent writing mode (on the surface, returning ack indicates that the writing is successful, but in fact, even RAM is not written) + * Shadow write mode (write to RAM, and return ack to indicate success, but not save to flash) * @see nfc_tag_mf1_write_mode_t */ nfc_tag_mf1_write_mode_t mode_block_write; @@ -66,8 +66,10 @@ typedef struct { * 使能侦测,将自动记录mf1的验证日志 */ uint8_t detection_enable: 1; + // Allow to write block 0 (CUID/gen2 mode) + uint8_t mode_gen2_magic: 1; // 保留 - uint8_t reserved1: 5; + uint8_t reserved1: 4; uint8_t reserved2; uint8_t reserved3; } nfc_tag_mf1_configure_t; @@ -140,6 +142,15 @@ void nfc_tag_mf1_set_detection_enable(bool enable); bool nfc_tag_mf1_is_detection_enable(void); void nfc_tag_mf1_detection_log_clear(void); uint32_t nfc_tag_mf1_detection_log_count(void); -nfc_tag_14a_coll_res_referen_t* get_miafre_coll_res(void); +nfc_tag_14a_coll_res_referen_t* get_mifare_coll_res(void); +void nfc_tag_mf1_set_gen1a_magic_mode(bool enable); +bool nfc_tag_mf1_is_gen1a_magic_mode(void); +void nfc_tag_mf1_set_gen2_magic_mode(bool enable); +bool nfc_tag_mf1_is_gen2_magic_mode(void); +void nfc_tag_mf1_set_use_mf1_coll_res(bool enable); +bool nfc_tag_mf1_is_use_mf1_coll_res(void); +void nfc_tag_mf1_set_write_mode(nfc_tag_mf1_write_mode_t write_mode); +nfc_tag_mf1_write_mode_t nfc_tag_mf1_get_write_mode(void); + #endif diff --git a/firmware/nrf52_sdk/components/toolchain/gcc/Makefile.posix b/firmware/nrf52_sdk/components/toolchain/gcc/Makefile.posix index 04bbd93..8066bb0 100644 --- a/firmware/nrf52_sdk/components/toolchain/gcc/Makefile.posix +++ b/firmware/nrf52_sdk/components/toolchain/gcc/Makefile.posix @@ -1,3 +1,3 @@ -GNU_INSTALL_ROOT ?= /usr/local/gcc-arm-none-eabi-10.3-2021.10/bin/ +GNU_INSTALL_ROOT ?= /usr/bin/ GNU_VERSION ?= 9.3.1 GNU_PREFIX ?= arm-none-eabi diff --git a/software/script/chameleon_cli_main.py b/software/script/chameleon_cli_main.py index bb17915..3c3e380 100755 --- a/software/script/chameleon_cli_main.py +++ b/software/script/chameleon_cli_main.py @@ -1,5 +1,4 @@ import argparse -import os import platform import sys import traceback @@ -10,9 +9,6 @@ import colorama import chameleon_cli_unit import os -if os.name == 'posix': - import readline - ULTRA = r""" ╦ ╦╦ ╔╦╗╦═╗╔═╗ ███████ ║ ║║ ║ ╠╦╝╠═╣ @@ -43,10 +39,7 @@ def new_uint(unit_clz, help_msg): :param help_msg: unit usage :return: a dict... """ - return { - 'unit': unit_clz, - 'help': help_msg, - } + return {'unit': unit_clz, 'help': help_msg} class ChameleonCLI: @@ -60,7 +53,7 @@ class ChameleonCLI: 'connect': new_uint(chameleon_cli_unit.HWConnect, "Connect to chameleon by serial port"), 'chipid': { 'get': new_uint(chameleon_cli_unit.HWChipIdGet, "Get device chipset ID"), - 'help': "Device chipsed ID get" + 'help': "Device chipset ID get" }, 'address': { 'get': new_uint(chameleon_cli_unit.HWAddressGet, "Get device address (used with Bluetooth)"), @@ -72,7 +65,7 @@ class ChameleonCLI: 'help': "Device mode get/set" }, 'slot': { - 'info': new_uint(chameleon_cli_unit.HWSlotInfo, "Get information about slots"), + 'list': new_uint(chameleon_cli_unit.HWSlotList, "Get information about slots"), 'change': new_uint(chameleon_cli_unit.HWSlotSet, "Set emulation tag slot activated."), 'type': new_uint(chameleon_cli_unit.HWSlotTagType, "Set emulation tag type"), 'init': new_uint(chameleon_cli_unit.HWSlotDataDefault, "Set emulation tag data to default"), @@ -87,7 +80,8 @@ class ChameleonCLI: 'help': "Emulation tag slot.", }, 'version': new_uint(chameleon_cli_unit.HWVersion, "Get current device firmware version"), - 'dfu': new_uint(chameleon_cli_unit.HWDFU, "Restart application to bootloader mode(Not yet implement dfu)."), + 'dfu': new_uint(chameleon_cli_unit.HWDFU, "Restart application to bootloader mode(Not yet implement " + "dfu)."), 'settings': { 'animation': { 'get': new_uint(chameleon_cli_unit.HWSettingsAnimationGet, "Get current animation mode value"), @@ -98,7 +92,8 @@ class ChameleonCLI: 'reset': new_uint(chameleon_cli_unit.HWSettingsReset, "Reset settings to default values"), 'help': "Chameleon settings management" }, - 'factory_reset': new_uint(chameleon_cli_unit.HWFactoryReset, "Wipe all data and return to factory settings"), + 'factory_reset': new_uint(chameleon_cli_unit.HWFactoryReset, "Wipe all data and return to factory " + "settings"), 'help': "hardware controller", }, 'hf': { @@ -110,15 +105,16 @@ class ChameleonCLI: 'mf': { 'nested': new_uint(chameleon_cli_unit.HFMFNested, "Mifare Classic nested recover key"), 'darkside': new_uint(chameleon_cli_unit.HFMFDarkside, "Mifare Classic darkside recover key"), - 'rdbl': new_uint(chameleon_cli_unit.HFMFRDBL, "MiFARE Classic read one block"), - 'wrbl': new_uint(chameleon_cli_unit.HFMFWRBL, "MiFARE Classic write one block"), + 'rdbl': new_uint(chameleon_cli_unit.HFMFRDBL, "Mifare Classic read one block"), + 'wrbl': new_uint(chameleon_cli_unit.HFMFWRBL, "Mifare Classic write one block"), 'detection': { 'enable': new_uint(chameleon_cli_unit.HFMFDetectionEnable, "Detection enable"), 'count': new_uint(chameleon_cli_unit.HFMFDetectionLogCount, "Detection log count"), 'decrypt': new_uint(chameleon_cli_unit.HFMFDetectionDecrypt, "Download log and decrypt keys"), 'help': "Mifare Classic detection log" }, - 'sim': new_uint(chameleon_cli_unit.HFMFSim, "Simulation a mifare classic card"), + 'settings': new_uint(chameleon_cli_unit.HFMFSettings, "Settings of Mifare Classic emulator"), + 'sim': new_uint(chameleon_cli_unit.HFMFSim, "Simulate a Mifare Classic card"), 'eload': new_uint(chameleon_cli_unit.HFMFELoad, "Load data to emulator memory"), 'help': "Mifare Classic mini/1/2/4, attack/read/write" }, @@ -154,12 +150,10 @@ class ChameleonCLI: """ cmds = cmd_str.split(" ") cmd_maps: dict or types.FunctionType = self.cmd_maps - cmd_end = "" cmd_end_position = 0 for cmd in cmds: if cmd in cmd_maps: # CMD found in map, we can continue find next cmd_maps = cmd_maps[cmd] - cmd_end = cmd cmd_end_position += len(cmd) + 1 else: # CMD not found break @@ -175,13 +169,15 @@ class ChameleonCLI: while True: # wait user input status = f"{colorama.Fore.GREEN}USB" if self.device_com.isOpen() else f"{colorama.Fore.RED}Offline" + cmd_str = "" try: cmd_str = input(f"[{status}{colorama.Style.RESET_ALL}] chameleon --> ").strip() except EOFError: print("") closing = True - if closing or cmd_str == "exit" or cmd_str == "quit" or cmd_str.startswith('q', 0) or cmd_str.startswith('e', 0): + if closing or cmd_str == "exit" or cmd_str == "quit" or cmd_str.startswith('q', 0) or cmd_str.startswith( + 'e', 0): print("Bye, thank you. ^.^ ") self.device_com.close() sys.exit(996) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 0470ce7..c5a2a60 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -45,10 +45,9 @@ class ArgumentParserNoExit(argparse.ArgumentParser): class BaseCLIUnit: - def __init__(self): # new a device command transfer and receiver instance(Send cmd and receive response) - self._device_com: chameleon_com.ChameleonCom = None + self._device_com: chameleon_com.ChameleonCom | None = None @property def device_com(self) -> chameleon_com.ChameleonCom: @@ -88,21 +87,18 @@ class BaseCLIUnit: raise NotImplementedError("Please implement this") @staticmethod - def sub_process(cmd, cwd=os.path.abspath("bin/"), ): + def sub_process(cmd, cwd=os.path.abspath("bin/")): class ShadowProcess: def __init__(self): self.time_start = timeit.default_timer() - self._process = subprocess.Popen( - cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE - ) + self._process = subprocess.Popen(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, + stdout=subprocess.PIPE) def get_time_distance(self, ms=True): - ret = 0 if ms: - ret = (timeit.default_timer() - self.time_start) * 1000 + return round((timeit.default_timer() - self.time_start) * 1000, 2) else: - ret = timeit.default_timer() - self.time_start - return round(ret, 2) + return round(timeit.default_timer() - self.time_start, 2) def is_running(self): return self._process.poll() is None @@ -195,13 +191,13 @@ class HWConnect(BaseCLIUnit): def on_exec(self, args: argparse.Namespace): try: - if args.port is None: # Chameleon Autodedect if no port is supplied + if args.port is None: # Chameleon auto-detect if no port is supplied # loop through all ports and find chameleon for port in serial.tools.list_ports.comports(): if port.vid == 0x6868: args.port = port.device break - if args.port is None: # If no chameleon was found, exit + if args.port is None: # If no chameleon was found, exit print("Chameleon not found, please connect the device or try connecting manually with the -p flag.") return self.device_com.open(args.port) @@ -211,7 +207,6 @@ class HWConnect(BaseCLIUnit): class HWModeSet(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() help_str = "reader or r = reader mode, emulator or e = tag emulator mode." @@ -237,7 +232,6 @@ class HWModeGet(DeviceRequiredUnit): class HWChipIdGet(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -246,15 +240,14 @@ class HWChipIdGet(DeviceRequiredUnit): class HWAddressGet(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): print(f' - Device address: ' + self.cmd_positive.get_device_address()) -class HWVersion(DeviceRequiredUnit): +class HWVersion(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -264,6 +257,7 @@ class HWVersion(DeviceRequiredUnit): git_version = self.cmd_positive.get_git_version() print(f' - Version: {fw_version} ({git_version})') + class HF14AScan(ReaderRequiredUint): def args_parser(self) -> ArgumentParserNoExit or None: pass @@ -286,7 +280,6 @@ class HF14AScan(ReaderRequiredUint): class HF14AInfo(ReaderRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: pass @@ -316,7 +309,6 @@ class HF14AInfo(ReaderRequiredUint): class HFMFNested(ReaderRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: type_choices = ['A', 'B', 'a', 'b'] parser = ArgumentParserNoExit() @@ -326,8 +318,7 @@ class HFMFNested(ReaderRequiredUint): help="The block where the key of the card is known") parser.add_argument('--type-known', type=str, required=True, choices=type_choices, help="The key type of the tag") - parser.add_argument('--key-known', type=str, required=True, metavar="hex", - help="tag sector key") + parser.add_argument('--key-known', type=str, required=True, metavar="hex", help="tag sector key") parser.add_argument('--block-target', type=int, metavar="decimal", help="The key of the target block to recover") parser.add_argument('--type-target', type=str, choices=type_choices, @@ -421,7 +412,6 @@ class HFMFNested(ReaderRequiredUint): class HFMFDarkside(ReaderRequiredUint): - def __init__(self): super().__init__() self.darkside_list = [] @@ -440,7 +430,7 @@ class HFMFDarkside(ReaderRequiredUint): retry_count = 0 while retry_count < 0xFF: darkside_resp = self.cmd_positive.acquire_darkside(block_target, type_target, first_recover, 15) - first_recover = False # not first run. + first_recover = False # not first run. darkside_obj = chameleon_cstruct.parse_darkside_acquire_result(darkside_resp.data) self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" @@ -462,7 +452,7 @@ class HFMFDarkside(ReaderRequiredUint): if 'key not found' in output_str: print(f" - No key found, retrying({retry_count})...") retry_count += 1 - continue # retry + continue # retry else: key_list = [] for line in output_str.split('\n'): @@ -487,7 +477,6 @@ class HFMFDarkside(ReaderRequiredUint): class BaseMF1AuthOpera(ReaderRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: type_choices = ['A', 'B', 'a', 'b'] parser = ArgumentParserNoExit() @@ -495,8 +484,7 @@ class BaseMF1AuthOpera(ReaderRequiredUint): help="The block where the key of the card is known") parser.add_argument('-t', '--type', type=str, required=True, choices=type_choices, help="The key type of the tag") - parser.add_argument('-k', '--key', type=str, required=True, metavar="hex", - help="tag sector key") + parser.add_argument('-k', '--key', type=str, required=True, metavar="hex", help="tag sector key") return parser def get_param(self, args): @@ -508,6 +496,7 @@ class BaseMF1AuthOpera(ReaderRequiredUint): if not re.match(r"^[a-fA-F0-9]{12}$", key): raise ArgsParserError("key must include 12 HEX symbols") self.key: bytearray = bytearray.fromhex(key) + return Param() def on_exec(self, args: argparse.Namespace): @@ -515,7 +504,6 @@ class BaseMF1AuthOpera(ReaderRequiredUint): class HFMFRDBL(BaseMF1AuthOpera): - # hf mf rdbl -b 2 -t A -k FFFFFFFFFFFF def on_exec(self, args: argparse.Namespace): param = self.get_param(args) @@ -524,11 +512,10 @@ class HFMFRDBL(BaseMF1AuthOpera): class HFMFWRBL(BaseMF1AuthOpera): - def args_parser(self) -> ArgumentParserNoExit or None: parser = super(HFMFWRBL, self).args_parser() parser.add_argument('-d', '--data', type=str, required=True, metavar="Your block data", - help="Your block data, a hex string.") + help="Your block data, a hex string.") return parser # hf mf wrbl -b 2 -t A -k FFFFFFFFFFFF -d 00000000000000000000000000000122 @@ -545,22 +532,19 @@ class HFMFWRBL(BaseMF1AuthOpera): class HFMFDetectionEnable(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-e', '--enable', type=int, required=True, choices=[1, 0], - help="1 = enable, 0 = disable") + parser.add_argument('-e', '--enable', type=int, required=True, choices=[1, 0], help="1 = enable, 0 = disable") return parser # hf mf detection enable -e 1 def on_exec(self, args: argparse.Namespace): enable = True if args.enable == 1 else False self.cmd_positive.set_mf1_detection_enable(enable) - print(f" - Set mf1 detection { 'enable' if enable else 'disable'}.") + print(f" - Set mf1 detection {'enable' if enable else 'disable'}.") class HFMFDetectionLogCount(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -572,7 +556,6 @@ class HFMFDetectionLogCount(DeviceRequiredUnit): class HFMFDetectionDecrypt(DeviceRequiredUnit): - detection_log_size = 18 def args_parser(self) -> ArgumentParserNoExit or None: @@ -654,7 +637,6 @@ class HFMFDetectionDecrypt(DeviceRequiredUnit): class HFMFELoad(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('-f', '--file', type=str, required=True, help="file path") @@ -700,8 +682,45 @@ class HFMFELoad(DeviceRequiredUnit): print("\n - Load success") -class HFMFSim(DeviceRequiredUnit): +class HFMFSettings(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + help_str = "" + for s in chameleon_cmd.MifareClassicWriteMode: + help_str += f"{s.value} = {s}, " + help_str = help_str[:-2] + + parser.add_argument('--gen1a', type=int, required=False, help="Gen1a magic mode, 1 - enable, 0 - disable", + default=-1, choices=[1, 0]) + parser.add_argument('--gen2', type=int, required=False, help="Gen2 magic mode, 1 - enable, 0 - disable", + default=-1, choices=[1, 0]) + parser.add_argument('--coll', type=int, required=False, + help="Use anti-collision data from block 0 for 4 byte UID tags, 1 - enable, 0 - disable", + default=-1, choices=[1, 0]) + parser.add_argument('--write', type=int, required=False, + help=f"Write mode: {help_str}", + default=-1, choices=chameleon_cmd.MifareClassicWriteMode.list()) + return parser + + # hf mf settings + def on_exec(self, args: argparse.Namespace): + if args.gen1a != -1: + self.cmd_positive.set_mf1_gen1a_mode(args.gen1a) + print(f' - Set gen1a mode to {"enabled" if args.gen1a else "disabled"} success') + if args.gen2 != -1: + self.cmd_positive.set_mf1_gen2_mode(args.gen2) + print(f' - Set gen2 mode to {"enabled" if args.gen2 else "disabled"} success') + if args.coll != -1: + self.cmd_positive.set_mf1_block_anti_coll_mode(args.coll) + print(f' - Set anti-collision mode to {"enabled" if args.coll else "disabled"} success') + if args.write != -1: + self.cmd_positive.set_mf1_write_mode(args.write) + print(f' - Set write mode to {chameleon_cmd.MifareClassicWriteMode(args.write)} success') + print(f' - Emulator settings updated') + + +class HFMFSim(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") @@ -715,17 +734,17 @@ class HFMFSim(DeviceRequiredUnit): atqa_str: str = args.atqa.strip() uid_str: str = args.uid.strip() - if re.match('[a-fA-F0-9]{2}', sak_str) is not None: + if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: sak = bytearray.fromhex(sak_str) else: raise Exception("SAK must be hex(2byte)") - if re.match('[a-fA-F0-9]{4}', atqa_str) is not None: + if re.match(r"[a-fA-F0-9]{4}", atqa_str) is not None: atqa = bytearray.fromhex(atqa_str) else: raise Exception("ATQA must be hex(4byte)") - if re.match('[a-fA-F0-9]+', uid_str) is not None: + if re.match(r"[a-fA-F0-9]+", uid_str) is not None: uid_len = len(uid_str) if uid_len != 8 and uid_len != 14 and uid_len != 20: raise Exception("UID length error") @@ -738,7 +757,6 @@ class HFMFSim(DeviceRequiredUnit): class LFEMRead(ReaderRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -749,7 +767,6 @@ class LFEMRead(ReaderRequiredUint): class LFEMCardRequiredUint(DeviceRequiredUnit): - @staticmethod def add_card_arg(parser: ArgumentParserNoExit): parser.add_argument("--id", type=str, required=True, help="EM410x tag id", metavar="hex") @@ -770,7 +787,6 @@ class LFEMCardRequiredUint(DeviceRequiredUnit): class LFEMWriteT55xx(LFEMCardRequiredUint, ReaderRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() return self.add_card_arg(parser) @@ -789,7 +805,6 @@ class LFEMWriteT55xx(LFEMCardRequiredUint, ReaderRequiredUint): class SlotIndexRequireUint(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: raise NotImplementedError() @@ -801,12 +816,12 @@ class SlotIndexRequireUint(DeviceRequiredUnit): slot_choices = [x.value for x in chameleon_cmd.SlotNumber] help_str = f"Slot Indexes: {slot_choices}" - parser.add_argument('-s', "--slot", type=int, required=True, - help=help_str, metavar="number", choices=slot_choices) + parser.add_argument('-s', "--slot", type=int, required=True, help=help_str, metavar="number", + choices=slot_choices) return parser -class SenseTypeRequireUint(DeviceRequiredUnit): +class SenseTypeRequireUint(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: raise NotImplementedError() @@ -823,26 +838,55 @@ class SenseTypeRequireUint(DeviceRequiredUnit): continue help_str += f"{s.value} = {s}, " - parser.add_argument('-st', "--sense_type", type=int, required=True, - help=help_str, metavar="number", choices=sense_choices) + parser.add_argument('-st', "--sense_type", type=int, required=True, help=help_str, metavar="number", + choices=sense_choices) return parser -class HWSlotInfo(DeviceRequiredUnit): +class HWSlotList(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: - return + parser = ArgumentParserNoExit() + parser.add_argument('-e', '--extend', type=int, required=False, + help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (" + "default)", choices=[0, 1], default=1) + return parser - # hw slot info + def get_slot_name(self, slot, sense): + try: + return self.cmd_positive.get_slot_tag_nick_name(slot, sense).data.decode() + except chameleon_cmd.NegativeResponseError: + return "Empty" + except UnicodeDecodeError: + return "Non UTF-8" + + # hw slot list def on_exec(self, args: argparse.Namespace): data = self.cmd_positive.get_slot_info().data + enabled = self.cmd_positive.get_enabled_slots().data selected = chameleon_cmd.SlotNumber.from_fw(self.cmd_positive.get_active_slot().data[0]) for slot in chameleon_cmd.SlotNumber: - print(f' - Slot {slot} data{" (active)" if slot == selected else ""}:') - print(f' HF: {chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2])}') - print(f' LF: {chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2 + 1])}') + print( + f' - Slot {slot} data{" (active)" if slot == selected else ""}' + f'{" (disabled)" if not enabled[chameleon_cmd.SlotNumber.to_fw(slot)] else ""}:') + print( + f' HF: ' + f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + " - ") if args.extend else ""}' + f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2])}') + print( + f' LF: ' + f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + " - ") if args.extend else ""}' + f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2 + 1])}') + if args.extend: + config = self.cmd_positive.get_mf1_emulator_settings().data + print(' - Mifare Classic emulator settings:') + print(f' Detection (mfkey32) mode: {"enabled" if config[0] else "disabled"}') + print(f' Gen1A magic mode: {"enabled" if config[1] else "disabled"}') + print(f' Gen2 magic mode: {"enabled" if config[2] else "disabled"}') + print(f' Use anti-collision data from block 0: {"enabled" if config[3] else "disabled"}') + print(f' Write mode: {chameleon_cmd.MifareClassicWriteMode(config[4])}') + class HWSlotSet(SlotIndexRequireUint): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() return self.add_slot_args(parser) @@ -855,7 +899,6 @@ class HWSlotSet(SlotIndexRequireUint): class TagTypeRequiredUint(DeviceRequiredUnit): - @staticmethod def add_type_args(parser: ArgumentParserNoExit): type_choices = chameleon_cmd.TagSpecificType.list() @@ -864,8 +907,9 @@ class TagTypeRequiredUint(DeviceRequiredUnit): if t == chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN: continue help_str += f"{t.value} = {t}, " - parser.add_argument('-t', "--type", type=int, required=True, help=help_str, - metavar="number", choices=type_choices) + help_str = help_str[:-2] + parser.add_argument('-t', "--type", type=int, required=True, help=help_str, metavar="number", + choices=type_choices) return parser def args_parser(self) -> ArgumentParserNoExit or None: @@ -876,7 +920,6 @@ class TagTypeRequiredUint(DeviceRequiredUnit): class HWSlotTagType(TagTypeRequiredUint, SlotIndexRequireUint): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() self.add_type_args(parser) @@ -892,7 +935,6 @@ class HWSlotTagType(TagTypeRequiredUint, SlotIndexRequireUint): class HWSlotDataDefault(TagTypeRequiredUint, SlotIndexRequireUint): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() self.add_type_args(parser) @@ -924,7 +966,6 @@ class HWSlotEnableSet(SlotIndexRequireUint): class LFEMSim(LFEMCardRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() return self.add_card_arg(parser) @@ -969,11 +1010,10 @@ class HWSlotNickGet(SlotIndexRequireUint, SenseTypeRequireUint): slot_num = args.slot sense_type = args.sense_type res = self.cmd_positive.get_slot_tag_nick_name(slot_num, sense_type) - print(f' - Get tag nick name for slot {slot_num}: {res.data.decode(encoding="utf8")}') + print(f' - Get tag nick name for slot {slot_num}: {res.data.decode()}') class HWSlotUpdate(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -984,7 +1024,6 @@ class HWSlotUpdate(DeviceRequiredUnit): class HWSlotOpenAll(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -1013,7 +1052,6 @@ class HWSlotOpenAll(DeviceRequiredUnit): class HWDFU(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -1028,9 +1066,11 @@ class HWDFU(DeviceRequiredUnit): # let time for comm thread to send dfu cmd and close port time.sleep(0.1) + class HWSettingsAnimationGet(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: return None + def on_exec(self, args: argparse.Namespace): resp: chameleon_com.Response = self.cmd_standard.get_settings_animation() if resp.data[0] == 0: @@ -1042,22 +1082,25 @@ class HWSettingsAnimationGet(DeviceRequiredUnit): else: print("Unknown setting value, something failed.") + class HWSettingsAnimationSet(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-m', '--mode', type=int, required=True, help="0 is full (default), 1 is minimal (only single pass on button wakeup), 2 is none", choices=[0, 1, 2]) + parser.add_argument('-m', '--mode', type=int, required=True, + help="0 is full (default), 1 is minimal (only single pass on button wakeup), 2 is none", + choices=[0, 1, 2]) return parser - + def on_exec(self, args: argparse.Namespace): mode = args.mode self.cmd_standard.set_settings_animation(mode) print("Animation mode change success. Do not forget to store your settings in flash!") - + class HWSettingsStore(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: return None - + def on_exec(self, args: argparse.Namespace): print("Storing settings...") resp: chameleon_com.Response = self.cmd_standard.store_settings() @@ -1066,6 +1109,7 @@ class HWSettingsStore(DeviceRequiredUnit): else: print(" - Store failed") + class HWSettingsReset(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: return None @@ -1078,19 +1122,16 @@ class HWSettingsReset(DeviceRequiredUnit): else: print(" - Reset failed") + class HWFactoryReset(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() parser.description = "Permanently wipes Chameleon to factory settings. " \ - "This will delete all your slot data and custom settings. " \ - "There's no going back." - parser.add_argument( - "--i-know-what-im-doing", - default=False, - action="store_true", - help="Just to be sure :)" - ) + "This will delete all your slot data and custom settings. " \ + "There's no going back." + parser.add_argument("--i-know-what-im-doing", default=False, action="store_true", help="Just to be sure :)") return parser + def on_exec(self, args: argparse.Namespace): if not args.i_know_what_im_doing: print("This time your data's safe. Read the command documentation next time.") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 669b4eb..8db0db3 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -32,6 +32,8 @@ DATA_CMD_GET_SLOT_INFO = 1019 DATA_CMD_WIPE_FDS = 1020 +DATA_CMD_GET_ENABLED_SLOTS = 1023 + DATA_CMD_SCAN_14A_TAG = 2000 DATA_CMD_MF1_SUPPORT_DETECT = 2001 DATA_CMD_MF1_NT_LEVEL_DETECT = 2002 @@ -49,11 +51,22 @@ DATA_CMD_WRITE_EM410X_TO_T5577 = 3001 DATA_CMD_LOAD_MF1_BLOCK_DATA = 4000 DATA_CMD_SET_MF1_ANTI_COLLISION_RES = 4001 +DATA_CMD_GET_MF1_EMULATOR_CONFIG = 4009 +DATA_CMD_GET_MF1_GEN1A_MODE = 4010 +DATA_CMD_SET_MF1_GEN1A_MODE = 4011 +DATA_CMD_GET_MF1_GEN2_MODE = 4012 +DATA_CMD_SET_MF1_GEN2_MODE = 4013 +DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL = 4014 +DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL = 4015 +DATA_CMD_GET_MF1_WRITE_MODE = 4016 +DATA_CMD_SET_MF1_WRITE_MODE = 4017 + DATA_CMD_SET_EM410X_EMU_ID = 5000 DATA_CMD_SET_MF1_DETECTION_ENABLE = 5003 DATA_CMD_GET_MF1_DETECTION_COUNT = 5004 DATA_CMD_GET_MF1_DETECTION_RESULT = 5005 + @enum.unique class SlotNumber(enum.IntEnum): SLOT_1 = 1, @@ -66,7 +79,7 @@ class SlotNumber(enum.IntEnum): SLOT_8 = 8, @staticmethod - def to_fw(index: int): # can be int or SlotNumber + def to_fw(index: int): # can be int or SlotNumber # SlotNumber() will raise error for us if index not in slot range return SlotNumber(index).value - 1 @@ -78,13 +91,12 @@ class SlotNumber(enum.IntEnum): @enum.unique class TagSenseType(enum.IntEnum): - # 无场感应 - TAG_SENSE_NO = 0, - # 低频125khz场感应 - TAG_SENSE_LF = 1, - # 高频13.56mhz场感应 - TAG_SENSE_HF = 2, - + # Unknown + TAG_SENSE_NO = 0 + # 125 kHz + TAG_SENSE_LF = 1 + # 13.56 MHz + TAG_SENSE_HF = 2 @staticmethod def list(exclude_unknown=True): @@ -100,18 +112,19 @@ class TagSenseType(enum.IntEnum): return "HF" return "None" + @enum.unique class TagSpecificType(enum.IntEnum): - # 特定的且必须存在的标志不存在的类型 + # Empty slot TAG_TYPE_UNKNOWN = 0 - # 125khz(ID卡)系列 + # 125 kHz(ID)cards TAG_TYPE_EM410X = 1 - # Mifare系列 + # Mifare Classic TAG_TYPE_MIFARE_Mini = 2 TAG_TYPE_MIFARE_1024 = 3 TAG_TYPE_MIFARE_2048 = 4 TAG_TYPE_MIFARE_4096 = 5 - # NTAG系列 + # NTAG TAG_TYPE_NTAG_213 = 6 TAG_TYPE_NTAG_215 = 7 TAG_TYPE_NTAG_216 = 8 @@ -143,6 +156,32 @@ class TagSpecificType(enum.IntEnum): return "Unknown" +@enum.unique +class MifareClassicWriteMode(enum.IntEnum): + # Normal write + NORMAL = 0 + # Send NACK to write attempts + DEINED = 1 + # Acknowledge writes, but don't remember contents + DECEIVE = 2 + # Store data to RAM, but not to ROM + SHADOW = 3 + + @staticmethod + def list(): + return list(map(int, MifareClassicWriteMode)) + + def __str__(self): + if self == MifareClassicWriteMode.NORMAL: + return "Normal" + elif self == MifareClassicWriteMode.DEINED: + return "Deined" + elif self == MifareClassicWriteMode.DECEIVE: + return "Deceive" + elif self == MifareClassicWriteMode.SHADOW: + return "Shadow" + return "None" + class BaseChameleonCMD: """ @@ -159,25 +198,25 @@ class BaseChameleonCMD: """ Get firmware version number(application) """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION, 0x00, None) + resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION, 0x00) return int.from_bytes(resp.data, 'little') - + def get_device_chip_id(self) -> str: """ Get device chip id """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID, 0x00, None) + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID, 0x00) return resp.data.hex() - + def get_device_address(self) -> str: """ Get device address """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS, 0x00, None) + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS, 0x00) return resp.data[::-1].hex() def get_git_version(self) -> str: - resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION, 0x00, None) + resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION, 0x00) return resp.data.decode('utf-8') def is_reader_device_mode(self) -> bool: @@ -185,7 +224,7 @@ class BaseChameleonCMD: Get device mode, reader or tag :return: True is reader mode, else tag mode """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE, 0x00, None) + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE, 0x00) return True if resp.data[0] == 1 else False def set_reader_device_mode(self, reader_mode: bool = True): @@ -201,21 +240,21 @@ class BaseChameleonCMD: 扫描场内的14a标签 :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_14A_TAG, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_SCAN_14A_TAG, 0x00) def detect_mf1_support(self): """ 检测是否是mifare classic标签 :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_SUPPORT_DETECT, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_MF1_SUPPORT_DETECT, 0x00) def detect_mf1_nt_level(self): """ 检测mifare classic的nt漏洞的等级 :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_LEVEL_DETECT, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_MF1_NT_LEVEL_DETECT, 0x00) def detect_darkside_support(self): """ @@ -315,7 +354,7 @@ class BaseChameleonCMD: 读取EM410X的卡号 :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_EM410X_TAG, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_SCAN_EM410X_TAG, 0x00) def write_em_410x_to_t55xx(self, id_bytes: bytearray): """ @@ -324,10 +363,7 @@ class BaseChameleonCMD: :return: """ new_key = [0x20, 0x20, 0x66, 0x66] - old_keys = [ - [0x51, 0x24, 0x36, 0x48], - [0x19, 0x92, 0x04, 0x27], - ] + old_keys = [[0x51, 0x24, 0x36, 0x48], [0x19, 0x92, 0x04, 0x27]] if len(id_bytes) != 5: raise ValueError("The id bytes length must equal 5") data = bytearray() @@ -342,19 +378,19 @@ class BaseChameleonCMD: Get slots info :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO, 0x00) def get_active_slot(self): """ Get selected slot :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT, 0x00) def set_slot_activated(self, slot_index: SlotNumber): """ - 设置当前激活使用的卡槽 - :param slot_index: 卡槽索引,从 1 - 8(不是从0下标开始) + Set the card slot currently active for use + :param slot_index: Card slot index :return: """ # SlotNumber() will raise error for us if slot_index not in slot range @@ -428,7 +464,7 @@ class BaseChameleonCMD: 获取当前侦测记录的统计个数 :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00) def get_mf1_detection_log(self, index: int): """ @@ -465,7 +501,7 @@ class BaseChameleonCMD: data.extend(atqa) data.extend(uid) return self.device.send_cmd_sync(DATA_CMD_SET_MF1_ANTI_COLLISION_RES, 0X00, data) - + def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): """ 设置MF1的模拟卡的防冲撞资源信息 @@ -479,26 +515,61 @@ class BaseChameleonCMD: data.extend([SlotNumber.to_fw(slot), sense_type]) data.extend(name) return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, 0x00, data) - + def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType): """ 设置MF1的模拟卡的防冲撞资源信息 :param slot: 卡槽号码 :param sense_type: 场类型 - :param name: 卡槽昵称 :return: """ # SlotNumber() will raise error for us if slot not in slot range data = bytearray() data.extend([SlotNumber.to_fw(slot), sense_type]) return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, 0x00, data) - + + def get_mf1_emulator_settings(self): + """ + Get array of Mifare Classic emulators settings: + [0] - mf1_is_detection_enable (mfkey32) + [1] - mf1_is_gen1a_magic_mode + [2] - mf1_is_gen2_magic_mode + [3] - mf1_is_use_mf1_coll_res (use UID/BCC/SAK/ATQA from 0 block) + [4] - mf1_get_write_mode + :return: + """ + return self.device.send_cmd_sync(DATA_CMD_GET_MF1_EMULATOR_CONFIG, 0x00) + + def set_mf1_gen1a_mode(self, enabled: bool): + """ + Set gen1a magic mode + """ + return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN1A_MODE, 0x00, bytearray([1 if enabled else 0])) + + def set_mf1_gen2_mode(self, enabled: bool): + """ + Set gen2 magic mode + """ + return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN2_MODE, 0x00, bytearray([1 if enabled else 0])) + + def set_mf1_block_anti_coll_mode(self, enabled: bool): + """ + Set 0 block anti-collision data + """ + return self.device.send_cmd_sync(DATA_CMD_SET_MF1_ANTI_COLLISION_RES, 0x00, bytearray([1 if enabled else 0])) + + def set_mf1_write_mode(self, mode: int): + """ + Set write mode + """ + return self.device.send_cmd_sync(DATA_CMD_SET_MF1_WRITE_MODE, 0x00, bytearray([mode])) + def update_slot_data_config(self): """ 更新卡槽的配置和数据到flash中。 :return: """ - return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE, 0x00) def enter_dfu_mode(self): """ @@ -506,19 +577,25 @@ class BaseChameleonCMD: :return: """ return self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, 0x00, close=True) - + def get_settings_animation(self): """ Get animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE, 0x00, None) - + return self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE, 0x00) + + def get_enabled_slots(self): + """ + Get animation mode value + """ + return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS, 0x00) + def set_settings_animation(self, value: int): """ Set animation mode value """ return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, 0x00, bytearray([value])) - + def reset_settings(self): """ Reset settings stored in flash memory @@ -530,7 +607,7 @@ class BaseChameleonCMD: Store settings to flash memory """ return self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS, 0x00) - + def factory_reset(self): """ Reset to factory settings @@ -578,8 +655,8 @@ class PositiveChameleonCMD(BaseChameleonCMD): return ret def acquire_nested(self, block_known, type_known, key_known, block_target, type_target): - ret = super(PositiveChameleonCMD, self).acquire_nested( - block_known, type_known, key_known, block_target, type_target) + ret = super(PositiveChameleonCMD, self).acquire_nested(block_known, type_known, key_known, block_target, + type_target) self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK) return ret @@ -590,10 +667,7 @@ class PositiveChameleonCMD(BaseChameleonCMD): def auth_mf1_key(self, block, type_value, key): ret = super(PositiveChameleonCMD, self).auth_mf1_key(block, type_value, key) - self.check_status(ret.status, [ - chameleon_status.Device.HF_TAG_OK, - chameleon_status.Device.MF_ERRAUTH, - ]) + self.check_status(ret.status, [chameleon_status.Device.HF_TAG_OK, chameleon_status.Device.MF_ERRAUTH]) return ret def read_mf1_block(self, block, type_value, key): @@ -621,17 +695,17 @@ class PositiveChameleonCMD(BaseChameleonCMD): self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - def set_slot_tag_type(self, slot_index: int, tag_type: TagSpecificType): + def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): ret = super(PositiveChameleonCMD, self).set_slot_tag_type(slot_index, tag_type) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - def set_slot_data_default(self, slot_index: int, tag_type: TagSpecificType): + def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType): ret = super(PositiveChameleonCMD, self).set_slot_data_default(slot_index, tag_type) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - def set_slot_enable(self, slot_index: int, enable: bool): + def set_slot_enable(self, slot_index: SlotNumber, enable: bool): ret = super(PositiveChameleonCMD, self).set_slot_enable(slot_index, enable) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret @@ -656,17 +730,17 @@ class PositiveChameleonCMD(BaseChameleonCMD): self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - def set_mf1_anti_collision_res(self, sak: int, atqa: bytearray, uid: bytearray): + def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray): ret = super(PositiveChameleonCMD, self).set_mf1_anti_collision_res(sak, atqa, uid) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - - def set_slot_tag_nick_name(self, slot: int, sense_type: int, name: bytes): + + def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): ret = super(PositiveChameleonCMD, self).set_slot_tag_nick_name(slot, sense_type, name) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - - def get_slot_tag_nick_name(self, slot: int, sense_type: int): + + def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType): ret = super(PositiveChameleonCMD, self).get_slot_tag_nick_name(slot, sense_type) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret @@ -679,12 +753,12 @@ if __name__ == '__main__': cml = BaseChameleonCMD(dev) ver = cml.get_firmware_version() print(f"Firmware number of application: {ver}") - id = cml.get_device_chip_id() - print(f"Device chip id: {id}") - + chip = cml.get_device_chip_id() + print(f"Device chip id: {chip}") # disconnect dev.close() - - # nerver exit - while True: pass + + # never exit + while True: + pass diff --git a/software/script/chameleon_com.py b/software/script/chameleon_com.py index a58a8f9..92b5977 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -40,8 +40,6 @@ class ChameleonCom: Chameleon device base class Communication and Data frame implemented """ - - baudrate = 115200 data_frame_sof = 0x11 data_max_length = 512 @@ -49,7 +47,7 @@ class ChameleonCom: """ Create a chameleon device instance """ - self.serial_instance: serial.Serial = None + self.serial_instance: serial.Serial | None = None self.send_data_queue = queue.Queue() self.wait_response_map = {} self.event_closing = threading.Event() @@ -72,7 +70,7 @@ class ChameleonCom: error = None try: # open serial port - self.serial_instance = serial.Serial(port=port, baudrate=self.baudrate) + self.serial_instance = serial.Serial(port=port, baudrate=115200) except Exception as e: error = e finally: @@ -80,7 +78,7 @@ class ChameleonCom: raise OpenFailException(error) try: self.serial_instance.dtr = 1 # must make dtr enable - except Exception as e: + except: # not all serial support dtr, e.g. virtual serial over BLE pass self.serial_instance.timeout = 0 # noblock @@ -89,9 +87,9 @@ class ChameleonCom: self.wait_response_map.clear() # Start a sub thread to process data self.event_closing.clear() - threading.Thread(target=self.thread_data_receive, ).start() - threading.Thread(target=self.thread_data_transfer, ).start() - threading.Thread(target=self.thread_check_timeout, ).start() + threading.Thread(target=self.thread_data_receive).start() + threading.Thread(target=self.thread_data_transfer).start() + threading.Thread(target=self.thread_check_timeout).start() return self def check_open(self): @@ -200,11 +198,8 @@ class ChameleonCom: del self.wait_response_map[data_cmd] fn_call(data_cmd, data_status, data_response) else: - self.wait_response_map[data_cmd]['response'] = Response( - data_cmd, - data_status, - data_response - ) + self.wait_response_map[data_cmd]['response'] = Response(data_cmd, data_status, + data_response) else: print(f"No task wait process: ${data_cmd}") else: @@ -232,13 +227,9 @@ class ChameleonCom: task_close = task['close'] # register to wait map if 'callback' in task and callable(task['callback']): - self.wait_response_map[task_cmd] = { - 'callback': task['callback'] # The callback for this task - } + self.wait_response_map[task_cmd] = {'callback': task['callback']} # The callback for this task else: - self.wait_response_map[task_cmd] = { - 'response': None, - } + self.wait_response_map[task_cmd] = {'response': None} # set start time start_time = time.time() self.wait_response_map[task_cmd]['start_time'] = start_time @@ -279,7 +270,6 @@ class ChameleonCom: :return: frame """ frame = bytearray() - lrc = 0x00 # sof and sof lrc byte frame.append(self.data_frame_sof) frame.append(self.lrc_calc(frame[0:1])) @@ -295,7 +285,8 @@ class ChameleonCom: frame.append(self.lrc_calc(frame)) return frame - def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback=None, timeout: int = 3, close: bool = False): + def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback=None, timeout: int = 3, + close: bool = False): """ Send cmd to device :param timeout: wait response timeout @@ -303,6 +294,7 @@ class ChameleonCom: :param status: status(optional) :param callback: call on response :param data: bytes data + :param close: close connection after executing :return: """ self.check_open() @@ -311,12 +303,7 @@ class ChameleonCom: del self.wait_response_map[cmd] # make data frame data_frame = self.make_data_frame_bytes(cmd, status, data) - task = { - 'cmd': cmd, - 'frame': data_frame, - 'timeout': timeout, - 'close': close, - } + task = {'cmd': cmd, 'frame': data_frame, 'timeout': timeout, 'close': close} if callable(callback): task['callback'] = callback self.send_data_queue.put(task) diff --git a/software/script/chameleon_cstruct.py b/software/script/chameleon_cstruct.py index eb826ed..519cdc8 100644 --- a/software/script/chameleon_cstruct.py +++ b/software/script/chameleon_cstruct.py @@ -22,7 +22,7 @@ def parse_14a_scan_tag_result(data: bytearray): 'uid_size': data[10], 'uid_hex': data[0:data[10]].hex(), 'sak_hex': hex(data[12]).lstrip('0x').rjust(2, '0'), - 'atqa_hex': data[13:15].hex().upper(), + 'atqa_hex': data[13:15].hex().upper() } @@ -49,7 +49,7 @@ def parse_nested_nt_acquire_group(data: bytearray): group.append({ 'nt': bytes_to_u32(data[i: i + 4]), 'nt_enc': bytes_to_u32(data[i + 4: i + 8]), - 'par': data[i + 8], + 'par': data[i + 8] }) i += 9 return group @@ -62,32 +62,15 @@ def parse_darkside_acquire_result(data: bytearray): :return: """ return { - 'uid': bytes_to_u32(data[0 : 4]), - 'nt1': bytes_to_u32(data[4 : 8]), - 'par': bytes_to_u32(data[8 : 16]), + 'uid': bytes_to_u32(data[0: 4]), + 'nt1': bytes_to_u32(data[4: 8]), + 'par': bytes_to_u32(data[8: 16]), 'ks1': bytes_to_u32(data[16: 24]), 'nr': bytes_to_u32(data[24: 28]), - 'ar': bytes_to_u32(data[28: 32]), + 'ar': bytes_to_u32(data[28: 32]) } -""" -// 验证的基础信息 - struct { - uint8_t block; - uint8_t is_keyb: 1; - uint8_t is_nested: 1; - // 空域,占位置用的 - uint8_t : 6; - } cmd; - // mfkey32必要参数 - uint8_t uid[4]; - uint8_t nt[4]; - uint8_t nr[4]; - uint8_t ar[4]; -""" - - def parse_mf1_detection_result(data: bytearray): """ From bytes parse detection param @@ -105,7 +88,7 @@ def parse_mf1_detection_result(data: bytearray): 'uid': data[2 + pos: 6 + pos].hex(), 'nt': data[6 + pos: 10 + pos].hex(), 'nr': data[10 + pos: 14 + pos].hex(), - 'ar': data[14 + pos: 18 + pos].hex(), + 'ar': data[14 + pos: 18 + pos].hex() }) pos += 18 @@ -127,5 +110,3 @@ def parse_mf1_detection_result(data: bytearray): result_map[uid][block][type_chr].append(item) return result_map - - diff --git a/software/script/chameleon_status.py b/software/script/chameleon_status.py index 675aaea..63c8579 100644 --- a/software/script/chameleon_status.py +++ b/software/script/chameleon_status.py @@ -1,6 +1,4 @@ - class MetaDevice(type): - def __iter__(self): for attr in dir(self): if not attr.startswith("__"): @@ -16,60 +14,59 @@ class MetaDevice(type): class Device(metaclass=MetaDevice): + HF_TAG_OK = 0x00 # IC卡操作成功 + HF_TAG_NO = 0x01 # 没有发现IC卡 + HF_ERRSTAT = 0x02 # IC卡通信异常 + HF_ERRCRC = 0x03 # IC卡通信校验异常 + HF_COLLISION = 0x04 # IC卡冲突 + HF_ERRBCC = 0x05 # IC卡BCC错误 + MF_ERRAUTH = 0x06 # MF卡验证失败 + HF_ERRPARITY = 0x07 # IC卡奇偶校验错误 - HF_TAG_OK = 0x00 # IC卡操作成功 - HF_TAG_NO = 0x01 # 没有发现IC卡 - HF_ERRSTAT = 0x02 # IC卡通信异常 - HF_ERRCRC = 0x03 # IC卡通信校验异常 - HF_COLLISION = 0x04 # IC卡冲突 - HF_ERRBCC = 0x05 # IC卡BCC错误 - MF_ERRAUTH = 0x06 # MF卡验证失败 - HF_ERRPARITY = 0x07 # IC卡奇偶校验错误 - # - DARKSIDE_CANT_FIXED_NT = 0x20 # Darkside,无法固定随机数,这个情况可能出现在UID卡上 - DARKSIDE_LUCK_AUTH_OK = 0x21 # Darkside,直接验证成功了,可能刚好密钥是空的 - DARKSIDE_NACK_NO_SNED = 0x22 # Darkside,卡片不响应nack,可能是一张修复了nack逻辑漏洞的卡片 - DARKSIDE_TAG_CHANGED = 0x23 # Darkside,在运行darkside的过程中出现了卡片切换,可能信号问题,或者真的是两张卡迅速切换了 - NESTED_TAG_IS_STATIC = 0x24 # Nested,检测到卡片应答的随机数是固定的 - NESTED_TAG_IS_HARD = 0x25 # Nested,检测到卡片应答的随机数是不可预测的 - # - LF_TAG_OK = 0x40 # 低频卡的一些操作成功! - EM410X_TAG_NO_FOUND = 0x41 # 无法搜索到有效的EM410X标签 - # - STATUS_PAR_ERR = 0x60 # BLE指令传递的参数错误,或者是调用某些函数传递的参数错误 - STATUS_DEVIEC_MODE_ERROR = 0x66 # 当前设备所处的模式错误,无法调用对应的API - STATUS_INVALID_CMD = 0x67 # 无效的指令 - STATUS_DEVICE_SUCCESS = 0x68 # 设备相关操作成功执行 - STATUS_NOT_IMPLEMENTED = 0x69 # 调用了某些未实现的操作,属于开发者遗漏的错误 - STATUS_FLASH_WRITE_FAIL = 0x70 # flash写入失败 - STATUS_FLASH_READ_FAIL = 0x71 # flash读取失败 + DARKSIDE_CANT_FIXED_NT = 0x20 # Darkside,无法固定随机数,这个情况可能出现在UID卡上 + DARKSIDE_LUCK_AUTH_OK = 0x21 # Darkside,直接验证成功了,可能刚好密钥是空的 + DARKSIDE_NACK_NO_SEND = 0x22 # Darkside,卡片不响应nack,可能是一张修复了nack逻辑漏洞的卡片 + DARKSIDE_TAG_CHANGED = 0x23 # Darkside,在运行darkside的过程中出现了卡片切换,可能信号问题,或者真的是两张卡迅速切换了 + NESTED_TAG_IS_STATIC = 0x24 # Nested,检测到卡片应答的随机数是固定的 + NESTED_TAG_IS_HARD = 0x25 # Nested,检测到卡片应答的随机数是不可预测的 + + LF_TAG_OK = 0x40 # 低频卡的一些操作成功! + EM410X_TAG_NO_FOUND = 0x41 # 无法搜索到有效的EM410X标签 + + STATUS_PAR_ERR = 0x60 # BLE指令传递的参数错误,或者是调用某些函数传递的参数错误 + STATUS_DEVICE_MODE_ERROR = 0x66 # 当前设备所处的模式错误,无法调用对应的API + STATUS_INVALID_CMD = 0x67 # 无效的指令 + STATUS_DEVICE_SUCCESS = 0x68 # 设备相关操作成功执行 + STATUS_NOT_IMPLEMENTED = 0x69 # 调用了某些未实现的操作,属于开发者遗漏的错误 + STATUS_FLASH_WRITE_FAIL = 0x70 # flash写入失败 + STATUS_FLASH_READ_FAIL = 0x71 # flash读取失败 message = { - Device.HF_TAG_OK : "HF tag operation succeeded", - Device.HF_TAG_NO : "HF tag no found or lost", - Device.HF_ERRSTAT : "HF tag status error", - Device.HF_ERRCRC : "HF tag data crc error", - Device.HF_COLLISION : "HF tag collision", - Device.HF_ERRBCC : "HF tag uid bcc error", - Device.MF_ERRAUTH : "HF tag auth fail", - Device.HF_ERRPARITY : "HF tag data parity error", + Device.HF_TAG_OK: "HF tag operation succeeded", + Device.HF_TAG_NO: "HF tag no found or lost", + Device.HF_ERRSTAT: "HF tag status error", + Device.HF_ERRCRC: "HF tag data crc error", + Device.HF_COLLISION: "HF tag collision", + Device.HF_ERRBCC: "HF tag uid bcc error", + Device.MF_ERRAUTH: "HF tag auth fail", + Device.HF_ERRPARITY: "HF tag data parity error", - Device.DARKSIDE_CANT_FIXED_NT : "Darkside Can't select a nt(PRNG is unpredictable)", - Device.DARKSIDE_LUCK_AUTH_OK : "Darkside try to recover a default key", - Device.DARKSIDE_NACK_NO_SNED : "Darkside can't make tag response nack(enc)", - Device.DARKSIDE_TAG_CHANGED : "Darkside running, can't change tag", - Device.NESTED_TAG_IS_STATIC : "StaticNested tag, not weak nested", - Device.NESTED_TAG_IS_HARD : "HardNested tag, not weak nested", + Device.DARKSIDE_CANT_FIXED_NT: "Darkside Can't select a nt(PRNG is unpredictable)", + Device.DARKSIDE_LUCK_AUTH_OK: "Darkside try to recover a default key", + Device.DARKSIDE_NACK_NO_SEND: "Darkside can't make tag response nack(enc)", + Device.DARKSIDE_TAG_CHANGED: "Darkside running, can't change tag", + Device.NESTED_TAG_IS_STATIC: "StaticNested tag, not weak nested", + Device.NESTED_TAG_IS_HARD: "HardNested tag, not weak nested", - Device.LF_TAG_OK : "LF tag operation succeeded", - Device.EM410X_TAG_NO_FOUND : "EM410x tag no found", + Device.LF_TAG_OK: "LF tag operation succeeded", + Device.EM410X_TAG_NO_FOUND: "EM410x tag no found", - Device.STATUS_PAR_ERR : "API request fail, param error", - Device.STATUS_DEVIEC_MODE_ERROR : "API request fail, device mode error", - Device.STATUS_INVALID_CMD : "API request fail, cmd invalid", - Device.STATUS_DEVICE_SUCCESS : "Device operation succeeded", - Device.STATUS_NOT_IMPLEMENTED : "Some api not implemented", - Device.STATUS_FLASH_WRITE_FAIL : "Flash write failed", - Device.STATUS_FLASH_READ_FAIL : "Flash read failed" + Device.STATUS_PAR_ERR: "API request fail, param error", + Device.STATUS_DEVICE_MODE_ERROR: "API request fail, device mode error", + Device.STATUS_INVALID_CMD: "API request fail, cmd invalid", + Device.STATUS_DEVICE_SUCCESS: "Device operation succeeded", + Device.STATUS_NOT_IMPLEMENTED: "Some api not implemented", + Device.STATUS_FLASH_WRITE_FAIL: "Flash write failed", + Device.STATUS_FLASH_READ_FAIL: "Flash read failed" }