Skip to content

Commit

Permalink
Added command to check keys of multiple sectors at once
Browse files Browse the repository at this point in the history
  • Loading branch information
taichunmin committed Jan 20, 2024
1 parent 3d8451a commit 5c3e204
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 18 additions & 1 deletion firmware/application/src/app_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 },
Expand Down
1 change: 1 addition & 0 deletions firmware/application/src/data_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

//
// ******************************************************************
Expand Down
85 changes: 84 additions & 1 deletion firmware/application/src/rfid/reader/hf/mf1_toolbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -1005,11 +1005,94 @@ 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;
}
// 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;
}
25 changes: 24 additions & 1 deletion firmware/application/src/rfid/reader/hf/mf1_toolbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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
}
Expand Down
10 changes: 5 additions & 5 deletions firmware/application/src/rfid/reader/hf/rc522.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions firmware/application/src/rfid/reader/hf/rc522.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
117 changes: 117 additions & 0 deletions software/script/chameleon_cli_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -851,6 +852,122 @@ 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 (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
mask = bytearray(10)
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
mask1 = bytearray.fromhex(args.mask)
for i in range(len(mask1)):
mask[i] |= mask1[i]
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:
Expand Down
Loading

0 comments on commit 5c3e204

Please sign in to comment.