mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
Added command to check keys of multiple sectors at once
This commit is contained in:
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added command to check keys of multiple sectors at once (@taichunmin)
|
||||
- Skip already used items `hf mf elog --decrypt` (@p-l-)
|
||||
- Parallelize mfkey32v2 processes called from CLI (@p-l-)
|
||||
- Added support for mifare classic value block operations (@taichunmin)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -14,6 +14,7 @@ from multiprocessing import Pool, cpu_count
|
||||
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,119 @@ class HFMFDarkside(ReaderRequiredUnit):
|
||||
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 (in hex[12] format)', metavar='<hex>', type=str, nargs='*')
|
||||
parser.add_argument('-f', '--file', type=argparse.FileType('rb'), help='Read keys from file')
|
||||
|
||||
file_type_group = parser.add_mutually_exclusive_group()
|
||||
file_type_group.add_argument('--hex', action='store_const', dest='fileType', const='hex', help='Type of keys file is dictionary (in hex[12] format) (default)')
|
||||
file_type_group.add_argument('-b', '--bin', action='store_const', dest='fileType', const='bin', help='Type of keys file is binary')
|
||||
|
||||
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='<hex>')
|
||||
|
||||
parser.set_defaults(maxSectors=16, fileType='hex')
|
||||
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))
|
||||
|
||||
# keys from file
|
||||
if args.file is not None:
|
||||
buf = bytearray()
|
||||
|
||||
if args.fileType == 'bin':
|
||||
buf.extend(args.file.read())
|
||||
elif args.fileType == 'hex':
|
||||
text = re.sub(r'#.*$', '', args.file.read().decode('utf-8'), flags=re.MULTILINE)
|
||||
# print(text)
|
||||
buf.extend(bytearray.fromhex(text))
|
||||
|
||||
if len(buf) % 6 != 0:
|
||||
print(f' - {CR}keys from file not align for 6 bytes{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}")
|
||||
|
||||
# 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:
|
||||
|
||||
@@ -300,6 +300,45 @@ class ChameleonCMD:
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user