1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00

NFC: Ultralight C App Key Management, Dictionary Attack (#4271)

* Upstream Ultralight C dictionary attack (squashed)

* linter: formatting

* unit_tests: nfc: split nfc data to named var

* Fix mf_ultralight_poller_sync_read_card

* linter: suppressed warnings on TODOs

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
Nathan N
2025-09-29 13:05:06 -04:00
committed by GitHub
parent eea53491de
commit e7634d7563
25 changed files with 1094 additions and 165 deletions

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);
@@ -452,7 +452,48 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
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;
// 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: -nofl 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 {
instance->auth_context.tdes_key =
instance->mfu_event.data->key_request_data.key;
}
} else {
// Fallback: use key from auth context (for sync poller compatibility)
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
}
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};
@@ -469,20 +510,40 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
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_D(TAG, "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_D(TAG, "Auth failed");
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;
}
}
}
}
}
instance->state = MfUltralightPollerStateReadPages;
// Regression review
if(instance->mode != MfUltralightPollerModeDictAttack) {
instance->state = MfUltralightPollerStateReadPages;
}
return command;
}
@@ -505,12 +566,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) {
@@ -753,7 +818,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;

View File

@@ -134,7 +134,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start(
uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
mf_ultralight_3des_decrypt(
&instance->des_context,
instance->mfu_event.data->auth_context.tdes_key.data,
instance->auth_context.tdes_key.data,
iv,
encRndB,
sizeof(encRndB),
@@ -145,7 +145,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start(
mf_ultralight_3des_encrypt(
&instance->des_context,
instance->mfu_event.data->auth_context.tdes_key.data,
instance->auth_context.tdes_key.data,
encRndB,
output,
MF_ULTRALIGHT_C_AUTH_DATA_SIZE,
@@ -179,7 +179,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end(
mf_ultralight_3des_decrypt(
&instance->des_context,
instance->mfu_event.data->auth_context.tdes_key.data,
instance->auth_context.tdes_key.data,
RndB,
bit_buffer_get_data(instance->rx_buffer) + 1,
MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE,

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);