Merge remote-tracking branch 'xero/dev' into mntm-dev

This commit is contained in:
WillyJL
2025-09-06 03:21:05 +02:00
28 changed files with 1144 additions and 268 deletions

View File

@@ -38,7 +38,6 @@ extern "C" {
#define MF_ULTRALIGHT_TEARING_FLAG_NUM (3)
#define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4)
#define MF_ULTRALIGHT_AUTH_PACK_SIZE (2)
#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL)
#define MF_ULTRALIGHT_C_AUTH_RESPONSE_SIZE (9)
#define MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE (16)
@@ -48,11 +47,6 @@ extern "C" {
#define MF_ULTRALIGHT_C_AUTH_RND_A_BLOCK_OFFSET (0)
#define MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET (8)
#define MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE (MF_ULTRALIGHT_C_AUTH_DATA_SIZE + 1)
#define MF_ULTRALIGHT_C_DEFAULT_KEY \
(uint8_t[]) { \
0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, \
0x46 \
}
typedef enum {
MfUltralightErrorNone,

View File

@@ -251,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller*
instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version);
instance->state = MfUltralightPollerStateGetFeatureSet;
} else {
FURI_LOG_D(TAG, "Didn't response. Check Ultralight C");
FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
instance->state = MfUltralightPollerStateDetectMfulC;
}
@@ -266,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo
instance->data->type = MfUltralightTypeMfulC;
instance->state = MfUltralightPollerStateGetFeatureSet;
} else {
FURI_LOG_D(TAG, "Didn't response. Check NTAG 203");
FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203");
instance->state = MfUltralightPollerStateDetectNtag203;
}
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
@@ -445,36 +445,104 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance
static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPoller* instance) {
NfcCommand command = NfcCommandContinue;
FURI_LOG_D(TAG, "MfulC auth");
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest;
do {
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest;
command = instance->callback(instance->general_event, instance->context);
if(!instance->mfu_event.data->auth_context.skip_auth) {
FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
instance->error =
mf_ultralight_poller_auth_tdes(instance, &instance->auth_context);
if(instance->error == MfUltralightErrorNone &&
instance->auth_context.auth_success) {
FURI_LOG_D(TAG, "Auth success");
command = instance->callback(instance->general_event, instance->context);
if(!instance->mfu_event.data->auth_context.skip_auth) {
FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
// Only use the key if it was actually provided
if(instance->mfu_event.data->key_request_data.key_provided) {
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
} else if(instance->mode == MfUltralightPollerModeDictAttack) {
// TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary");
// Trigger dictionary key request
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
command = instance->callback(instance->general_event, instance->context);
if(!instance->mfu_event.data->key_request_data.key_provided) {
instance->state = MfUltralightPollerStateReadPages;
return command;
} else {
FURI_LOG_D(TAG, "Auth failed");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
}
} else {
// We assume here that it is card read without explicitly provided key
// So we try to auth with default one
instance->state = MfUltralightPollerStateTryDefaultMfulCKey;
break;
FURI_LOG_D(TAG, "No key provided, skipping auth");
instance->state = MfUltralightPollerStateReadPages;
return command;
}
instance->auth_context.auth_success = false;
// For debugging
FURI_LOG_D(
"TAG",
"Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
instance->auth_context.tdes_key.data[0],
instance->auth_context.tdes_key.data[1],
instance->auth_context.tdes_key.data[2],
instance->auth_context.tdes_key.data[3],
instance->auth_context.tdes_key.data[4],
instance->auth_context.tdes_key.data[5],
instance->auth_context.tdes_key.data[6],
instance->auth_context.tdes_key.data[7],
instance->auth_context.tdes_key.data[8],
instance->auth_context.tdes_key.data[9],
instance->auth_context.tdes_key.data[10],
instance->auth_context.tdes_key.data[11],
instance->auth_context.tdes_key.data[12],
instance->auth_context.tdes_key.data[13],
instance->auth_context.tdes_key.data[14],
instance->auth_context.tdes_key.data[15]);
do {
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
furi_hal_random_fill_buf(RndA, sizeof(RndA));
instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output);
if(instance->error != MfUltralightErrorNone) break;
uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
instance->error = mf_ultralight_poller_authenticate_end(
instance, RndB, output, decoded_shifted_RndA);
if(instance->error != MfUltralightErrorNone) break;
mf_ultralight_3des_shift_data(RndA);
instance->auth_context.auth_success =
(memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
if(instance->auth_context.auth_success) {
FURI_LOG_E(TAG, "Auth success");
if(instance->mode == MfUltralightPollerModeDictAttack) {
memcpy(
&instance->data->page[44],
instance->auth_context.tdes_key.data,
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
// Continue to read pages after successful authentication
instance->state = MfUltralightPollerStateReadPages;
}
}
} while(false);
if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) {
FURI_LOG_E(TAG, "Auth failed");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
if(instance->mode == MfUltralightPollerModeDictAttack) {
// Not needed? We already do a callback earlier?
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
command = instance->callback(instance->general_event, instance->context);
if(!instance->mfu_event.data->key_request_data.key_provided) {
instance->state = MfUltralightPollerStateReadPages;
} else {
instance->auth_context.tdes_key =
instance->mfu_event.data->key_request_data.key;
instance->state = MfUltralightPollerStateAuthMfulC;
}
}
}
}
}
// Regression review
if(instance->mode != MfUltralightPollerModeDictAttack) {
instance->state = MfUltralightPollerStateReadPages;
} while(false);
}
return command;
}
@@ -497,12 +565,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
instance->error = mf_ultralight_poller_read_page(instance, start_page, &data);
}
// Regression review
const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4;
if(instance->error == MfUltralightErrorNone) {
if(start_page < instance->pages_total) {
FURI_LOG_D(TAG, "Read page %d success", start_page);
instance->data->page[start_page] = data.page[0];
instance->pages_read++;
instance->data->pages_read = instance->pages_read;
for(size_t i = 0; i < read_cnt; i++) {
if(start_page + i < instance->pages_total) {
FURI_LOG_D(TAG, "Read page %d success", start_page + i);
instance->data->page[start_page + i] = data.page[i];
instance->pages_read++;
instance->data->pages_read = instance->pages_read;
}
}
if(instance->pages_read == instance->pages_total) {
@@ -570,40 +642,6 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll
return NfcCommandContinue;
}
static NfcCommand
mf_ultralight_poller_handler_try_default_ultralight_c_key(MfUltralightPoller* instance) {
do {
if(!mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
break;
}
if(instance->auth_context.auth_success) {
break;
}
FURI_LOG_D(TAG, "Trying authentication with default 3DES key");
memcpy(
&instance->auth_context.tdes_key.data,
MF_ULTRALIGHT_C_DEFAULT_KEY,
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
instance->error = mf_ultralight_poller_auth_tdes(instance, &instance->auth_context);
if(instance->error == MfUltralightErrorNone && instance->auth_context.auth_success) {
FURI_LOG_D(TAG, "Default 3DES key detected");
} else {
FURI_LOG_D(TAG, "Authentication attempt with default 3DES key failed");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
}
} while(false);
instance->state = MfUltralightPollerStateReadPages;
return NfcCommandContinue;
}
static NfcCommand
mf_ultralight_poller_handler_check_mfuc_auth_status(MfUltralightPoller* instance) {
instance->state = MfUltralightPollerStateReadSuccess;
@@ -768,8 +806,6 @@ static const MfUltralightPollerReadHandler
mf_ultralight_poller_handler_read_tearing_flags,
[MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth,
[MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass,
[MfUltralightPollerStateTryDefaultMfulCKey] =
mf_ultralight_poller_handler_try_default_ultralight_c_key,
[MfUltralightPollerStateCheckMfulCAuthStatus] =
mf_ultralight_poller_handler_check_mfuc_auth_status,
[MfUltralightPollerStateAuthMfulC] = mf_ultralight_poller_handler_auth_ultralight_c,
@@ -781,7 +817,6 @@ static const MfUltralightPollerReadHandler
[MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages,
[MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail,
[MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success,
};
static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) {

View File

@@ -27,6 +27,7 @@ typedef enum {
MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */
MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */
MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */
MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */
} MfUltralightPollerEventType;
/**
@@ -35,6 +36,7 @@ typedef enum {
typedef enum {
MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */
MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */
MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */
} MfUltralightPollerMode;
/**
@@ -42,20 +44,29 @@ typedef enum {
*/
typedef struct {
MfUltralightAuthPassword password; /**< Password to be used for authentication. */
MfUltralightC3DesAuthKey tdes_key;
MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */
MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */
MfUltralightAuthPack pack; /**< Pack received on successful authentication. */
bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */
bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */
} MfUltralightPollerAuthContext;
/**
* @brief MfUltralight poller key request data.
*/
typedef struct {
MfUltralightC3DesAuthKey key; /**< Key to try. */
bool key_provided; /**< Set to true if key was provided, false to stop attack. */
} MfUltralightPollerKeyRequestData;
/**
* @brief MfUltralight poller event data.
*/
typedef union {
MfUltralightPollerAuthContext auth_context; /**< Authentication context. */
MfUltralightError error; /**< Error code indicating reading fail reason. */
const MfUltralightData* write_data;
MfUltralightPollerMode poller_mode;
const MfUltralightData* write_data; /**< Data to be written to card. */
MfUltralightPollerMode poller_mode; /**< Mode to operate in. */
MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */
} MfUltralightPollerEventData;
/**
@@ -64,7 +75,7 @@ typedef union {
* Upon emission of an event, an instance of this struct will be passed to the callback.
*/
typedef struct {
MfUltralightPollerEventType type; /**< Type of emmitted event. */
MfUltralightPollerEventType type; /**< Type of emitted event. */
MfUltralightPollerEventData* data; /**< Pointer to event specific data. */
} MfUltralightPollerEvent;
@@ -81,19 +92,6 @@ MfUltralightError mf_ultralight_poller_auth_pwd(
MfUltralightPoller* instance,
MfUltralightPollerAuthContext* data);
/**
* @brief Perform 3DES authentication with key.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in, out] data pointer to the authentication context.
* @return MfUltralightErrorNone on success, an error code on failure.
*/
MfUltralightError mf_ultralight_poller_auth_tdes(
MfUltralightPoller* instance,
MfUltralightPollerAuthContext* data);
/**
* @brief Start authentication procedure.
*

View File

@@ -1,7 +1,6 @@
#include "mf_ultralight_poller_i.h"
#include <furi.h>
#include <furi_hal.h>
#define TAG "MfUltralightPoller"
@@ -63,38 +62,6 @@ MfUltralightError mf_ultralight_poller_auth_pwd(
return ret;
}
MfUltralightError mf_ultralight_poller_auth_tdes(
MfUltralightPoller* instance,
MfUltralightPollerAuthContext* data) {
furi_check(instance);
furi_check(data);
MfUltralightError ret = MfUltralightErrorNone;
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
furi_hal_random_fill_buf(RndA, sizeof(RndA));
ret = mf_ultralight_poller_authenticate_start(instance, RndA, output);
if(ret != MfUltralightErrorNone) {
return ret;
}
uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
ret = mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_shifted_RndA);
if(ret != MfUltralightErrorNone) {
return ret;
}
mf_ultralight_3des_shift_data(RndA);
data->auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
return ret;
}
static MfUltralightError mf_ultralight_poller_send_authenticate_cmd(
MfUltralightPoller* instance,
const uint8_t* cmd,

View File

@@ -11,6 +11,8 @@ extern "C" {
#define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000)
#define MF_ULTRALIGHT_MAX_BUFF_SIZE (64)
#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL)
#define MF_ULTRALIGHT_IS_NTAG_I2C(type) \
(((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \
((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K))
@@ -59,7 +61,6 @@ typedef enum {
MfUltralightPollerStateAuthMfulC,
MfUltralightPollerStateReadPages,
MfUltralightPollerStateTryDefaultPass,
MfUltralightPollerStateTryDefaultMfulCKey,
MfUltralightPollerStateCheckMfulCAuthStatus,
MfUltralightPollerStateReadFailed,
MfUltralightPollerStateReadSuccess,

View File

@@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur
furi_string_cat_printf(key_str, "%02X", key_int[i]);
}
static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) {
static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) {
furi_assert(instance);
furi_assert(key_str);
furi_assert(key_int);
furi_assert(key_out);
uint8_t key_byte_tmp;
char h, l;
*key_int = 0ULL;
// Process two hex characters at a time to create each byte
for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) {
h = furi_string_get_char(key_str, i);
l = furi_string_get_char(key_str, i + 1);
args_char_to_hex(h, l, &key_byte_tmp);
*key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2));
key_out[i / 2] = key_byte_tmp;
}
}
@@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) {
bool key_read = keys_dict_get_next_key_str(instance, temp_key);
if(key_read) {
size_t tmp_len = key_size;
uint64_t key_int = 0;
keys_dict_str_to_int(instance, temp_key, &key_int);
while(tmp_len--) {
key[tmp_len] = (uint8_t)key_int;
key_int >>= 8;
}
keys_dict_str_to_int(instance, temp_key, key);
}
furi_string_free(temp_key);