mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-10-23 21:33:18 +00:00
Merge branch 'main' into fix-hidprox-cli-args
This commit is contained in:
21 files changed
+1299
-143
No files matched your search
@@ -3,7 +3,17 @@ 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]
|
||||
- Fix for FAST_READ command for nfc - mf0 tags
|
||||
- Rewrite of the dynamic and static locks logic for NTAG213, NTAG215 and NTAG216; we shouldn't take into account the block lock bits
|
||||
- Fixed an issue where we wouldn't be able to change CFG0 and CFG1 for NTAG213, NTAG215 and NTG216 once a password was added even if the cfg bit was reset.
|
||||
- Fix for static nested key recovery (@jekkos)
|
||||
- Fix LEDs being stuck on after battery check (@suut)
|
||||
- Add TCP support for the CLI (@suut)
|
||||
- Fix build on Android in Termux (@suut)
|
||||
- Fix the issue where some reader cause CU to enter a strange state (@xianglin1998)
|
||||
- The transmission performance of USB has been improved (@xianglin1998)
|
||||
- Added cmd for set mf1 config 'field_off_do_reset' (@xianglin1998)
|
||||
|
||||
|
||||
## [v2.1.0][2025-09-02]
|
||||
- Added UV, formatter and linter. Contribution guidelines. (@GameTec-live)
|
||||
|
||||
@@ -996,7 +996,7 @@ static data_frame_tx_t *cmd_processor_mf1_write_emu_block_data(uint16_t cmd, uin
|
||||
uint8_t block_index = data[0];
|
||||
uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE;
|
||||
if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) {
|
||||
status = STATUS_PAR_ERR;
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
|
||||
}
|
||||
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096);
|
||||
nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer;
|
||||
@@ -1374,6 +1374,19 @@ static data_frame_tx_t *cmd_processor_mf1_set_write_mode(uint16_t cmd, uint16_t
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_mf1_get_field_off_do_reset(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
uint8_t enable = nfc_tag_mf1_is_field_off_do_reset();
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, 1, &enable);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_mf1_set_field_off_do_reset(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
if (length != 1 || data[0] >= 2) {
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
|
||||
}
|
||||
nfc_tag_mf1_set_field_off_do_reset(data[0]);
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
struct {
|
||||
uint8_t enabled_hf;
|
||||
@@ -1630,6 +1643,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
|
||||
{ DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_set_block_anti_coll_mode, NULL },
|
||||
{ DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL },
|
||||
{ DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL },
|
||||
{ DATA_CMD_MF1_GET_FIELD_OFF_DO_RESET, NULL, cmd_processor_mf1_get_field_off_do_reset, NULL },
|
||||
{ DATA_CMD_MF1_SET_FIELD_OFF_DO_RESET, NULL, cmd_processor_mf1_set_field_off_do_reset, NULL },
|
||||
|
||||
{ DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_get_uid_mode, NULL },
|
||||
{ DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL },
|
||||
|
||||
@@ -559,6 +559,12 @@ static void cycle_slot(bool dec) {
|
||||
}
|
||||
// Update status only if the new card slot switch is valid
|
||||
tag_emulation_change_slot(slot_new, true); // Tell the analog card module that we need to switch card slots
|
||||
// Turn off the LEDs in case we were showing the battery status
|
||||
rgb_marquee_stop();
|
||||
uint32_t *led_pins = hw_get_led_array();
|
||||
for (int i = 0; i < RGB_LIST_NUM; i++) {
|
||||
nrf_gpio_pin_clear(led_pins[i]);
|
||||
}
|
||||
// Go back to the color corresponding to the field enablement type
|
||||
apply_slot_change(slot_now, slot_new);
|
||||
}
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
#define DATA_CMD_MF0_NTAG_GET_DETECTION_LOG (4035)
|
||||
#define DATA_CMD_MF0_NTAG_GET_DETECTION_ENABLE (4036)
|
||||
#define DATA_CMD_MF0_NTAG_GET_EMULATOR_CONFIG (4037)
|
||||
#define DATA_CMD_MF1_SET_FIELD_OFF_DO_RESET (4038)
|
||||
#define DATA_CMD_MF1_GET_FIELD_OFF_DO_RESET (4039)
|
||||
//
|
||||
// ******************************************************************
|
||||
|
||||
|
||||
@@ -61,7 +61,10 @@ static volatile bool m_is_responded = false;
|
||||
static uint8_t m_nfc_rx_buffer[MAX_NFC_RX_BUFFER_SIZE] = { 0x00 };
|
||||
static uint8_t m_nfc_tx_buffer[MAX_NFC_TX_BUFFER_SIZE] = { 0x00 };
|
||||
// The N -secondary connection needs to use SAK, when the "third 'bit' in SAK is 1 is 1, the logo UID is incomplete
|
||||
static uint8_t m_uid_incomplete_sak[] = { 0x04, 0xda, 0x17 };
|
||||
static uint8_t m_uid_incomplete_sak[] = { 0x04, 0xda, 0x17 };
|
||||
// Reset nfc peripheral after field lost?
|
||||
static bool reset_if_field_lost = false; // default is 'false', Unless there is a genuine need for a reset.
|
||||
|
||||
|
||||
/**
|
||||
* @brief Calculate BCC
|
||||
@@ -349,7 +352,7 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
||||
m_tag_state_14a = NFC_TAG_STATE_14A_READY;
|
||||
// After receiving the WUPA or REQA instruction, we need to reply to ATQA
|
||||
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
|
||||
// NRF_LOG_INFO("ATQA reply.");
|
||||
// NRF_LOG_INFO("ATQA reply: %02x%02x", auto_coll_res->atqa[0], auto_coll_res->atqa[1]);
|
||||
} else {
|
||||
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
|
||||
NRF_LOG_INFO("Auto anti-collision resource no exists.");
|
||||
@@ -397,6 +400,15 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
||||
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
|
||||
}
|
||||
return;
|
||||
case NFC_TAG_14A_CMD_REQA:
|
||||
case NFC_TAG_14A_CMD_WUPA:
|
||||
// Reader is re-sending REQA/WUPA while in READY state
|
||||
// This can happen if reader retries or if frame was received incorrectly
|
||||
// Respond with ATQA again and stay in READY state
|
||||
if (auto_coll_res != NULL) {
|
||||
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
|
||||
}
|
||||
return;
|
||||
default: {
|
||||
// After receiving the wrong level instruction, directly reset the status machine
|
||||
NRF_LOG_INFO("[MFEMUL_SELECT] Incorrect cascade level received: %02x", p_data[0]);
|
||||
@@ -523,6 +535,43 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy from nrf_nfct.c and modified for nrf52840 adapted(no verify on nrf52832)
|
||||
static inline void nrf_nfct_reset(void) {
|
||||
uint32_t fdm;
|
||||
uint32_t int_enabled;
|
||||
|
||||
// Save parameter settings before the reset of the NFCT peripheral.
|
||||
fdm = nrf_nfct_frame_delay_max_get();
|
||||
int_enabled = nrf_nfct_int_enable_get();
|
||||
|
||||
// Reset the NFCT peripheral.
|
||||
*(volatile uint32_t *)0x40005FFC = 0;
|
||||
*(volatile uint32_t *)0x40005FFC;
|
||||
*(volatile uint32_t *)0x40005FFC = 1;
|
||||
|
||||
// Restore parameter settings after the reset of the NFCT peripheral.
|
||||
nrf_nfct_frame_delay_max_set(fdm);
|
||||
|
||||
// Use Window Grid frame delay mode.
|
||||
nrf_nfct_frame_delay_mode_set(NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID);
|
||||
|
||||
/* Begin: Workaround for anomaly 25 */
|
||||
/* Workaround for wrong SENSRES values require using SDD00001, but here SDD00100 is used
|
||||
because it is required to operate with Windows Phone */
|
||||
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00100);
|
||||
/* End: Workaround for anomaly 25 */
|
||||
|
||||
// Restore interrupts.
|
||||
nrf_nfct_int_enable(int_enabled);
|
||||
|
||||
// Disable interrupts associated with data exchange.
|
||||
nrf_nfct_int_disable(NRF_NFCT_INT_RXFRAMESTART_MASK |
|
||||
NRF_NFCT_INT_RXFRAMEEND_MASK |
|
||||
NRF_NFCT_INT_RXERROR_MASK |
|
||||
NRF_NFCT_INT_TXFRAMESTART_MASK |
|
||||
NRF_NFCT_INT_TXFRAMEEND_MASK);
|
||||
}
|
||||
|
||||
static inline void nfc_fdt_reset(void) {
|
||||
// STOP TX
|
||||
*(volatile uint32_t *)0x40005010 = 0x01;
|
||||
@@ -571,6 +620,13 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) {
|
||||
TAG_FIELD_LED_OFF()
|
||||
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
|
||||
|
||||
if (reset_if_field_lost) {
|
||||
// Fix a bug where certain special conditions prevent triggering TX start events and actually transmit incorrect data to the card reader.
|
||||
// After more more more testing, I found that simply going into sleep mode and restarting can restore work.
|
||||
// Therefore, I suspect that there may be some issues with the NFC peripheral that require a reset to resolve.
|
||||
nrf_nfct_reset();
|
||||
}
|
||||
|
||||
NRF_LOG_INFO("HF FIELD LOST");
|
||||
break;
|
||||
}
|
||||
@@ -688,3 +744,11 @@ bool is_valid_uid_size(uint8_t uid_length) {
|
||||
uid_length == NFC_TAG_14A_UID_DOUBLE_SIZE ||
|
||||
uid_length == NFC_TAG_14A_UID_TRIPLE_SIZE;
|
||||
}
|
||||
|
||||
void nfc_tag_14a_set_reset_enable(bool enable) {
|
||||
reset_if_field_lost = enable;
|
||||
}
|
||||
|
||||
bool nfc_tag_14a_is_reset_enable() {
|
||||
return reset_if_field_lost;
|
||||
}
|
||||
@@ -115,4 +115,8 @@ void nfc_tag_14a_tx_nbit(uint8_t data, uint32_t bits);
|
||||
// Determine whether it is an effective UID length
|
||||
bool is_valid_uid_size(uint8_t uid_length);
|
||||
|
||||
// Reset nfc peripheral after field lost
|
||||
void nfc_tag_14a_set_reset_enable(bool enable);
|
||||
bool nfc_tag_14a_is_reset_enable();
|
||||
|
||||
#endif
|
||||
@@ -675,29 +675,49 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) {
|
||||
|
||||
int block_max = get_block_max_by_tag_type(m_tag_type, true);
|
||||
|
||||
if (block_num >= end_block_num || end_block_num >= block_max) {
|
||||
if (block_num > end_block_num || end_block_num >= block_max) {
|
||||
nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
NRF_LOG_INFO("HANDLING FAST READ %02x %02x", block_num, end_block_num);
|
||||
|
||||
handle_any_read(block_num, end_block_num - block_num, block_max);
|
||||
// FAST_READ is inclusive: read from block_num to end_block_num (both included)
|
||||
handle_any_read(block_num, end_block_num - block_num + 1, block_max);
|
||||
}
|
||||
|
||||
static bool check_ro_lock_on_page(int block_num) {
|
||||
if (block_num < 3) return true;
|
||||
else if (block_num == 3) return (m_tag_information->memory[2][2] & 9) != 0; // bits 0 and 3
|
||||
else if (block_num <= MF0ICU1_PAGES) {
|
||||
else if (block_num == 3) {
|
||||
switch (m_tag_type) {
|
||||
case TAG_TYPE_NTAG_213:
|
||||
case TAG_TYPE_NTAG_215:
|
||||
case TAG_TYPE_NTAG_216:
|
||||
//page 3 can be locked or not independant of BL CC bit
|
||||
//the BL bit only freezes the lock bytes !
|
||||
return (m_tag_information->memory[2][2] & 8) != 0;
|
||||
default:
|
||||
return (m_tag_information->memory[2][2] & 9) != 0;
|
||||
}
|
||||
// bits 0 and 3
|
||||
} else if (block_num <= MF0ICU1_PAGES) {
|
||||
bool locked = false;
|
||||
switch (m_tag_type) {
|
||||
case TAG_TYPE_NTAG_213:
|
||||
case TAG_TYPE_NTAG_215:
|
||||
case TAG_TYPE_NTAG_216:
|
||||
// pages can be locked or not independant of BL bits
|
||||
//the BL bits only freezes the lock bytes !
|
||||
uint16_t lock_bits = *(uint16_t *)&m_tag_information->memory[2][2];
|
||||
return ((lock_bits >> block_num) & 0x01) == 1;
|
||||
default:
|
||||
// check block locking bits
|
||||
if (block_num <= 9) locked |= (m_tag_information->memory[2][2] & 2) == 2;
|
||||
else locked |= (m_tag_information->memory[2][2] & 4) == 4;
|
||||
|
||||
// check block locking bits
|
||||
if (block_num <= 9) locked |= (m_tag_information->memory[2][2] & 2) == 2;
|
||||
else locked |= (m_tag_information->memory[2][2] & 4) == 4;
|
||||
locked |= (((*(uint16_t *)&m_tag_information->memory[2][2]) >> block_num) & 1) == 1;
|
||||
|
||||
locked |= (((*(uint16_t *)&m_tag_information->memory[2][2]) >> block_num) & 1) == 1;
|
||||
|
||||
return locked;
|
||||
return locked;
|
||||
}
|
||||
} else {
|
||||
uint8_t *p_lock_bytes = NULL;
|
||||
int user_memory_end = 0;
|
||||
@@ -776,9 +796,43 @@ static bool check_ro_lock_on_page(int block_num) {
|
||||
|
||||
bool locked_small_range = ((lock_word >> (index / dyn_lock_bit_page_cnt)) & 1) != 0;
|
||||
bool locked_large_range = ((p_lock_bytes[2] >> (index / dyn_lock_bit_page_cnt / 2)) & 1) != 0;
|
||||
|
||||
return locked_small_range | locked_large_range;
|
||||
switch (m_tag_type) {
|
||||
case TAG_TYPE_NTAG_213:
|
||||
case TAG_TYPE_NTAG_215:
|
||||
case TAG_TYPE_NTAG_216:
|
||||
// For NTAG213/215/216: byte 2 contains block-locking bits (BL) which only freeze
|
||||
// the lock configuration. We only check the actual lock bits (L0-L15) in bytes 0-1.
|
||||
return locked_small_range;
|
||||
default:
|
||||
return locked_small_range | locked_large_range;
|
||||
}
|
||||
} else {
|
||||
//Check the block locking bits to see if we can touch the dynamic locks bytes for NTAG tags
|
||||
if(block_num == user_memory_end)
|
||||
{
|
||||
switch (m_tag_type) {
|
||||
case TAG_TYPE_NTAG_213:
|
||||
case TAG_TYPE_NTAG_215:
|
||||
case TAG_TYPE_NTAG_216:
|
||||
{
|
||||
uint8_t block_bytes = m_tag_information->memory[user_memory_end][2];
|
||||
uint16_t block_world = 0;
|
||||
|
||||
// Each bit in block_bytes maps to 2 bits in block_world
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (block_bytes & (0x01 << i)) {
|
||||
block_world |= (0x0003 << (i * 2));
|
||||
}
|
||||
}
|
||||
|
||||
p_lock_bytes = m_tag_information->memory[user_memory_end];
|
||||
uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0];
|
||||
return (lock_word & block_world) != 0;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check CFGLCK bit
|
||||
int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type);
|
||||
uint8_t access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE];
|
||||
@@ -793,7 +847,25 @@ static bool check_ro_lock_on_page(int block_num) {
|
||||
static int handle_write_command(uint8_t block_num, uint8_t *p_data) {
|
||||
int block_max = get_block_max_by_tag_type(m_tag_type, false);
|
||||
|
||||
if (block_num >= block_max) {
|
||||
bool out_of_bounds = false;
|
||||
switch (m_tag_type) {
|
||||
case TAG_TYPE_NTAG_213:
|
||||
case TAG_TYPE_NTAG_215:
|
||||
case TAG_TYPE_NTAG_216:
|
||||
int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type);
|
||||
uint8_t cfglck = m_tag_information->memory[first_cfg_page][0] & 0x40;
|
||||
// For NTAG cards we need to check CFGLCK bit for config pages
|
||||
bool is_config_page = (block_num >= first_cfg_page) && (block_num <= first_cfg_page + 1);
|
||||
bool config_locked = (cfglck != 0) && (!m_tag_information->config.mode_uid_magic);
|
||||
bool is_beyond_user_memory = (block_num >= block_max);
|
||||
out_of_bounds = (is_beyond_user_memory && !is_config_page) || (config_locked && is_config_page);
|
||||
break;
|
||||
default:
|
||||
out_of_bounds = block_num >= block_max;
|
||||
break;
|
||||
}
|
||||
// Reject out-of-bounds writes (except config pages)
|
||||
if (out_of_bounds) {
|
||||
NRF_LOG_ERROR("Write failed: block_num %08x >= block_max %08x", block_num, block_max);
|
||||
return NAK_INVALID_OPERATION_TBV;
|
||||
}
|
||||
|
||||
@@ -1111,6 +1111,8 @@ int nfc_tag_mf1_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer)
|
||||
.cb_reset = nfc_tag_mf1_reset_handler,
|
||||
};
|
||||
nfc_tag_14a_set_handler(&handler_for_14a);
|
||||
NRF_LOG_INFO("HF mf1 config 'field_off_do_reset' = %d", m_tag_information->config.field_off_do_reset);
|
||||
nfc_tag_14a_set_reset_enable(m_tag_information->config.field_off_do_reset);
|
||||
NRF_LOG_INFO("HF mf1 data load finish.");
|
||||
} else {
|
||||
NRF_LOG_ERROR("nfc_tag_mf1_information_t too big.");
|
||||
@@ -1157,6 +1159,12 @@ bool nfc_tag_mf1_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
|
||||
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;
|
||||
p_mf1_information->config.field_off_do_reset = false;
|
||||
|
||||
// zero for reserved byte
|
||||
p_mf1_information->config.reserved1 = 0x00;
|
||||
p_mf1_information->config.reserved2 = 0x00;
|
||||
p_mf1_information->config.reserved3 = 0x00;
|
||||
|
||||
// save data to flash
|
||||
tag_sense_type_t sense_type = get_sense_type_from_tag_type(tag_type);
|
||||
@@ -1236,3 +1244,10 @@ nfc_tag_mf1_write_mode_t nfc_tag_mf1_get_write_mode(void) {
|
||||
return m_tag_information->config.mode_block_write;
|
||||
}
|
||||
|
||||
void nfc_tag_mf1_set_field_off_do_reset(bool enable) {
|
||||
m_tag_information->config.field_off_do_reset = enable;
|
||||
}
|
||||
|
||||
bool nfc_tag_mf1_is_field_off_do_reset(void) {
|
||||
return m_tag_information->config.field_off_do_reset;
|
||||
}
|
||||
@@ -71,8 +71,15 @@ typedef struct {
|
||||
uint8_t detection_enable: 1;
|
||||
// Allow to write block 0 (CUID/gen2 mode)
|
||||
uint8_t mode_gen2_magic: 1;
|
||||
// reserve
|
||||
uint8_t reserved1: 4;
|
||||
/**
|
||||
* Should the NFC peripheral be reset after losing the RF field?
|
||||
* This configuration can fix the issue where some card readers cause the CU to enter a strange state of no response/incorrect response.
|
||||
* Once in this state, the device must be restarted to resolve the issue.
|
||||
* Alternatively, enabling this configuration for resetting the NFC after leaving the rf field can also solve the aforementioned problem.
|
||||
*/
|
||||
uint8_t field_off_do_reset: 1;
|
||||
// reserved
|
||||
uint8_t reserved1: 3;
|
||||
uint8_t reserved2;
|
||||
uint8_t reserved3;
|
||||
} nfc_tag_mf1_configure_t;
|
||||
@@ -157,6 +164,7 @@ 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);
|
||||
|
||||
void nfc_tag_mf1_set_field_off_do_reset(bool enable);
|
||||
bool nfc_tag_mf1_is_field_off_do_reset(void);
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,7 @@
|
||||
#define PREAMBLE_34BIT (0x009)
|
||||
#define PREAMBLE_35BIT (0x005)
|
||||
#define PREAMBLE_36BIT (0x003)
|
||||
#define PREAMBLE_ACTP (0x095)
|
||||
|
||||
/**@brief Set a bit in the uint64 word.
|
||||
*
|
||||
@@ -548,6 +549,37 @@ static wiegand_card_t *unpack_c15001(uint64_t hi, uint64_t lo) {
|
||||
return d;
|
||||
}
|
||||
|
||||
static uint64_t pack_actprox(wiegand_card_t *card) {
|
||||
if (card->oem == 0) {
|
||||
card->oem = 900;
|
||||
}
|
||||
uint64_t bits = PREAMBLE_ACTP;
|
||||
bits <<= 1;
|
||||
bits = (bits << 10) | (card->oem & 0x3ff);
|
||||
bits = (bits << 8) | (card->facility_code & 0xff);
|
||||
bits = (bits << 16) | (card->card_number & 0xffff);
|
||||
bits <<= 1;
|
||||
if (evenparity32((bits >> 18) & 0x1ffff)) {
|
||||
SET_BIT64(bits, 35);
|
||||
}
|
||||
if (oddparity32((bits >> 1) & 0x1ffff)) {
|
||||
SET_BIT64(bits, 0);
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
static wiegand_card_t *unpack_actprox(uint64_t hi, uint64_t lo) {
|
||||
if (!(IS_SET(lo, 35) == evenparity32((lo >> 18) & 0x1ffff) &&
|
||||
IS_SET(lo, 0) == oddparity32((lo >> 1) & 0x1ffff))) {
|
||||
return NULL;
|
||||
}
|
||||
wiegand_card_t *d = wiegand_card_alloc();
|
||||
d->oem = (lo >> 25) & 0x3ff;
|
||||
d->facility_code = (lo >> 17) & 0xff;
|
||||
d->card_number = (lo >> 1) & 0xffff;
|
||||
return d;
|
||||
}
|
||||
|
||||
static uint64_t pack_s12906(wiegand_card_t *card) {
|
||||
uint64_t bits = PREAMBLE_36BIT;
|
||||
bits <<= 1;
|
||||
@@ -819,6 +851,7 @@ static const card_format_table_t formats[] = {
|
||||
{P10004, pack_p10004, unpack_p10004, 37, {0, 0x1FFF, 0x3FFFF, 0, 0}}, // HID P10004 37-bit PCSC
|
||||
{HGEN37, pack_hgeneric37, unpack_hgeneric37, 37, {1, 0, 0xFFFFFFFF, 0, 0}}, // HID Generic 37-bit
|
||||
{MDI37, pack_mdi37, unpack_mdi37, 37, {1, 0xF, 0x1FFFFFFF, 0, 0}}, // PointGuard MDI 37-bit
|
||||
{ACTPHID, pack_actprox, unpack_actprox, 36, {1, 0xFF, 0xFFFF, 0x3FF, 0}}, // HID ACTProx 36-bit
|
||||
};
|
||||
|
||||
uint64_t pack(wiegand_card_t *card) {
|
||||
|
||||
@@ -72,6 +72,7 @@ typedef enum {
|
||||
C1K48S,
|
||||
AVIG56,
|
||||
IR56,
|
||||
ACTPHID,
|
||||
} card_format_t;
|
||||
|
||||
// Structure for defined Wiegand card formats available for packing/unpacking
|
||||
|
||||
@@ -40,19 +40,19 @@ APP_USBD_CDC_ACM_GLOBAL_DEF(m_app_cdc_acm,
|
||||
volatile bool g_usb_connected = false;
|
||||
volatile bool g_usb_port_opened = false;
|
||||
volatile bool g_usb_led_marquee_enable = true;
|
||||
static uint8_t cdc_data_buffer[NRF_DRV_USBD_EPSIZE];
|
||||
|
||||
/** @brief User event handler @ref app_usbd_cdc_acm_user_ev_handler_t */
|
||||
static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const *p_inst, app_usbd_cdc_acm_user_event_t event) {
|
||||
static uint8_t cdc_data_buffer[1];
|
||||
|
||||
// app_usbd_cdc_acm_t const *p_cdc_acm = app_usbd_cdc_acm_class_get(p_inst);
|
||||
|
||||
switch (event) {
|
||||
case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN: {
|
||||
/*
|
||||
*theProbabilityOfTheEntireUsbReceivingDataIsTheAppUsbdCdcAcmRead *AppUsbdCdcAcmReadFunctionIsNotASeriousReception,ItIsGivenAPointer,AndThenWaitForTheUsbBuffer *SoYouNeedToInitializeTheHeadPointerFirstWhenTheAppUsbdCdcAcmUserEvtPortOpenIsInitialized *IfTheAppUsbdCdcAcmUserEvtRxDoneUsesASubscribed0ToAccessTheBuffer,ItWillCauseTheFirstByteToLoseTheFirstSendEssence
|
||||
*/
|
||||
ret_code_t ret = app_usbd_cdc_acm_read(&m_app_cdc_acm, cdc_data_buffer, 1);
|
||||
// Setup first transfer
|
||||
ret_code_t ret = app_usbd_cdc_acm_read_any(&m_app_cdc_acm, cdc_data_buffer, sizeof(cdc_data_buffer));
|
||||
UNUSED_VARIABLE(ret);
|
||||
|
||||
NRF_LOG_INFO("CDC ACM port opened");
|
||||
g_usb_port_opened = true;
|
||||
break;
|
||||
@@ -68,16 +68,13 @@ static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const *p_inst, app_usb
|
||||
break;
|
||||
|
||||
case APP_USBD_CDC_ACM_USER_EVT_RX_DONE: {
|
||||
ret_code_t ret;
|
||||
//Take out the first byte first
|
||||
data_frame_receive(cdc_data_buffer, 1);
|
||||
do {
|
||||
ret = app_usbd_cdc_acm_read(&m_app_cdc_acm, cdc_data_buffer, 1);
|
||||
if (ret == NRF_SUCCESS) {
|
||||
// The byte after success
|
||||
data_frame_receive(cdc_data_buffer, 1);
|
||||
}
|
||||
} while (ret == NRF_SUCCESS);
|
||||
// Get amount of data transfered to process data
|
||||
size_t size = app_usbd_cdc_acm_rx_size(&m_app_cdc_acm);
|
||||
data_frame_receive(cdc_data_buffer, size);
|
||||
|
||||
// Setup next transfer
|
||||
ret_code_t ret = app_usbd_cdc_acm_read_any(&m_app_cdc_acm, cdc_data_buffer, sizeof(cdc_data_buffer));
|
||||
UNUSED_VARIABLE(ret);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -28,6 +28,11 @@ static uint8_t compute_lrc(uint8_t *buf, uint16_t bufsize) {
|
||||
return 0x100 - lrc;
|
||||
}
|
||||
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!! NRF_LOG_HEXDUMP_INFO() printing long data can cause freezing and needs to be fixed. !!!!!!!!!!!!!!!!!
|
||||
// FIXME.
|
||||
//
|
||||
|
||||
/**
|
||||
* @brief: create a packet, put the created data packet into the buffer, and wait for the post to set up a non busy state
|
||||
* @param cmd: instructionResponse
|
||||
@@ -44,10 +49,11 @@ data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t data_le
|
||||
NRF_LOG_ERROR("data_frame_make error, too much data.");
|
||||
return NULL;
|
||||
}
|
||||
NRF_LOG_INFO("TX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", cmd, cmd, status, data_length, data_length > 0 ? ", data =" : "");
|
||||
if (data_length > 0) {
|
||||
NRF_LOG_HEXDUMP_INFO(data, data_length);
|
||||
}
|
||||
|
||||
// NRF_LOG_INFO("TX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", cmd, cmd, status, data_length, data_length > 0 ? ", data =" : "");
|
||||
// if (data_length > 0) {
|
||||
// NRF_LOG_HEXDUMP_INFO(data, data_length);
|
||||
// }
|
||||
|
||||
netdata_frame_postamble_t *tx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_tx_buf + sizeof(netdata_frame_preamble_t) + data_length);
|
||||
// sof
|
||||
@@ -92,7 +98,7 @@ void data_frame_receive(uint8_t *data, uint16_t length) {
|
||||
return;
|
||||
}
|
||||
// buffer overflow
|
||||
if (m_data_rx_position + length >= sizeof(m_netdata_frame_rx_buf)) {
|
||||
if (m_data_rx_position + length > sizeof(m_netdata_frame_rx_buf)) {
|
||||
NRF_LOG_ERROR("Data frame wait overflow.");
|
||||
data_frame_reset();
|
||||
return;
|
||||
@@ -142,10 +148,10 @@ void data_frame_receive(uint8_t *data, uint16_t length) {
|
||||
// and we are receive completed
|
||||
m_data_buffer = m_data_len > 0 ? (uint8_t *)&m_netdata_frame_rx_buf.data : NULL;
|
||||
m_data_completed = true;
|
||||
NRF_LOG_INFO("RX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", m_data_cmd, m_data_cmd, m_data_status, m_data_len, m_data_len > 0 ? ", data =" : "");
|
||||
if (m_data_len > 0) {
|
||||
NRF_LOG_HEXDUMP_INFO(m_data_buffer, m_data_len);
|
||||
}
|
||||
// NRF_LOG_INFO("RX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", m_data_cmd, m_data_cmd, m_data_status, m_data_len, m_data_len > 0 ? ", data =" : "");
|
||||
// if (m_data_len > 0) {
|
||||
// NRF_LOG_HEXDUMP_INFO(m_data_buffer, m_data_len);
|
||||
// }
|
||||
} else {
|
||||
// data frame lrc error
|
||||
NRF_LOG_ERROR("Data frame finally lrc error.");
|
||||
|
||||
@@ -259,7 +259,8 @@ include $(TEMPLATE_PATH)/Makefile.common
|
||||
|
||||
# tolerate warnings in newer gcc versions
|
||||
# need to be called after $(TEMPLATE_PATH)/Makefile.common
|
||||
CC_VERSION = $(shell $(CC) -dumpversion 2>/dev/null|sed 's/\..*//')
|
||||
# The return value of the Windows+msys2 build platform has carriage returns and line breaks, which need to be removed.
|
||||
CC_VERSION = $(shell $(CC) -dumpversion 2>/dev/null | tr -d '\r' | cut -d. -f1)
|
||||
CC_VERSION := $(or $(strip $(CC_VERSION)),0)
|
||||
ifeq ($(shell expr $(CC_VERSION) \>= 12), 1)
|
||||
# avoid a couple of false warnings in nRF SDK
|
||||
|
||||
@@ -818,29 +818,9 @@ void nrfx_nfct_irq_handler(void)
|
||||
|
||||
NRFX_NFCT_CB_HANDLE(m_nfct_cb.config.cb, nfct_evt);
|
||||
|
||||
/* Clear TXFRAMESTART EVENT so it can be checked in hal_nfc_send */
|
||||
nrf_nfct_event_clear(NRF_NFCT_EVENT_TXFRAMESTART);
|
||||
|
||||
NRFX_LOG_DEBUG("Rx fend");
|
||||
}
|
||||
|
||||
if (NRFX_NFCT_EVT_ACTIVE(TXFRAMEEND))
|
||||
{
|
||||
nrf_nfct_event_clear(NRF_NFCT_EVENT_TXFRAMEEND);
|
||||
|
||||
nrfx_nfct_evt_t nfct_evt =
|
||||
{
|
||||
.evt_id = NRFX_NFCT_EVT_TX_FRAMEEND
|
||||
};
|
||||
|
||||
/* Disable TX END event to ignore frame transmission other than READ response */
|
||||
nrf_nfct_int_disable(NRFX_NFCT_TX_INT_MASK);
|
||||
|
||||
NRFX_NFCT_CB_HANDLE(m_nfct_cb.config.cb, nfct_evt);
|
||||
|
||||
NRFX_LOG_DEBUG("Tx fend");
|
||||
}
|
||||
|
||||
if (NRFX_NFCT_EVT_ACTIVE(SELECTED))
|
||||
{
|
||||
nrf_nfct_event_clear(NRF_NFCT_EVENT_SELECTED);
|
||||
@@ -913,6 +893,23 @@ void nrfx_nfct_irq_handler(void)
|
||||
m_nfct_cb.config.cb(&nfct_evt);
|
||||
}
|
||||
}
|
||||
|
||||
if (NRFX_NFCT_EVT_ACTIVE(TXFRAMEEND))
|
||||
{
|
||||
nrf_nfct_event_clear(NRF_NFCT_EVENT_TXFRAMEEND);
|
||||
|
||||
nrfx_nfct_evt_t nfct_evt =
|
||||
{
|
||||
.evt_id = NRFX_NFCT_EVT_TX_FRAMEEND
|
||||
};
|
||||
|
||||
/* Disable TX END event to ignore frame transmission other than READ response */
|
||||
nrf_nfct_int_disable(NRFX_NFCT_TX_INT_MASK);
|
||||
|
||||
NRFX_NFCT_CB_HANDLE(m_nfct_cb.config.cb, nfct_evt);
|
||||
|
||||
NRFX_LOG_DEBUG("Tx fend");
|
||||
}
|
||||
}
|
||||
|
||||
#endif // NRFX_CHECK(NRFX_NFCT_ENABLED)
|
||||
@@ -12,6 +12,7 @@ import time
|
||||
import serial.tools.list_ports
|
||||
import threading
|
||||
import struct
|
||||
import queue
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
@@ -198,7 +199,7 @@ class DeviceRequiredUnit(BaseCLIUnit):
|
||||
if ret:
|
||||
return True
|
||||
else:
|
||||
print("Please connect to chameleon device first(use 'hw connect').")
|
||||
print("Please connect to chameleon device first (use 'hw connect').")
|
||||
return False
|
||||
|
||||
|
||||
@@ -452,6 +453,7 @@ class LFHIDIdArgsUnit(DeviceRequiredUnit):
|
||||
HIDFormat.C1K35S: [0xFFF, 0xFFFFF, 0, 0],
|
||||
HIDFormat.C15001: [0xFF, 0xFFFF, 0, 0x3FF],
|
||||
HIDFormat.S12906: [0xFF, 0xFFFFFF, 0x3, 0],
|
||||
HIDFormat.ACTPHID: [0xFF, 0xFFFFFF, 0, 0x3FF],
|
||||
HIDFormat.SIE36: [0x3FFFF, 0xFFFF, 0, 0],
|
||||
HIDFormat.H10320: [0, 99999999, 0, 0],
|
||||
HIDFormat.H10302: [0, 0x7FFFFFFFF, 0, 0],
|
||||
@@ -926,7 +928,7 @@ class HFMFNested(ReaderRequiredUnit):
|
||||
if block_known == block_target and type_known == type_target:
|
||||
print(color_string((CR, "Target key already known")))
|
||||
return
|
||||
print(f" - Nested recover one key running...")
|
||||
print(" - Nested recover one key running...")
|
||||
key = self.recover_a_key(block_known, type_known, key_known_bytes, block_target, type_target)
|
||||
if key is None:
|
||||
print(color_string((CY, "No key found, you can retry.")))
|
||||
@@ -2153,6 +2155,9 @@ class HFMFEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredU
|
||||
log_group = parser.add_mutually_exclusive_group()
|
||||
log_group.add_argument('--enable-log', action='store_true', help="Enable logging of MFC authentication data")
|
||||
log_group.add_argument('--disable-log', action='store_true', help="Disable logging of MFC authentication data")
|
||||
field_off_reset_group = parser.add_mutually_exclusive_group()
|
||||
field_off_reset_group.add_argument('--enable_field_off_do_reset', action='store_true', help="Enable FIELD_OFF_DO_RESET")
|
||||
field_off_reset_group.add_argument('--disable_field_off_do_reset', action='store_true', help="Disable FIELD_OFF_DO_RESET")
|
||||
return parser
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
@@ -2183,6 +2188,8 @@ class HFMFEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredU
|
||||
write_mode = MifareClassicWriteMode(mfc_config["write_mode"])
|
||||
detection = mfc_config["detection"]
|
||||
change_requested, change_done, uid, atqa, sak, ats = self.update_hf14a_anticoll(args, uid, atqa, sak, ats)
|
||||
field_off_do_reset = self.cmd.mf1_get_field_off_do_reset()
|
||||
|
||||
if args.enable_gen1a:
|
||||
change_requested = True
|
||||
if not gen1a_mode:
|
||||
@@ -2256,6 +2263,22 @@ class HFMFEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredU
|
||||
change_done = True
|
||||
else:
|
||||
print(f'{color_string((CY, "Requested logging of MFC authentication data already disabled"))}')
|
||||
if args.enable_set_field_off_do_reset:
|
||||
change_requested = True
|
||||
if not field_off_do_reset:
|
||||
field_off_do_reset = True
|
||||
self.cmd.mf1_set_field_off_do_reset(field_off_do_reset)
|
||||
change_done = True
|
||||
else:
|
||||
print(f'{color_string((CY, "Requested FIELD_OFF_DO_RESET already enabled"))}')
|
||||
elif args.disable_set_field_off_do_reset:
|
||||
change_requested = True
|
||||
if field_off_do_reset:
|
||||
field_off_do_reset = False
|
||||
self.cmd.mf1_set_field_off_do_reset(field_off_do_reset)
|
||||
change_done = True
|
||||
else:
|
||||
print(f'{color_string((CY, "Requested FIELD_OFF_DO_RESET already disabled"))}')
|
||||
|
||||
if change_done:
|
||||
print(' - MF1 Emulator settings updated')
|
||||
@@ -2282,6 +2305,8 @@ class HFMFEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredU
|
||||
print(f'- {"Write mode:":40}{color_string((CR, "invalid value!"))}')
|
||||
print(
|
||||
f'- {"Log (mfkey32) mode:":40}{f"{enabled_str}" if detection else f"{disabled_str}"}')
|
||||
print(
|
||||
f'- {"FIELD_OFF_DO_RESET:":40}{f"{enabled_str}" if field_off_do_reset else f"{disabled_str}"}')
|
||||
|
||||
|
||||
@hf_mfu.command('ercnt')
|
||||
@@ -2365,7 +2390,7 @@ class HFMFURDPG(MFUAuthArgsUnit):
|
||||
else:
|
||||
try:
|
||||
self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page))
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
# we may lose the tag again here
|
||||
pass
|
||||
print(color_string((CR, " - Auth failed")))
|
||||
@@ -2430,7 +2455,7 @@ class HFMFUWRPG(MFUAuthArgsUnit):
|
||||
# send a command just to disable the field. use read to avoid corrupting the data
|
||||
try:
|
||||
self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page))
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
# we may lose the tag again here
|
||||
pass
|
||||
print(color_string((CR, " - Auth failed")))
|
||||
@@ -2569,7 +2594,7 @@ class HFMFUESAVE(DeviceRequiredUnit):
|
||||
version = self.cmd.mf0_ntag_get_version_data()
|
||||
|
||||
fd.write(f"# Version: {version.hex()}\n")
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
pass # slot does not have version data
|
||||
|
||||
try:
|
||||
@@ -2577,7 +2602,7 @@ class HFMFUESAVE(DeviceRequiredUnit):
|
||||
|
||||
if signature != b"\x00" * 32:
|
||||
fd.write(f"# Signature: {signature.hex()}\n")
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
pass # slot does not have signature data
|
||||
|
||||
page = 0
|
||||
@@ -2640,7 +2665,7 @@ class HFMFURCNT(MFUAuthArgsUnit):
|
||||
else:
|
||||
try:
|
||||
self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, args.counter))
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
# we may lose the tag again here
|
||||
pass
|
||||
print(color_string((CR, " - Auth failed")))
|
||||
@@ -2697,14 +2722,14 @@ class HFMFUDUMP(MFUAuthArgsUnit):
|
||||
version = self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0x60))
|
||||
if len(version) == 0:
|
||||
version = None
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
version = None
|
||||
|
||||
# try sending AUTHENTICATE command and observe the result
|
||||
try:
|
||||
supports_auth = len(self.cmd.hf14a_raw(
|
||||
options=options, resp_timeout_ms=100, data=struct.pack('!B', 0x1A))) != 0
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
supports_auth = False
|
||||
|
||||
if version is not None and not supports_auth:
|
||||
@@ -2743,7 +2768,7 @@ class HFMFUDUMP(MFUAuthArgsUnit):
|
||||
|
||||
print(color_string((CY, "Tag is likely NTAG 20x, reading until first error.")))
|
||||
stop_page = 256
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
# Regular Ultralight
|
||||
tag_name = 'Mifare Ultralight'
|
||||
stop_page = 16
|
||||
@@ -2793,7 +2818,7 @@ class HFMFUDUMP(MFUAuthArgsUnit):
|
||||
|
||||
try:
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i))
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
# probably lost tag, but we still need to disable rf field
|
||||
resp = None
|
||||
|
||||
@@ -2893,6 +2918,457 @@ class HFMFUSIGNATURE(ReaderRequiredUnit):
|
||||
print(f" - Data: {resp[:32].hex()}")
|
||||
|
||||
|
||||
@hf_mfu.command('authnonce')
|
||||
class HFMFUAUTHNONCE(ReaderRequiredUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit:
|
||||
parser = ArgumentParserNoExit()
|
||||
parser.description = 'Get authentication nonce from MIFARE Ultralight C tag.'
|
||||
return parser
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
options = {
|
||||
'activate_rf_field': 0,
|
||||
'wait_response': 1,
|
||||
'append_crc': 1,
|
||||
'auto_select': 1,
|
||||
'keep_rf_field': 0,
|
||||
'check_response_crc': 1,
|
||||
}
|
||||
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x1A, 0x00))
|
||||
# Response is 0xAF + 8 bytes nonce + 2 bytes CRC = 11 bytes
|
||||
# We want to display just the 8-byte nonce (skip 0xAF prefix)
|
||||
if len(resp) >= 9 and resp[0] == 0xAF:
|
||||
print(f" - Nonce: {resp[1:9].hex()}")
|
||||
else:
|
||||
print(f" - Error: Unexpected response: {resp.hex()}")
|
||||
|
||||
|
||||
class CrackEffect:
|
||||
"""
|
||||
A class to create a visual effect of cracking blocks of data.
|
||||
"""
|
||||
|
||||
def __init__(self, num_blocks: int = 4, block_size: int = 8, scramble_delay: float = 0.01):
|
||||
"""
|
||||
Initialize the CrackEffect class with the given parameters.
|
||||
|
||||
Args:
|
||||
num_blocks (int): Number of blocks to display. Default is 4.
|
||||
block_size (int): Size of each block in characters. Default is 8.
|
||||
scramble_delay (float): Delay between each scramble update in seconds. Default is 0.01.
|
||||
"""
|
||||
self.num_blocks = num_blocks
|
||||
self.block_size = block_size
|
||||
self.scramble_delay = scramble_delay
|
||||
self.message_queue = queue.Queue()
|
||||
self.revealed = [''] * num_blocks
|
||||
self.stop_event = threading.Event()
|
||||
self.cracked_blocks = set()
|
||||
self.display_lock = threading.Lock()
|
||||
self.output_enabled = True
|
||||
|
||||
def generate_random_hex(self) -> str:
|
||||
"""Generate a random hex string of block_size length."""
|
||||
import random
|
||||
hex_chars = '0123456789ABCDEF'
|
||||
return ''.join(random.choice(hex_chars) for _ in range(self.block_size))
|
||||
|
||||
def format_block(self, block: str, is_cracked: bool) -> str:
|
||||
"""Format a block with appropriate color based on its state."""
|
||||
if is_cracked:
|
||||
return f"\033[1;34m{block}\033[0m" # Bold blue
|
||||
return f"\033[96m{block}\033[0m" # Bright cyan
|
||||
|
||||
def draw_static_box(self):
|
||||
"""Draw the initial static box."""
|
||||
if not self.output_enabled:
|
||||
return
|
||||
width = (self.block_size + 1) * self.num_blocks + 4
|
||||
print("") # Add some padding above
|
||||
print("╔" + "═" * width + "╗")
|
||||
print("║" + " " * width + "║")
|
||||
print("║" + " " * width + "║")
|
||||
print("║" + " " * width + "║")
|
||||
print("╚" + "═" * width + "╝")
|
||||
# Move cursor to the middle line
|
||||
sys.stdout.write("\033[3A") # Move up 3 lines to middle row
|
||||
sys.stdout.flush()
|
||||
|
||||
def print_above(self, data):
|
||||
"""Print the given data above the box and redraws the box."""
|
||||
if not self.output_enabled:
|
||||
print(data)
|
||||
return
|
||||
with self.display_lock:
|
||||
# Move cursor above the box and clean the line
|
||||
sys.stdout.write("\033[2A\033[1G\033[K" + data)
|
||||
self.draw_static_box()
|
||||
|
||||
def display_current_state(self):
|
||||
"""Display the current state of all blocks."""
|
||||
if not self.output_enabled:
|
||||
return
|
||||
with self.display_lock:
|
||||
formatted_blocks = [
|
||||
self.format_block(block, i in self.cracked_blocks)
|
||||
for i, block in enumerate(self.revealed)
|
||||
]
|
||||
display_text = ' '.join(formatted_blocks)
|
||||
|
||||
# Update only the middle line
|
||||
sys.stdout.write(f"\r║ {display_text} ║")
|
||||
sys.stdout.flush()
|
||||
|
||||
def scramble_effect(self):
|
||||
"""Run the main loop for the scrambling effect."""
|
||||
if not self.output_enabled:
|
||||
return
|
||||
while not self.stop_event.is_set():
|
||||
# Update all non-cracked blocks with random values
|
||||
for block in range(self.num_blocks):
|
||||
if block not in self.cracked_blocks:
|
||||
self.revealed[block] = self.generate_random_hex()
|
||||
|
||||
self.display_current_state()
|
||||
time.sleep(self.scramble_delay)
|
||||
|
||||
def erase_key(self):
|
||||
"""Erase random parts of the key."""
|
||||
if not self.output_enabled:
|
||||
return
|
||||
for block in range(self.num_blocks):
|
||||
if block not in self.cracked_blocks:
|
||||
self.revealed[block] = '.' * self.block_size
|
||||
self.display_current_state()
|
||||
|
||||
def process_message_queue(self):
|
||||
"""Process incoming cracked blocks from the queue."""
|
||||
if not self.output_enabled:
|
||||
return
|
||||
while not self.stop_event.is_set():
|
||||
try:
|
||||
block_idx, cracked_text = self.message_queue.get(timeout=0.1)
|
||||
self.revealed[block_idx] = cracked_text
|
||||
self.cracked_blocks.add(block_idx)
|
||||
self.display_current_state()
|
||||
|
||||
# Check if all blocks are cracked
|
||||
if len(self.cracked_blocks) == self.num_blocks:
|
||||
self.stop_event.set()
|
||||
print("\n" * 3) # Add newlines after completion
|
||||
break
|
||||
except queue.Empty:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"\nError processing message: {e}")
|
||||
break
|
||||
|
||||
def add_cracked_block(self, block_idx: int, text: str):
|
||||
"""Add a cracked block to the message queue."""
|
||||
if not 0 <= block_idx < self.num_blocks:
|
||||
raise ValueError(f"Block index {block_idx} out of range")
|
||||
if len(text) != self.block_size:
|
||||
raise ValueError(f"Block text must be {self.block_size} characters")
|
||||
self.message_queue.put((block_idx, text))
|
||||
|
||||
def start(self):
|
||||
"""Start the cracking effect."""
|
||||
self.draw_static_box()
|
||||
|
||||
# Create and start the worker threads
|
||||
scramble_thread = threading.Thread(target=self.scramble_effect)
|
||||
process_thread = threading.Thread(target=self.process_message_queue)
|
||||
|
||||
scramble_thread.daemon = True
|
||||
process_thread.daemon = True
|
||||
|
||||
scramble_thread.start()
|
||||
process_thread.start()
|
||||
|
||||
# Wait for both threads to complete
|
||||
process_thread.join()
|
||||
self.stop_event.set()
|
||||
scramble_thread.join()
|
||||
|
||||
|
||||
@hf_mfu.command('ulcg')
|
||||
class HFMFUULCG(ReaderRequiredUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit:
|
||||
parser = ArgumentParserNoExit()
|
||||
parser.description = 'Key recovery for Giantec ULCG and USCUID-UL cards (won\'t work on NXP cards!)'
|
||||
parser.add_argument('-c', '--challenges', type=int, default=1000,
|
||||
help='Number of challenges to collect (default: 1000)')
|
||||
parser.add_argument('-t', '--threads', type=int, default=1,
|
||||
help='Number of threads for key recovery (default: 1)')
|
||||
parser.add_argument('-j', '--json', type=str,
|
||||
help='Path to JSON file to load or save challenges')
|
||||
parser.add_argument('-o', '--offline', action='store_true',
|
||||
help='Use offline mode with pre-collected challenges')
|
||||
return parser
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
import json
|
||||
|
||||
if not args.offline:
|
||||
challenges = self.collect_challenges(args.challenges)
|
||||
if challenges is None:
|
||||
return
|
||||
if args.json:
|
||||
with open(args.json, "w") as f:
|
||||
json.dump(challenges, f)
|
||||
print(f"[+] Challenges saved to {args.json}.")
|
||||
print("[!] Beware that the card key is now erased!")
|
||||
return
|
||||
else:
|
||||
if not args.json:
|
||||
print("[-] Error: --json required for offline mode")
|
||||
return
|
||||
with open(args.json, "r") as f:
|
||||
challenges = json.load(f)
|
||||
|
||||
self.crack_key(challenges, args.threads, args.offline)
|
||||
|
||||
def collect_challenges(self, num_challenges):
|
||||
"""Collect challenges from the card and check if it is vulnerable."""
|
||||
# Sanity check: make sure an Ultralight C is detected
|
||||
resp = self.cmd.hf14a_scan()
|
||||
if resp is None or len(resp) == 0:
|
||||
print("[-] Error: No tag detected")
|
||||
return None
|
||||
|
||||
# Check SAK for Ultralight C (SAK should be 0x00)
|
||||
print("[+] Checking for Ultralight C...")
|
||||
|
||||
# Check AUTH0 configuration
|
||||
options = {
|
||||
'activate_rf_field': 0,
|
||||
'wait_response': 1,
|
||||
'append_crc': 1,
|
||||
'auto_select': 1,
|
||||
'keep_rf_field': 0,
|
||||
'check_response_crc': 1,
|
||||
}
|
||||
|
||||
# Read page 40-43 (config pages)
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200,
|
||||
data=struct.pack('!BB', 0x30, 0x28)) # READ page 40
|
||||
|
||||
if len(resp) < 16:
|
||||
print("[-] Error: Card not unlocked. Run relay attack in UNLOCK mode first.")
|
||||
return None
|
||||
|
||||
# Check AUTH0 (should be >= 0x30)
|
||||
minimum_auth_page = resp[8]
|
||||
if minimum_auth_page < 48:
|
||||
print("[-] Error: Card not unlocked. Run relay attack in UNLOCK mode first.")
|
||||
return None
|
||||
|
||||
# Check lock bit
|
||||
is_locked_key = ((resp[1] & 0x80) >> 7) == 1
|
||||
if is_locked_key:
|
||||
print("[-] Error: Card is not vulnerable (key is locked)")
|
||||
return None
|
||||
|
||||
print("[+] All sanity checks \033[1;32mpassed\033[0m. Checking if card is vulnerable.\033[?25l")
|
||||
|
||||
# Collect 100 challenges to check for collision
|
||||
challenges_collected = 0
|
||||
challenges_100 = set()
|
||||
challenges = {}
|
||||
collision = False
|
||||
|
||||
while challenges_collected < num_challenges:
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200,
|
||||
data=struct.pack('!BB', 0x1A, 0x00))
|
||||
if len(resp) >= 9 and resp[0] == 0xAF:
|
||||
hex_challenge = resp[1:9].hex().upper()
|
||||
if hex_challenge in challenges_100:
|
||||
collision = True
|
||||
challenges["challenge_100"] = hex_challenge
|
||||
break
|
||||
else:
|
||||
challenges_100.add(hex_challenge)
|
||||
challenges_collected += 1
|
||||
|
||||
print(f"\r[+] Challenges collected: \033[96m{challenges_collected}\033[0m")
|
||||
if collision:
|
||||
print("[+] Status: \033[1;31mVulnerable\033[0m\033[?25h")
|
||||
else:
|
||||
print("[+] Status: \033[1;32mNot vulnerable\033[0m\033[?25h")
|
||||
return None
|
||||
|
||||
# Card is vulnerable, proceed with attack
|
||||
print("[+] Collecting key-specific challenges...")
|
||||
|
||||
# Overwrite block 47 and collect challenge_75
|
||||
self.write_block(47, b'\x00\x00\x00\x00')
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200,
|
||||
data=struct.pack('!BB', 0x1A, 0x00))
|
||||
if len(resp) >= 9 and resp[0] == 0xAF:
|
||||
challenges["challenge_75"] = resp[1:9].hex().upper()
|
||||
print("[+] 75 collection complete")
|
||||
|
||||
# Overwrite block 46 and collect challenge_50
|
||||
self.write_block(46, b'\x00\x00\x00\x00')
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200,
|
||||
data=struct.pack('!BB', 0x1A, 0x00))
|
||||
if len(resp) >= 9 and resp[0] == 0xAF:
|
||||
challenges["challenge_50"] = resp[1:9].hex().upper()
|
||||
print("[+] 50 collection complete")
|
||||
|
||||
# Overwrite block 45 and collect challenge_25
|
||||
self.write_block(45, b'\x00\x00\x00\x00')
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200,
|
||||
data=struct.pack('!BB', 0x1A, 0x00))
|
||||
if len(resp) >= 9 and resp[0] == 0xAF:
|
||||
challenges["challenge_25"] = resp[1:9].hex().upper()
|
||||
print("[+] 25 collection complete")
|
||||
|
||||
# Overwrite block 44 and collect challenge_0
|
||||
self.write_block(44, b'\x00\x00\x00\x00')
|
||||
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200,
|
||||
data=struct.pack('!BB', 0x1A, 0x00))
|
||||
if len(resp) >= 9 and resp[0] == 0xAF:
|
||||
challenges["challenge_0"] = resp[1:9].hex().upper()
|
||||
print("[+] 0 collection complete")
|
||||
|
||||
return challenges
|
||||
|
||||
def write_block(self, block, data):
|
||||
"""Write a block using hf14a_raw"""
|
||||
options = {
|
||||
'activate_rf_field': 0,
|
||||
'wait_response': 1,
|
||||
'append_crc': 1,
|
||||
'auto_select': 1,
|
||||
'keep_rf_field': 0,
|
||||
'check_response_crc': 1,
|
||||
}
|
||||
# WRITE command (0xA2) + block number + 4 bytes of data
|
||||
cmd_data = struct.pack('!BB4s', 0xA2, block, data)
|
||||
self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=cmd_data)
|
||||
|
||||
def crack_key(self, challenges, num_threads, offline):
|
||||
"""Crack the key using collected challenges"""
|
||||
import signal
|
||||
import traceback
|
||||
|
||||
key_segment_values = {0: "00"*4, 1: "00"*4, 2: "00"*4, 3: "00"*4}
|
||||
key_found = False
|
||||
|
||||
print("[+] Cracking in progress...\033[?25l")
|
||||
|
||||
# Create and start the cracking effect
|
||||
crack_effect = CrackEffect()
|
||||
effect_thread = threading.Thread(target=crack_effect.start)
|
||||
effect_thread.start()
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
print("\n\n\n[!] Interrupt received, stopping...\033[?25h")
|
||||
crack_effect.stop_event.set()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
ciphertexts = {1: challenges["challenge_25"],
|
||||
0: challenges["challenge_50"],
|
||||
3: challenges["challenge_75"],
|
||||
2: challenges["challenge_100"]}
|
||||
|
||||
try:
|
||||
for key_segment_idx in [1, 0, 3, 2]:
|
||||
ciphertext = ciphertexts[key_segment_idx]
|
||||
|
||||
cmd = [
|
||||
str(default_cwd / "mfulc_des_brute"),
|
||||
"-c",
|
||||
challenges['challenge_0'],
|
||||
ciphertext,
|
||||
"".join(key_segment_values.values()),
|
||||
str(key_segment_idx + 1),
|
||||
str(num_threads)
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
|
||||
|
||||
if "Could not detect LFSR" in result.stderr:
|
||||
key_found = False
|
||||
crack_effect.stop_event.set()
|
||||
crack_effect.erase_key()
|
||||
print(f"\n\n\n[-] Error: {result.stderr}\033[?25h")
|
||||
break
|
||||
|
||||
if "No matching key was found" in result.stdout:
|
||||
key_found = False
|
||||
crack_effect.stop_event.set()
|
||||
crack_effect.erase_key()
|
||||
print(f"\n\n\n[-] Error: No matching key found for segment {key_segment_idx + 1}\033[?25h")
|
||||
break
|
||||
|
||||
if "Full key (hex): " not in result.stdout:
|
||||
key_found = False
|
||||
crack_effect.stop_event.set()
|
||||
crack_effect.erase_key()
|
||||
print("\n\n\n[-] Error: Unexpected output from mfulc_des_brute\033[?25h")
|
||||
break
|
||||
|
||||
# Extract the key segment from output
|
||||
full_key_line = [line for line in result.stdout.split('\n') if "Full key (hex):" in line][0]
|
||||
full_key = full_key_line.split("Full key (hex): ")[1].strip()
|
||||
key_segment_values[key_segment_idx] = full_key[(8*key_segment_idx):][:8]
|
||||
key_found = True
|
||||
crack_effect.add_cracked_block(key_segment_idx, key_segment_values[key_segment_idx])
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
key_found = False
|
||||
crack_effect.stop_event.set()
|
||||
crack_effect.erase_key()
|
||||
print(f"\n\n\n[-] Error: Timeout cracking segment {key_segment_idx + 1}\033[?25h")
|
||||
break
|
||||
except Exception as e:
|
||||
key_found = False
|
||||
crack_effect.stop_event.set()
|
||||
crack_effect.erase_key()
|
||||
print(f"\n\n\n[-] Error: {e}\033[?25h")
|
||||
break
|
||||
except Exception as e:
|
||||
crack_effect.stop_event.set()
|
||||
print(f"\n\n\nAn error occurred: {e}\033[?25h")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
effect_thread.join()
|
||||
|
||||
if key_found:
|
||||
result_key = "".join(key_segment_values.values())
|
||||
formatted_key = f"\033[1;34m{result_key}\033[0m"
|
||||
print(f"[+] Found key: {formatted_key}\033[?25h")
|
||||
if offline:
|
||||
print("You can restore found key on the card with appropriate write commands")
|
||||
else:
|
||||
# Restore the key on the card
|
||||
print("[+] Restoring key to card...")
|
||||
key_bytes = bytes.fromhex(result_key)
|
||||
|
||||
# Need to swap endianness in 8-byte chunks before writing
|
||||
# UL-C stores key with swapped endianness
|
||||
key_swapped = bytearray(16)
|
||||
# Swap first 8 bytes
|
||||
for i in range(8):
|
||||
key_swapped[i] = key_bytes[7 - i]
|
||||
# Swap second 8 bytes
|
||||
for i in range(8):
|
||||
key_swapped[8 + i] = key_bytes[15 - i]
|
||||
|
||||
# Write 4 blocks of 4 bytes each
|
||||
for i in range(4):
|
||||
block = 44 + i
|
||||
data = bytes(key_swapped[i*4:(i+1)*4])
|
||||
self.write_block(block, data)
|
||||
print("[+] Key restored on the card")
|
||||
|
||||
|
||||
@hf_mfu.command('econfig')
|
||||
class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit:
|
||||
@@ -2937,7 +3413,7 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
|
||||
|
||||
try:
|
||||
self.cmd.mf0_ntag_set_version_data(args.set_version)
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
print(color_string((CR, "Tag type does not support GET_VERSION command.")))
|
||||
return
|
||||
|
||||
@@ -2951,7 +3427,7 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
|
||||
|
||||
try:
|
||||
self.cmd.mf0_ntag_set_signature_data(args.set_signature)
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
print(color_string((CR, "Tag type does not support READ_SIG command.")))
|
||||
return
|
||||
|
||||
@@ -3013,7 +3489,7 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
|
||||
write_mode = new_write_mode
|
||||
else:
|
||||
print(color_string((CY, "Requested write mode already set")))
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
print(color_string((CR, "Failed to set write mode. Check if device firmware supports this feature.")))
|
||||
|
||||
detection = self.cmd.mf0_ntag_get_detection_enable()
|
||||
@@ -3059,7 +3535,7 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
|
||||
try:
|
||||
write_mode = MifareUltralightWriteMode(self.cmd.mf0_ntag_get_write_mode())
|
||||
print(f'- {"Write mode:":40}{color_string((CY, write_mode))}')
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
# Write mode not supported in current firmware
|
||||
pass
|
||||
|
||||
@@ -3067,20 +3543,20 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
|
||||
try:
|
||||
version = self.cmd.mf0_ntag_get_version_data().hex().upper()
|
||||
print(f'- {"Version:":40}{color_string((CY, version))}')
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
pass
|
||||
|
||||
try:
|
||||
signature = self.cmd.mf0_ntag_get_signature_data().hex().upper()
|
||||
print(f'- {"Signature:":40}{color_string((CY, signature))}')
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
pass
|
||||
|
||||
try:
|
||||
detection = color_string((CG, "enabled")) if self.cmd.mf0_ntag_get_detection_enable() else color_string((CR, "disabled"))
|
||||
print(
|
||||
f'- {"Log (password) mode:":40}{f"{detection}"}')
|
||||
except:
|
||||
except (ValueError, chameleon_com.CMDInvalidException, TimeoutError):
|
||||
pass
|
||||
|
||||
@hf_mfu.command('edetect')
|
||||
@@ -3135,7 +3611,7 @@ class LFEMRead(ReaderRequiredUnit):
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
data = self.cmd.em410x_scan()
|
||||
print(color_string((TagSpecificType(data[0])), (CG, data[1].hex())))
|
||||
print(f"{TagSpecificType(data[0])}: {color_string((CG, data[1].hex()))}")
|
||||
|
||||
|
||||
@lf_em_410x.command('write')
|
||||
@@ -3228,23 +3704,19 @@ class LFHIDProxEconfig(SlotIndexArgsAndGoUnit, LFHIDIdArgsUnit):
|
||||
format = HIDFormat[args.format]
|
||||
id = struct.pack(">BIBIBH", format.value, args.fc, (args.cn >> 32), args.cn & 0xffffffff, args.il, args.oem)
|
||||
self.cmd.hidprox_set_emu_id(id)
|
||||
print(' - Set hidprox tag id success.')
|
||||
fc = args.fc
|
||||
cn = args.cn
|
||||
il = args.il
|
||||
oem = args.oem
|
||||
print(' - SET hidprox tag id success.')
|
||||
else:
|
||||
(format, fc, cn1, cn2, il, oem) = self.cmd.hidprox_get_emu_id()
|
||||
cn = (cn1 << 32) + cn2
|
||||
print(' - Get hidprox tag id success.')
|
||||
print(' - GET hidprox tag id success.')
|
||||
print(f" - HIDProx/{HIDFormat(format)}")
|
||||
if fc > 0:
|
||||
print(f" FC: {color_string((CG, fc))}")
|
||||
if il > 0:
|
||||
print(f" IL: {color_string((CG, il))}")
|
||||
if oem > 0:
|
||||
print(f" OEM: {color_string((CG, oem))}")
|
||||
print(f" CN: {color_string((CG, cn))}")
|
||||
if fc > 0:
|
||||
print(f" FC: {color_string((CG, fc))}")
|
||||
if il > 0:
|
||||
print(f" IL: {color_string((CG, il))}")
|
||||
if oem > 0:
|
||||
print(f" OEM: {color_string((CG, oem))}")
|
||||
print(f" CN: {color_string((CG, cn))}")
|
||||
|
||||
@lf_viking.command('read')
|
||||
class LFVikingRead(ReaderRequiredUnit):
|
||||
@@ -3391,12 +3863,12 @@ class HWSlotList(DeviceRequiredUnit):
|
||||
cn = (cn1 << 32) + cn2
|
||||
print(f" {'Format:':40}{color_string((CY, HIDFormat(format)))}")
|
||||
if fc > 0:
|
||||
print(f" FC: {color_string((CG, fc))}")
|
||||
print(f" {'FC:':40}{color_string((CG, fc))}")
|
||||
if il > 0:
|
||||
print(f" IL: {color_string((CG, il))}")
|
||||
print(f" {'IL:':40}{color_string((CG, il))}")
|
||||
if oem > 0:
|
||||
print(f" OEM: {color_string((CG, oem))}")
|
||||
print(f" CN: {color_string((CG, cn))}")
|
||||
print(f" {'OEM:':40}{color_string((CG, oem))}")
|
||||
print(f" {'CN:':40}{color_string((CG, cn))}")
|
||||
if lf_tag_type == TagSpecificType.Viking:
|
||||
id = self.cmd.viking_get_emu_id()
|
||||
print(f" {'ID:':40}{color_string((CY, id.hex().upper()))}")
|
||||
|
||||
@@ -1304,6 +1304,18 @@ class ChameleonCMD:
|
||||
def set_ble_pairing_enable(self, enabled: bool):
|
||||
data = struct.pack('!B', enabled)
|
||||
return self.device.send_cmd_sync(Command.SET_BLE_PAIRING_ENABLE, data)
|
||||
|
||||
@expect_response(Status.SUCCESS)
|
||||
def mf1_get_field_off_do_reset(self):
|
||||
resp = self.device.send_cmd_sync(Command.MF1_GET_FIELD_OFF_DO_RESET)
|
||||
if resp.status == Status.SUCCESS:
|
||||
resp.parsed = struct.unpack('!B', resp.data)[0] == 1
|
||||
return resp
|
||||
|
||||
@expect_response(Status.SUCCESS)
|
||||
def mf1_set_field_off_do_reset(self, enabled: bool):
|
||||
data = struct.pack('!B', enabled)
|
||||
return self.device.send_cmd_sync(Command.MF1_SET_FIELD_OFF_DO_RESET, data)
|
||||
|
||||
|
||||
def test_fn():
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
import sys
|
||||
import queue
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
import serial
|
||||
import platform
|
||||
from typing import Union
|
||||
from enum import Enum, auto
|
||||
import serial
|
||||
import socket
|
||||
|
||||
from chameleon_utils import CR, CG, CC, CY, color_string
|
||||
from chameleon_enum import Command, Status
|
||||
|
||||
ANDROID = 'android' in platform.release()
|
||||
|
||||
# each thread is waiting for its data for 100 ms before looping again
|
||||
THREAD_BLOCKING_TIMEOUT = 0.1
|
||||
|
||||
# TODO: client settings
|
||||
DEBUG = False
|
||||
|
||||
class TransportType(Enum):
|
||||
NONE = auto()
|
||||
SERIAL = auto()
|
||||
SOCKET = auto()
|
||||
|
||||
class NotOpenException(Exception):
|
||||
"""
|
||||
@@ -57,7 +68,8 @@ class ChameleonCom:
|
||||
"""
|
||||
Create a chameleon device instance
|
||||
"""
|
||||
self.serial_instance: Union[serial.Serial, None] = None
|
||||
self.transport: Union[serial.Serial, socket.socket, None] = None
|
||||
self.transport_type = TransportType.NONE
|
||||
self.send_data_queue = queue.Queue()
|
||||
self.wait_response_map = {}
|
||||
self.event_closing = threading.Event()
|
||||
@@ -68,7 +80,7 @@ class ChameleonCom:
|
||||
|
||||
:return:
|
||||
"""
|
||||
return self.serial_instance is not None and self.serial_instance.is_open
|
||||
return self.transport is not None and (self.transport_type is TransportType.SOCKET or self.transport.is_open)
|
||||
|
||||
def open(self, port) -> "ChameleonCom":
|
||||
"""
|
||||
@@ -82,19 +94,35 @@ class ChameleonCom:
|
||||
error = None
|
||||
try:
|
||||
# open serial port
|
||||
self.serial_instance = serial.Serial(port=port, baudrate=115200)
|
||||
if port.startswith('tcp:'):
|
||||
host, _, port = port[4:].partition(':')
|
||||
if not host or not port:
|
||||
sys.exit(color_string(CR, 'Usage: tcp:127.0.0.1:4321'))
|
||||
self.transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
print('Connecting to', host, int(port))
|
||||
self.transport.connect((host, int(port)))
|
||||
self.transport_type = TransportType.SOCKET
|
||||
else:
|
||||
if ANDROID:
|
||||
sys.exit(color_string(CR, 'COM port is not supported on Android, make a USB-serial to TCP communication bridge'))
|
||||
self.transport = serial.Serial(port=port, baudrate=115200)
|
||||
self.transport_type = TransportType.SERIAL
|
||||
except Exception as e:
|
||||
error = e
|
||||
finally:
|
||||
if error is not None:
|
||||
raise OpenFailException(error)
|
||||
assert self.serial_instance is not None
|
||||
try:
|
||||
self.serial_instance.dtr = True # must make dtr enable
|
||||
except Exception:
|
||||
# not all serial support dtr, e.g. virtual serial over BLE
|
||||
pass
|
||||
self.serial_instance.timeout = THREAD_BLOCKING_TIMEOUT
|
||||
assert self.transport is not None
|
||||
assert self.transport_type is not TransportType.NONE
|
||||
if self.transport_type is TransportType.SERIAL:
|
||||
try:
|
||||
self.transport.dtr = True # must make dtr enable
|
||||
except Exception:
|
||||
# not all serial support dtr, e.g. virtual serial over BLE
|
||||
pass
|
||||
self.transport.timeout = THREAD_BLOCKING_TIMEOUT
|
||||
else: # SOCKET
|
||||
self.transport.settimeout(THREAD_BLOCKING_TIMEOUT)
|
||||
# clear variable
|
||||
self.send_data_queue.queue.clear()
|
||||
self.wait_response_map.clear()
|
||||
@@ -136,12 +164,14 @@ class ChameleonCom:
|
||||
"""
|
||||
self.event_closing.set()
|
||||
try:
|
||||
assert self.serial_instance is not None
|
||||
self.serial_instance.close()
|
||||
assert self.transport is not None
|
||||
if self.transport_type is TransportType.SOCKET:
|
||||
self.transport.shutdown()
|
||||
self.transport.close()
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self.serial_instance = None
|
||||
self.transport = None
|
||||
self.wait_response_map.clear()
|
||||
self.send_data_queue.queue.clear()
|
||||
|
||||
@@ -159,16 +189,29 @@ class ChameleonCom:
|
||||
|
||||
while self.isOpen():
|
||||
# receive
|
||||
try:
|
||||
assert self.serial_instance is not None
|
||||
data_bytes = self.serial_instance.read()
|
||||
except Exception as e:
|
||||
if not self.event_closing.is_set():
|
||||
print(f"Serial Error {e}, thread for receiver exit.")
|
||||
self.close()
|
||||
break
|
||||
if len(data_bytes) > 0:
|
||||
assert self.transport_type is not TransportType.NONE
|
||||
if self.transport_type is TransportType.SERIAL:
|
||||
try:
|
||||
assert self.transport is not None
|
||||
data_bytes = bytearray(self.transport.read())
|
||||
except Exception as e:
|
||||
if not self.event_closing.is_set():
|
||||
print(f"Serial Error {e}, thread for receiver exit.")
|
||||
self.close()
|
||||
break
|
||||
else: # SOCKET
|
||||
try:
|
||||
data_bytes = bytearray(self.transport.recv(1024))
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError:
|
||||
print(color_string(CR, 'socket closed'))
|
||||
self.transport = None
|
||||
break
|
||||
|
||||
while len(data_bytes) > 0:
|
||||
data_byte = data_bytes[0]
|
||||
data_bytes = data_bytes[1:]
|
||||
data_buffer.append(data_byte)
|
||||
if data_position < struct.calcsize('!BB'): # start of frame + lrc1
|
||||
if data_position == 0:
|
||||
@@ -267,14 +310,25 @@ class ChameleonCom:
|
||||
self.wait_response_map[task_cmd]['start_time'] = start_time
|
||||
self.wait_response_map[task_cmd]['end_time'] = start_time + task_timeout
|
||||
self.wait_response_map[task_cmd]['is_timeout'] = False
|
||||
try:
|
||||
assert self.serial_instance is not None
|
||||
# send to device
|
||||
self.serial_instance.write(task['frame'])
|
||||
except Exception as e:
|
||||
print(f"Serial Error {e}, thread for transfer exit.")
|
||||
self.close()
|
||||
break
|
||||
assert self.transport_type is not TransportType.NONE
|
||||
if self.transport_type == TransportType.SERIAL:
|
||||
try:
|
||||
assert self.transport is not None
|
||||
# send to device
|
||||
self.transport.write(task['frame'])
|
||||
except Exception as e:
|
||||
print(f"Serial Error {e}, thread for transfer exit.")
|
||||
self.close()
|
||||
break
|
||||
else: # SOCKET
|
||||
try:
|
||||
assert self.transport is not None
|
||||
self.transport.sendall(task['frame'])
|
||||
except OSError as e:
|
||||
self.transport = None
|
||||
print(f'Socket error {e}, thread for transfer exit.')
|
||||
self.close()
|
||||
break
|
||||
# update queue status
|
||||
self.send_data_queue.task_done()
|
||||
# disconnect if DFU command has been sent
|
||||
|
||||
@@ -124,6 +124,9 @@ class Command(enum.IntEnum):
|
||||
# FIXME: not implemented
|
||||
MF0_NTAG_GET_EMULATOR_CONFIG = 4037
|
||||
|
||||
MF1_SET_FIELD_OFF_DO_RESET = 4038
|
||||
MF1_GET_FIELD_OFF_DO_RESET = 4039
|
||||
|
||||
EM410X_SET_EMU_ID = 5000
|
||||
EM410X_GET_EMU_ID = 5001
|
||||
HIDPROX_SET_EMU_ID = 5002
|
||||
@@ -581,6 +584,7 @@ class HIDFormat(enum.IntEnum):
|
||||
C1K35S = 21
|
||||
C15001 = 22
|
||||
S12906 = 23
|
||||
ACTPHID = 42
|
||||
SIE36 = 24
|
||||
H10320 = 25
|
||||
H10302 = 26
|
||||
@@ -614,6 +618,7 @@ class HIDFormat(enum.IntEnum):
|
||||
HIDFormat.C1K35S: "HID Corporate 1000 35-bit Std",
|
||||
HIDFormat.C15001: "HID KeyScan 36-bit",
|
||||
HIDFormat.S12906: "HID Simplex 36-bit",
|
||||
HIDFormat.ACTPHID: "HID ACTProx 36-bit",
|
||||
HIDFormat.SIE36: "HID 36-bit Siemens",
|
||||
HIDFormat.H10320: "HID H10320 37-bit BCD",
|
||||
HIDFormat.H10302: "HID H10302 37-bit huge ID",
|
||||
|
||||
+25
-11
@@ -77,7 +77,7 @@ endif()
|
||||
|
||||
|
||||
# --- Platform specific settings ---
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
MESSAGE(STATUS "Run on linux.")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
@@ -126,7 +126,7 @@ endif()
|
||||
add_executable(nested ${COMMON_FILES} ${NESTED_UTIL} nested.c)
|
||||
target_include_directories(nested PRIVATE ${SRC_DIR})
|
||||
target_link_libraries(nested PRIVATE ${LIBTHREAD}) # Link common thread lib
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(nested PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -138,7 +138,7 @@ endif()
|
||||
add_executable(staticnested ${COMMON_FILES} ${NESTED_UTIL} staticnested.c)
|
||||
target_include_directories(staticnested PRIVATE ${SRC_DIR})
|
||||
target_link_libraries(staticnested PRIVATE ${LIBTHREAD}) # Link common thread lib
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(staticnested PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -150,7 +150,7 @@ endif()
|
||||
add_executable(darkside ${COMMON_FILES} ${MFKEY_UTIL} darkside.c)
|
||||
target_include_directories(darkside PRIVATE ${SRC_DIR})
|
||||
# darkside doesn't seem to need pthreads based on original file
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(darkside PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -161,7 +161,7 @@ endif()
|
||||
add_executable(mfkey32 ${COMMON_FILES} mfkey32.c)
|
||||
target_include_directories(mfkey32 PRIVATE ${SRC_DIR})
|
||||
# mfkey32 doesn't seem to need pthreads based on original file
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(mfkey32 PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -172,7 +172,7 @@ endif()
|
||||
add_executable(mfkey32v2 ${COMMON_FILES} mfkey32v2.c)
|
||||
target_include_directories(mfkey32v2 PRIVATE ${SRC_DIR})
|
||||
# mfkey32v2 doesn't seem to need pthreads based on original file
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(mfkey32v2 PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -183,7 +183,7 @@ endif()
|
||||
add_executable(mfkey64 ${COMMON_FILES} mfkey64.c)
|
||||
target_include_directories(mfkey64 PRIVATE ${SRC_DIR})
|
||||
# mfkey64 doesn't seem to need pthreads based on original file
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(mfkey64 PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -192,7 +192,7 @@ endif()
|
||||
|
||||
add_executable(staticnested_1nt ${COMMON_FILES} staticnested_1nt.c)
|
||||
target_include_directories(staticnested_1nt PRIVATE ${SRC_DIR})
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(staticnested_1nt PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -201,7 +201,7 @@ endif()
|
||||
|
||||
add_executable(staticnested_2x1nt_rf08s ${COMMON_FILES} staticnested_2x1nt_rf08s.c)
|
||||
target_include_directories(staticnested_2x1nt_rf08s PRIVATE ${SRC_DIR})
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(staticnested_2x1nt_rf08s PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
@@ -210,13 +210,27 @@ endif()
|
||||
|
||||
add_executable(staticnested_2x1nt_rf08s_1key ${COMMON_FILES} staticnested_2x1nt_rf08s_1key.c)
|
||||
target_include_directories(staticnested_2x1nt_rf08s_1key PRIVATE ${SRC_DIR})
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(staticnested_2x1nt_rf08s_1key PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
target_compile_definitions(staticnested_2x1nt_rf08s_1key PRIVATE HAVE_STRUCT_TIMESPEC)
|
||||
endif()
|
||||
|
||||
# --- mfulc_des_brute Executable ---
|
||||
add_executable(mfulc_des_brute mfulc_des_brute.c)
|
||||
target_include_directories(mfulc_des_brute PRIVATE ${SRC_DIR})
|
||||
target_link_libraries(mfulc_des_brute PRIVATE ${LIBTHREAD} OpenSSL::Crypto)
|
||||
target_compile_options(mfulc_des_brute PRIVATE -Wno-deprecated-declarations)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(mfulc_des_brute PRIVATE _GNU_SOURCE)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
target_compile_definitions(mfulc_des_brute PRIVATE HAVE_STRUCT_TIMESPEC)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
|
||||
|
||||
# --- hardnested Executable ---
|
||||
add_executable(hardnested ${COMMON_FILES} ${HARDNESTED_SOURCES})
|
||||
@@ -230,7 +244,7 @@ target_include_directories(hardnested PRIVATE
|
||||
)
|
||||
target_compile_options(hardnested PRIVATE -Wall)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android" OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
target_compile_definitions(hardnested PRIVATE _GNU_SOURCE)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
// noproto & doegox, 2025
|
||||
// cf "BREAKMEIFYOUCAN!: Exploiting Keyspace Reduction and Relay Attacks in 3DES and AES-protected NFC Technologies"
|
||||
// for more info
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <openssl/des.h>
|
||||
|
||||
#define BLOCK_SIZE 8 // DES (and 3DES) block size in bytes
|
||||
#define KEY_SIZE 16 // Full 2TDEA key size (K1 || K2)
|
||||
#define BENCHMARK_FULL_KEYSPACE 0
|
||||
|
||||
// Global flag to signal that a key has been found.
|
||||
volatile int key_found = 0;
|
||||
|
||||
typedef enum {
|
||||
LFSR_UNDEF = 0,
|
||||
LFSR_ULCG = 1,
|
||||
LFSR_USCUIDUL = 2
|
||||
} lfsr_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t start; // starting candidate (inclusive)
|
||||
uint32_t end; // ending candidate (exclusive)
|
||||
int key_mode; // 0 to 3 (i.e. brute force segment 1-4 as 0-indexed)
|
||||
unsigned char init_ciphertext[BLOCK_SIZE];
|
||||
unsigned char prev_ciphertext[BLOCK_SIZE]; // "IV" of ciphertext for CBC mode in reader mode
|
||||
unsigned char ciphertext[BLOCK_SIZE];
|
||||
unsigned char base_key[KEY_SIZE]; // the 3DES base key provided by the user
|
||||
int thread_id;
|
||||
lfsr_t lfsr_type;
|
||||
bool is_reader_mode; // true for -r mode, false for -c mode
|
||||
} thread_args_t;
|
||||
|
||||
// Converts a hex string to bytes. The hex string must be exactly 2*len hex digits long.
|
||||
static int hex_to_bytes(const char *hex, unsigned char *buf, size_t len) {
|
||||
if (strlen(hex) != len * 2)
|
||||
return 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned int byte;
|
||||
if (sscanf(hex + 2 * i, "%2x", &byte) != 1)
|
||||
return 0;
|
||||
buf[i] = (unsigned char) byte;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Print a byte array as hex.
|
||||
static void print_hex(const unsigned char *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
printf("%02X", buf[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool valid_lfsr_ulcg(uint64_t x64) {
|
||||
x64 = __builtin_bswap64(x64);
|
||||
uint16_t x16 = x64 >> 48;
|
||||
x16 = x16 << 15 | ((x16 >> 1) ^ ((x16 >> 3 ^ x16 >> 4 ^ x16 >> 6) & 1));
|
||||
if (x16 != ((x64 >> 32) & 0xFFFF)) return false;
|
||||
x16 = x16 << 15 | ((x16 >> 1) ^ ((x16 >> 3 ^ x16 >> 4 ^ x16 >> 6) & 1));
|
||||
if (x16 != ((x64 >> 16) & 0xFFFF)) return false;
|
||||
x16 = x16 << 15 | ((x16 >> 1) ^ ((x16 >> 3 ^ x16 >> 4 ^ x16 >> 6) & 1));
|
||||
if (x16 != (x64 & 0xFFFF)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_lfsr_uscuidul(uint64_t x64) {
|
||||
x64 = __builtin_bswap64(x64);
|
||||
uint16_t x16 = x64 & 0xFFFF;
|
||||
for (int i = 0; i < 16; i++) x16 = x16 >> 1 | (x16 ^ x16 >> 2 ^ x16 >> 3 ^ x16 >> 5) << 15;
|
||||
if (x16 != ((x64 >> 16) & 0xFFFF)) return false;
|
||||
for (int i = 0; i < 16; i++) x16 = x16 >> 1 | (x16 ^ x16 >> 2 ^ x16 >> 3 ^ x16 >> 5) << 15;
|
||||
if (x16 != ((x64 >> 32) & 0xFFFF)) return false;
|
||||
for (int i = 0; i < 16; i++) x16 = x16 >> 1 | (x16 ^ x16 >> 2 ^ x16 >> 3 ^ x16 >> 5) << 15;
|
||||
if (x16 != ((x64 >> 48) & 0xFFFF)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool valid_lfsr(uint64_t x64, lfsr_t lfsr_type) {
|
||||
switch (lfsr_type) {
|
||||
case LFSR_ULCG:
|
||||
return valid_lfsr_ulcg(x64);
|
||||
case LFSR_USCUIDUL:
|
||||
return valid_lfsr_uscuidul(x64);
|
||||
case LFSR_UNDEF:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static lfsr_t detect_lfsr_type(unsigned char *init_ciphertext) {
|
||||
DES_cblock fixed_key = {0};
|
||||
DES_key_schedule fixed_schedule;
|
||||
DES_set_key_unchecked(&fixed_key, &fixed_schedule);
|
||||
uint64_t out;
|
||||
DES_ecb_encrypt((DES_cblock *)init_ciphertext, (DES_cblock *)&out, &fixed_schedule, DES_DECRYPT);
|
||||
if (valid_lfsr_ulcg(out)) {
|
||||
return LFSR_ULCG;
|
||||
} else if (valid_lfsr_uscuidul(out)) {
|
||||
return LFSR_USCUIDUL;
|
||||
}
|
||||
return LFSR_UNDEF;
|
||||
}
|
||||
|
||||
// Worker thread function using low-level DES functions.
|
||||
static void *worker(void *arg) {
|
||||
thread_args_t *targs = (thread_args_t *) arg;
|
||||
uint32_t start = targs->start;
|
||||
uint32_t end = targs->end;
|
||||
int key_mode = targs->key_mode;
|
||||
|
||||
// Determine which half is being brute forced.
|
||||
// For key_mode 0 or 1 the candidate is in K1; for key_mode 2 or 3 the candidate is in K2.
|
||||
int candidate_in_K1 = (key_mode < 2) ? 1 : 0;
|
||||
|
||||
// Determine the 4-byte offset within the variable half:
|
||||
// For K1, key_mode 0 means segment1 (offset 0), key_mode 1 means segment2 (offset 4).
|
||||
// For K2, key_mode 2 means segment3 (offset 0), key_mode 3 means segment4 (offset 4).
|
||||
int var_offset = candidate_in_K1 ? ((key_mode % 2) * 4) : (((key_mode - 2) % 2) * 4);
|
||||
|
||||
// Precompute the fixed half's DES key schedule.
|
||||
DES_cblock fixed_key;
|
||||
if (candidate_in_K1) {
|
||||
// Fixed half is K2: bytes 8..15 of base_key.
|
||||
memcpy(fixed_key, targs->base_key + 8, 8);
|
||||
} else {
|
||||
// Candidate in K2; fixed half is K1: bytes 0..7 of base_key.
|
||||
memcpy(fixed_key, targs->base_key, 8);
|
||||
}
|
||||
DES_key_schedule fixed_schedule;
|
||||
DES_set_key_unchecked(&fixed_key, &fixed_schedule);
|
||||
uint64_t out;
|
||||
uint64_t init_out;
|
||||
|
||||
// For the candidate half, start with the corresponding half from the base key.
|
||||
unsigned char base_half[8];
|
||||
if (candidate_in_K1)
|
||||
memcpy(base_half, targs->base_key, 8);
|
||||
else
|
||||
memcpy(base_half, targs->base_key + 8, 8);
|
||||
|
||||
// Loop over the candidate key indices in this thread's range.
|
||||
for (uint32_t idx = start; idx < end; idx++) {
|
||||
if (key_found && !BENCHMARK_FULL_KEYSPACE)
|
||||
break; // Some other thread already found the key.
|
||||
// Convert the candidate index (28 bits) into 4 bytes.
|
||||
// Each candidate byte is constructed from a 7-bit chunk shifted left by 1 so that the LSB is zero.
|
||||
uint8_t b0 = ((idx) & 0x7F) << 1;
|
||||
uint8_t b1 = ((idx >> 7) & 0x7F) << 1;
|
||||
uint8_t b2 = ((idx >> 14) & 0x7F) << 1;
|
||||
uint8_t b3 = ((idx >> 21) & 0x7F) << 1;
|
||||
// Build the candidate half key by starting with the fixed base half and substituting candidate bytes.
|
||||
DES_cblock candidate_half;
|
||||
memcpy(candidate_half, base_half, 8);
|
||||
candidate_half[var_offset ] = b0;
|
||||
candidate_half[var_offset + 1] = b1;
|
||||
candidate_half[var_offset + 2] = b2;
|
||||
candidate_half[var_offset + 3] = b3;
|
||||
|
||||
// Compute the candidate half's DES key schedule.
|
||||
DES_key_schedule candidate_schedule;
|
||||
DES_set_key_unchecked(&candidate_half, &candidate_schedule);
|
||||
|
||||
// Perform 2-key triple DES decryption on the ciphertext.
|
||||
// If candidate is in K1: decryption = DES_ecb3_encrypt(cipher, out, candidate, fixed, candidate, DES_DECRYPT)
|
||||
// If candidate is in K2: decryption = DES_ecb3_encrypt(cipher, out, fixed, candidate, fixed, DES_DECRYPT)
|
||||
if (candidate_in_K1) {
|
||||
DES_ecb3_encrypt((DES_cblock *)targs->ciphertext, (DES_cblock *)&out,
|
||||
&candidate_schedule, &fixed_schedule, &candidate_schedule, DES_DECRYPT);
|
||||
} else {
|
||||
DES_ecb3_encrypt((DES_cblock *)targs->ciphertext, (DES_cblock *)&out,
|
||||
&fixed_schedule, &candidate_schedule, &fixed_schedule, DES_DECRYPT);
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
if (targs->is_reader_mode) {
|
||||
// In reader mode, also decrypt init_ciphertext and check for rotation relationship
|
||||
// Apply XOR block to the second decrypted block (for CBC mode)
|
||||
if (candidate_in_K1) {
|
||||
DES_ecb3_encrypt((DES_cblock *)targs->init_ciphertext, (DES_cblock *)&init_out,
|
||||
&candidate_schedule, &fixed_schedule, &candidate_schedule, DES_DECRYPT);
|
||||
} else {
|
||||
DES_ecb3_encrypt((DES_cblock *)targs->init_ciphertext, (DES_cblock *)&init_out,
|
||||
&fixed_schedule, &candidate_schedule, &fixed_schedule, DES_DECRYPT);
|
||||
}
|
||||
// Apply XOR block to the second decrypted block (for CBC mode)
|
||||
out ^= *(uint64_t *)targs->prev_ciphertext;
|
||||
|
||||
// Check if out is 8-bit (1-byte) left rotated version of init_out
|
||||
// Need to convert to big-endian for byte rotation, then back to little-endian
|
||||
uint64_t init_be = __builtin_bswap64(init_out);
|
||||
uint64_t rotated_be = (init_be << 8) | (init_be >> 56);
|
||||
uint64_t rotated = __builtin_bswap64(rotated_be);
|
||||
match = (out == rotated);
|
||||
} else {
|
||||
// In counterfeit mode, check the resulting plaintext against LFSR
|
||||
match = valid_lfsr(out, targs->lfsr_type);
|
||||
}
|
||||
|
||||
if (match) {
|
||||
key_found = 1; // signal to other threads
|
||||
|
||||
// Build the full 16-byte key: start with the base key and substitute the candidate 4 bytes.
|
||||
unsigned char full_key[KEY_SIZE];
|
||||
memcpy(full_key, targs->base_key, KEY_SIZE);
|
||||
int seg_offset = key_mode * 4; // key_mode: 0->bytes0, 1->bytes4, 2->bytes8, 3->bytes12.
|
||||
full_key[seg_offset] = b0;
|
||||
full_key[seg_offset + 1] = b1;
|
||||
full_key[seg_offset + 2] = b2;
|
||||
full_key[seg_offset + 3] = b3;
|
||||
printf("Thread %d: Found key index: %u\n", targs->thread_id, idx);
|
||||
printf("Full key (hex): ");
|
||||
print_hex(full_key, KEY_SIZE);
|
||||
if (!BENCHMARK_FULL_KEYSPACE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void print_help_and_exit(const char *cmd_name) {
|
||||
fprintf(stderr,
|
||||
"Usage:\n"
|
||||
" * Counterfeit key recovery:\n"
|
||||
" %s -c <null key ERndB (8 hex digits)> <target key ERndB (8 hex digits)> <3DES base key hex (32 hex digits)> <key segment (1-4)> <num threads>\n"
|
||||
" * Reader nonce key recovery:\n"
|
||||
" %s -r <ERndB (8 hex digits)> <ERndARndB' (16 hex digits)> <3DES base key hex (32 hex digits)> <key segment (1-4)> <num threads>\n",
|
||||
cmd_name,
|
||||
cmd_name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Check for -c or -r flag first to determine expected argument count
|
||||
if (argc < 2) {
|
||||
print_help_and_exit(argv[0]);
|
||||
}
|
||||
bool is_reader_mode = false;
|
||||
if (strcmp(argv[1], "-c") == 0) {
|
||||
is_reader_mode = false;
|
||||
if (argc != 7) {
|
||||
fprintf(stderr, "Error: -c mode requires exactly 6 arguments\n");
|
||||
print_help_and_exit(argv[0]);
|
||||
}
|
||||
} else if (strcmp(argv[1], "-r") == 0) {
|
||||
is_reader_mode = true;
|
||||
if (argc != 7) {
|
||||
fprintf(stderr, "Error: -r mode requires exactly 6 arguments\n");
|
||||
print_help_and_exit(argv[0]);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Error: first argument must be -c or -r\n");
|
||||
print_help_and_exit(argv[0]);
|
||||
}
|
||||
|
||||
unsigned char init_ciphertext[BLOCK_SIZE];
|
||||
unsigned char tmp_blocks[2 * BLOCK_SIZE];
|
||||
unsigned char ciphertext[BLOCK_SIZE];
|
||||
unsigned char base_key[KEY_SIZE];
|
||||
|
||||
if (is_reader_mode) {
|
||||
// In reader mode, the first ciphertext is ERndB and the second is ERndA|ERndB'
|
||||
if (!hex_to_bytes(argv[2], init_ciphertext, BLOCK_SIZE)) {
|
||||
fprintf(stderr, "Error: invalid ERndB hex string.\n");
|
||||
return 1;
|
||||
}
|
||||
if (!hex_to_bytes(argv[3], tmp_blocks, 2 * BLOCK_SIZE)) {
|
||||
fprintf(stderr, "Error: invalid ERndARndB' hex string.\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// In counterfeit mode, both ciphertexts are just ciphertext blocks
|
||||
if (!hex_to_bytes(argv[2], init_ciphertext, BLOCK_SIZE)) {
|
||||
fprintf(stderr, "Error: invalid null key ERndB hex string.\n");
|
||||
return 1;
|
||||
}
|
||||
if (!hex_to_bytes(argv[3], ciphertext, BLOCK_SIZE)) {
|
||||
fprintf(stderr, "Error: invalid target key ERndB hex string.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!hex_to_bytes(argv[4], base_key, KEY_SIZE)) {
|
||||
fprintf(stderr, "Error: invalid 3DES base key hex string.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int seg = atoi(argv[5]);
|
||||
if (seg < 1 || seg > 4) {
|
||||
fprintf(stderr, "Error: key segment must be between 1 and 4.\n");
|
||||
return 1;
|
||||
}
|
||||
int num_threads = atoi(argv[6]);
|
||||
if (num_threads < 1) {
|
||||
fprintf(stderr, "Error: number of threads must be at least 1.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
lfsr_t lfsr_type = LFSR_UNDEF;
|
||||
if (!is_reader_mode) {
|
||||
// Only detect LFSR type in counterfeit mode
|
||||
lfsr_type = detect_lfsr_type(init_ciphertext);
|
||||
switch (lfsr_type) {
|
||||
case LFSR_ULCG:
|
||||
printf("LFSR detection: ULCG\n");
|
||||
break;
|
||||
case LFSR_USCUIDUL:
|
||||
printf("LFSR detection: ULC_USCUIDUL\n");
|
||||
break;
|
||||
case LFSR_UNDEF:
|
||||
default:
|
||||
fprintf(stderr, "LFSR detection: Could not detect LFSR!!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// key_mode is zero-indexed (0,1,2,3)
|
||||
int key_mode = seg - 1;
|
||||
|
||||
// Total candidate space: 2^28 keys.
|
||||
uint32_t total = (1UL << 28);
|
||||
uint32_t chunk = total / num_threads;
|
||||
uint32_t remainder = total % num_threads;
|
||||
|
||||
pthread_t *threads = malloc(num_threads * sizeof(pthread_t));
|
||||
thread_args_t *targs = malloc(num_threads * sizeof(thread_args_t));
|
||||
if (!threads || !targs) {
|
||||
fprintf(stderr, "Allocation error.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Divide the candidate space as equally as possible among threads.
|
||||
uint32_t current = 0;
|
||||
for (int i = 0; i < num_threads; i++) {
|
||||
targs[i].start = current;
|
||||
targs[i].end = current + chunk;
|
||||
if (i == num_threads - 1)
|
||||
targs[i].end += remainder;
|
||||
targs[i].key_mode = key_mode;
|
||||
targs[i].lfsr_type = lfsr_type;
|
||||
targs[i].is_reader_mode = is_reader_mode;
|
||||
memcpy(targs[i].init_ciphertext, init_ciphertext, BLOCK_SIZE);
|
||||
if (is_reader_mode) {
|
||||
memcpy(targs[i].prev_ciphertext, tmp_blocks, BLOCK_SIZE);
|
||||
memcpy(targs[i].ciphertext, tmp_blocks + BLOCK_SIZE, BLOCK_SIZE);
|
||||
} else {
|
||||
memcpy(targs[i].ciphertext, ciphertext, BLOCK_SIZE);
|
||||
}
|
||||
memcpy(targs[i].base_key, base_key, KEY_SIZE);
|
||||
targs[i].thread_id = i;
|
||||
current = targs[i].end;
|
||||
pthread_create(&threads[i], NULL, worker, &targs[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_threads; i++)
|
||||
pthread_join(threads[i], NULL);
|
||||
|
||||
if (!key_found)
|
||||
printf("No matching key was found.\n");
|
||||
|
||||
free(threads);
|
||||
free(targs);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user