DEV Community

Guillaume VINET
Guillaume VINET

Posted on

Hacking NFC communication protocol with Flipper Zero

Image description
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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);
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Sending bits or bytes is a three-step process:

  1. Use the furi_hal_nfc_poller_tx function to send the data, specifying the size in bits.
  2. Wait for the command to be sent and the response to be received using furi_hal_nfc_wait_event_common.
  3. 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");
    }
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
Enter fullscreen mode Exit fullscreen mode

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).
Image description
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

... and then connect to it via GDB. This makes integration with any IDE quite straightforward.

arm-none-eabi-gdb -ex "target remote localhost:3333"
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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                     .@. ....   ...
Enter fullscreen mode Exit fullscreen mode

The next step is to clean everything up and then use the same principle to emulate a card!

Top comments (0)