Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion applications/main/nfc/helpers/mf_classic_key_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData*
if(!flipper_format_write_hex_uint64(ff, "Key A map", &data->key_a_mask, 1)) break;
if(!flipper_format_write_hex_uint64(ff, "Key B map", &data->key_b_mask, 1)) break;

uint8_t sector_num = mf_classic_get_total_sectors_num(data->type);
uint8_t sector_num = mf_classic_get_scannable_sectors_num(data->type);
bool key_save_success = true;
for(size_t i = 0; (i < sector_num) && (key_save_success); i++) {
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,13 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) {
}

static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) {
const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
// Use stored data; normalize ATQA/SAK in-place for 4-byte UID to avoid cascade-bit issues
MfClassicData* data = (MfClassicData*)nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
if(data->iso14443_3a_data && data->iso14443_3a_data->uid_len == 4) {
data->iso14443_3a_data->atqa[0] = 0x04;
data->iso14443_3a_data->atqa[1] = 0x00;
data->iso14443_3a_data->sak = 0x08; // no cascade bit
}
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfClassic, data);
nfc_listener_start(instance->listener, NULL, NULL);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ void nfc_render_mf_classic_info(
FuriString* str) {
nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str);

uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
uint8_t sectors_total = mf_classic_get_scannable_sectors_num(data->type);
uint8_t keys_total = sectors_total * 2;
uint8_t keys_found = 0;
uint8_t sectors_read = 0;
Expand Down
142 changes: 134 additions & 8 deletions applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
#include "mf_plus_render.h"

#include <nfc/protocols/mf_plus/mf_plus_poller.h>
#include <nfc/protocols/mf_classic/mf_classic.h>

#include <string.h>
#include <lib/bit_lib/bit_lib.h>

#include "nfc/nfc_app_i.h"
#include "../../mf_classic_key_cache.h"

#include "../nfc_protocol_support_common.h"
#include "../nfc_protocol_support_gui_common.h"
Expand Down Expand Up @@ -68,17 +73,138 @@ static void nfc_scene_read_success_on_enter_mf_plus(NfcApp* instance) {
}

static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) {
const Iso14443_4aData* iso14443_4a_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);

instance->listener =
nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data);
nfc_listener_start(
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
const MfPlusData* mf_plus_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfPlus);

// For SL1 2K cards, use Classic emulation (compatible with Classic readers)
// MIFARE Plus 2K SL1 emulates as Classic with 32 sectors (128 blocks total)
// This allows UID-only readers (like printers) to work, and exposes all 32 sectors
if(mf_plus_data->security_level == MfPlusSecurityLevel1 &&
mf_plus_data->size == MfPlusSize2K) {
MfClassicData* classic_data = NULL;

// Try to get Classic data if the card was read as Classic
// This ensures we emulate the actual data that was scanned (all sectors, keys, blocks)
const MfClassicData* existing_classic_data = NULL;
// Check if device protocol is Classic (card was read as Classic, not just Plus)
if(nfc_device_get_protocol(instance->nfc_device) == NfcProtocolMfClassic) {
existing_classic_data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
}

if(existing_classic_data && existing_classic_data->type == MfClassicTypePlus2k) {
// Use the actual Classic data that was read from the card
// This contains all the real sector data, keys, and blocks from the scan
classic_data = mf_classic_alloc();
mf_classic_copy(classic_data, existing_classic_data);

// Ensure sectors 18-31 are treated as uninitialized to match real card behavior
// On real MIFARE Plus 2K SL1 cards, sectors 18-31 are typically empty/uninitialized
// Clear key masks for sectors 18-31 if keys are zero (uninitialized)
for(uint8_t sector = 18; sector < 32; sector++) {
uint8_t sector_trailer_block = sector * 4 + 3;
MfClassicSectorTrailer* sec_tr =
(MfClassicSectorTrailer*)&classic_data->block[sector_trailer_block];

// Check if both keys are zero (uninitialized)
bool key_a_zero = true;
bool key_b_zero = true;
for(int i = 0; i < 6; i++) {
if(sec_tr->key_a.data[i] != 0) key_a_zero = false;
if(sec_tr->key_b.data[i] != 0) key_b_zero = false;
}

// Check if keys were found in original read
bool key_a_found_orig = mf_classic_is_key_found(existing_classic_data, sector, MfClassicKeyTypeA);
bool key_b_found_orig = mf_classic_is_key_found(existing_classic_data, sector, MfClassicKeyTypeB);

// Clear key masks if keys are zero (uninitialized) OR if they weren't found in original
// This ensures empty sectors appear as uninitialized, matching real card
// The listener will reject authentication attempts to sectors without keys found
if(key_a_zero || !key_a_found_orig) {
mf_classic_set_key_not_found(classic_data, sector, MfClassicKeyTypeA);
}
if(key_b_zero || !key_b_found_orig) {
mf_classic_set_key_not_found(classic_data, sector, MfClassicKeyTypeB);
}
}
} else {
// No Classic data available - create minimal Classic data from MF Plus
// This is a fallback when card was only read as Plus (without sector data)
classic_data = mf_classic_alloc();
classic_data->type = MfClassicTypePlus2k;

// Initialize key masks to zero (no keys found) - sectors are uninitialized
classic_data->key_a_mask = 0ULL;
classic_data->key_b_mask = 0ULL;

// Copy ISO14443-3A data from MF Plus (UID, ATQA, SAK)
const Iso14443_3aData* iso3_data =
iso14443_4a_get_base_data(mf_plus_data->iso14443_4a_data);
if(iso3_data) {
iso14443_3a_copy(classic_data->iso14443_3a_data, iso3_data);
// Force SL1 Classic view: ATQA 0x0004, SAK 0x08 (no cascade bit)
classic_data->iso14443_3a_data->atqa[0] = 0x04;
classic_data->iso14443_3a_data->atqa[1] = 0x00;
classic_data->iso14443_3a_data->sak = 0x08;
// Ensure 4-byte UID form (the real card uses 4B UID)
if(classic_data->iso14443_3a_data->uid_len > 4) {
classic_data->iso14443_3a_data->uid_len = 4;
}

// Try to load keys from key cache to speed up emulation
// This allows emulation to work faster if keys were previously cached
if(instance->mfc_key_cache) {
size_t uid_len = 0;
const uint8_t* uid = iso14443_3a_get_uid(iso3_data, &uid_len);
if(mf_classic_key_cache_load(instance->mfc_key_cache, uid, uid_len)) {
// Keys loaded from cache - copy them to classic_data
MfClassicDeviceKeys* cached_keys = &instance->mfc_key_cache->keys;
classic_data->key_a_mask = cached_keys->key_a_mask;
classic_data->key_b_mask = cached_keys->key_b_mask;

// Copy cached keys to sector trailers
for(uint8_t sector = 0; sector < 32; sector++) {
if(FURI_BIT(cached_keys->key_a_mask, sector)) {
MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(classic_data, sector);
sec_tr->key_a = cached_keys->key_a[sector];
mf_classic_set_key_found(
classic_data, sector, MfClassicKeyTypeA,
bit_lib_bytes_to_num_be(cached_keys->key_a[sector].data, 6));
}
if(FURI_BIT(cached_keys->key_b_mask, sector)) {
MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(classic_data, sector);
sec_tr->key_b = cached_keys->key_b[sector];
mf_classic_set_key_found(
classic_data, sector, MfClassicKeyTypeB,
bit_lib_bytes_to_num_be(cached_keys->key_b[sector].data, 6));
}
}
}
}
}
// Note: Without Classic data, sectors without cached keys are uninitialized (no keys found)
// This matches real card behavior for empty sectors
}

instance->listener =
nfc_listener_alloc(instance->nfc, NfcProtocolMfClassic, classic_data);
nfc_listener_start(instance->listener, NULL, NULL);
} else {
// For SL2/SL3, use ISO14443-4A emulation
const Iso14443_4aData* iso14443_4a_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);

instance->listener =
nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data);
nfc_listener_start(
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
}
}

const NfcProtocolSupportBase nfc_protocol_support_mf_plus = {
.features = NfcProtocolFeatureEmulateUid,
.features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEmulateFull,

.scene_info =
{
Expand Down
2 changes: 1 addition & 1 deletion applications/main/nfc/plugins/supported_cards/aime.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static bool aime_read(Nfc* nfc, NfcDevice* device) {

data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(aime_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(aime_key, sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
4 changes: 2 additions & 2 deletions applications/main/nfc/plugins/supported_cards/banapass.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ static bool banapass_read(Nfc* nfc, NfcDevice* device) {
MfClassicDeviceKeys keys = {};

// Access Code Read Attempt
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(
banapass_keys_if_access_code[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
Expand All @@ -116,7 +116,7 @@ static bool banapass_read(Nfc* nfc, NfcDevice* device) {
}

// Value Block Read Attempt
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(
banapass_keys_if_value_block[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
Expand Down
2 changes: 1 addition & 1 deletion applications/main/nfc/plugins/supported_cards/bip.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) {

data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ static bool disney_infinity_read(Nfc* nfc, NfcDevice* device) {
if(error != MfClassicErrorNone) break;

data->type = type;
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
di_key(uid_bytes, &keys.key_a[i]);
di_key(uid_bytes, &keys.key_b[i]);
FURI_BIT_SET(keys.key_a_mask, i);
Expand Down
4 changes: 2 additions & 2 deletions applications/main/nfc/plugins/supported_cards/hi.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ static bool hi_read(Nfc* nfc, NfcDevice* device) {
uint8_t keyB[HI_KEY_TO_GEN][KEY_LENGTH];
hi_generate_key(uid, keyA, keyB);

for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) {
cfg.keys[i].a = bit_lib_bytes_to_num_be(keyA[i], KEY_LENGTH);
cfg.keys[i].b = bit_lib_bytes_to_num_be(keyB[i], KEY_LENGTH);
}
}

MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
2 changes: 1 addition & 1 deletion applications/main/nfc/plugins/supported_cards/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static bool hid_read(Nfc* nfc, NfcDevice* device) {

data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(hid_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(hid_key, sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
4 changes: 2 additions & 2 deletions applications/main/nfc/plugins/supported_cards/hworld.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static bool hworld_read(Nfc* nfc, NfcDevice* device) {
data->type = type;

MfClassicDeviceKeys standard_keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(
hworld_standard_keys[i].a, sizeof(MfClassicKey), standard_keys.key_a[i].data);
FURI_BIT_SET(standard_keys.key_a_mask, i);
Expand All @@ -130,7 +130,7 @@ static bool hworld_read(Nfc* nfc, NfcDevice* device) {
FURI_LOG_I(TAG, "Standard card successfully read");
} else {
MfClassicDeviceKeys vip_keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(
hworld_vip_keys[i].a, sizeof(MfClassicKey), vip_keys.key_a[i].data);
FURI_BIT_SET(vip_keys.key_a_mask, i);
Expand Down
4 changes: 2 additions & 2 deletions applications/main/nfc/plugins/supported_cards/microel.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ static bool microel_read(Nfc* nfc, NfcDevice* device) {
}

// Save keys generated to stucture
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
if(microel_1k_keys[i].a == 0x000000000000) {
microel_1k_keys[i].a = bit_lib_bytes_to_num_be(keyA, KEY_LENGTH);
}
Expand All @@ -136,7 +136,7 @@ static bool microel_read(Nfc* nfc, NfcDevice* device) {
}
}
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(
microel_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
Expand Down
4 changes: 2 additions & 2 deletions applications/main/nfc/plugins/supported_cards/mizip.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ static bool mizip_read(Nfc* nfc, NfcDevice* device) {
uint8_t keyB[MIZIP_KEY_TO_GEN][KEY_LENGTH];
mizip_generate_key(uid, keyA, keyB);

for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) {
cfg.keys[i].a = bit_lib_bytes_to_num_be(keyA[i], KEY_LENGTH);
cfg.keys[i].b = bit_lib_bytes_to_num_be(keyB[i], KEY_LENGTH);
}
}

MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
2 changes: 1 addition & 1 deletion applications/main/nfc/plugins/supported_cards/ndef.c
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,7 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) {

// Check MADs for what sectors contain NDEF data AIDs
bool sectors_with_ndef[MF_CLASSIC_TOTAL_SECTORS_MAX] = {0};
const size_t sector_count = mf_classic_get_total_sectors_num(data->type);
const size_t sector_count = mf_classic_get_scannable_sectors_num(data->type);
const struct {
size_t block;
uint8_t aid_count;
Expand Down
2 changes: 1 addition & 1 deletion applications/main/nfc/plugins/supported_cards/plantain.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
}

MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) {

data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
if(i == 0) {
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ static bool social_moscow_read(Nfc* nfc, NfcDevice* device) {
if(!social_moscow_get_card_config(&cfg, data->type)) break;

MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
for(size_t i = 0; i < mf_classic_get_scannable_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
Expand Down
Loading