-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MIFARE Classic Key Recovery Improvements #3822
base: dev
Are you sure you want to change the base?
Changes from all commits
cf6d6bb
b154603
ef16770
d3bf372
213ec1d
9ba78bf
6d66638
e61b543
09f8a73
7d2cab5
8dd3daf
4b44288
5feeae8
3acba77
6332ec7
01b1948
8d1a220
cc8cae7
79bc887
0af28fb
08ca794
b7e63bf
bbc10cd
75a0e4b
c1f01ce
26845cb
5235592
0b33c85
c0331ba
4c14594
144424e
90d0c3d
2abeb07
ccc4326
9c7120e
2cb2f05
6e9fe1e
2e0cd32
3cb3eab
92122b2
7bb3349
b09d5a0
9ea7a46
9558a5f
cba58ed
ab8bc3e
d7484ee
c43806b
ab0debb
8d26636
13411da
8edafa3
18f8cfb
4836a54
3ab752b
8eae5c0
d8864a4
c1cdd49
96606dc
6eccdc8
c21b359
6a77ab7
901bdf9
4eb0f2a
6ae9506
cd76926
0ba8ac4
61e24fc
099bb40
ba672e7
232560f
00f3564
4f722a0
a905c14
f346412
a1590fc
bcc8d3e
56febb1
b843856
3976f12
a7c0819
5404788
889b19b
f530eee
02f7c6b
2bc02c0
4be9e79
1101748
2be0cfb
897817a
db26c85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
#include "../nfc_app_i.h" | ||
|
||
#include <bit_lib/bit_lib.h> | ||
#include <dolphin/dolphin.h> | ||
#include <lib/nfc/protocols/mf_classic/mf_classic_poller.h> | ||
|
||
#define TAG "NfcMfClassicDictAttack" | ||
|
||
// TODO: Fix lag when leaving the dictionary attack view after Hardnested | ||
// TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found | ||
|
||
typedef enum { | ||
DictAttackStateCUIDDictInProgress, | ||
DictAttackStateUserDictInProgress, | ||
DictAttackStateSystemDictInProgress, | ||
} DictAttackState; | ||
|
@@ -29,7 +33,9 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) | |
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { | ||
const MfClassicData* mfc_data = | ||
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); | ||
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; | ||
mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see one place where this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flag is to indicate whether nested attacks should be attempted. Nested attacks are disabled during the CUID dictionary attack to force the default behavior ( |
||
MfClassicPollerModeDictAttackEnhanced : | ||
MfClassicPollerModeDictAttackStandard; | ||
mfc_event->data->poller_mode.data = mfc_data; | ||
instance->nfc_dict_context.sectors_total = | ||
mf_classic_get_total_sectors_num(mfc_data->type); | ||
|
@@ -58,6 +64,11 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) | |
instance->nfc_dict_context.sectors_read = data_update->sectors_read; | ||
instance->nfc_dict_context.keys_found = data_update->keys_found; | ||
instance->nfc_dict_context.current_sector = data_update->current_sector; | ||
instance->nfc_dict_context.nested_phase = data_update->nested_phase; | ||
instance->nfc_dict_context.prng_type = data_update->prng_type; | ||
instance->nfc_dict_context.backdoor = data_update->backdoor; | ||
instance->nfc_dict_context.nested_target_key = data_update->nested_target_key; | ||
instance->nfc_dict_context.msb_count = data_update->msb_count; | ||
view_dispatcher_send_custom_event( | ||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); | ||
} else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { | ||
|
@@ -117,19 +128,75 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { | |
dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); | ||
dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); | ||
dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); | ||
dict_attack_set_nested_phase(instance->dict_attack, mfc_dict->nested_phase); | ||
dict_attack_set_prng_type(instance->dict_attack, mfc_dict->prng_type); | ||
dict_attack_set_backdoor(instance->dict_attack, mfc_dict->backdoor); | ||
dict_attack_set_nested_target_key(instance->dict_attack, mfc_dict->nested_target_key); | ||
dict_attack_set_msb_count(instance->dict_attack, mfc_dict->msb_count); | ||
} | ||
} | ||
|
||
static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { | ||
uint32_t state = | ||
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); | ||
if(state == DictAttackStateCUIDDictInProgress) { | ||
size_t cuid_len = 0; | ||
const uint8_t* cuid = nfc_device_get_uid(instance->nfc_device, &cuid_len); | ||
FuriString* cuid_dict_path = furi_string_alloc_printf( | ||
"%s/mf_classic_dict_%08lx.nfc", | ||
EXT_PATH("nfc/assets"), | ||
(uint32_t)bit_lib_bytes_to_num_be(cuid + (cuid_len - 4), 4)); | ||
|
||
do { | ||
if(!keys_dict_check_presence(furi_string_get_cstr(cuid_dict_path))) { | ||
state = DictAttackStateUserDictInProgress; | ||
noproto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
break; | ||
} | ||
|
||
instance->nfc_dict_context.dict = keys_dict_alloc( | ||
furi_string_get_cstr(cuid_dict_path), | ||
KeysDictModeOpenExisting, | ||
sizeof(MfClassicKey)); | ||
|
||
if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { | ||
keys_dict_free(instance->nfc_dict_context.dict); | ||
state = DictAttackStateUserDictInProgress; | ||
break; | ||
} | ||
|
||
dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary"); | ||
} while(false); | ||
|
||
furi_string_free(cuid_dict_path); | ||
} | ||
if(state == DictAttackStateUserDictInProgress) { | ||
do { | ||
instance->nfc_dict_context.enhanced_dict = true; | ||
|
||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { | ||
storage_common_remove( | ||
instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); | ||
} | ||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH)) { | ||
storage_common_copy( | ||
instance->storage, | ||
NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, | ||
NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); | ||
} | ||
|
||
if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { | ||
state = DictAttackStateSystemDictInProgress; | ||
break; | ||
} | ||
|
||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { | ||
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); | ||
} | ||
storage_common_copy( | ||
instance->storage, | ||
NFC_APP_MF_CLASSIC_DICT_USER_PATH, | ||
NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); | ||
|
||
instance->nfc_dict_context.dict = keys_dict_alloc( | ||
NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); | ||
if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { | ||
|
@@ -164,7 +231,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { | |
NfcApp* instance = context; | ||
|
||
scene_manager_set_scene_state( | ||
instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); | ||
instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); | ||
nfc_scene_mf_classic_dict_attack_prepare_view(instance); | ||
dict_attack_set_card_state(instance->dict_attack, true); | ||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); | ||
|
@@ -193,7 +260,21 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent | |
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); | ||
if(event.type == SceneManagerEventTypeCustom) { | ||
if(event.event == NfcCustomEventDictAttackComplete) { | ||
if(state == DictAttackStateUserDictInProgress) { | ||
bool ran_nested_dict = instance->nfc_dict_context.nested_phase != | ||
MfClassicNestedPhaseNone; | ||
if(state == DictAttackStateCUIDDictInProgress) { | ||
nfc_poller_stop(instance->poller); | ||
nfc_poller_free(instance->poller); | ||
keys_dict_free(instance->nfc_dict_context.dict); | ||
scene_manager_set_scene_state( | ||
instance->scene_manager, | ||
NfcSceneMfClassicDictAttack, | ||
DictAttackStateUserDictInProgress); | ||
nfc_scene_mf_classic_dict_attack_prepare_view(instance); | ||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); | ||
nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); | ||
consumed = true; | ||
} else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { | ||
nfc_poller_stop(instance->poller); | ||
nfc_poller_free(instance->poller); | ||
keys_dict_free(instance->nfc_dict_context.dict); | ||
|
@@ -222,7 +303,27 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent | |
} else if(event.event == NfcCustomEventDictAttackSkip) { | ||
const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); | ||
nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); | ||
if(state == DictAttackStateUserDictInProgress) { | ||
bool ran_nested_dict = instance->nfc_dict_context.nested_phase != | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion, this one is not needed here because we have the same on line 264 which we can move out from if to line 262. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And by the way, maybe it would be better to change type of nested_phase to appropriate enum instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This exists to reduce computation so we don't do the comparison on every custom event.
Will follow up on your other comment: #3822 (comment) |
||
MfClassicNestedPhaseNone; | ||
if(state == DictAttackStateCUIDDictInProgress) { | ||
if(instance->nfc_dict_context.is_card_present) { | ||
nfc_poller_stop(instance->poller); | ||
nfc_poller_free(instance->poller); | ||
keys_dict_free(instance->nfc_dict_context.dict); | ||
scene_manager_set_scene_state( | ||
instance->scene_manager, | ||
NfcSceneMfClassicDictAttack, | ||
DictAttackStateUserDictInProgress); | ||
nfc_scene_mf_classic_dict_attack_prepare_view(instance); | ||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); | ||
nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); | ||
} else { | ||
nfc_scene_mf_classic_dict_attack_notify_read(instance); | ||
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); | ||
dolphin_deed(DolphinDeedNfcReadSuccess); | ||
} | ||
consumed = true; | ||
} else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { | ||
if(instance->nfc_dict_context.is_card_present) { | ||
nfc_poller_stop(instance->poller); | ||
nfc_poller_free(instance->poller); | ||
|
@@ -240,7 +341,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent | |
dolphin_deed(DolphinDeedNfcReadSuccess); | ||
} | ||
consumed = true; | ||
} else if(state == DictAttackStateSystemDictInProgress) { | ||
} else { | ||
nfc_scene_mf_classic_dict_attack_notify_read(instance); | ||
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); | ||
dolphin_deed(DolphinDeedNfcReadSuccess); | ||
|
@@ -262,7 +363,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { | |
|
||
dict_attack_reset(instance->dict_attack); | ||
scene_manager_set_scene_state( | ||
instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); | ||
instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); | ||
|
||
keys_dict_free(instance->nfc_dict_context.dict); | ||
|
||
|
@@ -275,6 +376,20 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { | |
instance->nfc_dict_context.is_key_attack = false; | ||
instance->nfc_dict_context.key_attack_current_sector = 0; | ||
instance->nfc_dict_context.is_card_present = false; | ||
instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone; | ||
instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown; | ||
instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; | ||
instance->nfc_dict_context.nested_target_key = 0; | ||
instance->nfc_dict_context.msb_count = 0; | ||
instance->nfc_dict_context.enhanced_dict = false; | ||
|
||
// Clean up temporary files used for nested dictionary attack | ||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { | ||
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); | ||
} | ||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { | ||
storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); | ||
} | ||
|
||
nfc_blink_stop(instance); | ||
notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about these TODOs? Are they still actual or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are actual, but not for this PR necessarily. I have them documenting the planned performance and code style improvements. Ideally with a follow-up PR, so users have a working process before I've had the chance to fully optimize it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More context: to the best of my knowledge the PR is stable and testing is appreciated. The cost of the performance TODOs will add up to milliseconds for the vast majority of attacks.
For the two TODOs referenced here, I've observed under certain conditions Hardnested taking a few seconds to return from the dictionary attack. The backdoor re-entry cost is somewhere in the order of milliseconds.