mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-26 01:48:10 -07:00
MIFARE Classic Key Recovery Improvements (#3822)
* 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 <alleteam@gmail.com> Co-authored-by: gornekich <n.gorbadey@gmail.com>
This commit is contained in:
@@ -82,7 +82,7 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n) {
|
||||
uint32_t crypto1_prng_successor(uint32_t x, uint32_t n) {
|
||||
SWAPENDIAN(x);
|
||||
while(n--)
|
||||
x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
|
||||
@@ -169,11 +169,69 @@ void crypto1_encrypt_reader_nonce(
|
||||
nr[i] = byte;
|
||||
}
|
||||
|
||||
nt_num = prng_successor(nt_num, 32);
|
||||
nt_num = crypto1_prng_successor(nt_num, 32);
|
||||
for(size_t i = 4; i < 8; i++) {
|
||||
nt_num = prng_successor(nt_num, 8);
|
||||
nt_num = crypto1_prng_successor(nt_num, 8);
|
||||
uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num);
|
||||
bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) {
|
||||
int out;
|
||||
uint8_t ret;
|
||||
uint32_t t;
|
||||
|
||||
crypto1->odd &= 0xffffff;
|
||||
t = crypto1->odd;
|
||||
crypto1->odd = crypto1->even;
|
||||
crypto1->even = t;
|
||||
|
||||
out = crypto1->even & 1;
|
||||
out ^= LF_POLY_EVEN & (crypto1->even >>= 1);
|
||||
out ^= LF_POLY_ODD & crypto1->odd;
|
||||
out ^= !!in;
|
||||
out ^= (ret = crypto1_filter(crypto1->odd)) & (!!fb);
|
||||
|
||||
crypto1->even |= (nfc_util_even_parity32(out)) << 23;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) {
|
||||
uint32_t ret = 0;
|
||||
for(int i = 31; i >= 0; i--) {
|
||||
ret |= lfsr_rollback_bit(crypto1, BEBIT(in, i), fb) << (24 ^ i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) {
|
||||
return (nfc_util_even_parity8((nt >> 24) & 0xFF) ==
|
||||
(((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) &&
|
||||
(nfc_util_even_parity8((nt >> 16) & 0xFF) ==
|
||||
(((nt_par_enc >> 2) & 1) ^ FURI_BIT(ks, 8))) &&
|
||||
(nfc_util_even_parity8((nt >> 8) & 0xFF) ==
|
||||
(((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0)));
|
||||
}
|
||||
|
||||
bool crypto1_is_weak_prng_nonce(uint32_t nonce) {
|
||||
if(nonce == 0) return false;
|
||||
uint16_t x = nonce >> 16;
|
||||
x = (x & 0xff) << 8 | x >> 8;
|
||||
for(uint8_t i = 0; i < 16; i++) {
|
||||
x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15;
|
||||
}
|
||||
x = (x & 0xff) << 8 | x >> 8;
|
||||
return x == (nonce & 0xFFFF);
|
||||
}
|
||||
|
||||
uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) {
|
||||
uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6);
|
||||
Crypto1 crypto_temp;
|
||||
crypto1_init(&crypto_temp, known_key_int);
|
||||
crypto1_word(&crypto_temp, nt_enc ^ cuid, 1);
|
||||
uint32_t decrypted_nt_enc =
|
||||
(nt_enc ^ crypto1_lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1));
|
||||
return decrypted_nt_enc;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocols/mf_classic/mf_classic.h>
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -38,7 +39,15 @@ void crypto1_encrypt_reader_nonce(
|
||||
BitBuffer* out,
|
||||
bool is_nested);
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n);
|
||||
uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb);
|
||||
|
||||
bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc);
|
||||
|
||||
bool crypto1_is_weak_prng_nonce(uint32_t nonce);
|
||||
|
||||
uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key);
|
||||
|
||||
uint32_t crypto1_prng_successor(uint32_t x, uint32_t n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ static const uint8_t nfc_util_odd_byte_parity[256] = {
|
||||
0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1};
|
||||
|
||||
uint8_t nfc_util_even_parity8(uint8_t data) {
|
||||
return !nfc_util_odd_byte_parity[data];
|
||||
}
|
||||
|
||||
uint8_t nfc_util_even_parity32(uint32_t data) {
|
||||
// data ^= data >> 16;
|
||||
// data ^= data >> 8;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
uint8_t nfc_util_even_parity8(uint8_t data);
|
||||
|
||||
uint8_t nfc_util_even_parity32(uint32_t data);
|
||||
|
||||
uint8_t nfc_util_odd_parity8(uint8_t data);
|
||||
|
||||
Reference in New Issue
Block a user