Skip to content
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

Merged
merged 99 commits into from
Oct 31, 2024

Conversation

noproto
Copy link
Contributor

@noproto noproto commented Aug 4, 2024

What's new

  • MIFARE Classic Accelerated dictionary attack: dictionary attacks reduced to several seconds - checks ~3500 keys per second
  • MIFARE Classic Nested attack support: collects nested nonces to be cracked by MFKey, no longer requiring users to downgrade to FW 0.93.0
  • MIFARE Classic Static encrypted backdoor support: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor, allowing key recovery of all non-hardened MIFARE Classic tags on-device

Verification

This PR currently contains the minimum necessary code to achieve the intended functionality. It will be superseded with performance improvements.

  • Accelerated dictionary attack: Benchmark against standard dictionary attack
    • Note: This PR adds nonce collection methods which trade some of the time reclaimed by this improvement
  • Nested (weak PRNG): Using any weak PRNG tag and MFKey (v3.0 available for testing here)
  • Hardnested (hard PRNG): Verify nonces are stored at /ext/nfc/.nested.log (can be cracked using HardnestedRecovery)
  • Static encrypted (backdoor): Using static encrypted tag and MFKey

Checklist (For Reviewer)

  • PR has description of feature/bug or link to Confluence/Jira task
  • Description contains actions to verify feature/bugfix
  • I've built this code, uploaded it to the device and verified feature/bugfix

@noproto
Copy link
Contributor Author

noproto commented Aug 10, 2024

This PR also resolves bit_buffer_copy_bytes_with_parity improperly storing parity bits: 8dd3daf#diff-1d0dbb15ed26364f785d68ba753267b5326c95629d29641ef53276ac32336f7e

@hedger hedger added the NFC NFC-related label Aug 12, 2024
@noproto
Copy link
Contributor Author

noproto commented Aug 21, 2024

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:

OFW 0.105.0: 4 min 44 seconds (found 19/32 keys, 16 sectors read)
nestednonces 26845cb: 10 seconds (found 19/32 keys, 16 sectors read)
---
OFW 0.105.0: 5 min 8 seconds (found 18/32 keys, 2 sectors read) 
nestednonces 26845cb: 25 seconds (found 18/32 keys, 2 sectors read)
---
OFW 0.105.0: 22 seconds (found 32/32 keys, 16 sectors read) 
nestednonces 26845cb: 3 seconds (found 32/32 keys, 16 sectors read)
---
OFW 0.105.0: 35 seconds (found 32/32 keys, 16 sectors read) 
nestednonces 26845cb: 10 seconds (found 32/32 keys, 16 sectors read)
---
OFW 0.105.0: 5 min 3 seconds (found 18/32 keys, 16 sectors read) 
nestednonces 26845cb: 10 seconds (found 18/32 keys, 16 sectors read)

@noproto
Copy link
Contributor Author

noproto commented Oct 18, 2024

All issues resolved that I could reproduce, please re-test this PR @skotopes @RebornedBrain @doomwastaken:

So, @RebornedBrain @doomwastaken did some tests. Here is what they found:

Test 1: no plugins, stuck at particular key

Tests were performed without plugins (remove folder Plugins at "SD Card/apps_data/nfc") This card stuck at one particular key. In logs I see that process still goes, but key remains the same, but before keys were changing. See details on the screenshot.

test_1.zip

Test 2: MFC 4k, crash

This MFC 4k (not magic) card where first sector was protected with key A "DEADBEAFFFFF" which is not present in any dictionaries. Result - crash "Wrong sector num", repeats every time. This card has backdoor v2.

test_2.zip

Steps taken

  1. Noted power of 2 in both sector and keys found. Possible issue with datatype. Decided to not remove plugins for initial test.
  2. Using NFC Magic, cloned Plantan_white.nfc to a Gen4 UMC magic card
  3. Read card with NFC app. Crashes on sectors read 32/40, keys found 43/80, 33/40 on progress bar, "[CRASH][NfcWorker] furi_check failed" after "Found key candidate"
  4. Dumped call stack:
__furi_crash_implementation@0x08012280 (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/furi/core/check.c:170)
mf_classic_get_device_name@0x08036452 (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/lib/nfc/protocols/mf_classic/mf_classic.c:340)
mf_classic_get_first_block_num_of_sector@0x08036d6a (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/lib/nfc/protocols/mf_classic/mf_classic.c:565)
mf_classic_poller_handler_key_reuse_auth_key_a@0x080420fe (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/lib/nfc/protocols/mf_classic/mf_classic_poller.c:916)
(..)
  1. Identified issue: reuse_key_sector assigned to 43, when tag only has 40 sectors (0-39). This is because of a bad assumption (sector number = block number / 4, is not true for MFC 4K)
  2. Fixed in 4be9e79
  3. Re-ran dictionary attack, reproduced test 2 crash at end of nonce collection: "[CRASH][NfcWorker] Wrong sector num"
  4. Dumped call stack:
__furi_crash_implementation@0x08012280 (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/furi/core/check.c:170)
mf_classic_get_sector_trailer_num_by_sector@0x0803666c (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/lib/nfc/protocols/mf_classic/mf_classic.c:403)
mf_classic_get_sector_trailer_num_by_sector@0x0803666c (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/lib/nfc/protocols/mf_classic/mf_classic.c:395)
mf_classic_poller_handler_nested_collect_nt_enc@0x080427fa (/home/ubuntu/Flipper/nestednonces/flipperzero-firmware/lib/nfc/protocols/mf_classic/mf_classic_poller.c:1362)
(..)
  1. Identified issue: off by one in nested_target_key, doesn't crash on MFC 1K since no effect besides reduced performance (same issue as "TODO: Fix rare nested_target_key 64 bug")
  2. Fixed in 4be9e79
  3. Test: Read tag, recovered 29 keys with MFKey (78/80 keys), read tag, recovered 2 keys with MFKey, 80/80 keys 40/40 sectors read. Working.
  4. Began re-testing original issues. Removed plugins folder, unable to reproduce. Removed user dictionary, unable to reproduce.

Conclusion

Two issues: faulty state machine logic, target sector on 4K cards. Fixed in 4be9e79

Test 3: MFC 1k, can't find key

This MFC 1k (magic) card where first sector was also protected with key A "DEADBEAFFFFF". Result Flipper cannot find this key. This card has no any backdoor.

test_3.zip

Steps taken

  1. Verified Prox.nfc has sector 1 key A DEADBEAFFFFF
  2. Using NFC Magic, cloned Prox.nfc to a Gen1a magic card
  3. Read card with NFC app. 31/32 keys. 1 nonce collected in .nested.log
  4. Cracked nonce in MFKey.
  5. Returned to NFC app and read card. 32/32, 16/16 sectors read
  6. Flashed second Flipper device with nestednonces fork
  7. Same result

Conclusion

Possible cache issue? Verify no keys are cached for this tag.
If issue persists, debug logs would be useful.

Test 4: Test with plugins

Flipper reads this card totally when there is no plugins (it passes nested attack and etc). When plugins are present Flipper unable to read it and in logs I see some auth errors.

test_4.zip

Steps taken

  1. Ensured NFC plugins folder present on Flipper device
  2. Using NFC Magic, cloned Small_troyka.nfc to a Gen4 UMC magic card
  3. Read card with NFC app. 32/32 keys.
  4. Flashed second Flipper device with nestednonces fork
  5. Reproduced auth errors. Narrowed issue to interaction between nestednonces and troika_parser.fal NFC plugin.
  6. Identified issue: inconsistent assignment of known key and known key type/sector. This is because of a bad assumption (key found in dictionary attack was assumed to be first key)
  7. Fixed in 897817a and db26c85
  8. Re-ran dictionary attack on all card types, works. No longer able to reproduce original issue.

Conclusion

Inconsistent assignment of known key and known key type/sector led to repeated failed authentication attempts. Keys were provided by specific NFC plugins instead of the dictionary attack. Fixed in 897817a and db26c85

Test 5: On this card Flipper stuck

On this card Flipper stuck

test_5.zip

Steps taken

  1. Ensured NFC plugins folder present on Flipper device
  2. Using NFC Magic, cloned Disappeared_corridor.nfc to a Gen4 UMC magic card
  3. Read card with NFC app. 70/80 keys.
  4. Flashed second Flipper device with nestednonces fork
  5. Same result. Troyka card recognized
  6. Removed plugins folder.
  7. Read card with NFC app. 70/80 keys.
  8. Added a user dictionary file to the device with 1 irrelevant key
  9. Read card with NFC app. 70/80 keys. Unable to reproduce original issue.

Conclusion

Likely related to the other (now fixed) issues. Please re-test.
If issue persists, debug logs would be useful.

@noproto
Copy link
Contributor Author

noproto commented Oct 19, 2024

Since #3961 (comment) will delay 1.1, is it still possible to squeeze this PR in too if the issues identified in QA are resolved by #3822 (comment) ?

These changes would allow me to share an early, significantly easier process with the users as well as limit the scope of each PR versus rolling up more changes into this single PR making it unwieldy to do QA. Additionally, it would be useful to collect feedback from users so we can identify any rare or uncommon issues (thanks to the exceptional QA already done, every issue that was reported to me - even prior to the review - has been resolved).

RE: how users could use the nonces collected by this PR, PR 243 to good-faps would make a complete process available: flipperdevices/flipperzero-good-faps#243

Wanted to bring this up for your consideration. I understand if it's not possible. For context, this is what is required for reading cards today. It becomes a two step process with this PR (3822) plus PR 243 to good-faps.

@RebornedBrain
Copy link
Contributor

@noproto, Hi, I've retested those cases and here are the results:

Test 1: no plugins, stuck at particular key - Fixed, now works fine. ✅
Test 2: MFC 4k, crash - Fixed, now Flipper reads card and collect nonces. ✅
Test 3: MFC 1k, can't find key - Fixed, ✅ but new issue appeared, more info below
Test 4 - Fixed, in both cases (no plugins/with plugins) Flipper reads card ✅
Test 5: On this card Flipper stuck - Fixed. ✅
Nicely done 👍

Now about new issue which I've noticed. Actually, maybe it was even previously, but we didn't see it. When I tested test 2 and 3, sometimes Flipper tries to perform attack again, even if I already have cracked nonces with the help of MFKey and key DEADBEAFFFFF is already present in user_dict.
I tested a lot on two Flippers which I have, in order to narrow roots of possible problem, and came to a conclusion that it mostly depends on key count present in mf_classic_dict_user.nfc. I attach my dictionary with 89 keys in it, with this dictionary one Flipper fails to read one sector more often (1 to 3 fails in 10 attempts), and another one fails rearly (1 fail in 40 attempts), but still. On the other hand when I deleted user dictionary completely, and then added only one key DEADBEAFFFFF then issue dissapeared, which proofs that dictionary size can cause some artefacts.

Here are steps to reproduce:

  1. Import mf_classic_dict_user.nfc to nfc->assets
  2. Check that key DEADBEAFFFFF is already present in users' keys
  3. Try to read card from test 2 or test 3.
    After this step some steps may vary because I don't have the exact sequence.
  4. If card read fine, press retry (sometimes 1 or 2 retries might be enough, sometimes even 20 will give no effect).
  5. If card read fine, exit NFC app, then enter again and retry step 4.
  6. If card read fine, reboot Flipper completely and repeat steps from 4.
    Steps 4-6 can be combined in different ways before issue appears.
  7. If card read fails to read 1 sector, Flipper collect nonces to .nested.logs, then you can delete this file via qFlipper and press retry, this can cause issue to repeat again, but not every time.

Expected result: Flipper reads card totally each time without any attempts of collecting nonces, because it already has known key in user dictionary.

Result: Flipper sometimes doesn't apply known key during read process, instead it collects nonces.

dictionary_with_issue.zip

@noproto
Copy link
Contributor Author

noproto commented Oct 25, 2024

Hi @RebornedBrain, good catch. Fixed in 6dbb46a I believe. Try again.

@skotopes
Copy link
Member

PVS Report:
image

@skotopes
Copy link
Member

I guess we are ready to merge this PR, couple small things left to cleanup:

@noproto
Copy link
Contributor Author

noproto commented Oct 29, 2024

@skotopes :

Remaining TODOs marked with FL-3926.

I'll open the refactoring PR when 3822 is merged, and I'll keep improving it as needed (clearing all TODOs, unit tests for parity, backdoor detection moved outside the poller).

If its helpful, I'll add FL-3926 to the title of the next PR. RE: PVS:

  • mf_classic_poller.c:1292: Redundant assignment removed in eb1aabb
  • mf_classic_poller.c:1293: Redundant assignment removed in eb1aabb
  • mf_classic_poller.c:1707: Format specifier corrected in c240077
  • mf_classic_poller.c:1938: PVS optimization included in 907019c

All good warnings by PVS this time.

@skotopes skotopes merged commit 8427ec0 into flipperdevices:dev Oct 31, 2024
11 checks passed
@skotopes
Copy link
Member

@noproto awesome work )

@noproto noproto deleted the nestednonces branch October 31, 2024 01:38
@psifertex
Copy link

Thanks everyone involved for the work landing this! Looking forward to trying it out.

@mishamyte
Copy link
Contributor

Thanks for your work @noproto.

And an opened question. Do you think, guys, nonces recovery could be implemented on a mobile app side conceptually?
Just as we have for MFKey32, to speed up recovery

RogueMaster pushed a commit to RogueMaster/flipperzero-firmware-wPlugins that referenced this pull request Nov 2, 2024
* Initial structure for nonce collection
* Nonce logging
* Dictionary attack structure
* Fix compilation
* Identified method to reduce candidate states
* Use EXT_PATH instead of ANY_PATH
* Use median calibrated distance, collect parity bits
* Modify parity collection
* Fixed parity bit collection
* Add note to fix nonce logging
* Fix nonce logging
* Clean redundant code
* Fix valid_nonce
* First attempt disambiguous nonce implementation
* FM11RF08S backdoor detection
* Initial accelerated dictionary attack for weak PRNGs
* Refactor to nested dictionary attack
* Renaming some variables
* Hard PRNG support for accelerated dictionary attack
* Update found keys, initial attempt
* Update found keys, second attempt
* Code cleanup
* Misc bugfixes
* Only use dicts in search_dicts_for_nonce_key if we have them
* Collect nonces again
* Should be detecting both backdoors now
* Relocate backdoor detection
* Hardnested support
* Fix regression for regular nested attack
* Backdoor read
* Backdoor working up to calibration
* Backdoor nested calibration
* Don't recalibrate hard PRNG tags
* Static encrypted nonce collection
* Update TODO
* NFC app UI updates, MVP
* Bump f18 API version (all functions are NFC related)
* Add new backdoor key, fix UI status update carrying over from previous read
* Clear TODO line
* Fix v1/v2 backdoor nonce collection
* Speed up backdoor detection, alert on new backdoor
* Add additional condition to backdoor check
* I'll try freeing memory, that's a good trick!
* Do not enter nested attack if card is already finished
* Do not reset the poller between collected nonces
* Clean up various issues
* Fix Hardnested sector/key type logging
* Add nested_target_key 64 to TODO
* Implement progress bar for upgraded attacks in NFC app
* Typo
* Zero nested_target_key and msb_count on exit
* Note TODO (malloc)
* Dismiss duplicate nonces
* Fix calibration (ensure values are within 3 standard deviations)
* Log static
* No nested dictionary attack re-entry
* Note minor inefficiency
* Uniformly use crypto1_ prefix for symbols in Crypto1 API
* Fix include paths
* Fix include paths cont
* Support CUID dictionary
* Fix log levels
* Avoid storage errors, clean up temporary files
* Handle invalid key candidates
* Fix memory leak in static encrypted attack
* Fix memory leak, use COUNT_OF macro
* Use single call to free FuriString
* Refactor enums to avoid redefinition
* Fix multiple crashes and state machine logic
* Fix inconsistent assignment of known key and known key type/sector
* Backdoor known key logic still needs the current key
* Larger data type for 4K support
* Fix typo
* Fix issue with resume logic
* Mark TODOs for next PR
* Remove redundant assignment
* Fix size_t format specifier
* Simplify auth_passed condition

Co-authored-by: Aleksandr Kutuzov <[email protected]>
Co-authored-by: gornekich <[email protected]>
@noproto
Copy link
Contributor Author

noproto commented Nov 2, 2024

Thanks for your work @noproto.

And an opened question. Do you think, guys, nonces recovery could be implemented on a mobile app side conceptually? Just as we have for MFKey32, to speed up recovery

I appreciate your reply @mishamyte. As far as offloading goes, I am only interested in doing this for attacks which are not possible to run on the Zero (Hardnested).

Since the Flipper Zero is a first generation device, the on-device attacks today are the worst they will ever be. Optimization of the key recovery process on the device has brought multiple recovery methods from taking months to minutes. I will continue to optimize and develop code for the process defined in this PR, but as new hardware revisions are imminent I expect that even without changes it will soon run in seconds rather than minutes (which would immediately deprecate any offloading effort done except for Hardnested).

@mishamyte
Copy link
Contributor

Thanks for your work @noproto.

And an opened question. Do you think, guys, nonces recovery could be implemented on a mobile app side conceptually? Just as we have for MFKey32, to speed up recovery

I appreciate your reply @mishamyte. As far as offloading goes, I am only interested in doing this for attacks which are not possible to run on the Zero (Hardnested).

Since the Flipper Zero is a first generation device, the on-device attacks today are the worst they will ever be. Optimization of the key recovery process on the device has brought multiple recovery methods from taking months to minutes. I will continue to optimize and develop code for the process defined in this PR, but as new hardware revisions are imminent I expect that even without changes it will soon run in seconds rather than minutes (which would immediately deprecate any offloading effort done except for Hardnested).

Thank you!
Personally I don't think my proposal should replace existing functionality.
IMO for me it's just an alternative.
Calculating on Flipper is good for small amount of keys, but when you need to calculate full diversified tag, it takes an eternity (that's feedback from guys, who tested).

So personally I would prefer to have both options. As we had it previously - with Python script + old fap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NFC NFC-related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants