Files
Mykhailo Shevchuk 68952d814a [NFC]: Align MIFARE type detection with AN10833 (Classic SAK sizing, UL/NTAG GetVersion typing, Plus SL0/SL3/EV1/EV2, Ultralight AES) (#1014)
* nfc: size MIFARE Classic from SAK per AN10833

The Classic poller sized cards purely behaviorally (auth-probe block 254
-> 4K, else block 62 -> 1K, else Mini) and ignored the SAK it already
captures at activation. A magic CUID that answers every block was thus
mis-sized as 4K despite its 1K SAK.

Derive the size from the SAK bit map first (AN10833: 0x09 -> Mini, bit4 ->
4K, bit3 -> 1K) and fall back to the behavioral block probe only when the
SAK is not a recognized Classic value. Genuine cards are unchanged (their
SAK already matches their size); clones with a standard SAK are now sized
to what they claim instead of what every-block-answers implies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: report unmodelled GetVersion Ultralight as Unknown, not Origin

mf_ultralight_get_type_by_version defaulted an unrecognized GetVersion
response to MfUltralightTypeOrigin. But answering GetVersion at all proves
the IC is not an original Ultralight (which has no GetVersion), so a modern
card such as Ultralight AES was silently mislabelled a 16-page original.

Add MfUltralightTypeUnknown (conservative, Origin-identical features; not
offered for Write since the app's allow-list omits it) and default to it.
Reserves Origin for the no-GetVersion fallback path. The NTAG212/UL21
storage-size collision and full support for new product types are left for
a follow-up that adds those types with correct page counts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: derive MIFARE Plus security level from SAK bit map (AN10833)

The Plus version path inferred the security level from a binary SAK==0x20
test (SL3, else SL1), which collapsed SL2 entirely and mislabelled genuine
SL2 cards as SL1. Use the SAK bit map instead: 0x20 -> SL3, 0x10/0x11 ->
SL2, 0x08/0x18 -> SL1, anything else -> Unknown rather than a guessed SL1.

GetVersion stays authoritative for type and size; the SAK only refines the
level. The no-GetVersion ISO4 fallback (which still relies on ATS/ATQA,
against AN10833) is intentionally untouched here and left for a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: identify Ultralight/NTAG by GetVersion product family (AN10833)

get_type_by_version keyed on storage_size alone, so ICs that share a
storage size across families were mistyped: NTAG212 read as UL21, NTAG210
fell through to a false original, and Ultralight AES (storage 0x0F) was
mislabelled NTAG213. Gate on the GetVersion product-family nibble (0x03
Ultralight, 0x04 NTAG) first, then size within the family by storage_size.
NTAG I2C stays anchored on subtype/major since its nibble is unreliable
(real silicon 0x07 vs the in-tree generator 0x04).

Add NTAG210 (= UL11 layout), NTAG212 (= UL21 layout), and Ultralight AES
(detect/label only -- AES auth is not implemented, with a conservative
read-only page count pending datasheet validation). NTAG210/212 are also
added to the NDEF parser allow-list and NTAG212 to the listener dynamic-
lock granularity. The new types are read-only (omitted from the write
allow-list, so the write-end-page assert is never reached).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: drop forbidden ATQA/ATS over-claims in MIFARE Plus iso4 type-ID

mf_plus_get_type_from_iso4 asserted "Plus X" from a bare SAK 0x10/0x11 and
sized SL3 cards from the ATQA nibble -- both signals AN10833 says never to
use for type identification. Report SAK 0x10/0x11 as a generic Plus 2K/4K
SL2 (drop the unjustified X), and leave SL3 size Unknown instead of reading
ATQA.

The ATS-historical-byte match is kept as the SL3 detection gate on purpose:
SAK 0x20 is shared with DESFire and MfPlus is probed before MfDesfire, so
claiming Plus on a bare SAK would hijack DESFire detection. Dropping the
ATQA gate also means an SL3 Plus with a non-standard ATQA is now detected
(size Unknown) instead of missed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: never report an Ultralight AES read as complete

The UltralightAES feature set omits PasswordAuth, so mf_ultralight_is_all_data_read
returned true once the (conservative, unverified) page count was read -- silently
presenting a truncated dump as complete, even though the IC's memory is protected by
AES authentication that the poller does not perform. Treat AES as never-complete so
the read honestly surfaces as partial. Found in PR review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: tidy MIFARE type-id comments and hoist a local (no behavior change)

Trim the verbose AN10833/SL3/Unknown/AES comments to a single "why" line each
per the project's concise comment style, and hoist the repeated SAK deref in
mf_plus_get_type_from_version to a local. Pure cleanup from the simplify pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: type Ultralight/NTAG by storage_size, family nibble only for collisions

The family-nibble gate from the previous commit was too strict: a card whose
GetVersion prod_type low-nibble is neither 0x03 nor 0x04 (common on magic/clone
Ultralight cards) fell through to MfUltralightTypeUnknown, whose config_page is 0
-- so PWD/PACK stopped rendering and the poller skipped PWD auth. The legacy dev
logic keyed purely on storage_size and typed such cards as UL11/UL21.

Restore storage_size-primary typing and use the product-family nibble only to
disambiguate the storage values that collide across families (NTAG210/UL11 0x0B,
NTAG212/UL21 0x0E, NTAG213/Ultralight AES 0x0F). Keeps the three collision fixes
without regressing odd-nibble clones; Unknown now only for an unrecognized
storage_size.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: probe MIFARE Plus SL0 vs SL3 actively (AN10833/PM3 hf mfp info)

SAK 0x20 + ATS is shared by a Plus in SL0, a Plus in SL3, and a DESFire,
so it cannot be classified passively. Detection previously hard-coded
SAK 0x20 -> SL3, so a card in SL0 (personalization state) was reported as
SL3, and any SL3 Plus whose ATS was not in the S/X/SE table fell to
Unknown.

Mirror PM3: after activation, send WritePerso (0xA8) to the intentionally
invalid block 0x9090 and classify the reply -- 0x09 means SL0 (block
rejected, nothing written), 67 00 / 1C 83 0C / 6D 00 mean DESFire or
unsupported (not Plus), anything else is SL3. The probe runs only for
SAK 0x20 and feeds the resolved level into both the GetVersion and ISO-4
type paths.

Because the probe positively rules out DESFire, the ISO-4 SAK-0x20 path
can now report a generic Mifare Plus for an untabled/short ATS instead of
Unknown. When the probe yields no usable result the old strict ATS-table
gate is kept, so DESFire is never hijacked. 2K vs 4K still needs the
AN10833-forbidden ATQA nibble, so SL3 size stays Unknown.

Builds clean; not yet validated on real Plus hardware (SL0 + SL3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: harden MIFARE Plus SL probe (PR-review fixes)

Address review findings on the SL0/SL3 WritePerso probe:

- Classify the reply by an allowlist, not a denylist. Plus answers 0x09
  (SL0) or 0x06/0x0B (SL3); every other reply becomes NotPlus. This fixes
  two issues at once: (1) a single-byte 0x09 SL0 reply was misread as SL3
  due to a spurious "len > 1" guard, defeating SL0 detection; (2) the old
  "anything else maps to SL3" default could promote an unanticipated
  DESFire reply to a confident Plus and hijack DESFire in the ISO-4 path.
  No real DESFire native status code is 0x06/0x09/0x0B, so unknown replies
  now fall through DESFire-safe. Also removes the fragile multi-byte
  denylist.

- Version path: only log "(probe)" when the probe actually resolved the
  level; the SL3 fallback now logs "(default)" instead of claiming the
  probe ran.

- Log the raw WritePerso status byte (FURI_LOG_D) to aid the pending
  on-hardware validation.

- Initialize the probe result local defensively (fail-safe NotPlus).

- Rename the converter to mf_plus_poller_resolve_sak20_security_level to
  distinguish it from the probe, and trim duplicated comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: simplify MIFARE Plus SL probe (post-/simplify)

Quality cleanups from the /simplify pass, no detection-behavior change:

- Extract mf_plus_type_from_ats() and use it at both SAK-0x20 sites,
  removing the duplicated ATS-historical-bytes -> S/X/SE memcmp ladder
  that the probe change had introduced a second copy of.

- Skip the active probe on the detect-phase version path. There the SL is
  written to a throwaway detect poller and discarded, and DESFire is
  already excluded by the GetVersion family nibble, so the probe could not
  change the detect result -- only waste one WritePerso RF round-trip per
  EV-Plus/DESFire scan cycle. The iso4 fallback still probes, since that is
  where the probe provides real detection coverage.

- Route the probe through nxp_native_command_iso14443_4a_poller (the same
  plain-mode path read_version uses) and read its status_code out-param,
  instead of hand-rolling iso14443_4a_poller_send_block + manual byte-0
  extraction. Behaviour-equivalent (an empty reply yields LENGTH_ERROR ->
  NotPlus, as before).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: derive MIFARE Plus SL0/SL3 size from ATQA

SL0/SL3 cards expose no product-independent storage byte, so size showed
Unknown for any Plus reaching the ISO-4 / probe path (GetVersion absent or
hw_storage unrecognized). Once the active probe has confirmed the card is a
Plus, fall back to the ATQA size coding -- bit 2 (0x0004) = 2K, bit 1
(0x0002) = 4K -- the same signal PM3 hf mfp info uses.

This is size refinement of an already-confirmed Plus, not ATQA-based type
identification, so AN10833's caution against ATQA for product ID does not
apply. SE stays 1K regardless; a recognized GetVersion hw_storage still
wins, with ATQA only as the fallback when it is Unknown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: gate Ultralight detection on SAK/ATQA, not a bare read

mf_ultralight_poller_detect only sent a READ page 0 (0x30) and treated any
reply as Ultralight. A magic/Chinese MIFARE Classic clone (gen1a/gen2)
answers an unauthenticated READ 0x30 through its backdoor exactly like an
Ultralight, so such a card was offered to the user as BOTH MIFARE Classic
(correct -- e.g. a Mini) and NTAG/Ultralight (false positive). A genuine
Classic NAKs the unauthenticated read, which is why only clones misfired.

Gate the probe read behind mf_ultralight_detect_protocol() (SAK 0x00 +
ATQA 0x0044) so only cards advertising the Ultralight/NTAG SAK/ATQA are
read as Ultralight. This mirrors PM3, which routes SAK 0x00 to the
Ultralight path and aborts UL detection when atqa[1] != 0x00 || sak != 0x00
before ever issuing READ 0x30. The Chinese Mini clone (SAK 0x09, ATQA
0x0004) now fails the gate and is detected as Classic/Mini only; genuine
and magic Ultralight/NTAG (SAK 0x00, ATQA 0x0044) are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: loosen Ultralight detect gate to PM3's SAK/ATQA (PR-review fix)

mf_ultralight_detect_protocol required ATQA exactly 0x0044 (atqa[0]==0x44),
but that low byte encodes UID size (0x44 = 7-byte, 0x04 = 4-byte), so the
strict term silently excluded a 4-byte-UID Ultralight/NTAG -- e.g. a magic
UL configured with a 4-byte UID (ATQA 0x0004) -- which the previous bare
read would have detected. The strict term also contributes nothing to the
magic-Classic-clone fix it shipped with: those carry a non-zero Classic SAK
(Mini 0x09, 1K 0x08, 4K 0x18) and are already rejected by the SAK term.

Gate on atqa[1]==0x00 && sak==0x00 only, matching PM3 (which rejects solely
when atqa[1] != 0x00 || sak != 0x00). Magic Classic clones still fail on SAK;
4-byte-UID UL/NTAG are no longer wrongly dropped. Also trims the detect
comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: collapse mf_ultralight_detect_protocol to a direct return (/simplify)

Drop the single-use mfu_detected temporary; the comment already documents
the expression. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: detect MIFARE Plus EV1/EV2 via GetVersion (trust valid reply)

A MIFARE Plus EV1/EV2 answers GetVersion (0x60) with the full, correct
28-byte version block but terminates the native frame exchange with a
status byte other than 0x00 (unlike DESFire), so mf_plus_poller_send_chunks
reported MfPlusErrorProtocol and read_version discarded a perfectly valid
version. The card then fell back to ATS-based typing, which mis-identified
every EV1/EV2 as the older "Plus X" -- EV1/EV2 share the Plus-X ATS
historical bytes, so GetVersion is the only way to tell them apart. The
EV1/EV2 typing itself (hw_major 0x11 -> EV1, 0x22 -> EV2; storage 0x16 ->
2K, 0x18 -> 4K) was already correct but never reached.

Trust the assembled version whenever it parses as a genuine NXP Plus
(vendor 0x04, family nibble 0x02), regardless of the terminal status;
otherwise propagate the real error so older Plus S/X/SE (which do not
answer GetVersion) still fall back to ATS. Reset the result buffer before
the exchange so a first-frame transport failure can't leave stale bytes
that the payload-trust check would accept (nxp_native_command only clears
it after its first frame succeeds).

HW-validated: a Plus EV1 4K in SL0 now reads as "MIFARE Plus EV1 4K SL0"
(was "Plus X 4K SL0"). DESFire (family nibble 0x01) and older Plus S/X/SE
are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: handle MIFARE Ultralight AES as identity-only (no read hang)

A MIFARE Ultralight AES (MF0AES20) needs AES-128 authentication for every
page and returns a 48-byte signature (our reader expects 32), neither of
which the firmware implements. The read poller therefore failed at the
signature/page reads -> ReadFailed, and the app's Ultralight read callback
retries on failure, so the poller restarted from Idle endlessly and the read
froze on "Don't move" forever (confirmed by an on-device poller state trace).

Treat UL-AES as identity-only, like MIFARE Plus (which reads its version/ATS
and stops without dumping memory):

- Poller: GetFeatureSet routes UltralightAES straight to ReadSuccess after
  GetVersion, skipping the AES-gated signature/page reads that can never
  succeed. The card is identified and the read terminates.

- Render: show UL-AES like MIFARE Plus -- Tech + UID (+ ATQA/SAK in Full) --
  instead of "Pages Read: 0/40" / "Password-protected pages!" / counters. The
  Short (read-result) form of iso14443_3a_info omits the Tech line, so it is
  added explicitly to match the Plus result screen.

- Per-card features: add an optional get_features() hook to
  NfcProtocolSupportBase (overrides the static .features bitmask when set) and
  have mf_ultralight report EmulateUid (no MoreInfo/Write/EmulateFull) for
  UL-AES. This drives the menu label ("Emulate UID"), the emulate screen
  ("Emulating UID"), and the now-hidden empty page-dump "More" view from one
  place instead of scattered scene hacks.

- Emulate: UL-AES emulates UID only (Iso14443_3a listener) -- a full emulation
  would be an empty card (no readable memory, no AES-auth listener).

- Menu: don't offer "Unlock" for UL-AES; only the unimplemented AES auth could
  unlock it, so the password-based flow can't help.

No full UL-AES read/emulate (that needs UL-AES AES-128 auth, a separate
feature). Other UL/NTAG types are unaffected: get_features returns the full
set, and every other protocol keeps its static features. HW-validated: no
more hang; result screen and menu match MIFARE Plus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: name MIFARE Mini per NXP product name (drop "Classic")

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(changelog): AN10833 type-detection alignment; drop fixed Mini-clone regression

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com>
2026-07-02 20:25:45 +03:00
..

Structure

debug

Applications for factory testing the Flipper.

  • accessor - Wiegand server
  • battery_test_app - Battery debug app
  • blink_test - LED blinker
  • bt_debug_app - BT test app. Requires full BT stack installed
  • display_test - Various display tests & tweaks
  • file_browser_test - Test UI for file picker
  • keypad_test - Keypad test
  • lfrfid_debug - LF RFID debug tool
  • text_box_test - UI tests
  • uart_echo - UART mode test
  • unit_tests - Unit tests
  • usb_mouse - USB HID test
  • usb_test - Other USB tests
  • vibro_test - Vibro test

main

Applications for main Flipper menu.

  • archive - Archive and file manager
  • bad_usb - Bad USB application
  • gpio - GPIO application: includes USART bridge and GPIO control
  • ibutton - iButton application, onewire keys and more
  • infrared - Infrared application, controls your IR devices
  • lfrfid - LF RFID application
  • nfc - NFC application, HF rfid, EMV and etc
  • subghz - SubGhz application, 433 fobs and etc
  • u2f - U2F Application

services

Background services providing system APIs to applications.

  • applications.h - Firmware application list header
  • bt - BLE service and application
  • cli - Console service and API
  • crypto - Crypto cli tools
  • desktop - Desktop service
  • dialogs - Dialogs service: GUI Dialogs for your app
  • dolphin - Dolphin service and supplementary apps
  • gui - GUI service and API
  • input - Input service
  • loader - Application loader service
  • notification - Notification service
  • power - Power service
  • rpc - RPC service and API
  • storage - Storage service, internal + sdcard

settings

Small applications providing configuration for basic firmware and its services.

  • about - Small About application that shows flipper info
  • bt_settings_app - Bluetooth options
  • desktop_settings - Desktop configuration
  • dolphin_passport - Dolphin passport app
  • notification_settings - LCD brightness, sound volume, etc configuration
  • power_settings_app - Basic power options
  • storage_settings - Storage settings app
  • system - System settings
  • input_settings_app - Basic input options

system

Utility apps not visible in other menus, plus few external apps pre-packaged with the firmware.

  • hid_app - BLE & USB HID remote
  • js_app - JS engine runner
  • snake_game - Snake game
  • storage_move_to_sd - Data migration tool for internal storage
  • updater - Update service & application