mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-03-30 14:55:42 +00:00
1545 lines
56 KiB
Python
1545 lines
56 KiB
Python
import struct
|
|
import ctypes
|
|
from typing import Union
|
|
|
|
import chameleon_com
|
|
from chameleon_utils import expect_response, reconstruct_full_nt, parity_to_str
|
|
from chameleon_enum import Command, SlotNumber, Status, TagSenseType, TagSpecificType
|
|
from chameleon_enum import ButtonPressFunction, ButtonType, MifareClassicDarksideStatus
|
|
from chameleon_enum import MfcKeyType, MfcValueBlockOperator
|
|
|
|
CURRENT_VERSION_SETTINGS = 5
|
|
|
|
new_key = b'\x20\x20\x66\x66'
|
|
old_keys = [b'\x51\x24\x36\x48', b'\x19\x92\x04\x27']
|
|
|
|
class ChameleonCMD:
|
|
"""
|
|
Chameleon cmd function
|
|
"""
|
|
|
|
def __init__(self, chameleon: chameleon_com.ChameleonCom):
|
|
"""
|
|
:param chameleon: chameleon instance, @see chameleon_device.Chameleon
|
|
"""
|
|
self.device = chameleon
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_app_version(self):
|
|
"""
|
|
Get firmware version number(application)
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_APP_VERSION)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack('!BB', resp.data)
|
|
# older protocol, must upgrade!
|
|
if resp.status == 0 and resp.data == b'\x00\x01':
|
|
print("Chameleon does not understand new protocol. Please update firmware")
|
|
return chameleon_com.Response(cmd=Command.GET_APP_VERSION,
|
|
status=Status.NOT_IMPLEMENTED)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_device_chip_id(self):
|
|
"""
|
|
Get device chip id
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_DEVICE_CHIP_ID)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data.hex()
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_device_address(self):
|
|
"""
|
|
Get device address
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_DEVICE_ADDRESS)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data.hex()
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_git_version(self):
|
|
resp = self.device.send_cmd_sync(Command.GET_GIT_VERSION)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data.decode('utf-8')
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_device_mode(self):
|
|
resp = self.device.send_cmd_sync(Command.GET_DEVICE_MODE)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed, = struct.unpack('!?', resp.data)
|
|
return resp
|
|
|
|
def is_device_reader_mode(self) -> bool:
|
|
"""
|
|
Get device mode, reader or tag.
|
|
|
|
:return: True is reader mode, else tag mode
|
|
"""
|
|
return self.get_device_mode()
|
|
|
|
# Note: Will return NOT_IMPLEMENTED if one tries to set reader mode on Lite
|
|
@expect_response(Status.SUCCESS)
|
|
def change_device_mode(self, mode):
|
|
data = struct.pack('!B', mode)
|
|
return self.device.send_cmd_sync(Command.CHANGE_DEVICE_MODE, data)
|
|
|
|
def set_device_reader_mode(self, reader_mode: bool = True):
|
|
"""
|
|
Change device mode, reader or tag.
|
|
|
|
:param reader_mode: True if reader mode, False if tag mode.
|
|
:return:
|
|
"""
|
|
self.change_device_mode(reader_mode)
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def hf14a_scan(self):
|
|
"""
|
|
14a tags in the scanning field.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.HF14A_SCAN)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
# uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen]
|
|
offset = 0
|
|
data = []
|
|
while offset < len(resp.data):
|
|
uidlen, = struct.unpack_from('!B', resp.data, offset)
|
|
offset += struct.calcsize('!B')
|
|
uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset)
|
|
offset += struct.calcsize(f'!{uidlen}s2s1sB')
|
|
ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset)
|
|
offset += struct.calcsize(f'!{atslen}s')
|
|
data.append({'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats})
|
|
resp.parsed = data
|
|
return resp
|
|
|
|
def mf1_detect_support(self):
|
|
"""
|
|
Detect whether it is mifare classic tag.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF1_DETECT_SUPPORT)
|
|
return resp.status == Status.HF_TAG_OK
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_detect_prng(self):
|
|
"""
|
|
Detect mifare Class of classic nt vulnerabilities.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF1_DETECT_PRNG)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_detect_nt_dist(self, block_known, type_known, key_known):
|
|
"""
|
|
Detect the random number distance of the card.
|
|
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6s', type_known, block_known, key_known)
|
|
resp = self.device.send_cmd_sync(Command.MF1_DETECT_NT_DIST, data)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
uid, dist = struct.unpack('!II', resp.data)
|
|
resp.parsed = {'uid': uid, 'dist': dist}
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_nested_acquire(self, block_known, type_known, key_known, block_target, type_target):
|
|
"""
|
|
Collect the key NT parameters needed for Nested decryption
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target)
|
|
resp = self.device.send_cmd_sync(Command.MF1_NESTED_ACQUIRE, data)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
resp.parsed = [{'nt': nt, 'nt_enc': nt_enc, 'par': par}
|
|
for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)]
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_darkside_acquire(self, block_target, type_target, first_recover: Union[int, bool], sync_max):
|
|
"""
|
|
Collect the key parameters needed for Darkside decryption.
|
|
|
|
:param block_target:
|
|
:param type_target:
|
|
:param first_recover:
|
|
:param sync_max:
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BBBB', type_target, block_target, first_recover, sync_max)
|
|
resp = self.device.send_cmd_sync(Command.MF1_DARKSIDE_ACQUIRE, data, timeout=sync_max * 10)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
if resp.data[0] == MifareClassicDarksideStatus.OK:
|
|
darkside_status, uid, nt1, par, ks1, nr, ar = struct.unpack('!BIIQQII', resp.data)
|
|
resp.parsed = (darkside_status, {'uid': uid, 'nt1': nt1, 'par': par, 'ks1': ks1, 'nr': nr, 'ar': ar})
|
|
else:
|
|
resp.parsed = (resp.data[0],)
|
|
return resp
|
|
|
|
@expect_response([Status.HF_TAG_OK, Status.MF_ERR_AUTH])
|
|
def mf1_auth_one_key_block(self, block, type_value: MfcKeyType, key):
|
|
"""
|
|
Verify the mf1 key, only verify the specified type of key for a single sector.
|
|
|
|
:param block:
|
|
:param type_value:
|
|
:param key:
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6s', type_value, block, key)
|
|
resp = self.device.send_cmd_sync(Command.MF1_AUTH_ONE_KEY_BLOCK, data)
|
|
resp.parsed = resp.status == Status.HF_TAG_OK
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_read_one_block(self, block, type_value: MfcKeyType, key):
|
|
"""
|
|
Read one mf1 block.
|
|
|
|
:param block:
|
|
:param type_value:
|
|
:param key:
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6s', type_value, block, key)
|
|
resp = self.device.send_cmd_sync(Command.MF1_READ_ONE_BLOCK, data)
|
|
resp.parsed = resp.data
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_write_one_block(self, block, type_value: MfcKeyType, key, block_data):
|
|
"""
|
|
Write mf1 single block.
|
|
|
|
:param block:
|
|
:param type_value:
|
|
:param key:
|
|
:param block_data:
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6s16s', type_value, block, key, block_data)
|
|
resp = self.device.send_cmd_sync(Command.MF1_WRITE_ONE_BLOCK, data)
|
|
resp.parsed = resp.status == Status.HF_TAG_OK
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=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_ = [
|
|
("activate_rf_field", ctypes.c_uint8, 1),
|
|
("wait_response", ctypes.c_uint8, 1),
|
|
("append_crc", 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, 2),
|
|
]
|
|
|
|
cs = CStruct()
|
|
cs.activate_rf_field = options['activate_rf_field']
|
|
cs.wait_response = options['wait_response']
|
|
cs.append_crc = options['append_crc']
|
|
cs.auto_select = options['auto_select']
|
|
cs.keep_rf_field = options['keep_rf_field']
|
|
cs.check_response_crc = options['check_response_crc']
|
|
|
|
if bitlen is None:
|
|
bitlen = len(data) * 8 # bits = bytes * 8(bit)
|
|
else:
|
|
if len(data) == 0:
|
|
raise ValueError(f'bitlen={bitlen} but missing data')
|
|
if not ((len(data) - 1) * 8 < bitlen <= len(data) * 8):
|
|
raise ValueError(f'bitlen={bitlen} incompatible with provided data ({len(data)} bytes), '
|
|
f'must be between {((len(data) - 1) * 8)+1} and {len(data) * 8} included')
|
|
|
|
data = bytes(cs)+struct.pack(f'!HH{len(data)}s', resp_timeout_ms, bitlen, bytearray(data))
|
|
resp = self.device.send_cmd_sync(Command.HF14A_RAW, data, timeout=(resp_timeout_ms // 1000) + 1)
|
|
resp.parsed = resp.data
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_manipulate_value_block(self, src_block, src_type: MfcKeyType, src_key, operator: MfcValueBlockOperator, operand, dst_block, dst_type: MfcKeyType, dst_key):
|
|
"""
|
|
1. Increment: increments value from source block and write to dest block
|
|
2. Decrement: decrements value from source block and write to dest block
|
|
3. Restore: copy value from source block and write to dest block
|
|
|
|
|
|
:param src_block:
|
|
:param src_type:
|
|
:param src_key:
|
|
:param operator:
|
|
:param operand:
|
|
:param dst_block:
|
|
:param dst_type:
|
|
:param dst_key:
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6sBiBB6s', src_type, src_block, src_key, operator, operand, dst_type, dst_block, dst_key)
|
|
resp = self.device.send_cmd_sync(Command.MF1_MANIPULATE_VALUE_BLOCK, data)
|
|
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, Status.HF_TAG_NO, Status.MF_ERR_AUTH])
|
|
def mf1_check_keys_on_block(self, block: int, key_type: int, keys: list[bytes]):
|
|
if key_type not in [0x60, 0x61]:
|
|
raise ValueError("Wrong key type")
|
|
if len(keys) < 1 or len(keys) > 83:
|
|
raise ValueError("Invalid len(keys)")
|
|
data = struct.pack(f'!BBB{6*len(keys)}s', block, key_type, len(keys), b''.join(keys))
|
|
|
|
resp = self.device.send_cmd_sync(Command.MF1_CHECK_KEYS_ON_BLOCK, data, timeout=10)
|
|
|
|
if resp.status == Status.HF_TAG_OK and len(resp.data) == 7:
|
|
found, key = struct.unpack('!B6s', resp.data)
|
|
if found:
|
|
resp.parsed = key
|
|
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target):
|
|
"""
|
|
Collect the key NT parameters needed for StaticNested decryption
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target)
|
|
resp = self.device.send_cmd_sync(Command.MF1_STATIC_NESTED_ACQUIRE, data)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
resp.parsed = {
|
|
'uid': struct.unpack('!I', resp.data[0:4])[0],
|
|
'nts': [
|
|
{
|
|
'nt': nt,
|
|
'nt_enc': nt_enc
|
|
} for nt, nt_enc in struct.iter_unpack('!II', resp.data[4:])
|
|
]
|
|
}
|
|
return resp
|
|
|
|
@expect_response(Status.HF_TAG_OK)
|
|
def mf1_hard_nested_acquire(self, slow, block_known, type_known, key_known, block_target, type_target):
|
|
"""
|
|
Collect the NT_ENC list for HardNested decryption
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BBB6sBB', slow, type_known, block_known, key_known, type_target, block_target)
|
|
resp = self.device.send_cmd_sync(Command.MF1_HARDNESTED_ACQUIRE, data, timeout=30)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
resp.parsed = resp.data # we can return the raw nonces bytes
|
|
return resp
|
|
|
|
@expect_response([Status.HF_TAG_OK, Status.HF_TAG_NO])
|
|
def mf1_static_encrypted_nested_acquire(self, backdoor_key, sector_count, starting_sector):
|
|
data = struct.pack('!6sBB', backdoor_key, sector_count, starting_sector)
|
|
resp = self.device.send_cmd_sync(Command.MF1_ENC_NESTED_ACQUIRE, data, timeout=30)
|
|
if resp.status == Status.HF_TAG_OK:
|
|
resp.parsed = {
|
|
'uid': struct.unpack('!I', resp.data[0:4])[0],
|
|
'nts': {
|
|
'a': [],
|
|
'b': []
|
|
}
|
|
}
|
|
|
|
i = 4
|
|
|
|
while i < len(resp.data):
|
|
resp.parsed['nts']['a'].append(
|
|
{
|
|
'nt': reconstruct_full_nt(resp.data, i),
|
|
'nt_enc': int.from_bytes(resp.data[i + 3: i + 7], byteorder='big'),
|
|
'parity': parity_to_str(resp.data[i + 2])
|
|
}
|
|
)
|
|
|
|
resp.parsed['nts']['b'].append(
|
|
{
|
|
'nt': reconstruct_full_nt(resp.data, i + 7),
|
|
'nt_enc': int.from_bytes(resp.data[i + 10: i + 14], byteorder='big'),
|
|
'parity': parity_to_str(resp.data[i + 9])
|
|
}
|
|
)
|
|
|
|
i += 14
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def hf14a_get_config(self):
|
|
"""
|
|
Get hf 14a config
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.HF14A_GET_CONFIG)
|
|
if resp.status == Status.SUCCESS:
|
|
bcc, cl2, cl3, rats = struct.unpack('!bbbb', resp.data)
|
|
resp.parsed = {'bcc': bcc,
|
|
'cl2': cl2,
|
|
'cl3': cl3,
|
|
'rats': rats}
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def hf14a_set_config(self, data):
|
|
"""
|
|
Set hf 14a config
|
|
|
|
:return:
|
|
"""
|
|
data = struct.pack('!bbbb', data['bcc'], data['cl2'], data['cl3'], data['rats'])
|
|
return self.device.send_cmd_sync(Command.HF14A_SET_CONFIG, data)
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def em410x_scan(self):
|
|
"""
|
|
Read the card number of EM410X.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.EM410X_SCAN)
|
|
if resp.status == Status.LF_TAG_OK:
|
|
tag_type = struct.unpack('!H', resp.data[:2])[0]
|
|
if tag_type == TagSpecificType.EM410X_ELECTRA:
|
|
fmt = '!H13s'
|
|
else:
|
|
fmt = '!H5s'
|
|
resp.parsed = struct.unpack(fmt, resp.data[:struct.calcsize(fmt)]) # tag type + uid
|
|
return resp
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def em410x_write_to_t55xx(self, id_bytes: bytes):
|
|
"""
|
|
Write EM410X card number into T55XX.
|
|
|
|
:param id_bytes: ID card number
|
|
:return:
|
|
"""
|
|
if len(id_bytes) == 5:
|
|
data = struct.pack(f'!5s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))
|
|
return self.device.send_cmd_sync(Command.EM410X_WRITE_TO_T55XX, data)
|
|
if len(id_bytes) == 13:
|
|
data = struct.pack(f'!13s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))
|
|
return self.device.send_cmd_sync(Command.EM410X_ELECTRA_WRITE_TO_T55XX, data)
|
|
raise ValueError("The id bytes length must equal 5 (EM410X) or 13 (Electra)")
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def hidprox_scan(self, format: int):
|
|
"""
|
|
Read the length, facility code and card number of HID Prox.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.HIDPROX_SCAN, struct.pack('!B', format))
|
|
if resp.status == Status.LF_TAG_OK:
|
|
resp.parsed = struct.unpack('>BIBIBH', resp.data[:13])
|
|
return resp
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def hidprox_write_to_t55xx(self, id_bytes: bytes):
|
|
"""
|
|
Write HID Prox card number into T55XX.
|
|
|
|
:param id_bytes: ID card number
|
|
:return:
|
|
"""
|
|
if len(id_bytes) != 13:
|
|
raise ValueError("The id bytes length must equal 13")
|
|
data = struct.pack(f'!13s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))
|
|
return self.device.send_cmd_sync(Command.HIDPROX_WRITE_TO_T55XX, data)
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def ioprox_scan(self):
|
|
"""
|
|
Read ioProx (XSF): version, facility, number, raw.
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.IOPROX_SCAN)
|
|
if resp.status == Status.LF_TAG_OK:
|
|
resp.parsed = struct.unpack(">BBH8sBBBB", resp.data[:16])
|
|
return resp
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def ioprox_write_to_t55xx(self, id_bytes: bytes):
|
|
"""
|
|
Write ioProx card data to a T55XX tag.
|
|
"""
|
|
if len(id_bytes) != 16:
|
|
raise ValueError("The ioProx id bytes length must equal 16")
|
|
|
|
# Pack id_bytes (16), new_key (4), and all old_keys (4 each) into one buffer
|
|
fmt = f'!16s4s{4 * len(old_keys)}s'
|
|
data = struct.pack(fmt, id_bytes, new_key, b''.join(old_keys))
|
|
return self.device.send_cmd_sync(Command.IOPROX_WRITE_TO_T55XX, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def ioprox_decode_raw(self, raw8_bytes):
|
|
"""
|
|
Send 8 raw card bytes to firmware and return 16-byte card data structure.
|
|
Response layout: [0]=ver, [1]=fc, [2..3]=cn, [4..11]=raw8, [12..15]=padding.
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.IOPROX_DECODE_RAW, data=raw8_bytes)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack(">BBH8sBBBB", resp.data[:16])
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def ioprox_compose_id(self, ver, fc, cn):
|
|
"""
|
|
Encode ioProx parameters into a 16-byte card data structure via firmware.
|
|
Response layout: [0]=ver, [1]=fc, [2..3]=cn, [4..11]=raw8, [12..15]=padding.
|
|
"""
|
|
payload = struct.pack(">BBH", ver, fc, cn)
|
|
resp = self.device.send_cmd_sync(Command.IOPROX_COMPOSE_ID, data=payload)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack(">BBH8sBBBB", resp.data[:16])
|
|
return resp
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def viking_scan(self):
|
|
"""
|
|
Read the card number of Viking.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.VIKING_SCAN)
|
|
if resp.status == Status.LF_TAG_OK:
|
|
resp.parsed = resp.data # uid
|
|
return resp
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def viking_write_to_t55xx(self, id_bytes: bytes):
|
|
"""
|
|
Write Viking card number into T55XX.
|
|
|
|
:param id_bytes: ID card number
|
|
:return:
|
|
"""
|
|
if len(id_bytes) != 4:
|
|
raise ValueError("The id bytes length must equal 4")
|
|
data = struct.pack(f'!4s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))
|
|
return self.device.send_cmd_sync(Command.VIKING_WRITE_TO_T55XX, data)
|
|
|
|
@expect_response(Status.LF_TAG_OK)
|
|
def adc_generic_read(self):
|
|
"""
|
|
Read the ADC when the field is on.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.ADC_GENERIC_READ, None)
|
|
if resp.status == Status.LF_TAG_OK:
|
|
resp.parsed = resp.data
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_slot_info(self):
|
|
"""
|
|
Get slots info.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_SLOT_INFO)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = [{'hf': hf, 'lf': lf}
|
|
for hf, lf in struct.iter_unpack('!HH', resp.data)]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_active_slot(self):
|
|
"""
|
|
Get selected slot.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_ACTIVE_SLOT)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_active_slot(self, slot_index: SlotNumber):
|
|
"""
|
|
Set the card slot currently active for use.
|
|
|
|
:param slot_index: Card slot index
|
|
:return:
|
|
"""
|
|
# SlotNumber() will raise error for us if slot_index not in slot range
|
|
data = struct.pack('!B', SlotNumber.to_fw(slot_index))
|
|
return self.device.send_cmd_sync(Command.SET_ACTIVE_SLOT, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType):
|
|
"""
|
|
Set the label type of the emulated card of the current card slot
|
|
Note: This operation will not change the data in the flash,
|
|
and the change of the data in the flash will only be updated at the next save.
|
|
|
|
:param slot_index: Card slot number
|
|
:param tag_type: label type
|
|
:return:
|
|
"""
|
|
# SlotNumber() will raise error for us if slot_index not in slot range
|
|
data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type)
|
|
return self.device.send_cmd_sync(Command.SET_SLOT_TAG_TYPE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseType):
|
|
"""
|
|
Delete a sense type for a specific slot.
|
|
|
|
:param slot_index: Slot index
|
|
:param sense_type: Sense type to disable
|
|
:return:
|
|
"""
|
|
data = struct.pack('!BB', SlotNumber.to_fw(slot_index), sense_type)
|
|
return self.device.send_cmd_sync(Command.DELETE_SLOT_SENSE_TYPE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType):
|
|
"""
|
|
Set the data of the emulated card in the specified card slot as the default data
|
|
Note: This API will set the data in the flash together.
|
|
|
|
:param slot_index: Card slot number
|
|
:param tag_type: The default label type to set
|
|
:return:
|
|
"""
|
|
# SlotNumber() will raise error for us if slot_index not in slot range
|
|
data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type)
|
|
return self.device.send_cmd_sync(Command.SET_SLOT_DATA_DEFAULT, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_slot_enable(self, slot_index: SlotNumber, sense_type: TagSenseType, enabled: bool):
|
|
"""
|
|
Set whether the specified card slot is enabled.
|
|
|
|
:param slot_index: Card slot number
|
|
:param enable: Whether to enable
|
|
:return:
|
|
"""
|
|
# SlotNumber() will raise error for us if slot_index not in slot range
|
|
data = struct.pack('!BBB', SlotNumber.to_fw(slot_index), sense_type, enabled)
|
|
return self.device.send_cmd_sync(Command.SET_SLOT_ENABLE, data)
|
|
|
|
def _get_active_lf_tag_type(self) -> TagSpecificType:
|
|
slotinfo = self.get_slot_info()
|
|
active_slot = SlotNumber.from_fw(self.get_active_slot())
|
|
lf_tag_value = slotinfo[active_slot - 1]['lf']
|
|
return TagSpecificType(lf_tag_value)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def em410x_set_emu_id(self, id: bytes):
|
|
"""
|
|
Set the card number emulated by EM410x.
|
|
|
|
:param id_bytes: byte of the card number
|
|
:return:
|
|
"""
|
|
lf_tag_type = self._get_active_lf_tag_type()
|
|
if lf_tag_type == TagSpecificType.EM410X_ELECTRA:
|
|
expected_len = 13
|
|
elif lf_tag_type == TagSpecificType.EM410X:
|
|
expected_len = 5
|
|
else:
|
|
raise ValueError(f"Active LF slot type {lf_tag_type} is not EM410X")
|
|
|
|
if len(id) != expected_len:
|
|
raise ValueError(f"The id bytes length must equal {expected_len}")
|
|
|
|
data = struct.pack(f'!{expected_len}s', id)
|
|
return self.device.send_cmd_sync(Command.EM410X_SET_EMU_ID, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def em410x_get_emu_id(self):
|
|
"""
|
|
Get the emulated EM410x card id
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.EM410X_GET_EMU_ID)
|
|
if resp.status == Status.SUCCESS:
|
|
data = resp.data
|
|
id_bytes = data
|
|
tag_type = None
|
|
|
|
if len(data) >= 2:
|
|
try:
|
|
candidate = TagSpecificType(int.from_bytes(data[:2], byteorder='big'))
|
|
except ValueError:
|
|
candidate = None
|
|
|
|
if candidate in (TagSpecificType.EM410X, TagSpecificType.EM410X_ELECTRA):
|
|
expected_len = 13 if candidate == TagSpecificType.EM410X_ELECTRA else 5
|
|
if len(data) == expected_len + 2:
|
|
tag_type = candidate
|
|
id_bytes = data[2:2 + expected_len]
|
|
|
|
if tag_type is None:
|
|
lf_tag_type = self._get_active_lf_tag_type()
|
|
if lf_tag_type == TagSpecificType.EM410X_ELECTRA:
|
|
expected_len = 13
|
|
elif lf_tag_type == TagSpecificType.EM410X:
|
|
expected_len = 5
|
|
else:
|
|
expected_len = len(data)
|
|
id_bytes = data[:expected_len]
|
|
|
|
resp.parsed = id_bytes
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def hidprox_set_emu_id(self, id: bytes):
|
|
"""
|
|
Set the card number emulated by HID Prox.
|
|
|
|
:param id_bytes: byte of the card number
|
|
:return:
|
|
"""
|
|
if len(id) != 13:
|
|
raise ValueError("The id bytes length must equal 13")
|
|
return self.device.send_cmd_sync(Command.HIDPROX_SET_EMU_ID, id)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def hidprox_get_emu_id(self):
|
|
"""
|
|
Get the emulated HID Prox card id
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.HIDPROX_GET_EMU_ID)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack('>BIBIBH', resp.data[:13])
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def ioprox_set_emu_id(self, id: bytes):
|
|
"""
|
|
Set the card number emulated by ioProx.
|
|
|
|
:param id_bytes: byte of the card number
|
|
:return:
|
|
"""
|
|
if len(id) != 16:
|
|
raise ValueError("The id bytes length must equal 16")
|
|
return self.device.send_cmd_sync(Command.IOPROX_SET_EMU_ID, id)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def ioprox_get_emu_id(self):
|
|
"""
|
|
Get the emulated ioProx card id
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.IOPROX_GET_EMU_ID)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack(">BBH8sBBBB", resp.data[:16])
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def viking_set_emu_id(self, id: bytes):
|
|
"""
|
|
Set the card number emulated by Viking.
|
|
|
|
:param id_bytes: byte of the card number
|
|
:return:
|
|
"""
|
|
if len(id) != 4:
|
|
raise ValueError("The id bytes length must equal 4")
|
|
data = struct.pack('4s', id)
|
|
return self.device.send_cmd_sync(Command.VIKING_SET_EMU_ID, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def viking_get_emu_id(self):
|
|
"""
|
|
Get the emulated Viking card id
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.VIKING_GET_EMU_ID)
|
|
resp.parsed = resp.data
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_set_detection_enable(self, enabled: bool):
|
|
"""
|
|
Set whether to enable the detection of the current card slot.
|
|
|
|
:param enable: Whether to enable
|
|
:return:
|
|
"""
|
|
data = struct.pack('!B', enabled)
|
|
return self.device.send_cmd_sync(Command.MF1_SET_DETECTION_ENABLE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_get_detection_count(self):
|
|
"""
|
|
Get the statistics of the current detection records.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF1_GET_DETECTION_COUNT)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed, = struct.unpack('!I', resp.data)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_get_detection_log(self, index: int):
|
|
"""
|
|
Get detection logs from the specified index position.
|
|
|
|
:param index: start index
|
|
:return:
|
|
"""
|
|
data = struct.pack('!I', index)
|
|
resp = self.device.send_cmd_sync(Command.MF1_GET_DETECTION_LOG, data)
|
|
if resp.status == Status.SUCCESS:
|
|
# convert
|
|
result_list = []
|
|
pos = 0
|
|
while pos < len(resp.data):
|
|
block, bitfield, uid, nt, nr, ar = struct.unpack_from('!BB4s4s4s4s', resp.data, pos)
|
|
result_list.append({
|
|
'block': block,
|
|
'type': ['A', 'B'][bitfield & 0x01],
|
|
'is_nested': bool(bitfield & 0x02),
|
|
'uid': uid.hex(),
|
|
'nt': nt.hex(),
|
|
'nr': nr.hex(),
|
|
'ar': ar.hex()
|
|
})
|
|
pos += struct.calcsize('!BB4s4s4s4s')
|
|
resp.parsed = result_list
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_detection_enable(self):
|
|
"""
|
|
Get whether NTAG password detection is enabled.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_DETECTION_ENABLE)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack('!B', resp.data)[0] == 1
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_set_detection_enable(self, enabled: bool):
|
|
"""
|
|
Set whether to enable NTAG password detection.
|
|
|
|
:param enable: Whether to enable
|
|
:return:
|
|
"""
|
|
data = struct.pack('!B', enabled)
|
|
return self.device.send_cmd_sync(Command.MF0_NTAG_SET_DETECTION_ENABLE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_detection_count(self):
|
|
"""
|
|
Get the statistics of the current NTAG password detection records.
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_DETECTION_COUNT)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack('!I', resp.data)[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_detection_log(self, index: int):
|
|
"""
|
|
Get NTAG password detection logs from the specified index position.
|
|
|
|
:param index: start index
|
|
:return:
|
|
"""
|
|
data = struct.pack('!I', index)
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_DETECTION_LOG, data)
|
|
if resp.status == Status.SUCCESS:
|
|
# convert - each log entry is just a 4-byte password
|
|
result_list = []
|
|
pos = 0
|
|
while pos < len(resp.data):
|
|
password = resp.data[pos:pos+4]
|
|
result_list.append({
|
|
'password': password.hex()
|
|
})
|
|
pos += 4
|
|
resp.parsed = result_list
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_write_emu_block_data(self, block_start: int, block_data: bytes):
|
|
"""
|
|
Set the block data of the analog card of MF1.
|
|
|
|
:param block_start: Start setting the location of block data, including this location
|
|
:param block_data: The byte buffer of the block data to be set can contain multiple block data,
|
|
automatically from block_start increment
|
|
:return:
|
|
"""
|
|
data = struct.pack(f'!B{len(block_data)}s', block_start, block_data)
|
|
return self.device.send_cmd_sync(Command.MF1_WRITE_EMU_BLOCK_DATA, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_read_emu_block_data(self, block_start: int, block_count: int):
|
|
"""
|
|
Gets data for selected block range
|
|
"""
|
|
data = struct.pack('!BB', block_start, block_count)
|
|
resp = self.device.send_cmd_sync(Command.MF1_READ_EMU_BLOCK_DATA, data)
|
|
resp.parsed = resp.data
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mfu_get_emu_pages_count(self):
|
|
"""
|
|
Gets the number of pages available in the current MF0 / NTAG slot
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_PAGE_COUNT)
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mfu_read_emu_page_data(self, page_start: int, page_count: int):
|
|
"""
|
|
Gets data for selected block range
|
|
"""
|
|
data = struct.pack('!BB', page_start, page_count)
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_READ_EMU_PAGE_DATA, data)
|
|
resp.parsed = resp.data
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mfu_write_emu_page_data(self, page_start: int, data: bytes):
|
|
"""
|
|
Gets data for selected block range
|
|
"""
|
|
count = len(data) >> 2
|
|
|
|
assert (len(data) % 4) == 0
|
|
assert (page_start >= 0) and (count + page_start) <= 256
|
|
|
|
data = struct.pack('!BB', page_start, count) + data
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_WRITE_EMU_PAGE_DATA, data)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mfu_read_emu_counter_data(self, index: int) -> tuple[int, bool]:
|
|
"""
|
|
Gets data for selected counter
|
|
"""
|
|
data = struct.pack('!B', index)
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_COUNTER_DATA, data)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = (((resp.data[2] << 16) | (resp.data[1] << 8) | resp.data[0]), resp.data[3] == 0xBD)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mfu_write_emu_counter_data(self, index: int, value: int, reset_tearing: bool):
|
|
"""
|
|
Sets data for selected counter
|
|
"""
|
|
data = struct.pack('!BBBB', index | (int(reset_tearing) << 7),
|
|
value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF)
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_SET_COUNTER_DATA, data)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mfu_reset_auth_cnt(self):
|
|
"""
|
|
Resets authentication counter
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_RESET_AUTH_CNT, bytes())
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''):
|
|
"""
|
|
Set anti-collision data of current HF slot (UID/SAK/ATQA/ATS).
|
|
|
|
:param uid: uid bytes
|
|
:param atqa: atqa bytes
|
|
:param sak: sak bytes
|
|
:param ats: ats bytes (optional)
|
|
:return:
|
|
"""
|
|
data = struct.pack(f'!B{len(uid)}s2s1sB{len(ats)}s', len(uid), uid, atqa, sak, len(ats), ats)
|
|
return self.device.send_cmd_sync(Command.HF14A_SET_ANTI_COLL_DATA, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType, name: str):
|
|
"""
|
|
Set the nick name of the slot.
|
|
|
|
:param slot: Card slot number
|
|
:param sense_type: field type
|
|
:param name: Card slot nickname
|
|
:return:
|
|
"""
|
|
encoded_name = name.encode(encoding="utf8")
|
|
if len(encoded_name) > 32:
|
|
raise ValueError("Your tag nick name too long.")
|
|
# SlotNumber() will raise error for us if slot not in slot range
|
|
data = struct.pack(f'!BB{len(encoded_name)}s', SlotNumber.to_fw(slot), sense_type, encoded_name)
|
|
return self.device.send_cmd_sync(Command.SET_SLOT_TAG_NICK, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType):
|
|
"""
|
|
Get the nick name of the slot.
|
|
|
|
:param slot: Card slot number
|
|
:param sense_type: field type
|
|
:return:
|
|
"""
|
|
# SlotNumber() will raise error for us if slot not in slot range
|
|
data = struct.pack('!BB', SlotNumber.to_fw(slot), sense_type)
|
|
resp = self.device.send_cmd_sync(Command.GET_SLOT_TAG_NICK, data)
|
|
resp.parsed = resp.data.decode(encoding="utf8")
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_all_slot_nicks(self):
|
|
resp = self.device.send_cmd_sync(Command.GET_ALL_SLOT_NICKS, b'')
|
|
|
|
slots = []
|
|
i = 0
|
|
slot_index = 0
|
|
|
|
while i < len(resp.data) and slot_index < 8:
|
|
slot_names = {'hf': '', 'lf': ''}
|
|
|
|
if i < len(resp.data):
|
|
hf_len = resp.data[i]
|
|
i += 1
|
|
if hf_len > 0 and i + hf_len <= len(resp.data):
|
|
slot_names['hf'] = resp.data[i:i + hf_len].decode(encoding="utf8", errors="ignore")
|
|
i += hf_len
|
|
else:
|
|
i += hf_len
|
|
|
|
if i < len(resp.data):
|
|
lf_len = resp.data[i]
|
|
i += 1
|
|
if lf_len > 0 and i + lf_len <= len(resp.data):
|
|
slot_names['lf'] = resp.data[i:i + lf_len].decode(encoding="utf8", errors="ignore")
|
|
i += lf_len
|
|
else:
|
|
i += lf_len
|
|
|
|
slots.append(slot_names)
|
|
slot_index += 1
|
|
|
|
resp.parsed = slots
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def delete_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType):
|
|
"""
|
|
Delete the nick name of the slot.
|
|
|
|
:param slot: Card slot number
|
|
:param sense_type: field type
|
|
:return:
|
|
"""
|
|
# SlotNumber() will raise error for us if slot not in slot range
|
|
data = struct.pack('!BB', SlotNumber.to_fw(slot), sense_type)
|
|
return self.device.send_cmd_sync(Command.DELETE_SLOT_TAG_NICK, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_get_emulator_config(self):
|
|
"""
|
|
Get array of Mifare Classic emulators settings:
|
|
[0] - mf1_is_detection_enable (mfkey32)
|
|
[1] - mf1_is_gen1a_magic_mode
|
|
[2] - mf1_is_gen2_magic_mode
|
|
[3] - mf1_is_use_mf1_coll_res (use UID/BCC/SAK/ATQA from 0 block)
|
|
[4] - mf1_get_write_mode
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF1_GET_EMULATOR_CONFIG)
|
|
if resp.status == Status.SUCCESS:
|
|
b1, b2, b3, b4, b5 = struct.unpack('!????B', resp.data)
|
|
resp.parsed = {'detection': b1,
|
|
'gen1a_mode': b2,
|
|
'gen2_mode': b3,
|
|
'block_anti_coll_mode': b4,
|
|
'write_mode': b5}
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_set_gen1a_mode(self, enabled: bool):
|
|
"""
|
|
Set gen1a magic mode
|
|
"""
|
|
data = struct.pack('!B', enabled)
|
|
return self.device.send_cmd_sync(Command.MF1_SET_GEN1A_MODE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_set_gen2_mode(self, enabled: bool):
|
|
"""
|
|
Set gen2 magic mode
|
|
"""
|
|
data = struct.pack('!B', enabled)
|
|
return self.device.send_cmd_sync(Command.MF1_SET_GEN2_MODE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_set_block_anti_coll_mode(self, enabled: bool):
|
|
"""
|
|
Set 0 block anti-collision data
|
|
"""
|
|
data = struct.pack('!B', enabled)
|
|
return self.device.send_cmd_sync(Command.MF1_SET_BLOCK_ANTI_COLL_MODE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf1_set_write_mode(self, mode: int):
|
|
"""
|
|
Set write mode
|
|
"""
|
|
data = struct.pack('!B', mode)
|
|
return self.device.send_cmd_sync(Command.MF1_SET_WRITE_MODE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def slot_data_config_save(self):
|
|
"""
|
|
Update the configuration and data of the card slot to flash.
|
|
:return:
|
|
"""
|
|
return self.device.send_cmd_sync(Command.SLOT_DATA_CONFIG_SAVE)
|
|
|
|
def enter_bootloader(self):
|
|
"""
|
|
Reboot into DFU mode (bootloader)
|
|
:return:
|
|
"""
|
|
self.device.send_cmd_auto(Command.ENTER_BOOTLOADER, close=True)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_animation_mode(self):
|
|
"""
|
|
Get animation mode value
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_ANIMATION_MODE)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_enabled_slots(self):
|
|
"""
|
|
Get enabled slots
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_ENABLED_SLOTS)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = [{'hf': hf, 'lf': lf} for hf, lf in struct.iter_unpack('!BB', resp.data)]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_animation_mode(self, value: int):
|
|
"""
|
|
Set animation mode value
|
|
"""
|
|
data = struct.pack('!B', value)
|
|
return self.device.send_cmd_sync(Command.SET_ANIMATION_MODE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def reset_settings(self):
|
|
"""
|
|
Reset settings stored in flash memory
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.RESET_SETTINGS)
|
|
resp.parsed = resp.status == Status.SUCCESS
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def save_settings(self):
|
|
"""
|
|
Store settings to flash memory
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.SAVE_SETTINGS)
|
|
resp.parsed = resp.status == Status.SUCCESS
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def wipe_fds(self):
|
|
"""
|
|
Reset to factory settings
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.WIPE_FDS)
|
|
resp.parsed = resp.status == Status.SUCCESS
|
|
self.device.close()
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_battery_info(self):
|
|
"""
|
|
Get battery info
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_BATTERY_INFO)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = struct.unpack('!HB', resp.data)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_button_press_config(self, button: ButtonType):
|
|
"""
|
|
Get config of button press function
|
|
"""
|
|
data = struct.pack('!B', button)
|
|
resp = self.device.send_cmd_sync(Command.GET_BUTTON_PRESS_CONFIG, data)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_button_press_config(self, button: ButtonType, function: ButtonPressFunction):
|
|
"""
|
|
Set config of button press function
|
|
"""
|
|
data = struct.pack('!BB', button, function)
|
|
return self.device.send_cmd_sync(Command.SET_BUTTON_PRESS_CONFIG, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_long_button_press_config(self, button: ButtonType):
|
|
"""
|
|
Get config of long button press function
|
|
"""
|
|
data = struct.pack('!B', button)
|
|
resp = self.device.send_cmd_sync(Command.GET_LONG_BUTTON_PRESS_CONFIG, data)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_long_button_press_config(self, button: ButtonType, function: ButtonPressFunction):
|
|
"""
|
|
Set config of long button press function
|
|
"""
|
|
data = struct.pack('!BB', button, function)
|
|
return self.device.send_cmd_sync(Command.SET_LONG_BUTTON_PRESS_CONFIG, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def set_ble_connect_key(self, key: str):
|
|
"""
|
|
Set config of ble connect key
|
|
"""
|
|
data_bytes = key.encode(encoding='ascii')
|
|
|
|
# check key length
|
|
if len(data_bytes) != 6:
|
|
raise ValueError("The ble connect key length must be 6")
|
|
|
|
data = struct.pack('6s', data_bytes)
|
|
return self.device.send_cmd_sync(Command.SET_BLE_PAIRING_KEY, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_ble_pairing_key(self):
|
|
"""
|
|
Get config of ble connect key
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_BLE_PAIRING_KEY)
|
|
resp.parsed = resp.data.decode(encoding='ascii')
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def delete_all_ble_bonds(self):
|
|
"""
|
|
From peer manager delete all bonds.
|
|
"""
|
|
return self.device.send_cmd_sync(Command.DELETE_ALL_BLE_BONDS)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_device_capabilities(self):
|
|
"""
|
|
Get list of commands that client understands
|
|
"""
|
|
try:
|
|
resp = self.device.send_cmd_sync(Command.GET_DEVICE_CAPABILITIES)
|
|
except chameleon_com.CMDInvalidException:
|
|
print("Chameleon does not understand get_device_capabilities command. Please update firmware")
|
|
return chameleon_com.Response(cmd=Command.GET_DEVICE_CAPABILITIES,
|
|
status=Status.NOT_IMPLEMENTED)
|
|
else:
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = [x[0] for x in struct.iter_unpack('!H', resp.data)]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_device_model(self):
|
|
"""
|
|
Get device model
|
|
0 - Chameleon Ultra
|
|
1 - Chameleon Lite
|
|
"""
|
|
|
|
resp = self.device.send_cmd_sync(Command.GET_DEVICE_MODEL)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_device_settings(self):
|
|
"""
|
|
Get all possible settings
|
|
For version 5:
|
|
settings[0] = SETTINGS_CURRENT_VERSION; // current version
|
|
settings[1] = settings_get_animation_config(); // animation mode
|
|
settings[2] = settings_get_button_press_config('A'); // short A button press mode
|
|
settings[3] = settings_get_button_press_config('B'); // short B button press mode
|
|
settings[4] = settings_get_long_button_press_config('A'); // long A button press mode
|
|
settings[5] = settings_get_long_button_press_config('B'); // long B button press mode
|
|
settings[6] = settings_get_ble_pairing_enable(); // does device require pairing
|
|
settings[7:13] = settings_get_ble_pairing_key(); // BLE pairing key
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_DEVICE_SETTINGS)
|
|
if resp.status == Status.SUCCESS:
|
|
if resp.data[0] > CURRENT_VERSION_SETTINGS:
|
|
raise ValueError("Settings version in app older than Chameleon. "
|
|
"Please upgrade client")
|
|
if resp.data[0] < CURRENT_VERSION_SETTINGS:
|
|
raise ValueError("Settings version in app newer than Chameleon. "
|
|
"Please upgrade Chameleon firmware")
|
|
settings_version, animation_mode, btn_press_A, btn_press_B, btn_long_press_A, \
|
|
btn_long_press_B, ble_pairing_enable, ble_pairing_key = \
|
|
struct.unpack('!BBBBBBB6s', resp.data)
|
|
resp.parsed = {'settings_version': settings_version,
|
|
'animation_mode': animation_mode,
|
|
'btn_press_A': btn_press_A,
|
|
'btn_press_B': btn_press_B,
|
|
'btn_long_press_A': btn_long_press_A,
|
|
'btn_long_press_B': btn_long_press_B,
|
|
'ble_pairing_enable': ble_pairing_enable,
|
|
'ble_pairing_key': ble_pairing_key}
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def hf14a_get_anti_coll_data(self):
|
|
"""
|
|
Get anti-collision data from current HF slot (UID/SAK/ATQA/ATS)
|
|
|
|
:return:
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.HF14A_GET_ANTI_COLL_DATA)
|
|
if resp.status == Status.SUCCESS and len(resp.data) > 0:
|
|
# uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen]
|
|
offset = 0
|
|
uidlen, = struct.unpack_from('!B', resp.data, offset)
|
|
offset += struct.calcsize('!B')
|
|
uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset)
|
|
offset += struct.calcsize(f'!{uidlen}s2s1sB')
|
|
ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset)
|
|
offset += struct.calcsize(f'!{atslen}s')
|
|
resp.parsed = {'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats}
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_uid_magic_mode(self):
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_UID_MAGIC_MODE)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed, = struct.unpack('!?', resp.data)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_set_uid_magic_mode(self, enabled: bool):
|
|
return self.device.send_cmd_sync(Command.MF0_NTAG_SET_UID_MAGIC_MODE, struct.pack('?', enabled))
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_version_data(self):
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_VERSION_DATA)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[:8]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_set_version_data(self, data: bytes):
|
|
assert len(data) == 8
|
|
return self.device.send_cmd_sync(Command.MF0_NTAG_SET_VERSION_DATA, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_signature_data(self):
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_SIGNATURE_DATA)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[:32]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_set_signature_data(self, data: bytes):
|
|
assert len(data) == 32
|
|
return self.device.send_cmd_sync(Command.MF0_NTAG_SET_SIGNATURE_DATA, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_get_write_mode(self):
|
|
"""
|
|
Get write mode for MF0/NTAG
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_WRITE_MODE)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed = resp.data[0]
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def mf0_ntag_set_write_mode(self, mode: int):
|
|
"""
|
|
Set write mode for MF0/NTAG
|
|
"""
|
|
data = struct.pack('!B', mode)
|
|
return self.device.send_cmd_sync(Command.MF0_NTAG_SET_WRITE_MODE, data)
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
def get_ble_pairing_enable(self):
|
|
"""
|
|
Is ble pairing enable?
|
|
|
|
:return: True if pairing is enable, False if pairing disabled
|
|
"""
|
|
resp = self.device.send_cmd_sync(Command.GET_BLE_PAIRING_ENABLE)
|
|
if resp.status == Status.SUCCESS:
|
|
resp.parsed, = struct.unpack('!?', resp.data)
|
|
return resp
|
|
|
|
@expect_response(Status.SUCCESS)
|
|
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():
|
|
# connect to chameleon
|
|
dev = chameleon_com.ChameleonCom()
|
|
try:
|
|
dev.open('com19')
|
|
except chameleon_com.OpenFailException:
|
|
dev.open('/dev/ttyACM0')
|
|
|
|
cml = ChameleonCMD(dev)
|
|
ver = cml.get_app_version()
|
|
print(f"Firmware number of application: {ver[0]}.{ver[1]}")
|
|
chip = cml.get_device_chip_id()
|
|
print(f"Device chip id: {chip}")
|
|
|
|
# change to reader mode
|
|
cml.set_device_reader_mode()
|
|
|
|
options = {
|
|
'activate_rf_field': 1,
|
|
'wait_response': 1,
|
|
'append_crc': 0,
|
|
'auto_select': 0,
|
|
'keep_rf_field': 1,
|
|
'check_response_crc': 0,
|
|
}
|
|
|
|
try:
|
|
# unlock 1
|
|
resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bitlen=7)
|
|
|
|
if resp[0] == 0x0a:
|
|
print("Gen1A unlock 1 success")
|
|
# unlock 2
|
|
resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43])
|
|
if resp[0] == 0x0a:
|
|
print("Gen1A unlock 2 success")
|
|
print("Start dump gen1a memory...")
|
|
# Transfer with crc
|
|
options['append_crc'] = 1
|
|
options['check_response_crc'] = 1
|
|
block = 0
|
|
while block < 64:
|
|
# Tag read block cmd
|
|
cmd_read_gen1a_block = [0x30, block]
|
|
if block == 63:
|
|
options['keep_rf_field'] = 0
|
|
resp = cml.hf14a_raw(options=options, resp_timeout_ms=100, data=cmd_read_gen1a_block)
|
|
|
|
print(f"Block {block} : {resp.hex()}")
|
|
block += 1
|
|
|
|
else:
|
|
print("Gen1A unlock 2 fail")
|
|
raise
|
|
else:
|
|
print("Gen1A unlock 1 fail")
|
|
raise
|
|
except Exception:
|
|
options['keep_rf_field'] = 0
|
|
options['wait_response'] = 0
|
|
cml.hf14a_raw(options=options)
|
|
|
|
# disconnect
|
|
dev.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_fn()
|