When dealing with an NFC card, one might want to attack it with fuzzing by using a card reader to target its communication protocol:
- Sending well-formatted commands, but at unexpected times
- Sending poorly formatted commands, such as introducing inconsistencies between the field indicating the command size and the command itself.
- Mixing both scenarios
But are you sure that the malformed command will actually be received by the card? And if it is, will you be able to capture the card's unusual response?
The problem between your command and its reception by the card lies in the multiple layers it must pass through:
- Your PC software might reject the command before it even reaches the reader.
- Or it could be the reader's firmware that refuses to send the command.
That's why we need a "transparent reader" – something that performs the absolute minimum interaction between the two.
Now the question is: how do we do it?
For a long time, finding a contactless reader capable of this kind of task either came with many limitations or was extremely expensive. But nowadays, there’s a wealth of affordable and open-source hacking tools that address this issue. Examples include the Proxmark, Chameleon Mini, Hydra NFC v1 & v2, and one of the latest additions, the Flipper Zero.
A while back, I contributed to adding a transparent reader mode to the Hydra NFC’s code.
My requirements were straightforward:
- I communicated with the Hydra via serial.
- I implemented the basic commands needed for contactless card communication:
- Activating the RF field
- Deactivating the RF field
- Sending bits to the card
- Sending bytes to the card
- Selecting the communication type to emulate, like ISO 15693 or ISO 14443A/B for MIFARE, bank cards, or electronic passports.
After that, I developed a small Python library that could send commands, such as APDUs, to bank cards.
Here, we're talking about the classic scenario of conducting garage-based attacks on cards or any system emulating cards, using the Flipper Zero connected to a PC. With the simplicity of Python, you can quickly develop scripts and leverage a vast range of open-source libraries to target many contactless card applications. You can easily perform fuzzing, analyze your findings, and, in the next phase, try to hard-code it into a portable attack system.
Since I recently got a Flipper Zero, I was curious about the effort needed to pull this off and also wanted an excuse to start coding on the device (all this introduction just for that...).
I started with the code from the official GitHub repository because it was sufficient for what I needed to accomplish. Of course, I also explored unofficial firmware like Flipper Zero Xtreme (archived since July 28), Flipper Zero Momentum, and Rogue Master.
The Flipper Zero offers great ease of use; once you’ve cloned the repository, it takes just two commands to compile the code and flash it onto the device.
./fbt
./fbt flash
ufbt provides access to the Flipper Zero’s shell, and among the various menus, there’s one specifically for NFC.
./ufbt cli
[...]
>: help
Commands available:
! js
? led
bt loader
crypto log
date nfc
device_info onewire
factory_reset power
free rfid
free_blocks start_rpc_session
gpio storage
help subghz
i2c sysctl
ikey top
info update
input uptime
ir vibro
At this point, the question arises: how do you modify the firmware? The first solution is to create applications, specifically Flipper Application Packages (FAP). These allow you to add new features without altering the OS, as they are installed directly on the SD card. There’s even a store available on the Flipper Lab for this purpose.
I considered starting with that approach, but I chose to modify the OS instead, specifically the code implementing the NFC shell, since the communication between the PC and the Flipper was already handled. Looking at the code, you can see that adding a shell menu is done through cli_add_command
.
The code above is extracted from the applications/main/nfc/nfc_cli.c
file and demonstrates how this function is used. The fourth parameter specifies who will implement the shell. By examining the cli_add_command
function, you can also see how the other shells are implemented, which helps in understanding how to process the received commands.
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli, NULL);
As you can see below, nfc_cli.c
implements very few commands... almost none, in fact, if you don’t enable debug mode on the Flipper (you need to go to Settings, System, and set Debug to On), as the function furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)
will return false otherwise.
static void nfc_cli_print_usage(void) {
printf("Usage:\r\n");
printf("nfc <cmd>\r\n");
printf("Cmd list:\r\n");
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
printf("\tfield\t - turn field on\r\n");
}
}
If we look back at some earlier commits (see image below), such as the one from October 24, 2023, it could do much more, like send APDU commands or emulate a card.
However, this is still too high-level for what I want to achieve.
The solution lies in the applications/main/nfc/scenes
directory, which contains the default embedded NFC applications. By examining these, you’ll find a very useful low-level API defined in targets/furi_hal_include/furi_hal_nfc.h
that will suit our needs perfectly.
Let's see how to implement our transparent reader!
In general, using the main functions of this API must be enclosed by functions that lock the use of the NFC hardware layer (as shown below).
// Check if nfc worker is not busy
if (furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
printf("Error. NFC chip failed to start\r\n");
return;
}
furi_hal_nfc_acquire();
// Things to implement
// ...
furi_hal_nfc_release();
To turn the power on, simply use nfc_low_power_mode_stop()
, and to turn it off, use nfc_low_power_mode_start()
.
The protocol configuration is done using furi_hal_nfc_set_mode
. The FuriHalNfcTech
enumeration lists the available modes (see below).
typedef enum {
FuriHalNfcTechIso14443a, /**< Configure NFC HAL to use the ISO14443 (type A) technology. */
FuriHalNfcTechIso14443b, /**< Configure NFC HAL to use the ISO14443 (type B) technology. */
FuriHalNfcTechIso15693, /**< Configure NFC HAL to use the ISO15693 technology. */
FuriHalNfcTechFelica, /**< Configure NFC HAL to use the FeliCa technology. */
FuriHalNfcTechNum, /**< Special value equal to the supported technologies count. Internal use. */
FuriHalNfcTechInvalid, /**< Special value indicating the unconfigured state. Internal use. */
} FuriHalNfcTech;
furi_hal_nfc_set_mode(FuriHalNfcModePoller, NfcTech);
Sending bits or bytes is a three-step process:
- Use the
furi_hal_nfc_poller_tx
function to send the data, specifying the size in bits. - Wait for the command to be sent and the response to be received using
furi_hal_nfc_wait_event_common
. - Finally, use
furi_hal_nfc_poller_rx
to read the data.
For sending bytes, the code example is shown below.
furi_hal_nfc_poller_tx(data, bit_buffer_get_size(cmd));
while (timeout_ms > 0) {
event = furi_hal_nfc_wait_event_common(10);
#ifdef NFC_CLI_VERBOSE
printf("\t->wait 0x%X\n", event);
#endif // NFC_CLI_VERBOSE
timeout_ms -= 10;
if (event & FuriHalNfcEventRxEnd) {
break;
}
}
if (timeout_ms != 0) {
error = furi_hal_nfc_poller_rx(data, 100, &rx_bits);
if (error != FuriHalNfcErrorNone) {
printf("Error. >2 - r %d - size %d - data ", error, rx_bits);
return;
}
for (i = 0; i < (rx_bits / 8); i++) {
printf("%02X", data[i]);
}
printf("\r\n");
} else {
printf("Error. Timeout\r\n");
}
You might say that sometimes a CRC needs to be calculated and added to the end of the command. No worries—there are various pre-implemented functions that handle this!
if (add_crc == true) {
switch (g_NfcTech) {
case FuriHalNfcTechIso14443a:
iso14443_crc_append(Iso14443CrcTypeA, cmd);
break;
case FuriHalNfcTechIso14443b:
iso14443_crc_append(Iso14443CrcTypeB, cmd);
break;
case FuriHalNfcTechIso15693:
iso13239_crc_append(Iso13239CrcTypeDefault, cmd);
default:
break;
}
}
The NFC API of the Flipper Zero is very well designed, as I ended up using only a few functions to achieve the desired result. However, using an ST-Link v2 probe for debugging was incredibly helpful. In the blog post Debugging Flipper Zero Firmware with ST-Link v2, Drew Green mentions using a €10 ST-Link v2 clone from Amazon, which worked perfectly. I have the same one, and it indeed works great (and is much more convenient to use than the bulky ST-Link v3 I also own).
On his site, Drew Green provides images showing how to connect the probe to the Flipper Zero. Just remember to enable debug mode on the Flipper Zero, as I mentioned earlier; otherwise, debugging won’t work.
Starting GDB to debug the Flipper with the probe is also very straightforward and can be done with a single command.
./fbt debug
Basically, it involves running GDB, which in turn launches OpenOCD to debug the Flipper.
arm-none-eabi-gdb-py3 -ex "source scripts/debug/gdbinit"
-ex "target extended-remote |openocd -c 'gdb_port pipe;
log_output scripts/debug/openocd.log' -f interface/stlink.cfg
-c 'transport select hla_swd' -f scripts/debug/stm32wbx.cfg
-c 'stm32wbx.cpu configure -rtos auto' -c init"
-ex "source scripts/debug/FreeRTOS/FreeRTOS.py"
-ex "source scripts/debug/flipperapps.py"
-ex "source scripts/debug/flipperversion.py"
-ex "fap-set-debug-elf-root build/f7-firmware-D/.extapps"
-ex "source scripts/debug/PyCortexMDebug/PyCortexMDebug.py"
-ex "svd_load scripts/debug/STM32WB55_CM4.svd"
-ex compare-sections
-ex fw-version build/f7-firmware-D/firmware.elf
To simplify, you can start OpenOCD in one terminal ...
openocd -f interface/stlink.cfg -c 'transport select hla_swd' -f scripts/debug/stm32wbx.cfg -c 'stm32wbx.cpu configure -rtos auto' -c init
... and then connect to it via GDB. This makes integration with any IDE quite straightforward.
arm-none-eabi-gdb -ex "target remote localhost:3333"
So, I have everything I wanted... except for sending bits, where I currently only handle the REQA. I will fix it soon. The code is development is available here.
error = furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrameAllReq);
In the end, this allows me to communicate with contactless cards using ISO 15693, ISO 14443 A, or B modes. Below is an example of using the new NFC shell to start a conversation with a bank card.
>: nfc mode_14443_a
Set mode ISO 14443 A
>: nfc on
>: nfc reqa
0400
>: nfc send 0 9320
B9CC137117
>: nfc send 1 9370B9CC137117
20FC70
Of course, typing everything into the shell can be quite tedious. However, the goal has always been to control everything using a Python library. That’s why I updated my project, pynfcreader
, here. Below is a Python code example to initiate communication with a bank card and send an APDU. The APDU encapsulation is handled automatically.
import time
from pynfcreader.devices import flipper_zero
from pynfcreader.sessions.iso14443.iso14443a import Iso14443ASession
fz = flipper_zero.FlipperZero("/dev/ttyACM0", debug=False)
hn = Iso14443ASession(drv=fz, block_size=120)
hn.connect()
hn.field_off()
time.sleep(0.1)
hn.field_on()
hn.polling()
r = hn.send_apdu("00 a4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00")
I got this exchange using a bank card that expired 10 years ago, running a Mastercard application.
INFO :: Connect to Flipper Zero
INFO ::
INFO :: field off
INFO :: field on
INFO :: REQA (7 bits):
INFO :: 26 &
INFO :: ATQA:
INFO :: 04 00 ..
INFO :: Select cascade level 1:
INFO :: 93 20 .
INFO :: Select cascade level 1 response:
INFO :: B9 CC 13 71 17 ...q.
INFO :: Select cascade level 1:
INFO :: 93 70 B9 CC 13 71 17 .p...q.
INFO :: Select cascade level 1 response:
INFO :: 20 FC 70 .p
INFO :: Request for Answer To Select (RATS):
INFO :: PCD selected options:
INFO :: FSDI : 0x0 => max PCD frame size : 16 bytes
INFO :: CID : 0x0
INFO :: RATS
INFO :: E0 00 ..
INFO :: Answer to Select (ATS = RATS response):
INFO :: 0A 78 80 82 02 20 63 CB A3 A0 92 43 .x... c. ...C
INFO :: T0 : 0x78
INFO :: FSCI : 0x8 => max card frame size : 256 bytes
INFO :: TA(1) present
INFO :: TB(1) present
INFO :: TC(1) present
INFO :: TA(1) : 0x80
INFO :: Interpretation : TODO...
INFO :: TB(1) : 0x82
INFO :: Interpretation : TODO...
INFO :: TC(1) : 0x02
INFO :: NAD not supported
INFO :: CID supported
INFO :: Historical bytes : 20 63 CB A3 A0
INFO :: CRC : 92 43
INFO ::
INFO ::
INFO ::
INFO :: PPS
INFO :: PCD selected options:
INFO :: CID : 0x0
INFO :: PPS1 not transmitted
INFO :: PPS:
INFO :: D0 01 ..
INFO :: PPS response:
INFO :: D0 73 87 .s.
INFO :: PPS accepted
INFO ::
INFO :: APDU command:
INFO :: 00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 .....2PA Y.SYS.DD
INFO :: 46 30 31 00 F01.
INFO :: TPDU command:
INFO :: 0A 00 00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E .......2 PAY.SYS.
INFO :: 44 44 46 30 31 00 DDF01.
INFO :: TPDU response:
INFO :: 1A 00 6F 57 84 0E 32 50 41 59 2E 53 59 53 86 C7 ..oW..2P AY.SYS..
INFO :: TPDU command:
INFO :: A3 .
INFO :: TPDU response:
INFO :: 13 2E 44 44 46 30 31 A5 45 BF 0C 42 61 1B 64 B0 ..DDF01. E..Ba.d.
INFO :: TPDU command:
INFO :: A2 .
INFO :: TPDU response:
INFO :: 12 4F 07 A0 00 00 00 42 10 10 50 02 43 42 63 59 .O.....B ..P.CBcY
INFO :: TPDU command:
INFO :: A3 .
INFO :: TPDU response:
INFO :: 13 87 01 01 9F 28 08 40 02 00 00 00 00 00 43 51 .....(.@ ......CQ
INFO :: TPDU command:
INFO :: A2 .
INFO :: TPDU response:
INFO :: 12 00 61 23 4F 07 A0 00 00 00 04 10 10 50 6D 2B ..a#O... .....Pm+
INFO :: TPDU command:
INFO :: A3 .
INFO :: TPDU response:
INFO :: 13 0A 4D 41 53 54 45 52 43 41 52 44 87 01 4E 70 ..MASTER CARD..Np
INFO :: TPDU command:
INFO :: A2 .
INFO :: TPDU response:
INFO :: 12 02 9F 28 08 40 00 20 00 00 00 00 00 90 BD 88 ...(.@. ........
INFO :: TPDU command:
INFO :: A3 .
INFO :: TPDU response:
INFO :: 03 00 C8 34 ...4
INFO :: APDU response:
INFO :: 6F 57 84 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 oW..2PAY .SYS.DDF
INFO :: 30 31 A5 45 BF 0C 42 61 1B 4F 07 A0 00 00 00 42 01.E..Ba .O.....B
INFO :: 10 10 50 02 43 42 87 01 01 9F 28 08 40 02 00 00 ..P.CB.. ..(.@...
INFO :: 00 00 00 00 61 23 4F 07 A0 00 00 00 04 10 10 50 ....a#O. .......P
INFO :: 0A 4D 41 53 54 45 52 43 41 52 44 87 01 02 9F 28 .MASTERC ARD....(
INFO :: 08 40 00 20 00 00 00 00 00 90 00 .@. .... ...
The next step is to clean everything up and then use the same principle to emulate a card!
Top comments (0)