NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
#include "mf_classic_poller_i.h"
|
|
|
|
|
|
|
|
|
|
#include <nfc/protocols/nfc_poller_base.h>
|
|
|
|
|
|
|
|
|
|
#include <furi.h>
|
|
|
|
|
|
|
|
|
|
#define TAG "MfClassicPoller"
|
|
|
|
|
|
2024-09-16 05:28:04 -04:00
|
|
|
// TODO: Reflect status in NFC app (second text line, progress bar)
|
2024-09-03 05:54:59 -04:00
|
|
|
// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches
|
2024-09-03 11:36:19 -04:00
|
|
|
// TODO: Load dictionaries specific to a CUID to not clutter the user dictionary
|
2024-09-16 05:28:04 -04:00
|
|
|
// TODO: Validate Hardnested is collecting nonces from the correct block
|
2024-09-17 14:38:14 -04:00
|
|
|
// TODO: Nested entrypoint for cached keys
|
2024-09-02 12:45:39 -04:00
|
|
|
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
|
|
|
|
|
2024-09-09 20:50:15 -04:00
|
|
|
// Ordered by frequency, labeled chronologically
|
|
|
|
|
const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[] = {
|
|
|
|
|
{{{0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}, MfClassicBackdoorAuth3}, // Fudan (static encrypted)
|
|
|
|
|
{{{0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}, MfClassicBackdoorAuth1}, // Fudan, Infineon, NXP
|
|
|
|
|
{{{0x51, 0x8b, 0x33, 0x54, 0xe7, 0x60}}, MfClassicBackdoorAuth2}, // Fudan
|
|
|
|
|
};
|
|
|
|
|
const size_t mf_classic_backdoor_keys_count =
|
|
|
|
|
sizeof(mf_classic_backdoor_keys) / sizeof(mf_classic_backdoor_keys[0]);
|
2024-09-01 22:30:37 -04:00
|
|
|
const uint16_t valid_sums[] =
|
|
|
|
|
{0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256};
|
2024-08-28 09:26:59 -04:00
|
|
|
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance);
|
|
|
|
|
|
|
|
|
|
MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) {
|
|
|
|
|
furi_assert(iso14443_3a_poller);
|
|
|
|
|
|
|
|
|
|
MfClassicPoller* instance = malloc(sizeof(MfClassicPoller));
|
|
|
|
|
instance->iso14443_3a_poller = iso14443_3a_poller;
|
|
|
|
|
instance->data = mf_classic_alloc();
|
|
|
|
|
instance->crypto = crypto1_alloc();
|
|
|
|
|
instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
|
|
|
|
instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
|
|
|
|
instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
|
|
|
|
instance->rx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
|
|
|
|
instance->current_type_check = MfClassicType4k;
|
2024-01-12 17:41:19 +09:00
|
|
|
instance->card_state = MfClassicCardStateLost;
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
|
|
|
|
|
instance->mfc_event.data = &instance->mfc_event_data;
|
|
|
|
|
|
|
|
|
|
instance->general_event.protocol = NfcProtocolMfClassic;
|
|
|
|
|
instance->general_event.event_data = &instance->mfc_event;
|
|
|
|
|
instance->general_event.instance = instance;
|
|
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mf_classic_poller_free(MfClassicPoller* instance) {
|
|
|
|
|
furi_assert(instance);
|
|
|
|
|
furi_assert(instance->data);
|
|
|
|
|
furi_assert(instance->crypto);
|
|
|
|
|
furi_assert(instance->tx_plain_buffer);
|
|
|
|
|
furi_assert(instance->rx_plain_buffer);
|
|
|
|
|
furi_assert(instance->tx_encrypted_buffer);
|
|
|
|
|
furi_assert(instance->rx_encrypted_buffer);
|
|
|
|
|
|
|
|
|
|
mf_classic_free(instance->data);
|
|
|
|
|
crypto1_free(instance->crypto);
|
|
|
|
|
bit_buffer_free(instance->tx_plain_buffer);
|
|
|
|
|
bit_buffer_free(instance->rx_plain_buffer);
|
|
|
|
|
bit_buffer_free(instance->tx_encrypted_buffer);
|
|
|
|
|
bit_buffer_free(instance->rx_encrypted_buffer);
|
|
|
|
|
|
2024-09-11 16:22:52 -04:00
|
|
|
// Clean up resources in MfClassicPollerDictAttackContext
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
// Free the dictionaries
|
|
|
|
|
if(dict_attack_ctx->mf_classic_system_dict) {
|
|
|
|
|
keys_dict_free(dict_attack_ctx->mf_classic_system_dict);
|
|
|
|
|
dict_attack_ctx->mf_classic_system_dict = NULL;
|
|
|
|
|
}
|
|
|
|
|
if(dict_attack_ctx->mf_classic_user_dict) {
|
|
|
|
|
keys_dict_free(dict_attack_ctx->mf_classic_user_dict);
|
|
|
|
|
dict_attack_ctx->mf_classic_user_dict = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Free the nested nonce array if it exists
|
|
|
|
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
|
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
|
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
free(instance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance) {
|
|
|
|
|
MfClassicPollerEventDataUpdate* data_update = &instance->mfc_event_data.data_update;
|
|
|
|
|
|
|
|
|
|
mf_classic_get_read_sectors_and_keys(
|
|
|
|
|
instance->data, &data_update->sectors_read, &data_update->keys_found);
|
|
|
|
|
data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector;
|
2024-09-03 15:19:12 -04:00
|
|
|
data_update->nested_phase = instance->mode_ctx.dict_attack_ctx.nested_phase;
|
|
|
|
|
data_update->prng_type = instance->mode_ctx.dict_attack_ctx.prng_type;
|
|
|
|
|
data_update->backdoor = instance->mode_ctx.dict_attack_ctx.backdoor;
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate;
|
|
|
|
|
return instance->callback(instance->general_event, instance->context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void mf_classic_poller_check_key_b_is_readable(
|
|
|
|
|
MfClassicPoller* instance,
|
|
|
|
|
uint8_t block_num,
|
|
|
|
|
MfClassicBlock* data) {
|
|
|
|
|
do {
|
|
|
|
|
if(!mf_classic_is_sector_trailer(block_num)) break;
|
|
|
|
|
if(!mf_classic_is_allowed_access(
|
|
|
|
|
instance->data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBRead))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data;
|
2024-02-14 13:41:42 +09:00
|
|
|
uint64_t key_b = bit_lib_bytes_to_num_be(sec_tr->key_b.data, sizeof(MfClassicKey));
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
|
|
|
|
mf_classic_set_key_found(instance->data, sector_num, MfClassicKeyTypeB, key_b);
|
|
|
|
|
} while(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandReset;
|
|
|
|
|
|
|
|
|
|
if(instance->current_type_check == MfClassicType4k) {
|
|
|
|
|
iso14443_3a_copy(
|
|
|
|
|
instance->data->iso14443_3a_data,
|
|
|
|
|
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
2024-08-28 09:26:59 -04:00
|
|
|
MfClassicError error =
|
|
|
|
|
mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
instance->data->type = MfClassicType4k;
|
|
|
|
|
instance->state = MfClassicPollerStateStart;
|
|
|
|
|
instance->current_type_check = MfClassicType4k;
|
|
|
|
|
FURI_LOG_D(TAG, "4K detected");
|
|
|
|
|
} else {
|
|
|
|
|
instance->current_type_check = MfClassicType1k;
|
|
|
|
|
}
|
|
|
|
|
} else if(instance->current_type_check == MfClassicType1k) {
|
2024-08-28 09:26:59 -04:00
|
|
|
MfClassicError error =
|
|
|
|
|
mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
instance->data->type = MfClassicType1k;
|
|
|
|
|
FURI_LOG_D(TAG, "1K detected");
|
|
|
|
|
} else {
|
|
|
|
|
instance->data->type = MfClassicTypeMini;
|
|
|
|
|
FURI_LOG_D(TAG, "Mini detected");
|
|
|
|
|
}
|
|
|
|
|
instance->current_type_check = MfClassicType4k;
|
|
|
|
|
instance->state = MfClassicPollerStateStart;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
|
|
|
|
|
instance->sectors_total = mf_classic_get_total_sectors_num(instance->data->type);
|
|
|
|
|
memset(&instance->mode_ctx, 0, sizeof(MfClassicPollerModeContext));
|
|
|
|
|
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestMode;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
|
|
|
|
|
if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) {
|
|
|
|
|
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
2024-08-28 09:26:59 -04:00
|
|
|
instance->state = MfClassicPollerStateAnalyzeBackdoor;
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) {
|
|
|
|
|
instance->state = MfClassicPollerStateRequestReadSector;
|
|
|
|
|
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) {
|
|
|
|
|
instance->state = MfClassicPollerStateRequestSectorTrailer;
|
|
|
|
|
} else {
|
|
|
|
|
furi_crash("Invalid mode selected");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_request_sector_trailer(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
|
|
|
|
|
|
|
|
|
if(write_ctx->current_sector == instance->sectors_total) {
|
|
|
|
|
instance->state = MfClassicPollerStateSuccess;
|
|
|
|
|
} else {
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestSectorTrailer;
|
|
|
|
|
instance->mfc_event_data.sec_tr_data.sector_num = write_ctx->current_sector;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
if(instance->mfc_event_data.sec_tr_data.sector_trailer_provided) {
|
|
|
|
|
instance->state = MfClassicPollerStateCheckWriteConditions;
|
|
|
|
|
memcpy(
|
|
|
|
|
&write_ctx->sec_tr,
|
|
|
|
|
&instance->mfc_event_data.sec_tr_data.sector_trailer,
|
|
|
|
|
sizeof(MfClassicSectorTrailer));
|
|
|
|
|
write_ctx->current_block =
|
|
|
|
|
MAX(1, mf_classic_get_first_block_num_of_sector(write_ctx->current_sector));
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
write_ctx->current_sector++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_handler_check_write_conditions(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
|
|
|
|
|
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
|
|
|
|
MfClassicSectorTrailer* sec_tr = &write_ctx->sec_tr;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// Check last block in sector to write
|
|
|
|
|
uint8_t sec_tr_block_num =
|
|
|
|
|
mf_classic_get_sector_trailer_num_by_sector(write_ctx->current_sector);
|
|
|
|
|
if(write_ctx->current_block == sec_tr_block_num) {
|
|
|
|
|
write_ctx->current_sector++;
|
|
|
|
|
instance->state = MfClassicPollerStateRequestSectorTrailer;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check write and read access
|
|
|
|
|
if(mf_classic_is_allowed_access_data_block(
|
|
|
|
|
sec_tr, write_ctx->current_block, MfClassicKeyTypeA, MfClassicActionDataWrite)) {
|
|
|
|
|
write_ctx->key_type_write = MfClassicKeyTypeA;
|
|
|
|
|
} else if(mf_classic_is_allowed_access_data_block(
|
|
|
|
|
sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
MfClassicKeyTypeB,
|
|
|
|
|
MfClassicActionDataWrite)) {
|
|
|
|
|
write_ctx->key_type_write = MfClassicKeyTypeB;
|
|
|
|
|
} else if(mf_classic_is_value_block(sec_tr, write_ctx->current_block)) {
|
|
|
|
|
write_ctx->is_value_block = true;
|
|
|
|
|
} else {
|
|
|
|
|
FURI_LOG_D(TAG, "Not allowed to write block %d", write_ctx->current_block);
|
|
|
|
|
write_ctx->current_block++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mf_classic_is_allowed_access_data_block(
|
|
|
|
|
sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
write_ctx->key_type_write,
|
|
|
|
|
MfClassicActionDataRead)) {
|
|
|
|
|
write_ctx->key_type_read = write_ctx->key_type_write;
|
|
|
|
|
} else {
|
|
|
|
|
write_ctx->key_type_read = write_ctx->key_type_write == MfClassicKeyTypeA ?
|
|
|
|
|
MfClassicKeyTypeB :
|
|
|
|
|
MfClassicKeyTypeA;
|
|
|
|
|
if(!mf_classic_is_allowed_access_data_block(
|
|
|
|
|
sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
write_ctx->key_type_read,
|
|
|
|
|
MfClassicActionDataRead)) {
|
|
|
|
|
FURI_LOG_D(TAG, "Not allowed to read block %d", write_ctx->current_block);
|
|
|
|
|
write_ctx->current_block++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_ctx->need_halt_before_write =
|
|
|
|
|
(write_ctx->key_type_read != write_ctx->key_type_write);
|
|
|
|
|
instance->state = MfClassicPollerStateReadBlock;
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
|
|
|
|
|
|
|
|
|
MfClassicKey* auth_key = write_ctx->key_type_read == MfClassicKeyTypeA ?
|
|
|
|
|
&write_ctx->sec_tr.key_a :
|
|
|
|
|
&write_ctx->sec_tr.key_b;
|
|
|
|
|
MfClassicError error = MfClassicErrorNone;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// Authenticate to sector
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_auth(
|
2024-08-28 09:26:59 -04:00
|
|
|
instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block);
|
|
|
|
|
instance->state = MfClassicPollerStateFail;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read block from tag
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_read_block(
|
|
|
|
|
instance, write_ctx->current_block, &write_ctx->tag_block);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block);
|
|
|
|
|
instance->state = MfClassicPollerStateFail;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(write_ctx->is_value_block) {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance->state = MfClassicPollerStateWriteValueBlock;
|
|
|
|
|
} else {
|
|
|
|
|
if(write_ctx->need_halt_before_write) {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
}
|
|
|
|
|
instance->state = MfClassicPollerStateWriteBlock;
|
|
|
|
|
}
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
|
|
|
|
|
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
|
|
|
|
MfClassicKey* auth_key = write_ctx->key_type_write == MfClassicKeyTypeA ?
|
|
|
|
|
&write_ctx->sec_tr.key_a :
|
|
|
|
|
&write_ctx->sec_tr.key_b;
|
|
|
|
|
MfClassicError error = MfClassicErrorNone;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// Request block to write
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock;
|
|
|
|
|
instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
if(!instance->mfc_event_data.write_block_data.write_block_provided) break;
|
|
|
|
|
|
|
|
|
|
// Compare tag and saved block
|
|
|
|
|
if(memcmp(
|
|
|
|
|
write_ctx->tag_block.data,
|
|
|
|
|
instance->mfc_event_data.write_block_data.write_block.data,
|
|
|
|
|
sizeof(MfClassicBlock)) == 0) {
|
|
|
|
|
FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reauth if necessary
|
|
|
|
|
if(write_ctx->need_halt_before_write) {
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_auth(
|
2024-08-28 09:26:59 -04:00
|
|
|
instance,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
auth_key,
|
|
|
|
|
write_ctx->key_type_write,
|
|
|
|
|
NULL,
|
|
|
|
|
false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_D(
|
|
|
|
|
TAG, "Failed to auth to block %d for writing", write_ctx->current_block);
|
|
|
|
|
instance->state = MfClassicPollerStateFail;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write block
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_write_block(
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
&instance->mfc_event_data.write_block_data.write_block);
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block);
|
|
|
|
|
instance->state = MfClassicPollerStateFail;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} while(false);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
write_ctx->current_block++;
|
|
|
|
|
instance->state = MfClassicPollerStateCheckWriteConditions;
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// Request block to write
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock;
|
|
|
|
|
instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
if(!instance->mfc_event_data.write_block_data.write_block_provided) break;
|
|
|
|
|
|
|
|
|
|
// Compare tag and saved block
|
|
|
|
|
if(memcmp(
|
|
|
|
|
write_ctx->tag_block.data,
|
|
|
|
|
instance->mfc_event_data.write_block_data.write_block.data,
|
|
|
|
|
sizeof(MfClassicBlock)) == 0) {
|
|
|
|
|
FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block(
|
|
|
|
|
&write_ctx->sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
MfClassicKeyTypeA,
|
|
|
|
|
MfClassicActionDataInc);
|
|
|
|
|
bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block(
|
|
|
|
|
&write_ctx->sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
MfClassicKeyTypeB,
|
|
|
|
|
MfClassicActionDataInc);
|
|
|
|
|
bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block(
|
|
|
|
|
&write_ctx->sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
MfClassicKeyTypeA,
|
|
|
|
|
MfClassicActionDataDec);
|
|
|
|
|
bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block(
|
|
|
|
|
&write_ctx->sec_tr,
|
|
|
|
|
write_ctx->current_block,
|
|
|
|
|
MfClassicKeyTypeB,
|
|
|
|
|
MfClassicActionDataDec);
|
|
|
|
|
|
|
|
|
|
int32_t source_value = 0;
|
|
|
|
|
int32_t target_value = 0;
|
|
|
|
|
if(!mf_classic_block_to_value(
|
|
|
|
|
&instance->mfc_event_data.write_block_data.write_block, &source_value, NULL))
|
|
|
|
|
break;
|
|
|
|
|
if(!mf_classic_block_to_value(&write_ctx->tag_block, &target_value, NULL)) break;
|
|
|
|
|
|
|
|
|
|
MfClassicKeyType auth_key_type = MfClassicKeyTypeA;
|
|
|
|
|
MfClassicValueCommand value_cmd = MfClassicValueCommandIncrement;
|
|
|
|
|
int32_t diff = source_value - target_value;
|
|
|
|
|
if(diff > 0) {
|
|
|
|
|
if(key_a_inc_allowed) {
|
|
|
|
|
auth_key_type = MfClassicKeyTypeA;
|
|
|
|
|
value_cmd = MfClassicValueCommandIncrement;
|
|
|
|
|
} else if(key_b_inc_allowed) {
|
|
|
|
|
auth_key_type = MfClassicKeyTypeB;
|
|
|
|
|
value_cmd = MfClassicValueCommandIncrement;
|
|
|
|
|
} else {
|
|
|
|
|
FURI_LOG_D(TAG, "Unable to increment value block");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if(key_a_dec_allowed) {
|
|
|
|
|
auth_key_type = MfClassicKeyTypeA;
|
|
|
|
|
value_cmd = MfClassicValueCommandDecrement;
|
|
|
|
|
diff *= -1;
|
|
|
|
|
} else if(key_b_dec_allowed) {
|
|
|
|
|
auth_key_type = MfClassicKeyTypeB;
|
|
|
|
|
value_cmd = MfClassicValueCommandDecrement;
|
|
|
|
|
diff *= -1;
|
|
|
|
|
} else {
|
|
|
|
|
FURI_LOG_D(TAG, "Unable to decrement value block");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a :
|
|
|
|
|
&write_ctx->sec_tr.key_b;
|
|
|
|
|
|
2024-08-28 09:26:59 -04:00
|
|
|
MfClassicError error = mf_classic_poller_auth(
|
|
|
|
|
instance, write_ctx->current_block, key, auth_key_type, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) break;
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_value_cmd(instance, write_ctx->current_block, value_cmd, diff);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) break;
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_value_transfer(instance, write_ctx->current_block);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) break;
|
|
|
|
|
|
|
|
|
|
} while(false);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
write_ctx->is_value_block = false;
|
|
|
|
|
write_ctx->current_block++;
|
|
|
|
|
instance->state = MfClassicPollerStateCheckWriteConditions;
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_request_read_sector(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
|
|
|
|
|
MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx;
|
|
|
|
|
MfClassicPollerEventDataReadSectorRequest* sec_read =
|
|
|
|
|
&instance->mfc_event_data.read_sector_request_data;
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestReadSector;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
|
|
|
|
|
if(!sec_read->key_provided) {
|
|
|
|
|
instance->state = MfClassicPollerStateSuccess;
|
|
|
|
|
} else {
|
|
|
|
|
sec_read_ctx->current_sector = sec_read->sector_num;
|
|
|
|
|
sec_read_ctx->key = sec_read->key;
|
|
|
|
|
sec_read_ctx->key_type = sec_read->key_type;
|
|
|
|
|
sec_read_ctx->current_block =
|
|
|
|
|
mf_classic_get_first_block_num_of_sector(sec_read->sector_num);
|
|
|
|
|
sec_read_ctx->auth_passed = false;
|
|
|
|
|
instance->state = MfClassicPollerStateReadSectorBlocks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
|
|
|
|
|
MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
MfClassicError error = MfClassicErrorNone;
|
|
|
|
|
|
|
|
|
|
if(!sec_read_ctx->auth_passed) {
|
2024-02-14 13:41:42 +09:00
|
|
|
uint64_t key = bit_lib_bytes_to_num_be(sec_read_ctx->key.data, sizeof(MfClassicKey));
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
FURI_LOG_D(
|
|
|
|
|
TAG,
|
|
|
|
|
"Auth to block %d with key %c: %06llx",
|
|
|
|
|
sec_read_ctx->current_block,
|
|
|
|
|
sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B',
|
|
|
|
|
key);
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_auth(
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance,
|
|
|
|
|
sec_read_ctx->current_block,
|
|
|
|
|
&sec_read_ctx->key,
|
|
|
|
|
sec_read_ctx->key_type,
|
2024-08-28 09:26:59 -04:00
|
|
|
NULL,
|
|
|
|
|
false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) break;
|
|
|
|
|
|
|
|
|
|
sec_read_ctx->auth_passed = true;
|
|
|
|
|
if(!mf_classic_is_key_found(
|
|
|
|
|
instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type)) {
|
|
|
|
|
mf_classic_set_key_found(
|
|
|
|
|
instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type, key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(mf_classic_is_block_read(instance->data, sec_read_ctx->current_block)) break;
|
|
|
|
|
|
|
|
|
|
FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block);
|
|
|
|
|
MfClassicBlock read_block = {};
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_read_block(instance, sec_read_ctx->current_block, &read_block);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block);
|
|
|
|
|
if(sec_read_ctx->key_type == MfClassicKeyTypeA) {
|
|
|
|
|
mf_classic_poller_check_key_b_is_readable(
|
|
|
|
|
instance, sec_read_ctx->current_block, &read_block);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
sec_read_ctx->auth_passed = false;
|
|
|
|
|
}
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector);
|
|
|
|
|
sec_read_ctx->current_block++;
|
|
|
|
|
if(sec_read_ctx->current_block > sec_tr_num) {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance->state = MfClassicPollerStateRequestReadSector;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-28 09:26:59 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) {
|
2024-09-17 14:38:14 -04:00
|
|
|
NfcCommand command = NfcCommandReset;
|
2024-08-28 09:26:59 -04:00
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
2024-09-09 20:50:15 -04:00
|
|
|
|
|
|
|
|
size_t current_key_index =
|
|
|
|
|
mf_classic_backdoor_keys_count - 1; // Default to the last valid index
|
|
|
|
|
|
|
|
|
|
// Find the current key in the backdoor_keys array
|
|
|
|
|
for(size_t i = 0; i < mf_classic_backdoor_keys_count; i++) {
|
|
|
|
|
if(memcmp(
|
|
|
|
|
dict_attack_ctx->current_key.data,
|
|
|
|
|
mf_classic_backdoor_keys[i].key.data,
|
|
|
|
|
sizeof(MfClassicKey)) == 0) {
|
|
|
|
|
current_key_index = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-28 09:26:59 -04:00
|
|
|
}
|
|
|
|
|
|
2024-09-09 20:50:15 -04:00
|
|
|
// Choose the next key to try
|
|
|
|
|
size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count;
|
|
|
|
|
uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1;
|
|
|
|
|
|
|
|
|
|
FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version);
|
|
|
|
|
dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key;
|
|
|
|
|
|
2024-08-28 09:26:59 -04:00
|
|
|
// Attempt backdoor authentication
|
|
|
|
|
MfClassicError error = mf_classic_poller_auth(
|
|
|
|
|
instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true);
|
2024-09-10 19:20:53 -04:00
|
|
|
if((next_key_index == 0) &&
|
|
|
|
|
(error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) {
|
2024-09-10 14:10:09 -04:00
|
|
|
FURI_LOG_E(TAG, "No backdoor identified");
|
|
|
|
|
dict_attack_ctx->backdoor = MfClassicBackdoorNone;
|
|
|
|
|
instance->state = MfClassicPollerStateRequestKey;
|
|
|
|
|
} else if(error == MfClassicErrorNone) {
|
2024-09-09 20:50:15 -04:00
|
|
|
FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version);
|
|
|
|
|
dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type;
|
2024-09-02 12:45:39 -04:00
|
|
|
instance->state = MfClassicPollerStateBackdoorReadSector;
|
2024-09-10 14:10:09 -04:00
|
|
|
} else if(
|
|
|
|
|
(error == MfClassicErrorAuth) &&
|
|
|
|
|
(next_key_index == (mf_classic_backdoor_keys_count - 1))) {
|
|
|
|
|
// We've tried all backdoor keys, this is a unique key and an important research finding
|
|
|
|
|
furi_crash("New backdoor: please report!");
|
2024-08-28 09:26:59 -04:00
|
|
|
}
|
2024-09-09 20:50:15 -04:00
|
|
|
|
2024-08-28 09:26:59 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-02 12:45:39 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
MfClassicError error = MfClassicErrorNone;
|
|
|
|
|
MfClassicBlock block = {};
|
|
|
|
|
|
|
|
|
|
uint8_t current_sector = mf_classic_get_sector_by_block(dict_attack_ctx->current_block);
|
|
|
|
|
uint8_t blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector);
|
|
|
|
|
uint8_t first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector);
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
if(dict_attack_ctx->current_block >= instance->sectors_total * 4) {
|
|
|
|
|
// We've read all blocks, reset current_block and move to next state
|
|
|
|
|
dict_attack_ctx->current_block = 0;
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Authenticate with the backdoor key
|
|
|
|
|
error = mf_classic_poller_auth(
|
|
|
|
|
instance,
|
|
|
|
|
first_block_in_sector, // Authenticate to the first block of the sector
|
2024-09-02 18:53:39 -04:00
|
|
|
&(dict_attack_ctx->current_key),
|
2024-09-02 12:45:39 -04:00
|
|
|
MfClassicKeyTypeA,
|
|
|
|
|
NULL,
|
|
|
|
|
true);
|
|
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG, "Failed to authenticate with backdoor key for sector %d", current_sector);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read all blocks in the sector
|
|
|
|
|
for(uint8_t block_in_sector = 0; block_in_sector < blocks_in_sector; block_in_sector++) {
|
|
|
|
|
uint8_t block_to_read = first_block_in_sector + block_in_sector;
|
|
|
|
|
|
|
|
|
|
error = mf_classic_poller_read_block(instance, block_to_read, &block);
|
|
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to read block %d", block_to_read);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the block as read in the data structure
|
|
|
|
|
mf_classic_set_block_read(instance->data, block_to_read, &block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Move to the next sector
|
|
|
|
|
current_sector++;
|
|
|
|
|
dict_attack_ctx->current_block = mf_classic_get_first_block_num_of_sector(current_sector);
|
|
|
|
|
|
|
|
|
|
// Update blocks_in_sector and first_block_in_sector for the next sector
|
|
|
|
|
if(current_sector < instance->sectors_total) {
|
|
|
|
|
blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector);
|
|
|
|
|
first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Halt the card after each sector to reset the authentication state
|
|
|
|
|
mf_classic_poller_halt(instance);
|
|
|
|
|
|
|
|
|
|
// Send an event to the app that a sector has been read
|
|
|
|
|
command = mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestKey;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
if(instance->mfc_event_data.key_request_data.key_provided) {
|
|
|
|
|
dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key;
|
|
|
|
|
instance->state = MfClassicPollerStateAuthKeyA;
|
|
|
|
|
} else {
|
|
|
|
|
instance->state = MfClassicPollerStateNextSector;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
if(mf_classic_is_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
|
|
|
|
instance->state = MfClassicPollerStateAuthKeyB;
|
|
|
|
|
} else {
|
|
|
|
|
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
2024-02-14 13:41:42 +09:00
|
|
|
uint64_t key =
|
|
|
|
|
bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
MfClassicError error = mf_classic_poller_auth(
|
2024-08-28 09:26:59 -04:00
|
|
|
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_I(TAG, "Key A found");
|
|
|
|
|
mf_classic_set_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA, key);
|
|
|
|
|
|
|
|
|
|
command = mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
|
|
|
|
dict_attack_ctx->current_block = block;
|
|
|
|
|
dict_attack_ctx->auth_passed = true;
|
|
|
|
|
instance->state = MfClassicPollerStateReadSector;
|
|
|
|
|
} else {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance->state = MfClassicPollerStateAuthKeyB;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
if(mf_classic_is_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) {
|
|
|
|
|
if(mf_classic_is_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
|
|
|
|
instance->state = MfClassicPollerStateNextSector;
|
|
|
|
|
} else {
|
|
|
|
|
instance->state = MfClassicPollerStateRequestKey;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
2024-02-14 13:41:42 +09:00
|
|
|
uint64_t key =
|
|
|
|
|
bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
MfClassicError error = mf_classic_poller_auth(
|
2024-08-28 09:26:59 -04:00
|
|
|
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_I(TAG, "Key B found");
|
|
|
|
|
mf_classic_set_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB, key);
|
|
|
|
|
|
|
|
|
|
command = mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
|
|
|
|
dict_attack_ctx->current_block = block;
|
|
|
|
|
|
|
|
|
|
dict_attack_ctx->auth_passed = true;
|
|
|
|
|
instance->state = MfClassicPollerStateReadSector;
|
|
|
|
|
} else {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance->state = MfClassicPollerStateRequestKey;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
dict_attack_ctx->current_sector++;
|
|
|
|
|
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
|
|
|
|
instance->state = MfClassicPollerStateSuccess;
|
|
|
|
|
} else {
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeNextSector;
|
|
|
|
|
instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
instance->state = MfClassicPollerStateRequestKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
MfClassicError error = MfClassicErrorNone;
|
|
|
|
|
uint8_t block_num = dict_attack_ctx->current_block;
|
|
|
|
|
MfClassicBlock block = {};
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
if(mf_classic_is_block_read(instance->data, block_num)) break;
|
|
|
|
|
|
|
|
|
|
if(!dict_attack_ctx->auth_passed) {
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_auth(
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance,
|
|
|
|
|
block_num,
|
|
|
|
|
&dict_attack_ctx->current_key,
|
|
|
|
|
dict_attack_ctx->current_key_type,
|
2024-08-28 09:26:59 -04:00
|
|
|
NULL,
|
|
|
|
|
false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
instance->state = MfClassicPollerStateNextSector;
|
|
|
|
|
FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FURI_LOG_D(TAG, "Reading block %d", block_num);
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_read_block(instance, block_num, &block);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
|
|
|
|
FURI_LOG_D(TAG, "Failed to read block %d", block_num);
|
|
|
|
|
} else {
|
|
|
|
|
mf_classic_set_block_read(instance->data, block_num, &block);
|
|
|
|
|
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
|
|
|
|
mf_classic_poller_check_key_b_is_readable(instance, block_num, &block);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
uint8_t sec_tr_block_num =
|
|
|
|
|
mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->current_sector);
|
|
|
|
|
dict_attack_ctx->current_block++;
|
|
|
|
|
if(dict_attack_ctx->current_block > sec_tr_block_num) {
|
|
|
|
|
mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
|
|
|
|
|
|
|
|
|
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
|
|
|
|
instance->state = MfClassicPollerStateNextSector;
|
|
|
|
|
} else {
|
|
|
|
|
dict_attack_ctx->reuse_key_sector = dict_attack_ctx->current_sector;
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
|
|
|
|
instance->mfc_event_data.key_attack_data.current_sector =
|
|
|
|
|
dict_attack_ctx->reuse_key_sector;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
do {
|
|
|
|
|
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
|
|
|
|
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseAuthKeyB;
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
} else {
|
2024-08-01 09:10:02 -04:00
|
|
|
dict_attack_ctx->reuse_key_sector++;
|
|
|
|
|
if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) {
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
2024-08-20 16:31:39 -04:00
|
|
|
// Nested entrypoint
|
2024-09-12 14:28:18 -04:00
|
|
|
bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone;
|
|
|
|
|
if((nested_active &&
|
|
|
|
|
(dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) ||
|
|
|
|
|
(!(nested_active) && !(mf_classic_is_card_read(instance->data)))) {
|
2024-08-20 16:31:39 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-01 09:10:02 -04:00
|
|
|
instance->state = MfClassicPollerStateRequestKey;
|
|
|
|
|
} else {
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
|
|
|
|
instance->mfc_event_data.key_attack_data.current_sector =
|
|
|
|
|
dict_attack_ctx->reuse_key_sector;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseAuthKeyA;
|
|
|
|
|
}
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
}
|
2024-08-01 09:10:02 -04:00
|
|
|
} while(false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-21 00:50:27 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_key_reuse_start_no_offset(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
|
|
|
|
instance->mfc_event_data.key_attack_data.current_sector = dict_attack_ctx->reuse_key_sector;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseAuthKeyA;
|
|
|
|
|
} else {
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseAuthKeyB;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
if(mf_classic_is_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA)) {
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
} else {
|
|
|
|
|
uint8_t block =
|
|
|
|
|
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector);
|
2024-02-14 13:41:42 +09:00
|
|
|
uint64_t key =
|
|
|
|
|
bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
MfClassicError error = mf_classic_poller_auth(
|
2024-08-28 09:26:59 -04:00
|
|
|
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_I(TAG, "Key A found");
|
|
|
|
|
mf_classic_set_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key);
|
|
|
|
|
|
|
|
|
|
command = mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
|
|
|
|
dict_attack_ctx->current_block = block;
|
|
|
|
|
dict_attack_ctx->auth_passed = true;
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseReadSector;
|
|
|
|
|
} else {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
if(mf_classic_is_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB)) {
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
} else {
|
|
|
|
|
uint8_t block =
|
|
|
|
|
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector);
|
2024-02-14 13:41:42 +09:00
|
|
|
uint64_t key =
|
|
|
|
|
bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key);
|
|
|
|
|
|
2023-11-15 12:32:45 +04:00
|
|
|
MfClassicError error = mf_classic_poller_auth(
|
2024-08-28 09:26:59 -04:00
|
|
|
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error == MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_I(TAG, "Key B found");
|
|
|
|
|
mf_classic_set_key_found(
|
|
|
|
|
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key);
|
|
|
|
|
|
|
|
|
|
command = mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
|
|
|
|
dict_attack_ctx->current_block = block;
|
|
|
|
|
|
|
|
|
|
dict_attack_ctx->auth_passed = true;
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseReadSector;
|
|
|
|
|
} else {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
MfClassicError error = MfClassicErrorNone;
|
|
|
|
|
uint8_t block_num = dict_attack_ctx->current_block;
|
|
|
|
|
MfClassicBlock block = {};
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
if(mf_classic_is_block_read(instance->data, block_num)) break;
|
|
|
|
|
|
|
|
|
|
if(!dict_attack_ctx->auth_passed) {
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_auth(
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
instance,
|
|
|
|
|
block_num,
|
|
|
|
|
&dict_attack_ctx->current_key,
|
|
|
|
|
dict_attack_ctx->current_key_type,
|
2024-08-28 09:26:59 -04:00
|
|
|
NULL,
|
|
|
|
|
false);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FURI_LOG_D(TAG, "Reading block %d", block_num);
|
2023-11-15 12:32:45 +04:00
|
|
|
error = mf_classic_poller_read_block(instance, block_num, &block);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
|
|
|
|
FURI_LOG_D(TAG, "Failed to read block %d", block_num);
|
|
|
|
|
} else {
|
|
|
|
|
mf_classic_set_block_read(instance->data, block_num, &block);
|
|
|
|
|
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
|
|
|
|
mf_classic_poller_check_key_b_is_readable(instance, block_num, &block);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
uint16_t sec_tr_block_num =
|
|
|
|
|
mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector);
|
|
|
|
|
dict_attack_ctx->current_block++;
|
|
|
|
|
if(dict_attack_ctx->current_block > sec_tr_block_num) {
|
2023-11-15 12:32:45 +04:00
|
|
|
mf_classic_poller_halt(instance);
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
|
|
|
|
|
|
|
|
|
mf_classic_poller_handle_data_update(instance);
|
|
|
|
|
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
// Helper function to add a nonce to the array
|
2024-08-03 21:35:52 -04:00
|
|
|
static bool add_nested_nonce(
|
|
|
|
|
MfClassicNestedNonceArray* array,
|
|
|
|
|
uint32_t cuid,
|
2024-08-19 07:33:17 -04:00
|
|
|
uint16_t key_idx,
|
2024-08-03 21:35:52 -04:00
|
|
|
uint32_t nt,
|
|
|
|
|
uint32_t nt_enc,
|
|
|
|
|
uint8_t par,
|
|
|
|
|
uint16_t dist) {
|
2024-08-19 07:33:17 -04:00
|
|
|
MfClassicNestedNonce* new_nonces;
|
|
|
|
|
if(array->count == 0) {
|
|
|
|
|
new_nonces = malloc(sizeof(MfClassicNestedNonce));
|
|
|
|
|
} else {
|
|
|
|
|
new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce));
|
|
|
|
|
}
|
2024-08-03 21:35:52 -04:00
|
|
|
if(new_nonces == NULL) return false;
|
2024-08-01 09:10:02 -04:00
|
|
|
|
|
|
|
|
array->nonces = new_nonces;
|
|
|
|
|
array->nonces[array->count].cuid = cuid;
|
|
|
|
|
array->nonces[array->count].key_idx = key_idx;
|
|
|
|
|
array->nonces[array->count].nt = nt;
|
|
|
|
|
array->nonces[array->count].nt_enc = nt_enc;
|
|
|
|
|
array->nonces[array->count].par = par;
|
|
|
|
|
array->nonces[array->count].dist = dist;
|
|
|
|
|
array->count++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
2024-08-19 07:33:17 -04:00
|
|
|
uint8_t hard_nt_count = 0;
|
2024-08-01 09:10:02 -04:00
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) {
|
|
|
|
|
MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i];
|
|
|
|
|
if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++;
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
|
|
|
|
|
2024-09-01 22:30:37 -04:00
|
|
|
if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) {
|
2024-08-01 09:10:02 -04:00
|
|
|
dict_attack_ctx->prng_type = MfClassicPrngTypeHard;
|
|
|
|
|
// FIXME: E -> D
|
|
|
|
|
FURI_LOG_E(TAG, "Detected Hard PRNG");
|
|
|
|
|
} else {
|
|
|
|
|
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
|
|
|
|
|
// FIXME: E -> D
|
|
|
|
|
FURI_LOG_E(TAG, "Detected Weak PRNG");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandReset;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
MfClassicNt nt = {};
|
2024-08-28 09:26:59 -04:00
|
|
|
MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt, false);
|
2024-08-01 09:10:02 -04:00
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
|
|
|
|
// FIXME: E -> D
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to collect nt");
|
|
|
|
|
} else {
|
|
|
|
|
// FIXME: E -> D
|
|
|
|
|
FURI_LOG_E(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]);
|
|
|
|
|
uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt));
|
2024-08-19 07:33:17 -04:00
|
|
|
if(!add_nested_nonce(
|
|
|
|
|
&dict_attack_ctx->nested_nonce,
|
|
|
|
|
iso14443_3a_get_cuid(instance->data->iso14443_3a_data),
|
|
|
|
|
0,
|
|
|
|
|
nt_data,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
0)) {
|
|
|
|
|
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
|
|
|
|
}
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
2024-08-01 09:10:02 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 23:53:21 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) {
|
2024-09-10 14:10:09 -04:00
|
|
|
// TODO: Discard outliers (e.g. greater than 3 standard deviations)
|
2024-08-04 23:53:21 -04:00
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
2024-08-28 09:26:59 -04:00
|
|
|
uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT];
|
2024-08-04 23:53:21 -04:00
|
|
|
|
2024-08-14 02:25:57 -04:00
|
|
|
dict_attack_ctx->d_min = UINT16_MAX;
|
|
|
|
|
dict_attack_ctx->d_max = 0;
|
2024-08-20 16:31:39 -04:00
|
|
|
uint8_t block =
|
|
|
|
|
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector);
|
2024-08-04 23:53:21 -04:00
|
|
|
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
|
|
|
|
|
|
|
|
|
MfClassicAuthContext auth_ctx = {};
|
|
|
|
|
MfClassicError error;
|
|
|
|
|
|
|
|
|
|
uint32_t nt_prev = 0;
|
|
|
|
|
uint32_t nt_enc_prev = 0;
|
|
|
|
|
uint32_t same_nt_enc_cnt = 0;
|
2024-08-09 20:22:19 -04:00
|
|
|
uint8_t nt_enc_collected = 0;
|
2024-09-02 22:34:44 -04:00
|
|
|
bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
|
2024-08-04 23:53:21 -04:00
|
|
|
|
|
|
|
|
// Step 1: Perform full authentication once
|
|
|
|
|
error = mf_classic_poller_auth(
|
|
|
|
|
instance,
|
|
|
|
|
block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
|
|
|
|
dict_attack_ctx->nested_known_key_type,
|
2024-08-28 09:26:59 -04:00
|
|
|
&auth_ctx,
|
2024-09-02 22:34:44 -04:00
|
|
|
use_backdoor);
|
2024-08-04 23:53:21 -04:00
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to perform full authentication");
|
|
|
|
|
instance->state = MfClassicPollerStateNestedCalibrate;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FURI_LOG_E(TAG, "Full authentication successful");
|
|
|
|
|
|
|
|
|
|
nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
|
|
|
|
|
2024-09-02 22:34:44 -04:00
|
|
|
if((dict_attack_ctx->static_encrypted) &&
|
2024-09-09 20:50:15 -04:00
|
|
|
(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3)) {
|
2024-09-03 09:26:14 -04:00
|
|
|
command = NfcCommandReset;
|
2024-09-02 22:34:44 -04:00
|
|
|
uint8_t target_block =
|
|
|
|
|
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4);
|
2024-09-17 14:38:14 -04:00
|
|
|
MfClassicKeyType target_key_type =
|
|
|
|
|
((dict_attack_ctx->nested_target_key % 4) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB;
|
2024-09-02 22:34:44 -04:00
|
|
|
error = mf_classic_poller_auth_nested(
|
|
|
|
|
instance,
|
|
|
|
|
target_block,
|
|
|
|
|
&dict_attack_ctx->nested_known_key,
|
2024-09-17 14:38:14 -04:00
|
|
|
target_key_type,
|
2024-09-02 22:34:44 -04:00
|
|
|
&auth_ctx,
|
|
|
|
|
use_backdoor,
|
|
|
|
|
false);
|
|
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to perform nested authentication for static encrypted tag");
|
|
|
|
|
instance->state = MfClassicPollerStateNestedCalibrate;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
|
|
|
|
// Store the decrypted static encrypted nonce
|
|
|
|
|
dict_attack_ctx->static_encrypted_nonce =
|
|
|
|
|
decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key);
|
|
|
|
|
|
|
|
|
|
dict_attack_ctx->calibrated = true;
|
|
|
|
|
|
|
|
|
|
FURI_LOG_E(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc);
|
|
|
|
|
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Original calibration logic for non-static encrypted tags
|
2024-08-04 23:53:21 -04:00
|
|
|
// Step 2: Perform nested authentication multiple times
|
2024-08-28 09:26:59 -04:00
|
|
|
for(uint8_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT;
|
2024-08-04 23:53:21 -04:00
|
|
|
collection_cycle++) {
|
|
|
|
|
error = mf_classic_poller_auth_nested(
|
|
|
|
|
instance,
|
|
|
|
|
block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
|
|
|
|
dict_attack_ctx->nested_known_key_type,
|
2024-08-09 20:22:19 -04:00
|
|
|
&auth_ctx,
|
2024-09-02 22:34:44 -04:00
|
|
|
use_backdoor,
|
2024-08-09 20:22:19 -04:00
|
|
|
false);
|
2024-08-04 23:53:21 -04:00
|
|
|
|
|
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-09 20:22:19 -04:00
|
|
|
nt_enc_temp_arr[collection_cycle] =
|
|
|
|
|
bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
|
|
|
|
nt_enc_collected++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < nt_enc_collected; i++) {
|
|
|
|
|
if(nt_enc_temp_arr[i] == nt_enc_prev) {
|
2024-08-04 23:53:21 -04:00
|
|
|
same_nt_enc_cnt++;
|
|
|
|
|
if(same_nt_enc_cnt > 3) {
|
2024-08-09 20:22:19 -04:00
|
|
|
dict_attack_ctx->static_encrypted = true;
|
2024-08-04 23:53:21 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
same_nt_enc_cnt = 0;
|
2024-08-09 20:22:19 -04:00
|
|
|
nt_enc_prev = nt_enc_temp_arr[i];
|
2024-08-04 23:53:21 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-09 20:22:19 -04:00
|
|
|
if(dict_attack_ctx->static_encrypted) {
|
2024-08-04 23:53:21 -04:00
|
|
|
FURI_LOG_E(TAG, "Static encrypted nonce detected");
|
2024-09-02 22:34:44 -04:00
|
|
|
dict_attack_ctx->calibrated = true;
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
2024-08-04 23:53:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the distance between each nonce
|
|
|
|
|
FURI_LOG_E(TAG, "Calculating distance between nonces");
|
2024-08-20 16:31:39 -04:00
|
|
|
uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6);
|
2024-08-28 09:26:59 -04:00
|
|
|
for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT;
|
2024-08-04 23:53:21 -04:00
|
|
|
collection_cycle++) {
|
|
|
|
|
bool found = false;
|
2024-08-20 16:31:39 -04:00
|
|
|
uint32_t decrypted_nt_enc = decrypt_nt_enc(
|
|
|
|
|
cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key);
|
2024-08-04 23:53:21 -04:00
|
|
|
for(int i = 0; i < 65535; i++) {
|
|
|
|
|
uint32_t nth_successor = prng_successor(nt_prev, i);
|
2024-08-14 02:25:57 -04:00
|
|
|
if(nth_successor != decrypted_nt_enc) {
|
2024-08-04 23:53:21 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(collection_cycle > 0) {
|
|
|
|
|
FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor);
|
|
|
|
|
FURI_LOG_E(TAG, "dist from nt prev: %i", i);
|
2024-08-14 02:25:57 -04:00
|
|
|
if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i;
|
|
|
|
|
if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i;
|
2024-08-04 23:53:21 -04:00
|
|
|
}
|
|
|
|
|
nt_prev = nth_successor;
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if(!found) {
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
|
|
|
|
"Failed to find distance for nt_enc %08lx",
|
|
|
|
|
nt_enc_temp_arr[collection_cycle]);
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-14 02:25:57 -04:00
|
|
|
// Some breathing room, doesn't account for overflows or static nested (FIXME)
|
|
|
|
|
dict_attack_ctx->d_min -= 3;
|
|
|
|
|
dict_attack_ctx->d_max += 3;
|
|
|
|
|
|
|
|
|
|
furi_assert(dict_attack_ctx->d_min <= dict_attack_ctx->d_max);
|
2024-08-04 23:53:21 -04:00
|
|
|
dict_attack_ctx->calibrated = true;
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
|
|
|
|
|
mf_classic_poller_halt(instance);
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
2024-08-14 02:25:57 -04:00
|
|
|
"Calibration completed: min=%u max=%u static=%s",
|
|
|
|
|
dict_attack_ctx->d_min,
|
|
|
|
|
dict_attack_ctx->d_max,
|
|
|
|
|
(dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false");
|
2024-08-04 23:53:21 -04:00
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-01 22:30:37 -04:00
|
|
|
static inline void set_byte_found(uint8_t* found, uint8_t byte) {
|
|
|
|
|
SET_PACKED_BIT(found, byte);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline bool is_byte_found(uint8_t* found, uint8_t byte) {
|
|
|
|
|
return GET_PACKED_BIT(found, byte) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) {
|
2024-08-09 20:22:19 -04:00
|
|
|
// TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key)
|
2024-08-14 02:25:57 -04:00
|
|
|
// TODO: Look into using MfClassicNt more
|
2024-09-16 05:28:04 -04:00
|
|
|
NfcCommand command = NfcCommandContinue;
|
2024-08-01 09:10:02 -04:00
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
do {
|
2024-08-03 21:35:52 -04:00
|
|
|
uint8_t block =
|
2024-08-20 16:31:39 -04:00
|
|
|
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector);
|
2024-08-01 09:10:02 -04:00
|
|
|
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
2024-08-03 21:35:52 -04:00
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
MfClassicAuthContext auth_ctx = {};
|
|
|
|
|
MfClassicError error;
|
|
|
|
|
|
2024-09-10 09:03:11 -04:00
|
|
|
bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
|
2024-09-01 22:30:37 -04:00
|
|
|
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
|
|
|
|
|
uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0;
|
|
|
|
|
uint8_t nt_enc_per_collection =
|
2024-09-03 09:26:14 -04:00
|
|
|
(is_weak && !(dict_attack_ctx->static_encrypted)) ?
|
|
|
|
|
((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) :
|
|
|
|
|
1;
|
2024-09-17 14:38:14 -04:00
|
|
|
uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 4 : 2);
|
|
|
|
|
MfClassicKeyType target_key_type =
|
|
|
|
|
(dict_attack_ctx->nested_target_key % (is_weak ? 4 : 2) < (is_weak ? 2 : 1)) ?
|
|
|
|
|
MfClassicKeyTypeA :
|
|
|
|
|
MfClassicKeyTypeB;
|
|
|
|
|
uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector);
|
2024-08-09 20:22:19 -04:00
|
|
|
uint32_t nt_enc_temp_arr[nt_enc_per_collection];
|
|
|
|
|
uint8_t nt_enc_collected = 0;
|
2024-08-04 23:53:21 -04:00
|
|
|
uint8_t parity = 0;
|
2024-08-01 22:16:10 -04:00
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
// Step 1: Perform full authentication once
|
|
|
|
|
error = mf_classic_poller_auth(
|
|
|
|
|
instance,
|
|
|
|
|
block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
|
|
|
|
dict_attack_ctx->nested_known_key_type,
|
2024-08-28 09:26:59 -04:00
|
|
|
&auth_ctx,
|
2024-09-10 09:03:11 -04:00
|
|
|
use_backdoor);
|
2024-08-01 09:10:02 -04:00
|
|
|
|
2024-08-03 21:35:52 -04:00
|
|
|
if(error != MfClassicErrorNone) {
|
2024-08-01 09:10:02 -04:00
|
|
|
FURI_LOG_E(TAG, "Failed to perform full authentication");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FURI_LOG_E(TAG, "Full authentication successful");
|
|
|
|
|
|
2024-08-04 23:53:21 -04:00
|
|
|
// Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset
|
|
|
|
|
// eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the
|
|
|
|
|
// most commonly observed nonce from 4 auths to known sector and 5th to target. This gets us a nonce pair,
|
|
|
|
|
// at a known distance (confirmed by parity bits) telling us the nt_enc plain.
|
2024-08-09 20:22:19 -04:00
|
|
|
for(uint8_t collection_cycle = 0; collection_cycle < (nt_enc_per_collection - 1);
|
|
|
|
|
collection_cycle++) {
|
|
|
|
|
// This loop must match the calibrated loop
|
2024-08-01 22:16:10 -04:00
|
|
|
error = mf_classic_poller_auth_nested(
|
|
|
|
|
instance,
|
|
|
|
|
block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
|
|
|
|
dict_attack_ctx->nested_known_key_type,
|
2024-08-09 20:22:19 -04:00
|
|
|
&auth_ctx,
|
2024-09-10 09:03:11 -04:00
|
|
|
use_backdoor,
|
2024-08-09 20:22:19 -04:00
|
|
|
false);
|
2024-08-01 09:10:02 -04:00
|
|
|
|
2024-08-03 21:35:52 -04:00
|
|
|
if(error != MfClassicErrorNone) {
|
2024-08-09 20:22:19 -04:00
|
|
|
FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle);
|
|
|
|
|
break;
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-09 20:22:19 -04:00
|
|
|
nt_enc_temp_arr[collection_cycle] =
|
|
|
|
|
bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
|
|
|
|
nt_enc_collected++;
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-08-09 20:22:19 -04:00
|
|
|
error = mf_classic_poller_auth_nested(
|
|
|
|
|
instance,
|
|
|
|
|
target_block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
2024-08-09 20:22:19 -04:00
|
|
|
target_key_type,
|
|
|
|
|
&auth_ctx,
|
2024-08-28 09:26:59 -04:00
|
|
|
false,
|
2024-08-09 20:22:19 -04:00
|
|
|
true);
|
2024-08-01 09:10:02 -04:00
|
|
|
|
2024-08-09 20:22:19 -04:00
|
|
|
if(nt_enc_collected != (nt_enc_per_collection - 1)) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to collect sufficient nt_enc values");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
|
|
|
|
// Collect parity bits
|
|
|
|
|
const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer);
|
|
|
|
|
for(int i = 0; i < 4; i++) {
|
|
|
|
|
parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01);
|
|
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
|
|
|
|
|
uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0;
|
2024-09-03 09:26:14 -04:00
|
|
|
uint16_t dist = 0;
|
|
|
|
|
if(is_weak && !(dict_attack_ctx->static_encrypted)) {
|
2024-09-01 22:30:37 -04:00
|
|
|
// Decrypt the previous nonce
|
|
|
|
|
nt_prev = nt_enc_temp_arr[nt_enc_collected - 1];
|
|
|
|
|
decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key);
|
|
|
|
|
|
|
|
|
|
// Find matching nt_enc plain at expected distance
|
|
|
|
|
found_nt = 0;
|
|
|
|
|
uint8_t found_nt_cnt = 0;
|
|
|
|
|
uint16_t current_dist = dict_attack_ctx->d_min;
|
|
|
|
|
while(current_dist <= dict_attack_ctx->d_max) {
|
|
|
|
|
uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist);
|
|
|
|
|
if(nonce_matches_encrypted_parity_bits(
|
|
|
|
|
nth_successor, nth_successor ^ nt_enc, parity)) {
|
|
|
|
|
found_nt_cnt++;
|
|
|
|
|
if(found_nt_cnt > 1) {
|
|
|
|
|
FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
found_nt = nth_successor;
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
current_dist++;
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
if(found_nt_cnt != 1) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-09-03 09:26:14 -04:00
|
|
|
} else if(dict_attack_ctx->static_encrypted) {
|
2024-09-09 20:50:15 -04:00
|
|
|
if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) {
|
2024-09-03 09:26:14 -04:00
|
|
|
found_nt = dict_attack_ctx->static_encrypted_nonce;
|
|
|
|
|
} else {
|
|
|
|
|
dist = UINT16_MAX;
|
|
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
} else {
|
|
|
|
|
// Hardnested
|
|
|
|
|
if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) {
|
|
|
|
|
set_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF);
|
|
|
|
|
// Add unique parity to sum
|
|
|
|
|
dict_attack_ctx->msb_par_sum += nfc_util_even_parity32(parity & 0x08);
|
|
|
|
|
}
|
|
|
|
|
parity ^= 0x0F;
|
2024-08-14 02:25:57 -04:00
|
|
|
}
|
2024-08-01 09:10:02 -04:00
|
|
|
|
2024-08-09 20:22:19 -04:00
|
|
|
// Add the nonce to the array
|
2024-08-14 02:25:57 -04:00
|
|
|
if(add_nested_nonce(
|
|
|
|
|
&dict_attack_ctx->nested_nonce,
|
|
|
|
|
cuid,
|
|
|
|
|
dict_attack_ctx->nested_target_key,
|
|
|
|
|
found_nt,
|
|
|
|
|
nt_enc,
|
|
|
|
|
parity,
|
2024-09-03 09:26:14 -04:00
|
|
|
dist)) {
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = true;
|
2024-08-09 20:22:19 -04:00
|
|
|
} else {
|
2024-08-14 02:25:57 -04:00
|
|
|
FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?");
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-08-09 20:22:19 -04:00
|
|
|
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
|
|
|
|
"Target: %u (nonce pair %u, key type %s, block %u)",
|
|
|
|
|
dict_attack_ctx->nested_target_key,
|
|
|
|
|
nonce_pair_index,
|
|
|
|
|
(target_key_type == MfClassicKeyTypeA) ? "A" : "B",
|
|
|
|
|
target_block);
|
|
|
|
|
FURI_LOG_E(TAG, "cuid: %08lx", cuid);
|
|
|
|
|
FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc);
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
|
|
|
|
"parity: %u%u%u%u",
|
|
|
|
|
((parity >> 3) & 1),
|
|
|
|
|
((parity >> 2) & 1),
|
|
|
|
|
((parity >> 1) & 1),
|
|
|
|
|
(parity & 1));
|
|
|
|
|
FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev);
|
|
|
|
|
FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev);
|
2024-08-01 09:10:02 -04:00
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
|
|
|
|
|
mf_classic_poller_halt(instance);
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 16:31:39 -04:00
|
|
|
static MfClassicKey* search_dicts_for_nonce_key(
|
|
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx,
|
2024-08-19 07:33:17 -04:00
|
|
|
MfClassicNestedNonceArray* nonce_array,
|
2024-08-18 20:38:24 -04:00
|
|
|
KeysDict* system_dict,
|
|
|
|
|
KeysDict* user_dict,
|
2024-08-19 07:33:17 -04:00
|
|
|
bool is_weak) {
|
2024-08-18 20:38:24 -04:00
|
|
|
MfClassicKey stack_key;
|
2024-08-19 07:33:17 -04:00
|
|
|
KeysDict* dicts[] = {user_dict, system_dict};
|
2024-08-20 16:31:39 -04:00
|
|
|
bool is_resumed = dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume;
|
|
|
|
|
bool found_resume_point = false;
|
2024-08-18 20:38:24 -04:00
|
|
|
|
|
|
|
|
for(int i = 0; i < 2; i++) {
|
2024-08-27 06:32:00 -04:00
|
|
|
if(!dicts[i]) continue;
|
2024-08-18 20:38:24 -04:00
|
|
|
keys_dict_rewind(dicts[i]);
|
|
|
|
|
while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) {
|
2024-08-21 00:50:27 -04:00
|
|
|
if(is_resumed && !found_resume_point) {
|
|
|
|
|
found_resume_point =
|
|
|
|
|
(memcmp(
|
|
|
|
|
dict_attack_ctx->current_key.data,
|
|
|
|
|
stack_key.data,
|
|
|
|
|
sizeof(MfClassicKey)) == 0);
|
2024-08-20 16:31:39 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
2024-08-19 07:33:17 -04:00
|
|
|
bool full_match = true;
|
|
|
|
|
for(uint8_t j = 0; j < nonce_array->count; j++) {
|
|
|
|
|
// Verify nonce matches encrypted parity bits for all nonces
|
|
|
|
|
uint32_t nt_enc_plain = decrypt_nt_enc(
|
|
|
|
|
nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key);
|
|
|
|
|
if(is_weak) {
|
|
|
|
|
full_match &= is_weak_prng_nonce(nt_enc_plain);
|
|
|
|
|
if(!full_match) break;
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
2024-08-19 07:33:17 -04:00
|
|
|
full_match &= nonce_matches_encrypted_parity_bits(
|
|
|
|
|
nt_enc_plain,
|
|
|
|
|
nt_enc_plain ^ nonce_array->nonces[j].nt_enc,
|
|
|
|
|
nonce_array->nonces[j].par);
|
|
|
|
|
if(!full_match) break;
|
|
|
|
|
}
|
2024-08-20 16:31:39 -04:00
|
|
|
if(full_match) {
|
|
|
|
|
MfClassicKey* new_candidate = malloc(sizeof(MfClassicKey));
|
|
|
|
|
if(new_candidate == NULL) return NULL; // malloc failed
|
|
|
|
|
memcpy(new_candidate, &stack_key, sizeof(MfClassicKey));
|
|
|
|
|
return new_candidate;
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 16:31:39 -04:00
|
|
|
return NULL;
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-02 07:04:43 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) {
|
2024-09-17 14:38:14 -04:00
|
|
|
// TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key)
|
2024-08-18 20:38:24 -04:00
|
|
|
// TODO: Look into using MfClassicNt more
|
2024-09-16 05:28:04 -04:00
|
|
|
NfcCommand command = NfcCommandContinue;
|
2024-08-18 20:38:24 -04:00
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
uint8_t block =
|
2024-08-20 16:31:39 -04:00
|
|
|
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector);
|
2024-08-18 20:38:24 -04:00
|
|
|
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
|
|
|
|
|
|
|
|
|
MfClassicAuthContext auth_ctx = {};
|
|
|
|
|
MfClassicError error;
|
|
|
|
|
|
2024-09-02 18:53:39 -04:00
|
|
|
bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
|
2024-08-19 07:33:17 -04:00
|
|
|
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
|
|
|
|
|
bool is_last_iter_for_hard_key =
|
|
|
|
|
((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7));
|
|
|
|
|
MfClassicKeyType target_key_type =
|
|
|
|
|
(((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) ||
|
|
|
|
|
((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ?
|
|
|
|
|
MfClassicKeyTypeA :
|
|
|
|
|
MfClassicKeyTypeB;
|
2024-09-17 14:38:14 -04:00
|
|
|
uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 2 : 16);
|
|
|
|
|
uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector);
|
2024-08-18 20:38:24 -04:00
|
|
|
uint8_t parity = 0;
|
|
|
|
|
|
2024-08-20 16:31:39 -04:00
|
|
|
if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 0)) ||
|
|
|
|
|
((!is_weak) && (dict_attack_ctx->nested_nonce.count < 8))) {
|
2024-08-19 07:33:17 -04:00
|
|
|
// Step 1: Perform full authentication once
|
|
|
|
|
error = mf_classic_poller_auth(
|
|
|
|
|
instance,
|
|
|
|
|
block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
|
|
|
|
dict_attack_ctx->nested_known_key_type,
|
2024-08-28 09:26:59 -04:00
|
|
|
&auth_ctx,
|
2024-09-02 18:53:39 -04:00
|
|
|
use_backdoor_for_initial_auth);
|
2024-08-18 20:38:24 -04:00
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to perform full authentication");
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
2024-08-19 07:33:17 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2024-08-18 20:38:24 -04:00
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
FURI_LOG_E(TAG, "Full authentication successful");
|
2024-08-18 20:38:24 -04:00
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
// Step 2: Collect nested nt and parity
|
|
|
|
|
error = mf_classic_poller_auth_nested(
|
|
|
|
|
instance,
|
|
|
|
|
target_block,
|
2024-08-20 16:31:39 -04:00
|
|
|
&dict_attack_ctx->nested_known_key,
|
2024-08-19 07:33:17 -04:00
|
|
|
target_key_type,
|
|
|
|
|
&auth_ctx,
|
2024-08-28 09:26:59 -04:00
|
|
|
false,
|
2024-08-19 07:33:17 -04:00
|
|
|
true);
|
2024-08-18 20:38:24 -04:00
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
if(error != MfClassicErrorNone) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to perform nested authentication");
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
2024-08-19 07:33:17 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2024-08-18 20:38:24 -04:00
|
|
|
|
2024-08-19 07:33:17 -04:00
|
|
|
uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
|
|
|
|
// Collect parity bits
|
|
|
|
|
const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer);
|
|
|
|
|
for(int i = 0; i < 4; i++) {
|
|
|
|
|
parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool success = add_nested_nonce(
|
|
|
|
|
&dict_attack_ctx->nested_nonce,
|
|
|
|
|
cuid,
|
|
|
|
|
dict_attack_ctx->nested_target_key,
|
|
|
|
|
0,
|
|
|
|
|
nt_enc,
|
|
|
|
|
parity,
|
|
|
|
|
0);
|
|
|
|
|
if(!success) {
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?");
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
2024-08-19 07:33:17 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = true;
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
2024-08-19 07:33:17 -04:00
|
|
|
// If we have sufficient nonces, search the dictionaries for the key
|
|
|
|
|
if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) ||
|
|
|
|
|
(is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) {
|
2024-08-20 16:31:39 -04:00
|
|
|
// Identify key candidates
|
|
|
|
|
MfClassicKey* key_candidate = search_dicts_for_nonce_key(
|
|
|
|
|
dict_attack_ctx,
|
2024-08-19 07:33:17 -04:00
|
|
|
&dict_attack_ctx->nested_nonce,
|
|
|
|
|
dict_attack_ctx->mf_classic_system_dict,
|
|
|
|
|
dict_attack_ctx->mf_classic_user_dict,
|
|
|
|
|
is_weak);
|
2024-08-20 16:31:39 -04:00
|
|
|
if(key_candidate != NULL) {
|
2024-08-19 07:33:17 -04:00
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
|
|
|
|
"Found key candidate %06llx",
|
2024-08-20 16:31:39 -04:00
|
|
|
bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey)));
|
|
|
|
|
dict_attack_ctx->current_key = *key_candidate;
|
|
|
|
|
dict_attack_ctx->reuse_key_sector = (target_block / 4);
|
2024-08-21 00:50:27 -04:00
|
|
|
dict_attack_ctx->current_key_type = target_key_type;
|
2024-08-20 16:31:39 -04:00
|
|
|
free(key_candidate);
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
|
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
2024-08-19 07:33:17 -04:00
|
|
|
}
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
2024-08-19 07:33:17 -04:00
|
|
|
"Target: %u (key type %s, block %u) cuid: %08lx",
|
2024-08-18 20:38:24 -04:00
|
|
|
dict_attack_ctx->nested_target_key,
|
|
|
|
|
(target_key_type == MfClassicKeyTypeA) ? "A" : "B",
|
2024-08-19 07:33:17 -04:00
|
|
|
target_block,
|
|
|
|
|
cuid);
|
2024-08-18 20:38:24 -04:00
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
|
|
|
|
|
mf_classic_poller_halt(instance);
|
2024-08-02 07:04:43 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
|
2024-08-01 22:16:10 -04:00
|
|
|
furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0);
|
|
|
|
|
furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces);
|
|
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
NfcCommand command = NfcCommandContinue;
|
2024-08-01 22:16:10 -04:00
|
|
|
bool params_saved = false;
|
2024-08-01 09:10:02 -04:00
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
2024-08-01 22:16:10 -04:00
|
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
|
|
Stream* stream = buffered_file_stream_alloc(storage);
|
|
|
|
|
FuriString* temp_str = furi_string_alloc();
|
2024-08-09 22:01:43 -04:00
|
|
|
bool weak_prng = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
|
2024-09-03 09:26:14 -04:00
|
|
|
bool static_encrypted = dict_attack_ctx->static_encrypted;
|
2024-08-01 22:16:10 -04:00
|
|
|
|
|
|
|
|
do {
|
2024-09-03 09:26:14 -04:00
|
|
|
if(weak_prng && (!(static_encrypted)) && (dict_attack_ctx->nested_nonce.count != 2)) {
|
2024-08-03 21:35:52 -04:00
|
|
|
FURI_LOG_E(
|
|
|
|
|
TAG,
|
|
|
|
|
"MfClassicPollerStateNestedLog expected 2 nonces, received %u",
|
|
|
|
|
dict_attack_ctx->nested_nonce.count);
|
2024-08-01 22:16:10 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-09 22:01:43 -04:00
|
|
|
uint32_t nonce_pair_count = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak ?
|
|
|
|
|
1 :
|
|
|
|
|
dict_attack_ctx->nested_nonce.count;
|
|
|
|
|
|
2024-08-03 21:35:52 -04:00
|
|
|
if(!buffered_file_stream_open(
|
|
|
|
|
stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND))
|
|
|
|
|
break;
|
2024-08-01 22:16:10 -04:00
|
|
|
|
|
|
|
|
bool params_write_success = true;
|
2024-08-09 22:01:43 -04:00
|
|
|
for(size_t i = 0; i < nonce_pair_count; i++) {
|
2024-08-01 22:16:10 -04:00
|
|
|
MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i];
|
2024-08-03 21:35:52 -04:00
|
|
|
furi_string_printf(
|
|
|
|
|
temp_str,
|
|
|
|
|
"Sec %d key %c cuid %08lx",
|
2024-08-09 22:01:43 -04:00
|
|
|
(nonce->key_idx / 4),
|
|
|
|
|
((nonce->key_idx & 0x03) < 2) ? 'A' : 'B',
|
2024-08-03 21:35:52 -04:00
|
|
|
nonce->cuid);
|
2024-09-03 09:26:14 -04:00
|
|
|
for(uint8_t nt_idx = 0; nt_idx < ((weak_prng && (!(static_encrypted))) ? 2 : 1);
|
|
|
|
|
nt_idx++) {
|
2024-08-09 22:08:35 -04:00
|
|
|
if(nt_idx == 1) {
|
2024-08-09 22:01:43 -04:00
|
|
|
nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1];
|
|
|
|
|
}
|
2024-08-01 22:16:10 -04:00
|
|
|
furi_string_cat_printf(
|
|
|
|
|
temp_str,
|
|
|
|
|
" nt%u %08lx ks%u %08lx par%u ",
|
|
|
|
|
nt_idx,
|
|
|
|
|
nonce->nt,
|
|
|
|
|
nt_idx,
|
|
|
|
|
nonce->nt_enc ^ nonce->nt,
|
|
|
|
|
nt_idx);
|
|
|
|
|
for(uint8_t pb = 0; pb < 4; pb++) {
|
|
|
|
|
furi_string_cat_printf(temp_str, "%u", (nonce->par >> (3 - pb)) & 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-03 21:35:52 -04:00
|
|
|
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) {
|
2024-08-01 22:16:10 -04:00
|
|
|
furi_string_cat_printf(temp_str, " dist %u\n", nonce->dist);
|
|
|
|
|
} else {
|
|
|
|
|
furi_string_cat_printf(temp_str, "\n");
|
|
|
|
|
}
|
|
|
|
|
if(!stream_write_string(stream, temp_str)) {
|
|
|
|
|
params_write_success = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(!params_write_success) break;
|
|
|
|
|
|
|
|
|
|
params_saved = true;
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
|
|
furi_assert(params_saved);
|
2024-08-01 09:10:02 -04:00
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
2024-08-19 07:33:17 -04:00
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
2024-08-01 22:16:10 -04:00
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
|
|
|
|
furi_string_free(temp_str);
|
|
|
|
|
buffered_file_stream_close(stream);
|
|
|
|
|
stream_free(stream);
|
|
|
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
2024-08-01 09:10:02 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-01 22:30:37 -04:00
|
|
|
bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) {
|
2024-08-27 16:16:38 -04:00
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
|
|
|
|
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
|
2024-09-01 22:30:37 -04:00
|
|
|
uint8_t nested_target_key = dict_attack_ctx->nested_target_key;
|
|
|
|
|
|
|
|
|
|
MfClassicKeyType target_key_type;
|
|
|
|
|
uint8_t target_sector;
|
|
|
|
|
|
|
|
|
|
if(is_dict_attack) {
|
|
|
|
|
target_key_type = (((is_weak) && ((nested_target_key % 2) == 0)) ||
|
|
|
|
|
((!is_weak) && ((nested_target_key % 16) < 8))) ?
|
|
|
|
|
MfClassicKeyTypeA :
|
|
|
|
|
MfClassicKeyTypeB;
|
|
|
|
|
target_sector = is_weak ? (nested_target_key / 2) : (nested_target_key / 16);
|
|
|
|
|
} else {
|
|
|
|
|
target_key_type = (((is_weak) && ((nested_target_key % 4) < 2)) ||
|
|
|
|
|
((!is_weak) && ((nested_target_key % 2) == 0))) ?
|
|
|
|
|
MfClassicKeyTypeA :
|
|
|
|
|
MfClassicKeyTypeB;
|
|
|
|
|
target_sector = is_weak ? (nested_target_key / 4) : (nested_target_key / 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mf_classic_is_key_found(instance->data, target_sector, target_key_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) {
|
|
|
|
|
for(int i = 0; i < 32; i++) {
|
|
|
|
|
if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_valid_sum(uint16_t sum) {
|
|
|
|
|
for(size_t i = 0; i < 19; i++) {
|
|
|
|
|
if(sum == valid_sums[i]) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-08-27 16:16:38 -04:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 09:10:02 -04:00
|
|
|
NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) {
|
2024-09-17 14:38:14 -04:00
|
|
|
// This function guides the nested attack through its phases, and iterates over the target keys
|
2024-09-03 15:19:12 -04:00
|
|
|
NfcCommand command = mf_classic_poller_handle_data_update(instance);
|
2024-08-01 09:10:02 -04:00
|
|
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
2024-09-02 18:53:39 -04:00
|
|
|
bool initial_dict_attack_iter = false;
|
2024-08-19 07:33:17 -04:00
|
|
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) {
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = true;
|
|
|
|
|
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
|
2024-09-02 18:53:39 -04:00
|
|
|
bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
|
|
|
|
|
if(!(backdoor_present)) {
|
|
|
|
|
for(uint8_t sector = 0; sector < instance->sectors_total; sector++) {
|
|
|
|
|
for(uint8_t key_type = 0; key_type < 2; key_type++) {
|
|
|
|
|
if(mf_classic_is_key_found(instance->data, sector, key_type)) {
|
|
|
|
|
dict_attack_ctx->nested_known_key_sector = sector;
|
|
|
|
|
dict_attack_ctx->nested_known_key_type = key_type;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-21 00:50:27 -04:00
|
|
|
}
|
|
|
|
|
}
|
2024-09-02 18:53:39 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG;
|
|
|
|
|
} else {
|
|
|
|
|
dict_attack_ctx->nested_known_key_sector = 0;
|
|
|
|
|
dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA;
|
|
|
|
|
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
|
2024-09-09 20:50:15 -04:00
|
|
|
if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) {
|
2024-09-02 22:34:44 -04:00
|
|
|
dict_attack_ctx->static_encrypted = true;
|
|
|
|
|
}
|
2024-09-02 18:53:39 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
|
|
|
|
|
initial_dict_attack_iter = true;
|
2024-08-21 00:50:27 -04:00
|
|
|
}
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-09-17 14:38:14 -04:00
|
|
|
// Identify PRNG type
|
2024-08-19 07:33:17 -04:00
|
|
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) {
|
|
|
|
|
if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) {
|
|
|
|
|
instance->state = MfClassicPollerStateNestedCollectNt;
|
|
|
|
|
return command;
|
|
|
|
|
} else if(
|
|
|
|
|
(dict_attack_ctx->nested_nonce.count == MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) &&
|
|
|
|
|
(dict_attack_ctx->prng_type == MfClassicPrngTypeUnknown)) {
|
|
|
|
|
instance->state = MfClassicPollerStateNestedAnalyzePRNG;
|
|
|
|
|
return command;
|
|
|
|
|
} else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) {
|
|
|
|
|
FURI_LOG_E(TAG, "No tag detected");
|
|
|
|
|
// Free nonce array
|
|
|
|
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
|
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
|
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
|
|
|
|
}
|
2024-08-20 16:31:39 -04:00
|
|
|
instance->state = MfClassicPollerStateFail;
|
2024-08-19 07:33:17 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
|
|
|
|
// Free nonce array
|
|
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
|
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
|
|
|
|
}
|
|
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
|
2024-08-20 18:04:07 -04:00
|
|
|
initial_dict_attack_iter = true;
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-08-20 18:04:07 -04:00
|
|
|
// Accelerated nested dictionary attack
|
|
|
|
|
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
|
2024-08-19 07:33:17 -04:00
|
|
|
uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ?
|
|
|
|
|
(instance->sectors_total * 2) :
|
|
|
|
|
(instance->sectors_total * 16);
|
2024-08-20 16:31:39 -04:00
|
|
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) {
|
2024-09-01 22:30:37 -04:00
|
|
|
if(!(mf_classic_nested_is_target_key_found(instance, true))) {
|
2024-08-20 16:31:39 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedDictAttack;
|
|
|
|
|
return command;
|
2024-08-20 16:33:13 -04:00
|
|
|
} else {
|
|
|
|
|
dict_attack_ctx->auth_passed = true;
|
|
|
|
|
furi_assert(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
|
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
|
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
|
2024-08-20 16:31:39 -04:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-19 07:33:17 -04:00
|
|
|
if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) &&
|
2024-08-19 23:00:36 -04:00
|
|
|
(dict_attack_ctx->nested_target_key < dict_target_key_max)) {
|
2024-08-20 16:31:39 -04:00
|
|
|
bool is_last_iter_for_hard_key =
|
|
|
|
|
((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7));
|
2024-08-20 18:04:07 -04:00
|
|
|
if(initial_dict_attack_iter) {
|
2024-08-21 00:50:27 -04:00
|
|
|
// Initialize dictionaries
|
|
|
|
|
// Note: System dict should always exist
|
2024-08-27 06:32:00 -04:00
|
|
|
dict_attack_ctx->mf_classic_system_dict =
|
|
|
|
|
keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH) ?
|
|
|
|
|
keys_dict_alloc(
|
|
|
|
|
MF_CLASSIC_NESTED_SYSTEM_DICT_PATH,
|
|
|
|
|
KeysDictModeOpenExisting,
|
|
|
|
|
sizeof(MfClassicKey)) :
|
|
|
|
|
NULL;
|
|
|
|
|
|
|
|
|
|
dict_attack_ctx->mf_classic_user_dict =
|
|
|
|
|
keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH) ?
|
|
|
|
|
keys_dict_alloc(
|
|
|
|
|
MF_CLASSIC_NESTED_USER_DICT_PATH,
|
|
|
|
|
KeysDictModeOpenExisting,
|
|
|
|
|
sizeof(MfClassicKey)) :
|
|
|
|
|
NULL;
|
2024-08-20 18:04:07 -04:00
|
|
|
}
|
2024-08-20 16:31:39 -04:00
|
|
|
if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) {
|
|
|
|
|
// Key reuse
|
|
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume;
|
|
|
|
|
dict_attack_ctx->auth_passed = false;
|
2024-08-21 00:50:27 -04:00
|
|
|
instance->state = MfClassicPollerStateKeyReuseStartNoOffset;
|
2024-08-20 16:31:39 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
if(!(dict_attack_ctx->auth_passed)) {
|
2024-08-19 23:00:36 -04:00
|
|
|
dict_attack_ctx->attempt_count++;
|
2024-08-20 18:04:07 -04:00
|
|
|
} else if(dict_attack_ctx->auth_passed && !(initial_dict_attack_iter)) {
|
2024-08-19 23:00:36 -04:00
|
|
|
dict_attack_ctx->nested_target_key++;
|
|
|
|
|
dict_attack_ctx->attempt_count = 0;
|
|
|
|
|
}
|
2024-08-20 16:31:39 -04:00
|
|
|
dict_attack_ctx->auth_passed = true;
|
2024-08-19 07:33:17 -04:00
|
|
|
if(dict_attack_ctx->nested_target_key == dict_target_key_max) {
|
2024-08-18 20:38:24 -04:00
|
|
|
if(dict_attack_ctx->mf_classic_system_dict) {
|
|
|
|
|
keys_dict_free(dict_attack_ctx->mf_classic_system_dict);
|
2024-09-11 16:22:52 -04:00
|
|
|
dict_attack_ctx->mf_classic_system_dict = NULL;
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
|
|
|
|
if(dict_attack_ctx->mf_classic_user_dict) {
|
|
|
|
|
keys_dict_free(dict_attack_ctx->mf_classic_user_dict);
|
2024-09-11 16:22:52 -04:00
|
|
|
dict_attack_ctx->mf_classic_user_dict = NULL;
|
2024-08-18 20:38:24 -04:00
|
|
|
}
|
2024-08-28 09:26:59 -04:00
|
|
|
dict_attack_ctx->nested_target_key = 0;
|
2024-08-20 16:31:39 -04:00
|
|
|
if(mf_classic_is_card_read(instance->data)) {
|
2024-08-20 16:33:13 -04:00
|
|
|
// All keys have been collected
|
2024-08-19 07:33:17 -04:00
|
|
|
FURI_LOG_E(TAG, "All keys collected and sectors read");
|
|
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
|
|
|
|
|
instance->state = MfClassicPollerStateSuccess;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-09-17 14:38:14 -04:00
|
|
|
if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) {
|
|
|
|
|
// Skip initial calibration for static encrypted backdoored tags
|
|
|
|
|
dict_attack_ctx->calibrated = true;
|
|
|
|
|
}
|
2024-08-28 09:26:59 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate;
|
2024-08-18 20:38:24 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-08-20 16:33:13 -04:00
|
|
|
if(dict_attack_ctx->attempt_count == 0) {
|
|
|
|
|
// Check if the nested target key is a known key
|
2024-09-01 22:30:37 -04:00
|
|
|
if(mf_classic_nested_is_target_key_found(instance, true)) {
|
2024-08-21 00:50:27 -04:00
|
|
|
// Continue to next key
|
2024-08-20 16:33:13 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-08-20 16:31:39 -04:00
|
|
|
}
|
2024-08-18 20:38:24 -04:00
|
|
|
if(dict_attack_ctx->attempt_count >= 3) {
|
|
|
|
|
// Unpredictable, skip
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to collect nonce, skipping key");
|
2024-08-19 07:33:17 -04:00
|
|
|
dict_attack_ctx->nested_target_key++;
|
2024-08-18 20:38:24 -04:00
|
|
|
dict_attack_ctx->attempt_count = 0;
|
|
|
|
|
}
|
|
|
|
|
instance->state = MfClassicPollerStateNestedDictAttack;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-08-19 07:33:17 -04:00
|
|
|
// Calibration
|
2024-09-02 10:28:50 -04:00
|
|
|
bool initial_collect_nt_enc_iter = false;
|
2024-09-17 14:38:14 -04:00
|
|
|
bool recalibrated = false;
|
2024-08-27 16:16:38 -04:00
|
|
|
if(!(dict_attack_ctx->calibrated)) {
|
|
|
|
|
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) {
|
2024-08-19 07:33:17 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedCalibrate;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-09-02 10:28:50 -04:00
|
|
|
initial_collect_nt_enc_iter = true;
|
2024-09-03 05:54:59 -04:00
|
|
|
dict_attack_ctx->calibrated = true;
|
2024-08-27 16:16:38 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc;
|
|
|
|
|
} else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) {
|
2024-09-02 10:28:50 -04:00
|
|
|
initial_collect_nt_enc_iter = true;
|
2024-09-17 14:38:14 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc;
|
|
|
|
|
} else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseRecalibrate) {
|
|
|
|
|
recalibrated = true;
|
2024-08-27 16:16:38 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc;
|
2024-08-19 07:33:17 -04:00
|
|
|
}
|
2024-08-20 18:04:07 -04:00
|
|
|
// Collect and log nonces
|
2024-08-19 07:33:17 -04:00
|
|
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) {
|
2024-08-20 18:04:07 -04:00
|
|
|
if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) ||
|
2024-09-09 20:50:15 -04:00
|
|
|
((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
|
2024-09-03 09:26:14 -04:00
|
|
|
(dict_attack_ctx->nested_nonce.count == 1)) ||
|
2024-08-20 18:04:07 -04:00
|
|
|
((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) {
|
2024-08-19 07:33:17 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedLog;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-09-03 09:26:14 -04:00
|
|
|
uint16_t nonce_collect_key_max;
|
|
|
|
|
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) {
|
|
|
|
|
nonce_collect_key_max = dict_attack_ctx->static_encrypted ?
|
|
|
|
|
((instance->sectors_total * 4) - 2) :
|
|
|
|
|
(instance->sectors_total * 4);
|
|
|
|
|
} else {
|
|
|
|
|
nonce_collect_key_max = instance->sectors_total * 2;
|
|
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
// Target all remaining sectors, key A and B
|
2024-09-03 09:26:14 -04:00
|
|
|
if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) {
|
2024-09-01 22:30:37 -04:00
|
|
|
if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) {
|
|
|
|
|
if(is_valid_sum(dict_attack_ctx->msb_par_sum)) {
|
|
|
|
|
// All Hardnested nonces collected
|
|
|
|
|
dict_attack_ctx->nested_target_key++;
|
|
|
|
|
dict_attack_ctx->current_key_checked = false;
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
} else {
|
|
|
|
|
// Nonces do not match an expected sum
|
|
|
|
|
dict_attack_ctx->attempt_count++;
|
|
|
|
|
instance->state = MfClassicPollerStateNestedCollectNtEnc;
|
|
|
|
|
}
|
|
|
|
|
dict_attack_ctx->msb_par_sum = 0;
|
|
|
|
|
memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb));
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-09-17 14:38:14 -04:00
|
|
|
if(initial_collect_nt_enc_iter) {
|
|
|
|
|
dict_attack_ctx->current_key_checked = false;
|
|
|
|
|
}
|
|
|
|
|
if(!(dict_attack_ctx->auth_passed) && !(initial_collect_nt_enc_iter)) {
|
2024-08-20 18:04:07 -04:00
|
|
|
dict_attack_ctx->attempt_count++;
|
|
|
|
|
} else {
|
2024-09-17 14:38:14 -04:00
|
|
|
if(is_weak && !(initial_collect_nt_enc_iter) && !(recalibrated)) {
|
2024-09-03 09:26:14 -04:00
|
|
|
if(!(dict_attack_ctx->static_encrypted)) {
|
|
|
|
|
dict_attack_ctx->nested_target_key++;
|
|
|
|
|
} else {
|
|
|
|
|
dict_attack_ctx->nested_target_key += 2;
|
|
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
if(dict_attack_ctx->nested_target_key % 2 == 0) {
|
|
|
|
|
dict_attack_ctx->current_key_checked = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-20 18:04:07 -04:00
|
|
|
dict_attack_ctx->attempt_count = 0;
|
2024-08-09 20:22:19 -04:00
|
|
|
}
|
2024-08-20 18:04:07 -04:00
|
|
|
dict_attack_ctx->auth_passed = true;
|
2024-09-01 22:30:37 -04:00
|
|
|
if(!(dict_attack_ctx->current_key_checked)) {
|
2024-09-17 14:38:14 -04:00
|
|
|
dict_attack_ctx->current_key_checked = true;
|
|
|
|
|
|
2024-08-27 16:16:38 -04:00
|
|
|
// Check if the nested target key is a known key
|
2024-09-01 22:30:37 -04:00
|
|
|
if(mf_classic_nested_is_target_key_found(instance, false)) {
|
2024-08-27 16:16:38 -04:00
|
|
|
// Continue to next key
|
2024-09-17 14:38:14 -04:00
|
|
|
if(!(dict_attack_ctx->static_encrypted)) {
|
2024-09-01 22:30:37 -04:00
|
|
|
dict_attack_ctx->nested_target_key++;
|
|
|
|
|
dict_attack_ctx->current_key_checked = false;
|
|
|
|
|
}
|
2024-08-27 16:16:38 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-09-17 14:38:14 -04:00
|
|
|
|
|
|
|
|
// If it is not a known key, we'll need to calibrate for static encrypted backdoored tags
|
|
|
|
|
if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
|
|
|
|
|
(dict_attack_ctx->nested_target_key < nonce_collect_key_max) &&
|
|
|
|
|
!(recalibrated)) {
|
|
|
|
|
dict_attack_ctx->calibrated = false;
|
|
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate;
|
|
|
|
|
instance->state = MfClassicPollerStateNestedController;
|
|
|
|
|
return command;
|
|
|
|
|
}
|
2024-08-27 16:16:38 -04:00
|
|
|
}
|
2024-09-17 14:38:14 -04:00
|
|
|
|
|
|
|
|
// If we have tried to collect this nonce too many times, skip
|
2024-09-01 22:30:37 -04:00
|
|
|
if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) ||
|
|
|
|
|
(!(is_weak) &&
|
|
|
|
|
(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) {
|
2024-08-20 18:04:07 -04:00
|
|
|
// Unpredictable, skip
|
|
|
|
|
FURI_LOG_E(TAG, "Failed to collect nonce, skipping key");
|
|
|
|
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
|
|
|
|
free(dict_attack_ctx->nested_nonce.nonces);
|
|
|
|
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
|
|
|
|
dict_attack_ctx->nested_nonce.count = 0;
|
|
|
|
|
}
|
2024-09-01 22:30:37 -04:00
|
|
|
if(is_weak) {
|
|
|
|
|
dict_attack_ctx->nested_target_key += 2;
|
|
|
|
|
dict_attack_ctx->current_key_checked = false;
|
|
|
|
|
} else {
|
|
|
|
|
dict_attack_ctx->msb_par_sum = 0;
|
|
|
|
|
memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb));
|
|
|
|
|
dict_attack_ctx->nested_target_key++;
|
|
|
|
|
dict_attack_ctx->current_key_checked = false;
|
|
|
|
|
}
|
2024-08-20 18:04:07 -04:00
|
|
|
dict_attack_ctx->attempt_count = 0;
|
|
|
|
|
}
|
2024-09-17 14:38:14 -04:00
|
|
|
|
|
|
|
|
// Collect a nonce
|
2024-09-02 10:28:50 -04:00
|
|
|
dict_attack_ctx->auth_passed = false;
|
2024-08-20 18:04:07 -04:00
|
|
|
instance->state = MfClassicPollerStateNestedCollectNtEnc;
|
|
|
|
|
return command;
|
2024-08-09 20:22:19 -04:00
|
|
|
}
|
2024-08-01 09:10:02 -04:00
|
|
|
}
|
2024-08-20 16:33:13 -04:00
|
|
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
|
|
|
|
|
instance->state = MfClassicPollerStateSuccess;
|
2024-08-01 09:10:02 -04:00
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeSuccess;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_handler_fail(MfClassicPoller* instance) {
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeFail;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
instance->state = MfClassicPollerStateDetectType;
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const MfClassicPollerReadHandler
|
|
|
|
|
mf_classic_poller_dict_attack_handler[MfClassicPollerStateNum] = {
|
|
|
|
|
[MfClassicPollerStateDetectType] = mf_classic_poller_handler_detect_type,
|
|
|
|
|
[MfClassicPollerStateStart] = mf_classic_poller_handler_start,
|
|
|
|
|
[MfClassicPollerStateRequestSectorTrailer] =
|
|
|
|
|
mf_classic_poller_handler_request_sector_trailer,
|
|
|
|
|
[MfClassicPollerStateCheckWriteConditions] = mf_classic_handler_check_write_conditions,
|
|
|
|
|
[MfClassicPollerStateReadBlock] = mf_classic_poller_handler_read_block,
|
|
|
|
|
[MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block,
|
|
|
|
|
[MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block,
|
|
|
|
|
[MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector,
|
2024-08-28 09:26:59 -04:00
|
|
|
[MfClassicPollerStateAnalyzeBackdoor] = mf_classic_poller_handler_analyze_backdoor,
|
2024-09-02 12:45:39 -04:00
|
|
|
[MfClassicPollerStateBackdoorReadSector] = mf_classic_poller_handler_backdoor_read_sector,
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
[MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key,
|
|
|
|
|
[MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector,
|
|
|
|
|
[MfClassicPollerStateReadSectorBlocks] =
|
|
|
|
|
mf_classic_poller_handler_request_read_sector_blocks,
|
|
|
|
|
[MfClassicPollerStateAuthKeyA] = mf_classic_poller_handler_auth_a,
|
|
|
|
|
[MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b,
|
|
|
|
|
[MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector,
|
|
|
|
|
[MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start,
|
2024-08-21 00:50:27 -04:00
|
|
|
[MfClassicPollerStateKeyReuseStartNoOffset] =
|
|
|
|
|
mf_classic_poller_handler_key_reuse_start_no_offset,
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
[MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a,
|
|
|
|
|
[MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b,
|
|
|
|
|
[MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector,
|
2024-08-01 09:10:02 -04:00
|
|
|
[MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng,
|
2024-08-04 23:53:21 -04:00
|
|
|
[MfClassicPollerStateNestedCalibrate] = mf_classic_poller_handler_nested_calibrate,
|
2024-08-03 21:35:52 -04:00
|
|
|
[MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt,
|
|
|
|
|
[MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller,
|
|
|
|
|
[MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc,
|
|
|
|
|
[MfClassicPollerStateNestedDictAttack] = mf_classic_poller_handler_nested_dict_attack,
|
2024-08-01 09:10:02 -04:00
|
|
|
[MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log,
|
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.
Starring:
- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer
Supporting roles:
- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance
Special thanks:
@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
2023-10-24 07:08:09 +04:00
|
|
|
[MfClassicPollerStateSuccess] = mf_classic_poller_handler_success,
|
|
|
|
|
[MfClassicPollerStateFail] = mf_classic_poller_handler_fail,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
NfcCommand mf_classic_poller_run(NfcGenericEvent event, void* context) {
|
|
|
|
|
furi_assert(event.event_data);
|
|
|
|
|
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
|
|
|
|
furi_assert(context);
|
|
|
|
|
|
|
|
|
|
MfClassicPoller* instance = context;
|
|
|
|
|
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
|
|
|
|
NfcCommand command = NfcCommandContinue;
|
|
|
|
|
|
|
|
|
|
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
|
|
|
|
if(instance->card_state == MfClassicCardStateLost) {
|
|
|
|
|
instance->card_state = MfClassicCardStateDetected;
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeCardDetected;
|
|
|
|
|
instance->callback(instance->general_event, instance->context);
|
|
|
|
|
}
|
|
|
|
|
command = mf_classic_poller_dict_attack_handler[instance->state](instance);
|
|
|
|
|
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
|
|
|
|
|
if(instance->card_state == MfClassicCardStateDetected) {
|
|
|
|
|
instance->card_state = MfClassicCardStateLost;
|
|
|
|
|
instance->mfc_event.type = MfClassicPollerEventTypeCardLost;
|
|
|
|
|
command = instance->callback(instance->general_event, instance->context);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return command;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool mf_classic_poller_detect(NfcGenericEvent event, void* context) {
|
|
|
|
|
furi_assert(event.event_data);
|
|
|
|
|
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
|
|
|
|
furi_assert(context);
|
|
|
|
|
|
|
|
|
|
Iso14443_3aPoller* iso3_poller = event.instance;
|
|
|
|
|
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
|
|
|
|
bool detected = false;
|
|
|
|
|
const uint8_t auth_cmd[] = {MF_CLASSIC_CMD_AUTH_KEY_A, 0};
|
|
|
|
|
BitBuffer* tx_buffer = bit_buffer_alloc(COUNT_OF(auth_cmd));
|
|
|
|
|
bit_buffer_copy_bytes(tx_buffer, auth_cmd, COUNT_OF(auth_cmd));
|
|
|
|
|
BitBuffer* rx_buffer = bit_buffer_alloc(sizeof(MfClassicNt));
|
|
|
|
|
|
|
|
|
|
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
|
|
|
|
Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
|
|
|
|
|
iso3_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC);
|
|
|
|
|
if(error == Iso14443_3aErrorWrongCrc) {
|
|
|
|
|
if(bit_buffer_get_size_bytes(rx_buffer) == sizeof(MfClassicNt)) {
|
|
|
|
|
detected = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bit_buffer_free(tx_buffer);
|
|
|
|
|
bit_buffer_free(rx_buffer);
|
|
|
|
|
|
|
|
|
|
return detected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mf_classic_poller_set_callback(
|
|
|
|
|
MfClassicPoller* instance,
|
|
|
|
|
NfcGenericCallback callback,
|
|
|
|
|
void* context) {
|
|
|
|
|
furi_assert(instance);
|
|
|
|
|
furi_assert(callback);
|
|
|
|
|
|
|
|
|
|
instance->callback = callback;
|
|
|
|
|
instance->context = context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MfClassicData* mf_classic_poller_get_data(const MfClassicPoller* instance) {
|
|
|
|
|
furi_assert(instance);
|
|
|
|
|
furi_assert(instance->data);
|
|
|
|
|
|
|
|
|
|
return instance->data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NfcPollerBase mf_classic_poller = {
|
|
|
|
|
.alloc = (NfcPollerAlloc)mf_classic_poller_alloc,
|
|
|
|
|
.free = (NfcPollerFree)mf_classic_poller_free,
|
|
|
|
|
.set_callback = (NfcPollerSetCallback)mf_classic_poller_set_callback,
|
|
|
|
|
.run = (NfcPollerRun)mf_classic_poller_run,
|
|
|
|
|
.detect = (NfcPollerDetect)mf_classic_poller_detect,
|
|
|
|
|
.get_data = (NfcPollerGetData)mf_classic_poller_get_data,
|
2024-08-03 21:35:52 -04:00
|
|
|
};
|