mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-25 20:44:44 +00:00
Merge remote-tracking branch 'upstream/main' into pr-merge
This commit is contained in:
@@ -28,9 +28,10 @@ jobs:
|
||||
- name: Download release artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
name: release-artifacts
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
path: release-artifacts
|
||||
- name: Upload to dev release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
@@ -65,9 +66,10 @@ jobs:
|
||||
- name: Download release artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
name: release-artifacts
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
path: release-artifacts
|
||||
- name: Upload to tagged release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
@@ -4,6 +4,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added `firmware/docker-compose.yml` to build firmware in local docker (@taichunmin)
|
||||
- Added cmd to acquire nonces for hardnested(Protocol doc need update) (@xianglin1998)
|
||||
- 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-)
|
||||
|
||||
@@ -363,6 +363,44 @@ static data_frame_tx_t *cmd_processor_mf1_check_keys_of_sectors(uint16_t cmd, ui
|
||||
return data_frame_make(cmd, status, sizeof(out), (uint8_t *)&out);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_mf1_hardnested_nonces_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
typedef struct {
|
||||
uint8_t slow;
|
||||
uint8_t type_known;
|
||||
uint8_t block_known;
|
||||
uint8_t key_known[6];
|
||||
uint8_t type_target;
|
||||
uint8_t block_target;
|
||||
} PACKED payload_t;
|
||||
if (length != sizeof(payload_t)) {
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
|
||||
}
|
||||
payload_t *payload = (payload_t *)data;
|
||||
|
||||
// It is enough to collect 110 nonces at a time. The total transmitted data payload is 495 + 1 bytes
|
||||
// Then, the total length can be controlled within 512, so that when encountering a BLE host that supports large packets, one communication can be completed.
|
||||
// There is no need to send or receive packets in separate packets, which improves communication speed.
|
||||
uint8_t nonces[500] = { 0x00 };
|
||||
if (length < 11) {
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
|
||||
}
|
||||
status = mf1_hardnested_nonces_acquire(
|
||||
payload->slow,
|
||||
payload->block_known,
|
||||
payload->type_known,
|
||||
bytes_to_num(payload->key_known, 6),
|
||||
payload->block_target,
|
||||
payload->type_target,
|
||||
nonces + 1,
|
||||
sizeof(nonces) - 1, // The upper limit of the buffer size. Here we take out the first byte to mark the number of collections.
|
||||
&nonces[0] // The number of random numbers collected above
|
||||
);
|
||||
if (status != STATUS_HF_TAG_OK) {
|
||||
return data_frame_make(cmd, status, 0, NULL);
|
||||
}
|
||||
return data_frame_make(cmd, status, nonces[0] * 4.5, (uint8_t *)(nonces + 1));
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1295,7 +1333,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
|
||||
{ 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_MF1_HARDNESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_hardnested_nonces_acquire, 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 },
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
#define DATA_CMD_HF14A_RAW (2010)
|
||||
#define DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK (2011)
|
||||
#define DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS (2012)
|
||||
#define DATA_CMD_MF1_HARDNESTED_ACQUIRE (2013)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// The default delay of the antenna reset
|
||||
static uint32_t g_ant_reset_delay = 100;
|
||||
|
||||
// Label information used for global operations
|
||||
// tag information used for this module.
|
||||
static picc_14a_tag_t m_tag_info;
|
||||
static picc_14a_tag_t *p_tag_info = &m_tag_info;
|
||||
|
||||
@@ -1095,4 +1095,97 @@ uint16_t mf1_toolbox_check_keys_of_sectors (
|
||||
}
|
||||
|
||||
return STATUS_HF_TAG_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief : HardNested random number acquisition implementation
|
||||
* @param :slow : Is it a low-speed acquisition mode? Low-speed acquisition is suitable for some non-standard cards
|
||||
* @param :keyKnown : The known secret key of the card
|
||||
* @param :blkKnown : The sector to which the known secret key of the card belongs
|
||||
* @param :typKnown : The type of the known secret key of the card, 0x60 (A secret key) or 0x61 (B secret key)
|
||||
* @param :targetBlk : The target sector for nested attack
|
||||
* @param :targetTyp : The target secret key type for nested attack
|
||||
* @param :nonces : The buffer for storing random numbers
|
||||
* @param :noncesMax : The upper limit of the random number buffer in bytes
|
||||
* @retval : STATUS_HF_TAG_OK is returned if the acquisition is successful, and non-HF_TAG_OK is returned if the acquisition is unsuccessful Value
|
||||
*
|
||||
*/
|
||||
uint8_t mf1_hardnested_nonces_acquire(bool slow, uint8_t blkKnown, uint8_t typKnown, uint64_t keyKnown,
|
||||
uint8_t targetBlk, uint8_t targetTyp, uint8_t* nonces, uint16_t noncesMax, uint8_t* num_nonces) {
|
||||
struct Crypto1State mpcs = { 0, 0 };
|
||||
struct Crypto1State *pcs = &mpcs;
|
||||
uint8_t answer[] = { 0x00, 0x00, 0x00, 0x00 };
|
||||
uint8_t parity[] = { 0x00, 0x00, 0x00, 0x00 };
|
||||
uint8_t nt_par_enc = 0;
|
||||
uint8_t status = STATUS_HF_TAG_NO;
|
||||
uint32_t cuid = 0; // cuid can be fixed when selecting card
|
||||
uint16_t len = 0;
|
||||
*num_nonces = 0; // The number of random numbers currently counted must be reset
|
||||
bool tag_selected = false;
|
||||
uint8_t err_count = 0;
|
||||
|
||||
for (uint16_t i = 0; i <= noncesMax - 9;) {
|
||||
// NRF_LOG_INFO("AcquireEncryptedNonces: %d\r\n", i);
|
||||
if (tag_selected) {
|
||||
mf1_toolbox_report_healthy();
|
||||
if (pcd_14a_reader_fast_select(p_tag_info) != STATUS_HF_TAG_OK) {
|
||||
NRF_LOG_INFO("AcquireEncryptedNonces: Tag lost\r\n");
|
||||
if (++err_count >= 15) {
|
||||
return STATUS_HF_TAG_NO;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Slow mode, delay some time?
|
||||
if (slow) {
|
||||
bsp_delay_us(400);
|
||||
}
|
||||
// First auth
|
||||
if (authex(pcs, cuid, blkKnown, typKnown, keyKnown, AUTH_FIRST, NULL) != STATUS_HF_TAG_OK) {
|
||||
NRF_LOG_INFO("AcquireEncryptedNonces: Auth1 error\r\n");
|
||||
if (++err_count >= 15) {
|
||||
return STATUS_MF_ERR_AUTH;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Nested auth
|
||||
len = send_cmd(pcs, AUTH_NESTED, targetTyp, targetBlk, &status, answer, parity, U8ARR_BIT_LEN(answer));
|
||||
if (len != 32) {
|
||||
NRF_LOG_INFO("AcquireEncryptedNonces: Auth2 error len=%d\r\n", len);
|
||||
if (++err_count >= 15) {
|
||||
return STATUS_HF_ERR_STAT;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Reset err count
|
||||
err_count = 0;
|
||||
// merge parity
|
||||
uint8_t par_enc = 0;
|
||||
par_enc |= parity[3] << 4;
|
||||
par_enc |= parity[2] << 5;
|
||||
par_enc |= parity[1] << 6;
|
||||
par_enc |= parity[0] << 7;
|
||||
// copy to buffer
|
||||
*num_nonces = *num_nonces + 1;
|
||||
if (*num_nonces % 2) {
|
||||
memcpy(nonces + i, answer, 4);
|
||||
nt_par_enc = par_enc & 0xf0;
|
||||
} else {
|
||||
nt_par_enc |= par_enc >> 4;
|
||||
memcpy(nonces + i + 4, answer, 4);
|
||||
memcpy(nonces + i + 8, &nt_par_enc, 1);
|
||||
i += 9;
|
||||
}
|
||||
} else {
|
||||
// scan the tag to fixed cuid.
|
||||
status = pcd_14a_reader_scan_auto(p_tag_info);
|
||||
if (status != STATUS_HF_TAG_OK) {
|
||||
return STATUS_HF_TAG_NO;
|
||||
}
|
||||
cuid = get_u32_tag_uid(p_tag_info);
|
||||
tag_selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// OK!
|
||||
return STATUS_HF_TAG_OK;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ uint16_t mf1_toolbox_check_keys_of_sectors (
|
||||
mf1_toolbox_check_keys_of_sectors_out_t *out
|
||||
);
|
||||
|
||||
uint8_t mf1_hardnested_nonces_acquire(bool slow, uint8_t blkKnown, uint8_t typKnown, uint64_t keyKnown,
|
||||
uint8_t targetBlk, uint8_t targetTyp, uint8_t* nonces, uint16_t noncesMax, uint8_t* num_nonces);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -42,7 +42,18 @@ type_id_SAK_dict = {0x00: "MIFARE Ultralight Classic/C/EV1/Nano | NTAG 2xx",
|
||||
|
||||
default_cwd = Path.cwd() / Path(__file__).with_name("bin")
|
||||
|
||||
def load_key_file(import_key, keys):
|
||||
"""
|
||||
Load key file and append its content to the provided set of keys.
|
||||
Each key is expected to be on a new line in the file.
|
||||
"""
|
||||
with open(import_key.name, 'rb') as file:
|
||||
keys.update(line.encode('utf-8') for line in file.read().decode('utf-8').splitlines())
|
||||
return keys
|
||||
|
||||
def load_dic_file(import_dic, keys):
|
||||
return keys
|
||||
|
||||
def check_tools():
|
||||
tools = ['staticnested', 'nested', 'darkside', 'mfkey32v2']
|
||||
if sys.platform == "win32":
|
||||
|
||||
@@ -358,6 +358,18 @@ class ChameleonCMD:
|
||||
]
|
||||
}
|
||||
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.DATA_CMD_MF1_HARDNESTED_ACQUIRE, data)
|
||||
if resp.status == Status.HF_TAG_OK:
|
||||
resp.parsed = resp.data # we can return the raw nonces bytes
|
||||
return resp
|
||||
|
||||
@expect_response(Status.LF_TAG_OK)
|
||||
def em410x_scan(self):
|
||||
|
||||
@@ -69,6 +69,7 @@ class Command(enum.IntEnum):
|
||||
HF14A_RAW = 2010
|
||||
MF1_MANIPULATE_VALUE_BLOCK = 2011
|
||||
MF1_CHECK_KEYS_OF_SECTORS = 2012
|
||||
DATA_CMD_MF1_HARDNESTED_ACQUIRE = 2013
|
||||
|
||||
EM410X_SCAN = 3000
|
||||
EM410X_WRITE_TO_T55XX = 3001
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
hardnested_sums = [0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256]
|
||||
hardnested_nonces_sum_map = []
|
||||
hardnested_first_byte_num = 0
|
||||
hardnested_first_byte_sum = 0
|
||||
|
||||
|
||||
def evenparity32(n):
|
||||
"""
|
||||
calc evenparity32, can replace to any fast native impl...
|
||||
@param n - NT_ENC
|
||||
"""
|
||||
ret = 0
|
||||
for i in range(32):
|
||||
if (n & (1 << i)) != 0:
|
||||
ret += 1
|
||||
return ret % 2
|
||||
|
||||
|
||||
def check_nonce_unique_sum(nt, par):
|
||||
"""
|
||||
Check nt_enc is unique and calc first byte sum
|
||||
Pay attention: thread unsafe!!!
|
||||
@param nt - NT_ENC
|
||||
@param par - parity of NT_ENC
|
||||
"""
|
||||
global hardnested_first_byte_sum, hardnested_first_byte_num
|
||||
first_byte = nt >> 24
|
||||
if not hardnested_nonces_sum_map[first_byte]:
|
||||
hardnested_first_byte_sum += evenparity32((nt & 0xff000000) | (par & 0x08))
|
||||
hardnested_nonces_sum_map[first_byte] = True
|
||||
hardnested_first_byte_num += 1
|
||||
|
||||
|
||||
def reset():
|
||||
global hardnested_first_byte_sum, hardnested_first_byte_num, hardnested_nonces_sum_map
|
||||
# clear the history
|
||||
hardnested_nonces_sum_map = list()
|
||||
for i in range(256):
|
||||
hardnested_nonces_sum_map.append(False)
|
||||
hardnested_first_byte_sum = 0
|
||||
hardnested_first_byte_num = 0
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
|
||||
from chameleon_com import ChameleonCom, OpenFailException
|
||||
from chameleon_cmd import ChameleonCMD
|
||||
import hardnested_utils
|
||||
|
||||
|
||||
def test_hardnested_acquire():
|
||||
nonces_buffer = bytearray()
|
||||
acquire_count = 0
|
||||
|
||||
# known key and target block
|
||||
key = bytes.fromhex("????????????")
|
||||
block_known = 0x00
|
||||
type_known = 0x60
|
||||
block_target = 0x07
|
||||
type_target = 0x60
|
||||
|
||||
|
||||
# Before acquire start, we need to reset history
|
||||
hardnested_utils.reset()
|
||||
|
||||
# The nonces file format required by PM3:
|
||||
# (4byte uid of card) - (block_target 1byte) - (type_target 1byte) - (nonces from device Nbytes)
|
||||
|
||||
# ------------------------ open the device ------------------------
|
||||
try:
|
||||
cml = ChameleonCom().open('com19')
|
||||
except OpenFailException:
|
||||
cml = ChameleonCom().open('/dev/ttyACM0')
|
||||
cml_cmd = ChameleonCMD(cml)
|
||||
|
||||
# ------------------------ append tag info ------------------------
|
||||
|
||||
resp = cml_cmd.hf14a_scan()
|
||||
if resp is None or len(resp) == 0:
|
||||
print("ISO14443-A Tag no found")
|
||||
return
|
||||
|
||||
uidbytes = bytearray.fromhex(resp['uid'])
|
||||
uid_len = len(uidbytes)
|
||||
if uid_len == 4:
|
||||
nonces_buffer.extend(uidbytes[0: 4])
|
||||
if uid_len == 7:
|
||||
nonces_buffer.extend(uidbytes[3: 7])
|
||||
if uid_len == 10:
|
||||
nonces_buffer.extend(uidbytes[6: 10])
|
||||
|
||||
nonces_buffer.extend([block_target, type_target & 0x01])
|
||||
|
||||
# ------------------------ append nonces from device ------------------------
|
||||
|
||||
while True:
|
||||
# 1, acquire from device
|
||||
acquire_datas = cml_cmd.mf1_hard_nested_acquire(0, block_known, type_known, key, block_target, type_target) # slow = 0 to fast acquire...
|
||||
if acquire_datas is not None:
|
||||
acquire_count += 1
|
||||
print(f"Acquire success, count: {acquire_count}")
|
||||
else:
|
||||
raise Exception(f"acquire failed")
|
||||
# 2. check data
|
||||
data_check_index = 0
|
||||
while data_check_index < len(acquire_datas):
|
||||
# Memory Layout: nt_enc1(4byte) - nt_enc2(4byte) - par(1byte)...
|
||||
# To integer
|
||||
nt_enc1 = int.from_bytes(acquire_datas[data_check_index + 0: data_check_index + 0 + 4])
|
||||
nt_enc2 = int.from_bytes(acquire_datas[data_check_index + 4: data_check_index + 4 + 4])
|
||||
par_enc = acquire_datas[data_check_index + 8]
|
||||
# check unique and sum
|
||||
hardnested_utils.check_nonce_unique_sum(nt_enc1, par_enc >> 4)
|
||||
hardnested_utils.check_nonce_unique_sum(nt_enc2, par_enc & 0x0F)
|
||||
data_check_index += 9 # The two ciphertext random numbers have a total of 8 bytes, and the parity bits corresponding to the two ciphertext random numbers occupy one byte
|
||||
# 3. store data
|
||||
nonces_buffer.extend(acquire_datas)
|
||||
# 4. After collecting 256 possible different groups, determine whether the collected data is summed correctly. If not, it may not be an EV1 tag.
|
||||
if hardnested_utils.hardnested_first_byte_num == 256:
|
||||
got_match = False
|
||||
for i in range(len(hardnested_utils.hardnested_sums)):
|
||||
if hardnested_utils.hardnested_first_byte_sum == hardnested_utils.hardnested_sums[i]:
|
||||
got_match = True # Sum matches successfully, and we can try to decrypt it next.
|
||||
break
|
||||
if got_match:
|
||||
print(f"Acquire finish, save to file [nonces.bin], size is {len(nonces_buffer)}bytes")
|
||||
break
|
||||
else:
|
||||
print(
|
||||
f"hardnested_first_byte_num exceeds the limit but got_match is false: {hardnested_utils.hardnested_first_byte_sum}")
|
||||
else:
|
||||
continue # Continue acquire
|
||||
|
||||
# ------------------------ write nonces to bin ------------------------
|
||||
with open("nonces.bin", mode="wb+") as fd:
|
||||
fd.write(nonces_buffer)
|
||||
|
||||
# You can decrypt nonce bin by pm3 client, or any app if support pm3 nonce bin format.
|
||||
# TODO If CU bin can decrypt, run cmd on here...
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required (VERSION 3.1)
|
||||
cmake_minimum_required (VERSION 3.5)
|
||||
|
||||
project (mifare C)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user