-
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?
Conversation
This PR also resolves |
The accelerated dictionary attack is mostly working. I'm tracking down a minor bug in it and making sure the UI reflects the state of the attack. The fork takes an average of 10 seconds to run dictionary attacks in my tests (1 second of the average is backdoor detection - separate from the dictionary attack). OFW 0.105.0 takes an average of 3 minutes 10 seconds. On real tags the number of unknown keys and the offset in the dictionary are all different, here are five benchmarks on random MIFARE Classic tags:
|
I’d consider this PR to be beta quality now. A review would be helpful in guiding the next steps and prioritizing fixes. Some FAQ: How do I crack the nonces?: I am working on MFKey 3.0 and a fork of FlipperNestedRecovery. These will be the primary methods of key recovery when I am finished. It gets stuck on collecting nonces.: This is a calibration issue and will be fixed. The process isn't actually stuck — it's taking longer than expected due to difficulties finding a candidate range.
|
@@ -2040,6 +2041,7 @@ Function,-,initstate,char*,"unsigned, char*, size_t" | |||
Function,+,input_get_key_name,const char*,InputKey | |||
Function,+,input_get_type_name,const char*,InputType | |||
Function,-,iprintf,int,"const char*, ..." | |||
Function,+,is_weak_prng_nonce,_Bool,uint32_t |
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.
We need to prefix all symbols in crypto1 package if symbols are exported to api symbol table
@@ -5,6 +5,8 @@ | |||
|
|||
#define TAG "NfcMfClassicDictAttack" | |||
|
|||
// TODO: Update progress bar with nested attacks |
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.
TODOs will fail CI/CD lint stage, normally we don't leave them without appropriate issue in task tracker.
command = instance->callback(instance->general_event, instance->context); | ||
// Nested entrypoint | ||
// TODO: Ensure nested attack isn't run if tag is fully read | ||
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || |
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.
@@ -3,13 +3,39 @@ | |||
#include "mf_classic_poller.h" | |||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h> | |||
#include <bit_lib/bit_lib.h> | |||
#include "nfc/helpers/iso14443_crc.h" |
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.
<>
- for global includes(outside of this file folder)
""
- for local includes(in this file folder)
@@ -113,7 +113,7 @@ void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size | |||
uint8_t bit = | |||
FURI_BIT(data[bits_processed / BITS_IN_BYTE + 1], bits_processed % BITS_IN_BYTE); | |||
|
|||
if(bits_processed % BITS_IN_BYTE) { | |||
if((bits_processed % BITS_IN_BYTE) == 0) { |
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.
unit tests maybe?
@noproto that is some very cool thing that you've made ) |
We'll include this one in next release after @gornekich review and QA testing. |
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.
Hi @noproto
Thank you for working on that! I started reviewing the code, I will come back with the review soon
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; | ||
} | ||
} | ||
|
||
// 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; | ||
|
||
// Attempt backdoor authentication | ||
MfClassicError error = mf_classic_poller_auth( | ||
instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); | ||
if((next_key_index == 0) && | ||
(error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { | ||
FURI_LOG_E(TAG, "No backdoor identified"); | ||
dict_attack_ctx->backdoor = MfClassicBackdoorNone; | ||
instance->state = MfClassicPollerStateRequestKey; |
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.
@noproto Could you check this part?
Now I can see that you check only first backdoor key (MfClassicBackdoorAuth3), and if backdoor auth is failed we don't check other 2 known backdoor keys. Also the instance->mode_ctx.dict_attack_ctx
is always 0-initialized at this point, so I think the current_key_index
will be always mf_classic_backdoor_keys_count - 1
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.
It depends on how backdoor authentication fails. If the tag does not respond with a nonce to the backdoor authentication command, we skip detection (as it is an unrecognized command by the tag) and save time. If it does, we continue unless we've failed to authenticate with all known backdoor keys. current_keys_index is initialized to 2 initially, so that the "next" key is 0 (since current_key
is initially not a backdoor key) and starts the loop through the backdoor key array.
Verified with v1, v3, and a tag with no backdoor present:
v1:
59810309 [E][MfClassicPoller] Trying backdoor v3
59810423 [E][MfClassicPoller] Trying backdoor v1
59810428 [E][MfClassicPoller] Backdoor identified: v1
v3:
20070 [E][MfClassicPoller] Trying backdoor v3
20075 [E][MfClassicPoller] Backdoor identified: v3
No backdoor:
15543 [E][MfClassicPoller] Trying backdoor v3
15547 [E][MfClassicPoller] No backdoor identified
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.
MfClassicErrorProtocol
is the error code when a nonce is not sent by the tag. Let me know if I can test anything else for this.
Here is what I'll resolve before moving this PR back out of draft, will keep this list updated:
|
What's new
Verification
This PR currently contains the minimum necessary code to achieve the intended functionality. It will be superseded with performance improvements and UI improvements.
Checklist (For Reviewer)