From c1eb213d4a50009728ab1b8c1967a192cc858667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=B4=E5=9D=87=E6=B0=91?= Date: Thu, 7 Mar 2024 15:07:12 +0800 Subject: [PATCH] Added command to check keys of multiple sectors at once (#199) thx @taichunmin --- CHANGELOG.md | 1 + docs/protocol.md | 7 + firmware/application/src/app_cmd.c | 19 ++- firmware/application/src/data_cmd.h | 1 + .../src/rfid/reader/hf/mf1_toolbox.c | 85 +++++++++++- .../src/rfid/reader/hf/mf1_toolbox.h | 25 +++- .../application/src/rfid/reader/hf/rc522.c | 10 +- .../application/src/rfid/reader/hf/rc522.h | 6 +- software/script/chameleon_cli_unit.py | 127 ++++++++++++++++++ software/script/chameleon_cmd.py | 39 ++++++ software/script/chameleon_enum.py | 1 + 11 files changed, 310 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce908c21..135277e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added command to check keys of multiple sectors at once (@taichunmin) - Fixed unused target key type parameter for nested (@petepriority) - Skip already used items `hf mf elog --decrypt` (@p-l-) - Parallelize mfkey32v2 processes called from CLI (@p-l-) diff --git a/docs/protocol.md b/docs/protocol.md index 8905e583..2eef2a63 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -294,6 +294,13 @@ Notes: * Command: 21 bytes: `src_type|src_block|src_key[6]|operator|operand[4]|dst_type|dst_block|dst_key[6]`. Key as 6 bytes. Type=`0x60` for key A, `0x61` for key B. Operator=`0xC0` for decrement, `0xC1` for increment, `0xC2` for restore. Operand as I32 in Network byte order. * Response: no data * CLI: cf `hf mf value` +### 2012: MF1_CHECK_KEYS_OF_SECTORS +* Command: 10+N*6 bytes: `mask[10]|keys[N][6]` (1<=N<=83) + * `mask`: 40 sectors, 2 bits/sector, MSB: `0A|0B|1A|1B|...|39A|39B`. `0b1` represent to skip checking the key. +* Response: 490 bytes: `found[10]|sectorKey[40][2][6]`. + * `found`: 40 sectors, 2 bits/sector, MSB: `0A|0B|1A|1B|...|39A|39B`. `0b1` represent the key is found. + * `sectorKey`: 40 sectors, 2 keys/sector, 6 bytes/key: `key0A[6]|key0B[6]|key1A[6]|key1B[6]|...|key39A[6]|key39B[6]` +* CLI: cf `hf mf fchk` ### 3000: EM410X_SCAN * Command: no data * Response: 5 bytes. `id[5]`. ID as 5 bytes. diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 45b833a8..8a7e9ab1 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -31,7 +31,6 @@ static void change_slot_auto(uint8_t slot) { set_slot_light_color(RGB_RED); } - static data_frame_tx_t *cmd_processor_get_app_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { struct { uint8_t version_major; @@ -347,6 +346,23 @@ static data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint1 return data_frame_make(cmd, status, 0, NULL); } +static data_frame_tx_t *cmd_processor_mf1_check_keys_of_sectors(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length < 16 || (length - 10) % 6 != 0) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + // init + mf1_toolbox_check_keys_of_sectors_in_t in = { + .mask = *(mf1_toolbox_check_keys_of_sectors_mask_t *) &data[0], + .keys_len = (length - 10) / 6, + .keys = (mf1_key_t *) &data[10] + }; + mf1_toolbox_check_keys_of_sectors_out_t out; + status = mf1_toolbox_check_keys_of_sectors(&in, &out); + + return data_frame_make(cmd, status, sizeof(out), (uint8_t *)&out); +} + static data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { typedef struct { uint8_t type; @@ -1068,6 +1084,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, { DATA_CMD_HF14A_RAW, before_reader_run, cmd_processor_hf14a_raw, NULL }, { DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK, before_hf_reader_run, cmd_processor_mf1_manipulate_value_block, after_hf_reader_run }, + { DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS, before_hf_reader_run, cmd_processor_mf1_check_keys_of_sectors, after_hf_reader_run }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index e605ff97..10313b16 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -67,6 +67,7 @@ #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) #define DATA_CMD_HF14A_RAW (2010) #define DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK (2011) +#define DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS (2012) // // ****************************************************************** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index f82b7425..32e2bf4d 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -1005,7 +1005,7 @@ uint8_t static_nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t t * @retval : validationResults * */ -uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key) { +uint16_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key) { // Each verification of a block must re -find a card if (pcd_14a_reader_scan_auto(p_tag_info) != STATUS_HF_TAG_OK) { return STATUS_HF_TAG_NO; @@ -1013,3 +1013,86 @@ uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key) { // After finding the card, we start to verify! return pcd_14a_reader_mf1_auth(p_tag_info, type, block, key); } + +inline void mf1_toolbox_antenna_restart () { + pcd_14a_reader_reset(); + pcd_14a_reader_antenna_on(); + bsp_delay_ms(8); +} + +inline void mf1_toolbox_report_healthy () { + bsp_wdt_feed(); + while (NRF_LOG_PROCESS()); +} + +uint16_t mf1_toolbox_check_keys_of_sectors ( + mf1_toolbox_check_keys_of_sectors_in_t *in, + mf1_toolbox_check_keys_of_sectors_out_t *out +) { + memset(out, 0, sizeof(mf1_toolbox_check_keys_of_sectors_out_t)); + uint8_t trailer[18] = {}; // trailer 16 bytes + padding 2 bytes + + // keys unique + uint8_t i, j, maskSector, maskShift, trailerNo; + for (i = 0; i < in->keys_len; i++) { + for (j = i + 1; j < in->keys_len; j++) { + if (memcmp(&in->keys[i], &in->keys[j], sizeof(mf1_key_t)) != 0) continue; + + // key duplicated, replace with last key + if (j != in->keys_len - 1) in->keys[j] = in->keys[in->keys_len - 1]; + in->keys_len--; + j--; + } + } + + uint16_t status = STATUS_HF_TAG_OK; + bool skipKeyB; + for (i = 0; i < 40; i++) { + maskShift = 6 - i % 4 * 2; + maskSector = (in->mask.b[i / 4] >> maskShift) & 0b11; + trailerNo = i < 32 ? i * 4 + 3 : i * 16 - 369; // trailerNo of sector + skipKeyB = (maskSector & 0b1) > 0; + if ((maskSector & 0b10) == 0) { + for (j = 0; j < in->keys_len; j++) { + mf1_toolbox_report_healthy(); + if (status != STATUS_HF_TAG_OK) mf1_toolbox_antenna_restart(); + + status = auth_key_use_522_hw(trailerNo, PICC_AUTHENT1A, in->keys[j].key); + if (status != STATUS_HF_TAG_OK) { // auth failed + if (status == STATUS_HF_TAG_NO) return STATUS_HF_TAG_NO; + continue; + } + // key A found + out->found.b[i / 4] |= 0b10 << maskShift; + out->keys[i][0] = in->keys[j]; + // try to read keyB from trailer of sector + status = pcd_14a_reader_mf1_read(trailerNo, trailer); + // key B not in trailer + if (status != STATUS_HF_TAG_OK || 0 == *(uint64_t*) &trailer[10]) break; + // key B found + skipKeyB = true; + out->found.b[i / 4] |= 0b1 << maskShift; + out->keys[i][1] = *(mf1_key_t*)&trailer[10]; + break; + } + } + if (skipKeyB) continue; + + for (j = 0; j < in->keys_len; j++) { + mf1_toolbox_report_healthy(); + if (status != STATUS_HF_TAG_OK) mf1_toolbox_antenna_restart(); + + status = auth_key_use_522_hw(trailerNo, PICC_AUTHENT1B, in->keys[j].key); + if (status != STATUS_HF_TAG_OK) { // auth failed + if (status == STATUS_HF_TAG_NO) return STATUS_HF_TAG_NO; + continue; + } + // key B found + out->found.b[i / 4] |= 0b1 << maskShift; + out->keys[i][1] = in->keys[j]; + break; + } + } + + return STATUS_HF_TAG_OK; +} \ No newline at end of file diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 48dfb7cd..c26fe471 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -57,6 +57,24 @@ typedef struct { uint8_t ar[4]; } PACKED DarksideCore_t; +typedef struct { + uint8_t key[6]; +} PACKED mf1_key_t; + +typedef struct { + uint8_t b[10]; // 80 bits: 40 sectors * 2 keys +} PACKED mf1_toolbox_check_keys_of_sectors_mask_t; + +typedef struct { + mf1_toolbox_check_keys_of_sectors_mask_t mask; + uint8_t keys_len; + mf1_key_t *keys; +} mf1_toolbox_check_keys_of_sectors_in_t; + +typedef struct { + mf1_toolbox_check_keys_of_sectors_mask_t found; + mf1_key_t keys[40][2]; // 6 bytes * 2 keys * 40 sectors = 480 bytes +} PACKED mf1_toolbox_check_keys_of_sectors_out_t; #ifdef __cplusplus extern "C" { @@ -93,7 +111,12 @@ uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_ uint8_t check_prng_type(mf1_prng_type_t *type); uint8_t check_std_mifare_nt_support(); void antenna_switch_delay(uint32_t delay_ms); -uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key); +uint16_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key); + +uint16_t mf1_toolbox_check_keys_of_sectors ( + mf1_toolbox_check_keys_of_sectors_in_t *in, + mf1_toolbox_check_keys_of_sectors_out_t *out +); #ifdef __cplusplus } diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index d08a01e3..17f6dd84 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -958,7 +958,7 @@ uint8_t pcd_14a_reader_gen1a_uplock(void) { * PSNR: Card serial number, 4 bytes * @retval : The status value STATUS_HF_TAG_OK is successful, tag_errauth fails, and other returns indicate some abnormalities related to communication errors! */ -uint8_t pcd_14a_reader_mf1_auth(picc_14a_tag_t *tag, uint8_t type, uint8_t addr, uint8_t *pKey) { +uint16_t pcd_14a_reader_mf1_auth(picc_14a_tag_t *tag, uint8_t type, uint8_t addr, uint8_t *pKey) { uint8_t dat_buff[12] = { type, addr }; uint16_t data_len = 0; @@ -991,7 +991,7 @@ void pcd_14a_reader_mf1_unauth(void) { * p : Read data, 16 bytes * @retval : Status value hf_tag_ok, success */ -uint8_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p) { +uint16_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p) { uint8_t status; uint16_t len; uint8_t dat_buff[MAX_MIFARE_FRAME_SIZE] = { cmd, addr }; @@ -1028,7 +1028,7 @@ uint8_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p) { * p : Read data, 16 bytes * @retval : Status value hf_tag_ok, success */ -uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *p) { +uint16_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *p) { // Standard M1 Card Reading Card Reading return pcd_14a_reader_mf1_read_by_cmd(PICC_READ, addr, p); } @@ -1261,14 +1261,14 @@ inline void pcd_14a_reader_antenna_off(void) { } /** -* @brief : Qi Dian school inspection enabled +* @brief : Enable the parity bit check. */ inline void pcd_14a_reader_parity_on(void) { clear_register_mask(MfRxReg, 0x10); } /** -* @brief : Qi Tong school inspection position closed +* @brief : Disable the parity bit check. */ inline void pcd_14a_reader_parity_off(void) { set_register_mask(MfRxReg, 0x10); diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 2c42d1b9..98ec525b 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -221,14 +221,14 @@ uint8_t pcd_14a_reader_ats_request(uint8_t *pAts, uint16_t *szAts, uint16_t szAt uint8_t pcd_14a_reader_atqa_request(uint8_t *resp, uint8_t *resp_par, uint16_t resp_max_bit); // M1 tag operation -uint8_t pcd_14a_reader_mf1_auth(picc_14a_tag_t *tag, uint8_t type, uint8_t addr, uint8_t *pKey); +uint16_t pcd_14a_reader_mf1_auth(picc_14a_tag_t *tag, uint8_t type, uint8_t addr, uint8_t *pKey); void pcd_14a_reader_mf1_unauth(void); // writeCardOperation uint8_t pcd_14a_reader_mf1_write_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p); uint8_t pcd_14a_reader_mf1_write(uint8_t addr, uint8_t *pData); // cardReadingOperation -uint8_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p); -uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); +uint16_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p); +uint16_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); // value block operation uint8_t pcd_14a_reader_mf1_manipulate_value_block(uint8_t operator, uint8_t addr, int32_t operand); uint8_t pcd_14a_reader_mf1_transfer_value_block(uint8_t addr); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 62b5379c..bcd22395 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -14,6 +14,7 @@ from typing import Union from pathlib import Path from platform import uname +from datetime import datetime import chameleon_com import chameleon_cmd @@ -851,6 +852,132 @@ def on_exec(self, args: argparse.Namespace): return +@hf_mf.command('fchk') +class HFMFFCHK(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + + mifare_type_group = parser.add_mutually_exclusive_group() + mifare_type_group.add_argument('--mini', help='MIFARE Classic Mini / S20', action='store_const', dest='maxSectors', const=5) + mifare_type_group.add_argument('--1k', help='MIFARE Classic 1k / S50 (default)', action='store_const', dest='maxSectors', const=16) + mifare_type_group.add_argument('--2k', help='MIFARE Classic/Plus 2k', action='store_const', dest='maxSectors', const=32) + mifare_type_group.add_argument('--4k', help='MIFARE Classic 4k / S70', action='store_const', dest='maxSectors', const=40) + + parser.add_argument(dest='keys', help='Key (as hex[12] format)', metavar='', type=str, nargs='*') + parser.add_argument('--key', dest='import_key', type=argparse.FileType('rb'), help='Read keys from .key format file') + parser.add_argument('--dic', dest='import_dic', type=argparse.FileType('r', encoding='utf8'), help='Read keys from .dic format file') + + parser.add_argument('--export-key', type=argparse.FileType('wb'), help=f'Export result as .key format, file will be {CR}OVERWRITTEN{C0} if exists') + parser.add_argument('--export-dic', type=argparse.FileType('w', encoding='utf8'), help=f'Export result as .dic format, file will be {CR}OVERWRITTEN{C0} if exists') + + parser.add_argument('-m', '--mask', help='Which sectorKey to be skip, 1 bit per sectorKey. `0b1` represent to skip to check. (in hex[20] format)', type=str, default='00000000000000000000', metavar='') + + parser.set_defaults(maxSectors=16) + return parser + + def check_keys(self, mask: bytearray, keys: list[bytes], chunkSize=20): + sectorKeys = dict() + + for i in range(0, len(keys), chunkSize): + # print("mask = {}".format(mask.hex(sep=' ', bytes_per_sep=1))) + chunkKeys = keys[i:i+chunkSize] + print(f' - progress of checking keys... {CY}{i}{C0} / {len(keys)} ({CY}{100 * i / len(keys):.1f}{C0} %)') + resp = self.cmd.mf1_check_keys_of_sectors(mask, chunkKeys) + # print(resp) + + if resp["status"] != Status.HF_TAG_OK: + print(f' - check interrupted, reason: {CR}{str(Status(resp["status"]))}{C0}') + break + elif 'sectorKeys' not in resp: + print(f' - check interrupted, reason: {CG}All sectorKey is found or masked{C0}') + break + + for j in range(10): + mask[j] |= resp['found'][j] + sectorKeys.update(resp['sectorKeys']) + + return sectorKeys + + def on_exec(self, args: argparse.Namespace): + # print(args) + + keys = set() + + # keys from args + for key in args.keys: + if not re.match(r'^[a-fA-F0-9]{12}$', key): + print(f' - {CR}Key should in hex[12] format, invalid key is ignored{C0}, key = "{key}"') + continue + keys.add(bytes.fromhex(key)) + + # read keys from key format file + if args.import_key is not None: + buf = args.import_key.read() + if len(buf) % 6 != 0: + print(f' - {CR}Failed to parse keys from {args.import_key.name} (as .key format){C0}') + return + for i in range(0, len(buf), 6): + keys.add(bytes(buf[i:i+6])) + + if args.import_dic is not None: + text = re.sub(r'#.*$', '', args.import_dic.read(), flags=re.MULTILINE) + buf = bytearray.fromhex(text) + if len(buf) % 6 != 0: + print(f' - {CR}Failed to parse keys from {args.import_dic.name} (as .dic format){C0}') + return + for i in range(0, len(buf), 6): + keys.add(bytes(buf[i:i+6])) + + if len(keys) == 0: + print(f' - {CR}No keys{C0}') + return + + print(f" - loaded {CG}{len(keys)}{C0} keys") + + # mask + if not re.match(r'^[a-fA-F0-9]{1,20}$', args.mask): + print(f' - {CR}mask should in hex[20] format{C0}, mask = "{args.mask}"') + return + mask = bytearray.fromhex(f'{args.mask:0<20}') + for i in range(args.maxSectors, 40): + mask[i // 4] |= 3 << (6 - i % 4 * 2) + + # check keys + startedAt = datetime.now() + sectorKeys = self.check_keys(mask, list(keys)) + endedAt = datetime.now() + duration = endedAt - startedAt + print(f" - elapsed time: {CY}{duration.total_seconds():.3f}s{C0}") + + if args.export_key is not None: + unknownkey = bytes(6) + for sectorNo in range(args.maxSectors): + args.export_key.write(sectorKeys.get(2 * sectorNo, unknownkey)) + args.export_key.write(sectorKeys.get(2 * sectorNo + 1, unknownkey)) + print(f" - result exported to: {CG}{args.export_key.name}{C0} (as .key format)") + + if args.export_dic is not None: + uniq_result = set(sectorKeys.values()) + for key in uniq_result: + args.export_dic.write(key.hex().upper() + '\n') + print(f" - result exported to: {CG}{args.export_dic.name}{C0} (as .dic format)") + + # print sectorKeys + print(f"\n - {CG}result of key checking:{C0}\n") + print("-----+-----+--------------+---+--------------+----") + print(" Sec | Blk | key A |res| key B |res ") + print("-----+-----+--------------+---+--------------+----") + for sectorNo in range(args.maxSectors): + blk = (sectorNo * 4 + 3) if sectorNo < 32 else (sectorNo * 16 - 369) + keyA = sectorKeys.get(2 * sectorNo, None) + keyA = f"{CG}{keyA.hex().upper()}{C0} | {CG}1{C0}" if keyA else f"{CR}------------{C0} | {CR}0{C0}" + keyB = sectorKeys.get(2 * sectorNo + 1, None) + keyB = f"{CG}{keyB.hex().upper()}{C0} | {CG}1{C0}" if keyB else f"{CR}------------{C0} | {CR}0{C0}" + print(f" {CY}{sectorNo:03d}{C0} | {blk:03d} | {keyA} | {keyB} ") + print("-----+-----+--------------+---+--------------+----") + print(f"( {CR}0{C0}: Failed, {CG}1{C0}: Success )\n\n") + + @hf_mf.command('rdbl') class HFMFRDBL(MF1AuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 12c11cc1..2ef363dd 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -300,6 +300,45 @@ def mf1_manipulate_value_block(self, src_block, src_type: MfcKeyType, src_key, o resp.parsed = resp.status == Status.HF_TAG_OK return resp + @expect_response([Status.HF_TAG_OK, Status.HF_TAG_NO]) + def mf1_check_keys_of_sectors(self, mask: bytes, keys: list[bytes]): + """ + Check keys of sectors. + :return: + """ + if len(mask) != 10: + raise ValueError("len(mask) should be 10") + if len(keys) < 1 or len(keys) > 83: + raise ValueError("Invalid len(keys)") + data = struct.pack(f'!10s{6*len(keys)}s', mask, b''.join(keys)) + + bitsCnt = 80 # maximum sectorKey_to_be_checked + for b in mask: + while b > 0: + [bitsCnt, b] = [bitsCnt - (b & 0b1), b >> 1] + if bitsCnt < 1: + # All sectorKey is masked + return chameleon_com.Response( + cmd=Command.MF1_CHECK_KEYS_OF_SECTORS, + status=Status.HF_TAG_OK, + parsed={ 'status': Status.HF_TAG_OK }, + ) + # base timeout: 1s + # auth: len(keys) * sectorKey_to_be_checked * 0.1s + # read keyB from trailer block: 0.1s + timeout = 1 + (bitsCnt + 1) * len(keys) * 0.1 + + resp = self.device.send_cmd_sync(Command.MF1_CHECK_KEYS_OF_SECTORS, data, timeout=timeout) + resp.parsed = { 'status': resp.status } + if len(resp.data) == 490: + found = ''.join([format(i, '08b') for i in resp.data[0:10]]) + # print(f'{found = }') + resp.parsed.update({ + 'found': resp.data[0:10], + 'sectorKeys': {k: resp.data[6 * k + 10:6 * k + 16] for k, v in enumerate(found) if v == '1'} + }) + return resp + @expect_response(Status.HF_TAG_OK) def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index 58d63595..2c13b8fe 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -68,6 +68,7 @@ class Command(enum.IntEnum): MF1_WRITE_ONE_BLOCK = 2009 HF14A_RAW = 2010 MF1_MANIPULATE_VALUE_BLOCK = 2011 + MF1_CHECK_KEYS_OF_SECTORS = 2012 EM410X_SCAN = 3000 EM410X_WRITE_TO_T55XX = 3001