From 879b8e34592623dc33ec6666702a535ee11cfc36 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Thu, 21 Sep 2023 18:30:05 +0800 Subject: [PATCH] Implemented hf 14a raw --- firmware/application/src/app_cmd.c | 99 ++++++++++++++++++++++----- firmware/application/src/data_cmd.h | 2 + software/script/chameleon_cli_unit.py | 59 ++++++++++++++++ software/script/chameleon_cmd.py | 99 ++++++++++++++++++++++++++- 4 files changed, 239 insertions(+), 20 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 93518efc..3df5071b 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -256,6 +256,30 @@ static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t sta return data_frame_make(cmd, HF_TAG_OK, sizeof(type), &type); } +// We have a reusable payload structure. +typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; +} PACKED nested_common_payload_t; + +static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + mf1_static_nested_core_t sncs; + if (length != sizeof(nested_common_payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + nested_common_payload_t *payload = (nested_common_payload_t *)data; + status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); +} + static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (length != 4) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); @@ -300,15 +324,6 @@ static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); } -// We have a reusable payload structure. -typedef struct { - uint8_t type_known; - uint8_t block_known; - uint8_t key_known[6]; - uint8_t type_target; - uint8_t block_target; -} PACKED nested_common_payload_t; - static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { mf1_nested_core_t ncs[SETS_NR]; if (length != sizeof(nested_common_payload_t)) { @@ -383,19 +398,64 @@ static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t return data_frame_make(cmd, status, 0, NULL); } -static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - mf1_static_nested_core_t sncs; - if (length != sizeof(nested_common_payload_t)) { +static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // Response Buffer + uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 }; + uint16_t resp_length = 0; + + typedef struct { + struct { // MSB -> LSB + uint8_t reserved : 1; + + uint8_t check_response_crc : 1; + uint8_t keep_rf_field : 1; + uint8_t auto_select : 1; + uint8_t bit_frame : 1; + uint8_t append_crc : 1; + uint8_t wait_response : 1; + uint8_t open_rf_field : 1; + } options; + + // U16NTOHS + uint16_t resp_timeout; + uint16_t data_length; + + uint8_t data_buffer[0]; // We can have a lot of data or no data. struct just to compute offsets with min options. + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if (length < sizeof(payload_t)) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - nested_common_payload_t *payload = (nested_common_payload_t *)data; - status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); - if (status != HF_TAG_OK) { - return data_frame_make(cmd, status, 0, NULL); - } - // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly - return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); + NRF_LOG_INFO("open_rf_field = %d", payload->options.open_rf_field); + NRF_LOG_INFO("wait_response = %d", payload->options.wait_response); + NRF_LOG_INFO("append_crc = %d", payload->options.append_crc); + NRF_LOG_INFO("bit_frame = %d", payload->options.bit_frame); + NRF_LOG_INFO("auto_select = %d", payload->options.auto_select); + NRF_LOG_INFO("keep_rf_field = %d", payload->options.keep_rf_field); + NRF_LOG_INFO("check_response_crc = %d", payload->options.check_response_crc); + NRF_LOG_INFO("reserved = %d", payload->options.reserved); + + status = pcd_14a_reader_raw_cmd( + payload->options.open_rf_field, + payload->options.wait_response, + payload->options.append_crc, + payload->options.bit_frame, + payload->options.auto_select, + payload->options.keep_rf_field, + payload->options.check_response_crc, + + U16NTOHS(payload->resp_timeout), + + U16NTOHS(payload->data_length), + payload->data_buffer, + + resp, + &resp_length, + U8ARR_BIT_LEN(resp) + ); + + return data_frame_make(cmd, status, resp_length, resp); } static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { @@ -937,6 +997,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, + { DATA_CMD_HF14A_RAW, before_reader_run, cmd_processor_hf14a_raw, NULL }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index ce8efc8a..267e09c4 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -64,6 +64,8 @@ #define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) +#define DATA_CMD_HF14A_RAW (2010) + // // ****************************************************************** diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 9d08c39e..df345c61 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1573,3 +1573,62 @@ def on_exec(self, args: argparse.Namespace): else: print(f" Status: {response.status:#02x}") print(f" Data (HEX): {response.data.hex()}") + + +@hf_14a.command('raw', 'Send raw command') +class HF14ARaw(ReaderRequiredUnit): + + def bool_to_bit(self, value): + return 1 if value else 0 + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + parser.add_argument('-r', '--response', help="do not read response", action='store_true', default=False,) + parser.add_argument('-c', '--crc', help="calculate and append CRC", action='store_true', default=False,) + parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", action='store_true', default=False,) + parser.add_argument('-k', '--keep-rf', help="keep signal field ON after receive", action='store_true', default=False,) + parser.add_argument('-o', '--open-rf', help="active signal field ON", action='store_true', default=False,) + parser.add_argument('-s', '--select-tag', help="Select the tag before executing the command", action='store_true', default=False,) + parser.add_argument('-b', '--bits', type=int, help="number of bits to send. Useful for send partial byte") + parser.add_argument('-t', '--timeout', type=int, help="timeout in ms", default=100) + parser.add_argument('-d', '--data', type=str, help="Data to be sent") + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'open_rf_field': self.bool_to_bit(args.open_rf), + 'wait_response': self.bool_to_bit(args.response == False), + 'append_crc': self.bool_to_bit(args.crc), + 'bit_frame': self.bool_to_bit(args.bits is not None), + 'auto_select': self.bool_to_bit(args.select_tag), + 'keep_rf_field': self.bool_to_bit(args.keep_rf), + 'check_response_crc': self.bool_to_bit(args.crc_clear), + } + data: str = args.data + if data is not None: + data = data.replace(' ', '') + if re.match(r'^[0-9a-fA-F]+$', data): + if len(data) % 2 != 0: + print(f" [!] {CR}The length of the data must be an integer multiple of 2.{C0}") + return + else: + data_bytes = bytes.fromhex(data) + else: + print(f" [!] {CR}The data must be a HEX string{C0}") + return + else: + data_bytes = [] + # Exec 14a raw cmd. + resp = self.cmd.hf14a_raw(options, args.timeout, data_bytes, args.bits) + if resp.status == chameleon_status.Device.HF_TAG_OK: + if len(resp.data) > 0: + print( + # print head + " - " + + # print data + ' '.join([ hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data ]) + ) + else: + print(F" [*] {CY}No data response{C0}") + else: + print(f" [!] {CR}{chameleon_status.message[resp.status]}{C0} ") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index f12695c0..c3e12ad2 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -1,5 +1,6 @@ import enum import struct +import ctypes import chameleon_com import chameleon_status @@ -68,6 +69,7 @@ DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 +DATA_CMD_HF14A_RAW = 2010 DATA_CMD_EM410X_SCAN = 3000 DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 @@ -550,6 +552,52 @@ def mf1_write_one_block(self, block, type_value, key, block_data): resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp + def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bit_owned_by_the_last_byte=None): + """ + Send raw cmd to 14a tag + :param options: + :param resp_timeout_ms: + :param data: + :param bit_owned_by_the_last_byte: + :return: + """ + + class CStruct(ctypes.BigEndianStructure): + _fields_ = [ + ("open_rf_field", ctypes.c_uint8, 1), + ("wait_response", ctypes.c_uint8, 1), + ("append_crc", ctypes.c_uint8, 1), + ("bit_frame", ctypes.c_uint8, 1), + ("auto_select", ctypes.c_uint8, 1), + ("keep_rf_field", ctypes.c_uint8, 1), + ("check_response_crc", ctypes.c_uint8, 1), + ("reserved", ctypes.c_uint8, 1), + ] + + cs = CStruct() + cs.open_rf_field = options['open_rf_field'] + cs.wait_response = options['wait_response'] + cs.append_crc = options['append_crc'] + cs.bit_frame = options['bit_frame'] + cs.auto_select = options['auto_select'] + cs.keep_rf_field = options['keep_rf_field'] + cs.check_response_crc = options['check_response_crc'] + + if options['bit_frame'] == 1: + bits_or_bytes = len(data) * 8 # bits = bytes * 8(bit) + if bit_owned_by_the_last_byte is not None and bit_owned_by_the_last_byte != 8: + bits_or_bytes = bits_or_bytes - (8 - bit_owned_by_the_last_byte) + else: + bits_or_bytes = len(data) # bytes length + + if len(data) > 0: + data = struct.pack(f'!BHH{len(data)}s', bytes(cs)[0], resp_timeout_ms, bits_or_bytes, bytearray(data)) + else: + data = struct.pack(f'!BHH', bytes(cs)[0], resp_timeout_ms, 0) + + return self.device.send_cmd_sync(DATA_CMD_HF14A_RAW, data, timeout=(resp_timeout_ms / 1000) + 1) + + @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ @@ -1098,7 +1146,7 @@ def set_ble_pairing_enable(self, enabled: bool): return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, data) -if __name__ == '__main__': +def test_fn(): # connect to chameleon dev = chameleon_com.ChameleonCom() dev.open("com19") @@ -1108,9 +1156,58 @@ def set_ble_pairing_enable(self, enabled: bool): chip = cml.get_device_chip_id() print(f"Device chip id: {chip}") + # change to reader mode + cml.set_device_reader_mode() + + options = { + 'open_rf_field': 1, + 'wait_response': 1, + 'append_crc': 0, + 'bit_frame': 1, + 'auto_select': 0, + 'keep_rf_field': 1, + 'check_response_crc': 0, + } + + # unlock 1 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bit_owned_by_the_last_byte=7) + + if resp.status == 0x00 and resp.data[0] == 0x0a: + print("Gen1A unlock 1 success") + # unlock 2 + options['bit_frame'] = 0 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43]) + if resp.status == 0x00 and resp.data[0] == 0x0a: + print("Gen1A unlock 2 success") + print("Start dump gen1a memeory...") + block = 0 + while block < 64: + # Tag read block cmd + cmd_read_gen1a_block = [0x30, block] + + # Transfer with crc + options['append_crc'] = 1 + options['check_response_crc'] = 1 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=100, data=cmd_read_gen1a_block) + + print(f"Block {block} : {resp.data.hex()}") + block += 1 + + # Close rf field + options['keep_rf_field'] = 0 + resp = cml.hf14a_raw(options=options) + else: + print("Gen1A unlock 2 fail") + else: + print("Gen1A unlock 1 fail") + # disconnect dev.close() # never exit while True: pass + + +if __name__ == '__main__': + test_fn()