Merge pull request #276 from Foxushka/ultralight-key-log

Mifare Ultralight key logger from reader
This commit is contained in:
GameTec-live
2025-08-19 19:59:39 +02:00
committed by GitHub
8 changed files with 276 additions and 22 deletions
+75 -20
View File
@@ -1415,6 +1415,54 @@ static data_frame_tx_t *cmd_processor_mf0_ntag_set_write_mode(uint16_t cmd, uint
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}
static data_frame_tx_t *cmd_processor_mf0_ntag_set_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
if (length != 1 || data[0] > 1) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
nfc_tag_mf0_ntag_detection_log_clear();
nfc_tag_mf0_ntag_set_detection_enable(data[0]);
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}
static data_frame_tx_t *cmd_processor_mf0_ntag_get_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
uint8_t is_enable = nfc_tag_mf0_ntag_is_detection_enable();
return data_frame_make(cmd, STATUS_SUCCESS, 1, (uint8_t *)(&is_enable));
}
static data_frame_tx_t *cmd_processor_mf0_ntag_get_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
uint32_t count = nfc_tag_mf0_ntag_detection_log_count();
if (count == 0xFFFFFFFF) {
count = 0;
}
uint32_t payload = U32HTONL(count);
return data_frame_make(cmd, STATUS_SUCCESS, sizeof(uint32_t), (uint8_t *)&payload);
}
static data_frame_tx_t *cmd_processor_mf0_ntag_get_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
uint32_t count;
uint32_t index;
uint8_t *resp = NULL;
nfc_tag_mf0_ntag_auth_log_t *logs = mf0_get_auth_log(&count);
if (length != 4 || count == 0xFFFFFFFF) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
index = U32NTOHL(*(uint32_t *)data);
if (index >= count) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
resp = (uint8_t *)(logs + index);
length = MIN(count - index, NETDATA_MAX_DATA_LENGTH / sizeof(nfc_tag_mf0_ntag_auth_log_t)) * sizeof(nfc_tag_mf0_ntag_auth_log_t);
return data_frame_make(cmd, STATUS_SUCCESS, length, resp);
}
static data_frame_tx_t *cmd_processor_mf0_get_emulator_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
uint8_t mf0_info[3] = {};
mf0_info[0] = nfc_tag_mf0_ntag_is_detection_enable();
mf0_info[1] = nfc_tag_mf0_ntag_get_uid_mode();
mf0_info[2] = nfc_tag_mf0_ntag_get_write_mode();
return data_frame_make(cmd, STATUS_SUCCESS, 3, mf0_info);
}
/**
* (cmd -> processor) function map, the map struct is:
* cmd code before process cmd processor after process
@@ -1486,9 +1534,10 @@ static cmd_data_map_t m_data_cmd_map[] = {
#endif
{ DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_write_emu_block_data, NULL },
{ DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL },
{ DATA_CMD_HF14A_SET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_set_anti_coll_data, NULL },
{ DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_write_emu_block_data, NULL },
{ DATA_CMD_MF1_SET_DETECTION_ENABLE, NULL, cmd_processor_mf1_set_detection_enable, NULL },
{ DATA_CMD_MF1_GET_DETECTION_COUNT, NULL, cmd_processor_mf1_get_detection_count, NULL },
{ DATA_CMD_MF1_GET_DETECTION_LOG, NULL, cmd_processor_mf1_get_detection_log, NULL },
@@ -1503,25 +1552,31 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_set_block_anti_coll_mode, NULL },
{ DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL },
{ DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL },
{ DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_get_uid_mode, NULL },
{ DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL },
{ DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_read_emu_page_data, NULL },
{ DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_write_emu_page_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_get_version_data, NULL },
{ DATA_CMD_MF0_NTAG_SET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_set_version_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_get_signature_data, NULL },
{ DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_set_signature_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_get_counter_data, NULL },
{ DATA_CMD_MF0_NTAG_SET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_set_counter_data, NULL },
{ DATA_CMD_MF0_NTAG_RESET_AUTH_CNT, NULL, cmd_processor_mf0_ntag_reset_auth_cnt, NULL },
{ DATA_CMD_MF0_NTAG_GET_PAGE_COUNT, NULL, cmd_processor_mf0_ntag_get_emu_page_count, NULL },
{ DATA_CMD_MF0_NTAG_GET_WRITE_MODE, NULL, cmd_processor_mf0_ntag_get_write_mode, NULL },
{ DATA_CMD_MF0_NTAG_SET_WRITE_MODE, NULL, cmd_processor_mf0_ntag_set_write_mode, NULL },
{ DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL },
{ DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL },
{ DATA_CMD_HIDPROX_SET_EMU_ID, NULL, cmd_processor_hidprox_set_emu_id, NULL },
{ DATA_CMD_HIDPROX_GET_EMU_ID, NULL, cmd_processor_hidprox_get_emu_id, NULL },
{ DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_get_uid_mode, NULL },
{ DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL },
{ DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_read_emu_page_data, NULL },
{ DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_write_emu_page_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_get_version_data, NULL },
{ DATA_CMD_MF0_NTAG_SET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_set_version_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_get_signature_data, NULL },
{ DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_set_signature_data, NULL },
{ DATA_CMD_MF0_NTAG_GET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_get_counter_data, NULL },
{ DATA_CMD_MF0_NTAG_SET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_set_counter_data, NULL },
{ DATA_CMD_MF0_NTAG_RESET_AUTH_CNT, NULL, cmd_processor_mf0_ntag_reset_auth_cnt, NULL },
{ DATA_CMD_MF0_NTAG_GET_PAGE_COUNT, NULL, cmd_processor_mf0_ntag_get_emu_page_count, NULL },
{ DATA_CMD_MF0_NTAG_GET_WRITE_MODE, NULL, cmd_processor_mf0_ntag_get_write_mode, NULL },
{ DATA_CMD_MF0_NTAG_SET_WRITE_MODE, NULL, cmd_processor_mf0_ntag_set_write_mode, NULL },
{ DATA_CMD_MF0_NTAG_SET_DETECTION_ENABLE, NULL, cmd_processor_mf0_ntag_set_detection_enable, NULL },
{ DATA_CMD_MF0_NTAG_GET_DETECTION_COUNT, NULL, cmd_processor_mf0_ntag_get_detection_count, NULL },
{ DATA_CMD_MF0_NTAG_GET_DETECTION_LOG, NULL, cmd_processor_mf0_ntag_get_detection_log, NULL },
{ DATA_CMD_MF0_NTAG_GET_DETECTION_ENABLE, NULL, cmd_processor_mf0_ntag_get_detection_enable, NULL },
{ DATA_CMD_MF0_NTAG_GET_EMULATOR_CONFIG, NULL, cmd_processor_mf0_get_emulator_config, NULL },
{ DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL },
{ DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL },
{ DATA_CMD_HIDPROX_SET_EMU_ID, NULL, cmd_processor_hidprox_set_emu_id, NULL },
{ DATA_CMD_HIDPROX_GET_EMU_ID, NULL, cmd_processor_hidprox_get_emu_id, NULL },
};
data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
+5
View File
@@ -128,6 +128,11 @@
#define DATA_CMD_MF0_NTAG_GET_PAGE_COUNT (4030)
#define DATA_CMD_MF0_NTAG_GET_WRITE_MODE (4031)
#define DATA_CMD_MF0_NTAG_SET_WRITE_MODE (4032)
#define DATA_CMD_MF0_NTAG_SET_DETECTION_ENABLE (4033)
#define DATA_CMD_MF0_NTAG_GET_DETECTION_COUNT (4034)
#define DATA_CMD_MF0_NTAG_GET_DETECTION_LOG (4035)
#define DATA_CMD_MF0_NTAG_GET_DETECTION_ENABLE (4036)
#define DATA_CMD_MF0_NTAG_GET_EMULATOR_CONFIG (4037)
//
// ******************************************************************
@@ -135,6 +135,12 @@ static tag_specific_type_t m_tag_type;
static bool m_tag_authenticated = false;
static bool m_did_first_read = false;
#define MF0_NTAG_AUTH_LOG_MAX 32
static __attribute__((section(".noinit_mf0"))) struct nfc_tag_mf0_auth_log_buffer {
nfc_tag_mf0_ntag_auth_log_t logs[MF0_NTAG_AUTH_LOG_MAX];
uint32_t count;
} m_auth_log = {.count = 0};
int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type) {
int nr_pages = -1;
@@ -924,6 +930,14 @@ static void handle_pwd_auth_command(uint8_t *p_data) {
uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET];
uint32_t supplied_pwd = *(uint32_t *)&p_data[1];
if (m_tag_information->config.detection_enable && m_auth_log.count < MF0_NTAG_AUTH_LOG_MAX) {
memcpy(m_auth_log.logs[m_auth_log.count].pwd, &p_data[1], 4);
m_auth_log.count++;
NRF_LOG_INFO("NTAG password: %02x%02x%02x%02x",
p_data[1], p_data[2], p_data[3], p_data[4]);
}
if (pwd != supplied_pwd) {
if (auth_lim) {
cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR;
@@ -1238,6 +1252,7 @@ bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
// default ntag config
p_ntag_information->config.mode_uid_magic = false;
p_ntag_information->config.mode_block_write = NFC_TAG_MF0_NTAG_WRITE_NORMAL;
p_ntag_information->config.detection_enable = false;
// save data to flash
tag_sense_type_t sense_type = get_sense_type_from_tag_type(tag_type);
@@ -1282,3 +1297,26 @@ nfc_tag_mf0_ntag_write_mode_t nfc_tag_mf0_ntag_get_write_mode(void) {
return m_tag_information->config.mode_block_write;
}
nfc_tag_mf0_ntag_auth_log_t *mf0_get_auth_log(uint32_t *count) {
*count = m_auth_log.count;
return m_auth_log.logs;
}
void nfc_tag_mf0_ntag_set_detection_enable(bool enable) {
if (m_tag_type == TAG_TYPE_UNDEFINED || m_tag_information == NULL) return;
m_tag_information->config.detection_enable = enable;
}
bool nfc_tag_mf0_ntag_is_detection_enable(void) {
if (m_tag_type == TAG_TYPE_UNDEFINED || m_tag_information == NULL) return false;
return m_tag_information->config.detection_enable;
}
void nfc_tag_mf0_ntag_detection_log_clear(void) {
m_auth_log.count = 0;
}
uint32_t nfc_tag_mf0_ntag_detection_log_count(void) {
return m_auth_log.count;
}
@@ -56,12 +56,19 @@ typedef enum {
NFC_TAG_MF0_NTAG_WRITE_SHADOW_REQ = 4u,
} nfc_tag_mf0_ntag_write_mode_t;
// M0/NTAG password authentication log for key collection
typedef struct {
uint8_t pwd[4]; // Password used in authentication
} PACKED nfc_tag_mf0_ntag_auth_log_t;
typedef struct {
uint8_t mode_uid_magic: 1;
// New field for write mode
nfc_tag_mf0_ntag_write_mode_t mode_block_write: 3;
// Enable key detection (password logging)
uint8_t detection_enable: 1;
// reserve remaining bits
uint8_t reserved1: 4;
uint8_t reserved1: 3;
uint8_t reserved2;
uint8_t reserved3;
} nfc_tag_mf0_ntag_configure_t;
@@ -93,4 +100,10 @@ bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled);
void nfc_tag_mf0_ntag_set_write_mode(nfc_tag_mf0_ntag_write_mode_t write_mode);
nfc_tag_mf0_ntag_write_mode_t nfc_tag_mf0_ntag_get_write_mode(void);
nfc_tag_mf0_ntag_auth_log_t *mf0_get_auth_log(uint32_t *count);
void nfc_tag_mf0_ntag_set_detection_enable(bool enable);
bool nfc_tag_mf0_ntag_is_detection_enable(void);
void nfc_tag_mf0_ntag_detection_log_clear(void);
uint32_t nfc_tag_mf0_ntag_detection_log_count(void);
#endif
@@ -201,7 +201,7 @@ static struct Crypto1State *pcs = &mpcs;
// Define the buffer of the data that stored the detected data
// Place this data in a dormant RAM to save time and space to write into Flash
#define MF1_AUTH_LOG_MAX_SIZE 1000
static __attribute__((section(".noinit"))) struct nfc_tag_mf1_auth_log_buffer {
static __attribute__((section(".noinit_mf1"))) struct nfc_tag_mf1_auth_log_buffer {
uint32_t count;
nfc_tag_mf1_auth_log_t logs[MF1_AUTH_LOG_MAX_SIZE];
} m_auth_log;
+79
View File
@@ -2882,6 +2882,12 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
help="Set data to be returned by the READ_SIG command.")
parser.add_argument('--reset-auth-cnt', action='store_true',
help="Resets the counter of unsuccessful authentication attempts.")
detection_group = parser.add_mutually_exclusive_group()
detection_group.add_argument('--enable-log', action='store_true',
help="Enable password authentication logging")
detection_group.add_argument('--disable-log', action='store_true',
help="Disable password authentication logging")
return parser
def on_exec(self, args: argparse.Namespace):
@@ -2977,6 +2983,30 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
except:
print(f"{CR}Failed to set write mode. Check if device firmware supports this feature.{C0}")
detection = self.cmd.mf0_ntag_get_detection_enable()
if args.enable_log:
change_requested = True
if detection is not None:
if not detection:
detection = True
self.cmd.mf0_ntag_set_detection_enable(detection)
change_done = True
else:
print(f'{CY}Requested logging of MFU authentication data already enabled{C0}')
else:
print(f'{CR}Detection functionality not available in this firmware{C0}')
elif args.disable_log:
change_requested = True
if detection is not None:
if detection:
detection = False
self.cmd.mf0_ntag_set_detection_enable(detection)
change_done = True
else:
print(f'{CY}Requested logging of MFU authentication data already disabled{C0}')
else:
print(f'{CR}Detection functionality not available in this firmware{C0}')
if change_done or aux_data_changed:
print(' - MFU/NTAG Emulator settings updated')
if not (change_requested or aux_data_change_requested):
@@ -3015,6 +3045,55 @@ class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequired
except:
pass
try:
detection = self.cmd.mf0_ntag_get_detection_enable()
print(
f'- {"Log (password) mode:":40}{f"{CG}enabled{C0}" if detection else f"{CR}disabled{C0}"}')
except:
pass
@hf_mfu.command('edetect')
class HFMFUEDetect(SlotIndexArgsAndGoUnit, DeviceRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = 'Get Mifare Ultralight / NTAG emulator detection logs'
self.add_slot_args(parser)
parser.add_argument('--count', type=int, help="Number of log entries to retrieve", metavar="COUNT")
parser.add_argument('--index', type=int, default=0, help="Starting index (default: 0)", metavar="INDEX")
return parser
def on_exec(self, args: argparse.Namespace):
detection_enabled = self.cmd.mf0_ntag_get_detection_enable()
if not detection_enabled:
print(f"{CY}Detection logging is disabled for this slot{C0}")
return
total_count = self.cmd.mf0_ntag_get_detection_count()
print(f"Total detection log entries: {total_count}")
if total_count == 0:
print(f"{CY}No detection logs available{C0}")
return
if args.count is not None:
entries_to_get = min(args.count, total_count - args.index)
else:
entries_to_get = total_count - args.index
if entries_to_get <= 0:
print(f"{CY}No entries available from index {args.index}{C0}")
return
logs = self.cmd.mf0_ntag_get_detection_log(args.index)
print(f"\nPassword detection logs (showing {len(logs)} entries from index {args.index}):")
print("-" * 50)
for i, log_entry in enumerate(logs):
actual_index = args.index + i
password = log_entry['password']
print(f"{actual_index:3d}: {CY}{password.upper()}{C0}")
@lf_em_410x.command('read')
class LFEMRead(ReaderRequiredUnit):
+58
View File
@@ -662,6 +662,64 @@ class ChameleonCMD:
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):
"""
+6
View File
@@ -115,6 +115,12 @@ class Command(enum.IntEnum):
MF0_NTAG_GET_PAGE_COUNT = 4030
MF0_NTAG_GET_WRITE_MODE = 4031
MF0_NTAG_SET_WRITE_MODE = 4032
MF0_NTAG_SET_DETECTION_ENABLE = 4033
MF0_NTAG_GET_DETECTION_COUNT = 4034
MF0_NTAG_GET_DETECTION_LOG = 4035
MF0_NTAG_GET_DETECTION_ENABLE = 4036
# FIXME: not implemented
MF0_NTAG_GET_EMULATOR_CONFIG = 4037
EM410X_SET_EMU_ID = 5000
EM410X_GET_EMU_ID = 5001