Enhanced Contactless Polling (ECP) is a proprietary extension to the ISO/IEC 14443 (A/B) standard developed by Apple.
It defines a custom data frame that a contactless reader has to transmit during the polling sequence, providing an end device with contextual info about the reader field, allowing it to decide if it wants to resolve routing to a particular applet or system feature even before any back and forth communication starts.
This extension:
ECP is also sometimes referred to as Enhanced Contactless Protocol. For explanation, look into extras section
Express mode for most passes (apart from FeliCa and CATHAY) is implemented using ECP. That includes:
Other features use ECP as well:
Reader side:
Device side:
Upon entering a loop, the device does not answer to the first polling frame it sees, instead opting to wait and see what other technologies does the field poll for, allowing it to make a fully informed decision on what applet or feature to trigger later.
When the device makes a decision, it is mostly, although not in all cases (excluding keys) signified by a card image appearing along with a spinner.
Even though ECP is sent during the polling loop, device does not answer to it directly. Instead, it responds to a polling frame related to the technology of the pass that the device had decided to use.
When device enters the loop initially:
(ENTRY) -> A -> ECP_A -> B -> ECP_B -> F -> (DECISION) -> A -> (RESPONSE)
A -> ECP_A -> (ENTRY) -> B -> ECP_B -> F -> A -> ECP_A -> (DECISION) -> B -> (RESPONSE)
(ENTRY) -> A -> ECP_A -> A -> ECP_A -> (DECISION) -> A -> (RESPONSE)
(ENTRY) -> F -> B -> ECP_B -> A -> F -> B -> (DECISION) -> ECP_B -> A -> (RESPONSE)
(ENTRY) -> A -> ECP_B -> F -> A -> ECP_B -> (DECISION) -> F -> A -> (RESPONSE)
(ENTRY) -> F -> F -> F -> (DECISION) -> F -> (RESPONSE)
(ENTRY) -> A -> A -> A -> (DECISION)
(ENTRY) -> A -> ECP_A -> F -> A -> ECP_A -> F -> (DECISION) -> A -> (RESPONSE)
Characters A, B, and F were used in examples as a shorthand for full polling frame names: WUPA, WUPB, SENSF_REQ respectively. ECP frame has different values depending on a use case _A/B suffix refers to the modulation used.
Tests were conducted using very big intervals between polling frames. IRL if polling is faster device might respond after more frames than shown, presumably because of internal processing delay.
In conclusion, if the reader is polling for:
Apple devices seem to have a grace period after leaving an NFC field, where if a device re-enters the field in time, it will still consider it to be the same field.
It was done in order to better support express mode with devices that turn off the NFC field in between polling iterations in order to conserve energy.
This way, an end device wouldn't think that it entered a new field on each polling iteration, which would've prevented express mode from being triggered, as it needs at least two loops for selection.
According to tests, the grace period duration depends on what pass types are enabled for express mode on the device. The grace period duration setting seems to be separate for each NFC technology, with the worst/maximum value taken from enabled pass categories:
300
ms;750
ms.750
ms;For improved stability and increased polling performance, it is advised to do polling in 250
-100
ms intervals or less, keeping in mind the field activity duty cycle.
Even though NFC polling can be really fast, it still takes some time for a device to analyse polling frames and make a decision on which routing to activate in order to begin communication.
During tests, following delays have been measured:
100
ms;1031640 / 13560000 * 1000 ~= 76
ms or ~45 polling iterations;50
ms.547584 / 13560000 * 1000 ~= 40
ms or 3 polling iterations;1042912 / 13560000 * 1000 ~= 77
ms or ~150 polling iterations;5
ms delay: 979680 / 13560000 * 1000 ~= 72
ms or ~12 polling iterations;It's important to understand that the hardware used for measuring might have some operational delay itself.
PC + PN532 method yields a bigger measurement error as it runs on a non-real-time system.
Proxmark3 method gives more accurate results as the measurement is done on a separate device, which is intended specifically for NFC analysis.
As a result of the tests, we have the following findings:
82
ms).5
ms guard time between polling attempts, which can reduce delay down to about 70
ms.Although not possible during normal operation, if a reader is polling for multiple cards using express mode that use different technology qualifiers for selection, the following technology priority will be applied:
ecp
):
ecp.2.tci
);ecp.2.open_loop
);felica.*
);generic.type_a
). There could be more valid express mode qualifiers available, but they have to be verified to be used on real passes before being listed here. If you have any info, feel free to create a PR.
Each ECP frame consists of a header, version, payload and CRC:
6A XX XX... XX XX
[Header] [Version] [Payload (n)] [CRC]
For V1 payload consists only of a single TCI:
XX XX XX
[TCI]
For V2 payload contains terminal configuration, terminal type, terminal subtype, and data:
XX XX XX XX...
[Config] [Type] [Subtype] [Data (n)]
Configuration byte is a mandatory part of a V2 ECP payload. It consists of 4 flag bits and a length nibble:
Bit | 07 | 06 | 05 | 04 | 03 02 01 00 |
---|---|---|---|---|---|
Function | Automatic pass presentment | Authentication not required | Unknown flag 1 | Unknown flag 2 | Length |
1
. A matching pass does not appear on a screen if this value is set to 0
, which also disables express mode as a result. ECP frames with this value being 0
are not found IRL.1
. Disables express mode if the value is 0
, the device will bring up a matching pass for manual authentication instead. Access readers may set it when auth is required. Also set to 0
for Identity and AirDrop/NameDrop. 0
;Data is a part of the payload in V2, it contains TCIs and extra data:
XX XX XX... XX..
[TCIs (n)] [Extra data (n)]
Terminal Capabilities Identifier (TCI), is an arbitrary three-byte-long value that establishes reader relation to a particular pass type (Home key, Car key, Transit) or system feature (Ignore, GymKit, AirDrop, NameDrop).
The following restrictions apply to the use of TCI:
TCI format is arbitrary, although several patterns related to the grouping of similar functionality can be established:
Note that CRC A/B, ECP Header, Configuration bytes are omitted from this table.
NA - not applicable; XX - any; ?? - unknown
Name | Version | Type | Subtype | TCI | Data | Source | Description |
---|---|---|---|---|---|---|---|
VAS or payment | 01 | NA | NA | 00 00 00 | NA | Sniffing | VAS ECP configurations are sometimes regarded to as VASUP-A(B) |
VAS and payment | 01 | NA | NA | 00 00 01 | NA | Bruteforce | |
VAS only | 01 | NA | NA | 00 00 02 | NA | Bruteforce | |
Payment only | 01 | NA | NA | 00 00 03 | NA | Bruteforce | As all other VAS frames, also serves as anti-CATHAY |
GymKit | 01 | NA | NA | c3 00 00 | NA | Bruteforce | The only way of triggering GymKit on an apple watch. There are other frames that trigger GymKit too, but they also trigger express transit for iPhones |
Ignore | 01 | NA | NA | cf 00 00 | NA | Sniffing | |
Transit | 02 | 01 | 00 | XX XX XX | XX XX XX XX XX | Bruteforce based on TFL example | TCI refers to a transit agency, Data is a mask of allowed EMV payment networks for fallback |
Transit: Ventra | 02 | 01 | 00 | 03 00 00 | ?? ?? ?? ?? ?? | Bruteforce | |
Transit: HOP Fastpass | 02 | 01 | 00 | 03 04 00 | ?? ?? ?? ?? ?? | Bruteforce | |
Transit: WMATA | 02 | 01 | 00 | 03 00 01 | ?? ?? ?? ?? ?? | Bruteforce | Will select a Smart Trip card |
Transit: TFL | 02 | 01 | 00 | 03 00 02 | 79 00 00 00 00 | Sniffing: Payment Village/Proxmark community | Allows Amex, Visa, Mastercard, Maestro, VPay |
Transit: LA Tap | 02 | 01 | 00 | 03 00 05 | ?? ?? ?? ?? ?? | Bruteforce | |
Transit: Clipper | 02 | 01 | 00 | 03 00 07 | ?? ?? ?? ?? ?? | Bruteforce | |
Transit: Navigo | 02 | 01 | 00 | 03 09 5a | ?? ?? ?? ?? ?? | Bruteforce | |
Access | 02 | 02 | XX | XX XX XX | NA/XX XX XX XX XX XX XX XX | Assumption based on other data | TCI refers to a pass provider, Data is reader group identifier |
Access: Venue | 02 | 02 | 00 | XX XX XX | NA | HID Reader configuration manual | |
Access: Hotel: Hilton | 02 | 02 | 00 | 02 ff ff | NA | File | TCI might be a wildcard in case a booking is not made, needs further testing |
Access: Home Key | 02 | 02 | 06 | 02 11 00 | XX XX XX XX XX XX XX XX | Sniffing/File | Having more than one key breaks usual ECP logic |
Access: Car Pairing | 02 | 02 | 09 | XX XX XX | NA | Bruteforce | TCI refers to a combination of car manufacturer + reader position. Too many combinations involved. For rest refer to smp-device-content |
Access: Car Pairing: BMW/MINI | 02 | 02 | 09 | 01 00 01 | NA | Configuration | |
Access: Car Pairing: Mercedes | 02 | 02 | 09 | 01 02 01 | NA | Configuration | |
Access: Car Pairing: Genesis | 02 | 02 | 09 | 01 00 51 | NA | Configuration | |
Access: Car Pairing: KIA | 02 | 02 | 09 | 01 00 41 | NA | Configuration | |
Access: Car Pairing: Hyundai | 02 | 02 | 09 | 01 03 01 | NA | Configuration | |
Access: Car Pairing: BYD | 02 | 02 | 09 | 01 07 01 | NA | Configuration | |
Access: Car Pairing: Denza | 02 | 02 | 09 | 01 02 41 | NA | Configuration | |
Access: Car Pairing: YW | 02 | 02 | 09 | 01 02 B1 | NA | Configuration | |
Access: Car Pairing: FCB | 02 | 02 | 09 | 01 02 D1 | NA | Configuration | |
Access: Car Pairing: Lotus | 02 | 02 | 09 | 01 00 91 | NA | Configuration | |
Identity | 02 | 03 | 00 | NA/00 | NA/00 | Sniffing | Only ECP frame found IRL that lacks a full TCI. Could this mean that TCI length is variable or it could be missing and the extra byte is data instead? |
AirDrop | 02 | 05 | 00 | 01 00 00 | 00 00 00 00 00 00 | Sniffing | Sent only after device sees a NameDrop frame |
NameDrop | 02 | 05 | 00 | 01 00 01 | XX XX XX XX XX XX | Sniffing | Data part contains a BLE MAC-address |
Source: Bruteforce - found by going over all config combinations; Sniffing - sniffed from a real reader; File - retrieved from pass file; Configuration - retreived from smp-device-content configuration json
Frames found via brute force may be working but not actually used. In case you have a real samle - let us know if it is different or the same
Examples contain frames without CRC, which needs to be calculated according to the modulation used;
VAS or payment:
6a01000000
6a 01 000000
[Header] [Version] [TCI]
GymKit:
6a01c30000
6a 01 c30000
[Header] [Version] [TCI]
Ignore
6a01cf0000
6a 01 cf0000
[Header] [Version] [TCI]
Access: Hotel: Hilton:
6a02c3020002ffff
6a 02 c3 02 00 02ffff
[Header] [Version] [Config] [Type] [Subtype] [TCI]
NameDrop:
6a02890500010001deadbeef6969
6a 02 89 05 00 010001 deadbeef6969
[Header] [Version] [Config] [Type] [Subtype] [TCI] [Data]
1000 1001
[NA] [Payload length]
Access: Car Pairing: Mercedes:
6a02c30209010201
6a 02 c3 02 09 010201
[Header] [Version] [Config] [Type] [Subtype] [TCI]
Replace TCI for any car TCI given in the table to get pairing prompt for other brand
Note that for examples to work 8-bit byte setting should be set in case of NFC-A, 2-byte CRC has to be appended beforehand.
If you have a Proxmark3, you can test those frames using commands hf 14a raw -akc
for NFC-A and hf 14b raw -kc -d
for NFC-B. After sending the frame try polling the device using the hf 14a reader -sk
or hf 14b reader
command.
Express mode for EMV is triggered as a fallback in case a pass for a particular transit TCI has not been found in a system. EMV brand support mask is contained in the last 5 bytes of the frame's data. At the current moment, only the first byte is known to be used IRL.
The following table presents an encoding scheme for the first byte (bit 00 is the rightmost), with 1 (true) in a particular position signifying support:
Bit | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
---|---|---|---|---|---|---|---|---|
Brand | ?? | Possibly VPAY/Electron | VISA | MAESTRO | MASTERCARD | ?? | ?? | Possibly AMEX |
To find a bit responsible for your card network, you can modify a particular bit inside the mask. Afterward, having activated a specific card brand for express mode on your device, observe if the express mode will activate when brought near to a test reader.
Some applets have the ability to modify behavior based on the conditions that the transaction has been started under:
Requirements regarding the EMV transaction seem to be dependent on a payment network brand (as each has a separate applet implementation):
Other card brands may have different success conditions and behavior changes. If you have any info, feel free to create a PR.
When first researching the topic of ECP, in some rare situations I noticed that some brochures refer to ECP as "Enhanced Contactless Protocol". The first assumption was either made to put potential researchers off the track or that it was a simple mistake when creating the material.
When looking into some promotional documents, "Enhanced Contactless Protocol" arose once again, this time in the context of "DESFire ECP compatibility mode", which rang a bell.
After a bit of analysis, it turned out that DESFire protocol indeed has a special command created specifically for Apple devices, the sole purpose of which is to notify a device that a transaction has been done successfully.
This leads to the thought that ECP (Polling) and ECP (Protocol) are indeed two different terms when used by Apple and/or their partners. In conclusion, a new explanation for ECP (Protocol) has been formulated.
Enhanced Contactless Protocol is a protocol that implements commands that allow to explicitly notify an end device about the state of a transaction, without resorting to making any assumptions about a particular command sequence or operations that need to be done for a transaction to be deemed successful (or not).
An NFC transaction is deemed successful when a device produces a checkmark on a screen, followed by a notification in special cases.
Failure condition is displayed with an exclamation mark and an optional error popup/device vibration.
NFC protocols can be divided into two categories, depending on how a UX success condition is determined:
Following protocols are considered "enhanced" as they implement explicit status commands.
Protocol name | Success condition command | Failure condition | Notes |
---|---|---|---|
Mifare DESFire | NOTIFY_TRANSACTION_SUCCESS(0xEE ) |
DESELECT/TRESET without the command | |
Unified Access (all CarKey, HomeKey, AccessKey, Aliro variants) | OP_CONTROL_FLOW(0x3C ) with success flags or DESELECT after attestation exchange |
OP_CONTROL_FLOW(0x3C ) with failure flags or DESELECT/TRESET before it |
DESFire command name was made up by me as it's newely discovered, no info about it online.
Following protocols have implicit transaction status detection:
Protocol name | Success condition | Failure condition | Notes |
---|---|---|---|
T-Union | DESELECT/TRESET after AID selection | DESELECT/TRESET before AID selection | |
FeliCa | 0.5 second delay after TRESET if REQUEST_SERVICE(0x02 ) has been used. 5 second delay after TRESET otherwise |
READ/WRITE commands with invalid keys | Current success condition causes lots of confusion for users, as a top-up machine may do a TRESET and an additional POLLING for data verification, but it causes a check mark to appear thus misleading users, making them think that they can take the device out prematurely. |
EMV | Cryptogram generation (EMV mode) or magstripe data read (MAG compatability mode) | DSELECT/TRESET before success condition | |
VAS | Successful GET_DATA followed with a DESELECT/TRESET | DSELECT/TRESET after failed GET_DATA |
Other protocols supported by Apple Wallet, such as:
Were not researched due to lack of samples to do tests on. If you have access to any of them (on a device), feel free to add info in a PR.
Service mode, also internally called "Plastic card mode" is a feature that is available on some contactless passes. It is intended to be used when you need to give a device to someone to conduct service/help operations with the card.
After some experimentation, the following changes to the behavior have been noticed when this mode is activated:
Judging from this information, it is safe to assume that what this mode does is disables regular transaction status tracking and end condition fulfillment, allowing even an incompatible/slow/unique reader to conduct any NFC transaction sequences imaginable, with DESELECTs/TRESETs and so on. Extra time in comparison to regular NFC auth is also given in order to accommodate for all possible service processes and communication delays.
Android 15 introduced two brand-new concepts to the operating system's NFC stack:
Observe mode works by suppressing the ability of a device to respond to external NFC polling frames by default, instead passing the information about the polling loop down to the operating system and application level, allowing the former to decide on what to do next.
The following steps could include any of the following, based on characteristics of the polling loop as deemed fit by the device:
As a side effect, Observe Mode enables an additional feature, called "Polling Loop Filters". It allows any NFC-powered application to define a specific polling data pattern so that the operating system delivers control of the NFC communication to that application regardless of which app is set as a primary wallet application.
Those cases partially cover the feature set that ECP has, as described in Overview section, meaning that anticollision-level credential selection and NFC polling loop augmentation is now available on both Apple and Android devices.
The best way to help is to provide more samples of ECP frames and TCIs.
Especially interesting (missing) are the following:
Meanwhile, some other unanswered questions remain, which require further analysis and testing, such as:
If you have any findings or thoughts on this matter, feel free to discuss them in the issues section.
The easiest way of retrieving useful information that doesn't require any special tools is .pkpass
file analysis.
It can be done in the following way:
~/Library/Passes/Cards/
;~/Library/Passes/Cards/
;HomeDomain/Library/Passes/Cards
(Whoever patched it, send my regards)..pkpass
file, select "Show Package Contents"pass.json
file.tci
, openloop
, ecp
, transit
, automatic
, selection
, express
, as in example:
{
"formatVersion":1,
"passTypeIdentifier":"paymentpass.com.apple",
"teamIdentifier":"Apple Inc.",
"paymentApplications":[
{
"secureElementIdentifier":"133713371337",
"applicationIdentifier":"69696969696969696969",
"applicationDescription":"Mastercard",
"defaultPaymentApplication":true,
"supportsOptionalAuthentication":1,
"automaticSelectionCriteria":[
{
"type":"ecp.2.open_loop",
"supportsExpress":true,
"openLoopExpressMask":"0800000000"
}
],
"supportedTransitNetworkIdentifiers":[],
}
]
}
Other fields removed to reduce space taken
Here we see that a Mastercard has automatic selection criteria 0800000000
which corresponds to 00001000
in binary for the first byte of transit data mask.
All cards can be analyzed the same way.
The second way of collecting information about ECP is via sniffing.
Thanks to Observe Mode feature introduced in Android 15, most flagship mobile handsets now have an ability to sniff polling frames without the use of any external hardware.
Android 15 Observe Mode Demo App can be used to collect data about the polling loop, including all custom polling frames such as ECP.
Sniffing can also be done using the functionality of a device like Proxmark (Easy or RDV2/4) connected to a Proxmark client inside of Termux running on an Android phone.
A couple of tidbits encountered:
More info on installing and running Proxmark client on your Android device here.
The command needed to collect traces is hf 14a sniff
, after activating the command hold the Proxmark near a reader for a couple of seconds. In some cases, it is needed to tap/touch the reader in order to wake it up as it might not poll to save energy.
After that, press a button on a device, and traces will be downloaded and can be viewed with a hf 14a list
command. You'll know which ones are the ones.
Some other devices might also be able to sniff the frames, but due to a lack of personal experience, I cannot recommend any.
For the most dedicated people, there is a third method of finding useful information.
It is possible to find ECP frames using sheer brute force.
To do that, you have to program an NFC reader that does the following in a loop:
A couple of tips:
ECP
, TCI
, VASUP-A
.