---
title: "Breaking NFC Based Travel Cards (Updated)"
date: "2024-11-06"
summary: >
  Reverse engineering MiFare DESFire travel cards; from reconnaissance and relay attacks to writing exploits that manipulate NFC transactions in real time.
description: >
  A deep dive into breaking NFC-based travel authorization systems.
  Covers hardware reconnaissance, understanding the MiFare DESFire protocol,
  building a peer-to-peer relay attack with NFCGate and Golang, parsing encrypted logs,
  and writing real-time packet manipulation exploits to bypass fare validation.
toc: true
readTime: true
autonumber: true
math: false
tags: ["security", "nfc", "reverse engineering", "golang", "python"]
showTags: true
hideBackToTop: false
---

NFC is always hard to work with. Imagine reverse engineering an entire travel authorization system and exploiting it.

> [!CAUTION]+ Disclaimer
> Do not attempt on any real system. This blog is intended solely for educational purposes.

> [!NOTE]
> This post contains some ambiguity to avoid disclosing specific targets. The issue hasn't been fixed yet, and the target hasn't acknowledged it as a bug. However, entering a station for free likely isn't intended. ʕ•ᴥ• ʔ

## Before we start …

Before we start you should know that I'm **NOT** an expert in NFC or RFID technology. I just know enough about the software and hardware to break it. I'll be more than happy to answer your questions about the stack but expect some hiccups in the answer.

This is the second iteration of this blog. I wanted to write about more stuff in the previous blog but didn't due to my lack of understanding. If you've heard the talk or read the previous blog, please jump to the Updates section.

I had the opportunity to present this talk across India and in Germany. A recording is available on Chaos Computer Club's *(CCC)* YouTube page —

{{< youtube U4Mhr3gED0g >}}

Now with all that said, Let's start with the part you're here for —

***

## Reconnaissance (Non Technical)

Even though you'd think that you know everything about the train station and the authorization system, it's always better to go through everything again and ask yourself questions starting with "Why?" and "How?". Deepening your understanding of the target is essential. Here's what I learned —

To start a journey, you have `x` number of ways to enter the station premise. Out of which only 2 are NFC based — a one way ticket or a card that you buy from them and top-up money to. There are two ways of top-up your travel card —

1. At the counter in the station
    1. You pay the amount and give your card to them.
    2. They put your card on their NFC reader. Followed by some NFC business.
    3. You get the card and the receipt back.

2. Online top-up (broken in 2 phases)
    1. Phase 1: You recharge your card using their website.
    2. Phase 2: You go to any station premise and use a machine ("Mogambo" from now on) to complete the recharge.

Now ask this question - Why is tapping the travel card into a reader in station premise a necessity in either way of top-up? The answer is simple, the balance is stored in the card itself.


## Reconnaissance (Technical)

NFC is a peer-to-peer *(P2P)* communication protocol. While MiTM or proxy logging might come to mind, it's not that simple in this context. Before talking about methods of eavesdropping NFC communication, let's recon more.

#### 1. Recon: Travel card

I used TagInfo[^1] application to scan the travel card and found that the card is made by NXP Semiconductors *(they made TagInfo app as well)* and is called MiFare DESFire EV1 card. The card is full of "applications" with "files" inside of them holding on to the actual data.

Upon reading the datasheet[^2], I figured that there's a file called a Value file *(Section 8.5, Page 8)* that more or less behaves like a wallet. You can perform certain transactions on this file like Credit or Debit *(Section 9.6, Page 11)*. This is where Mogambo is writing to while recharging the card. Everything that Mogambo does during a session is transactional *(deduced using row “Commit/Abort Transaction”, Section 9.6 Page 12)*.

The communication between the card and the reader can be Plain, MAC'd or Encrypted *(Section 8.6, Page 8)*. Every application is encrypted inside of the card and has different permission level *(deduced by reading the properties of applications using TagInfo)*. This means that even if you can eavesdrop the communication, there's a chance that everything will be encrypted in the logs.

> [!NOTE]
> Even the encrypted data in the files won't be available to read for any reader. So, NO, you cannot clone this card and call it a day.

#### 2. Recon: Mogambo

While trying different things during phase 2 of online top-up, I stumbled upon a bug. If you remove your card just before you see the top-up confirmation screen on Mogambo, there's a chance that your top-up will be successful from your card's POV but will be still pending from Mogambo's POV.

Why did this happen? The assumption I made at that time was that maybe I removed the card at the time when the card was sending an acknowledgement to the commit command sent *(to close the transaction)* by Mogambo. This means that the communication during committing a transaction is not MAC'd, so it's possibly encrypted or plain text. Let's try to automate this.

***

## Eavesdropping

Coming back to this. Why is it not so simple? The travel card is a passive NFC card. It means that it has no battery inside of it and it relies on induced power from the reader to participate in the communication. You can put a coiled oscilloscope probe *(grounded to itself)* and eavesdrop but it won't give you the power to change packets in real time. Although this sounds good, how are you planning to take a DSO into a station premise which is full of station officials and law enforcement guards.

Let's think of the situation differently. Ask yourself if at any given point during the communication does Mogambo or the travel card have any mechanism to know who they are talking to? The answer is NO. Think about it, Mogambo can ask for a special token from the card to know the authenticity but this can be faked as well. So what if we can just relay the communication? This problem is nothing new and is famously called as Chess Grandmaster Problem. The Chess Grandmaster problem[^3] is a scenario where a young girl wins a game of chess against two grandmasters by copying their moves. Here the young girl is just relaying one grandmaster's move to the other and vice-versa. We can have 2 devices acting like Mogambo and travel card and perform something similar.

#### Eavesdropping: Relay attack

```mermaid {title="Fig. 1: flowchart for NFC based relay attacks"}
flowchart LR
    C[Travel Card] --> D1[Device acting like <br> Mogambo]
    D1 --> S[Relay Server]
    S --> D2[Device acting like <br> Travel Card]
    D2 --> R["Mogambo <br> (Reader)"]

    R --> D2
    D2 --> S
    S --> D1
    D1 --> C
```

Fig. 1 shows how the flow should be. We are missing 3 things: 2 devices and a server. I took a cardboard box, hotglue, RPi Zero W, 2 UART-TTL and 2 PN532s to make this monstrosity —


{{< figure src="/posts/breaking-nfc-travel-cards/rpi_relay_1.png" caption="The relay rig — RPi Zero W, 2 PN532s, hotglue and cardboard" >}}
{{< figure src="/posts/breaking-nfc-travel-cards/rpi_relay_2.png" caption="Close-up of the wiring" >}}

The idea was there but it won't work as PN532s don't really emulate 7 byte UIDs *(DESFire cards have it)* that well. The half written server was based on libnfc[^4] and was similar to the example relay executable[^5]. Now you must wonder, why it took me this late to realise this small thing?

The issue is that nearly all NFC documentation is behind a paywall. To even get DESFire developer documentation, you need to buy the actual device and then visit their store and then purchase the documentation there after signing a NDA. Even the ISO standard defining guidelines for NFC communication *(ISO-14443)* is behind a paywall. One of the major reason why hacking around NFC is kinda hard *(but very enjoyable)*.

I had my eyes on a project called NFCGate[^6] which promises relay attack by having your NFC enabled android phones as the actor devices and it ships with a Python based relay server. It has low chances of working on newer devices so I bought an old Nexus 6P. Now while testing it locally I figured that I probably won't work because of FWT *(Frame Wait Time)*.

FWT defines a set time in which if the card fails to reply to the reader, the reader will close the connection. In this case FWT was 77ms. A normal read of all applications, files and their permissions takes ~2 seconds on my phone. With this setup running locally, the same read took ~35 seconds. FWT is not enforced by default, the vendor can enable it. So there was still a chance that this slow setup will still work.

I packed my bags. My laptop running the server in my bag. Both phones in my hand and ready to run to log the communication. Aaaaand... it failed. They have enforced FWT.

#### Eavesdropping: Fixing the relay setup

The easiest way to do it is to rewrite the server in a faster *(compiled)* language. I also wanted to drop some server/app features that I won't use. I rewrote the entire thing in Golang[^7] and out of the box the ~35 second read time was down to ~16 seconds. I packed my bags again and this time it worked. After that, I quickly started an online top-up and completed phase 2 of it while recording the communication. Finally, I had a log file that I could do something with.

***

## Understanding MiFare DESFire

The log file has supposed encrypted stuff in it. I had to look for the transaction getting committed, but how? Everything in the log was alien to me. While researching a bit more, I stumbled upon libfreefare[^8]. It had all the commands (that the card can understand) implemented. They have implemented API around DESFire card[^9] as well.

The implementation of commit command[^10] tells us that `0xC7` is what commits the transaction and the response is going to be 1 byte long. Cross checking with the log, I was able to find it and response was `0x00` which means everything is ok and the card has succesfully ran the command. Credit command is `0x0C` followed by the file number and then 4 bytes denoting the amount to credit.

The actual command bytes *(`0xC7`, `0x0C`, etc.)* are always unencrypted, the following data maybe encrypted *(it depends on file's configuration)*.

I wrote a small script[^11] to explain the meaning of important commands. It will parse logs made by NFCGate.

***

## Writing the exploit

If you go through my rewrite of the server, you'd see that you can write rules[^12] to change packets in real time. I wrote a rule to send `0x7E` *(random byte, instead of `0x00`)* as reply to `0xC7`. This would tell the reader that something went wrong and will probably won't mark the top-up as done. I again packed my bags and relayed the session during phase 2 of online top-up. It worked. This is how it looks like.

You can be really creative with these attacks. Like changing the debit command *(`0xDC`, has same signature as credit command)* by just replacing the first byte to `0x0C` *(credit command)*. This will mean that upon exit you'll get the amount for which you've traveled. The more you travel the more balance you have in your travel card.

***

## Updates!

While logging ~5 communication while completing phase 2 of online top-up, I recognized that even though the session key is different every-time *(which means that encrypted commands will be different in every transaction)*. Every command related to value file remained the same. Which means that the file is mis-configured and the amount that I though is encrypted was actually plain text. The amount was just in big endian. A new exploit can be to just sniff for `0x0C` during phase 2 of top-up and replace it with a bigger number *(max value is `0x7FFFFFFF`)* and let the recharge go through.

Another thing I noticed was that in order to show the available balance you have *(it's the first thing Mogambo shows you when you tap your card)*, Mogambo will have to decrypt the application which contains the value file. This means that you don't even have to initiate any online top-up. The moment it you see `0x0A` from Mogambo, it means that the reader is trying to authenticate and trying to unlock an application. After this stage you can inject `0x0C` *(it is plaintext)* and then inject `0xC7` to commit your changes.

One last thing, upon further inspection, I figured that out of 7 defined application in this travel card, only 2 were getting used. Rest of the applications were at their default settings with their keys set to `0x00`. What if Mogambo still tries to authenticate with default keys if the original key fails? It'd mean that you can clone the skeleton applications and their files to a new magic card and that'd work as well. I wrote a small script[^13] for proxmark3 that you can use to mimic this particular travel card, you can write the applications and files to a flash card.

```bash {linenos=inline title="mifare-desfire-clone.sh" }
hf mfdes formatpicc

hf mfdes createapp -m plain --aid 014d44 --numkeys 10
hf mfdes createfile --aid 014d44 --fid 00 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createvaluefile --aid 014d44 --fid 01 --lower 80000000 --upper 7fffffff --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --lcredit 0 --value 00000000
hf mfdes createvaluefile --aid 014d44 --fid 02 --lower 80f0fc18 --upper 7fffffff --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --lcredit 0 --value 00000000
hf mfdes createrecordfile --aid 014d44 --fid 03 --size 000020 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --maxrecord 00000B --cyclic
hf mfdes createfile --aid 014d44 --fid 05 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createfile --aid 014d44 --fid 06 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createfile --aid 014d44 --fid 08 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020
hf mfdes createfile --aid 014d44 --fid 09 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020

hf mfdes createapp -m plain --aid 024d44 --numkeys 5
hf mfdes createfile --aid 024d44 --fid 00 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createfile --aid 024d44 --fid 01 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createfile --aid 024d44 --fid 02 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createfile --aid 024d44 --fid 03 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020 --backup
hf mfdes createfile --aid 024d44 --fid 08 --cmode plain --amode plain --rrights free --wrights free --rwrights free --chrights key0 --size 000020

hf mfdes createapp -m plain --aid 034d44 --numkeys 5
hf mfdes createapp -m plain --aid 044d44 --numkeys 5
hf mfdes createapp -m plain --aid 054d44 --numkeys 5
hf mfdes createapp -m plain --aid 064d44 --numkeys 5
hf mfdes createapp -m plain --aid 074d44 --numkeys 5

hf mfdes lsapp --files
```


[^1]: TagInfo's playstore page: https://play.google.com/store/apps/details?id=com.nxp.taginfolite
[^2]: MiFare DESFire EV1 card's datasheet: https://www.nxp.com/docs/en/data-sheet/MF3ICDX21_41_81_SDS.pdf
[^3]: JH Conway's On numbers and games, Page 75: https://dokumen.pub/qdownload/on-numbers-and-games-2ed-1-56881-127-6-9781568811277.html
[^4]: libnfc: https://github.com/nfc-tools/libnfc
[^5]: libnfc's example of a realy server: https://github.com/nfc-tools/libnfc/blob/master/examples/nfc-relay.c
[^6]: NFCGate: https://github.com/nfcgate/nfcgate
[^7]: My rewrite of NFCGate's server: https://github.com/nk521/nfcgate-server-go
[^8]: libfreefare: https://github.com/nfc-tools/libfreefare
[^9]: MiFare DESFire's implementation based on ISO 14443-4 draft: https://github.com/nfc-tools/libfreefare/blob/master/libfreefare/mifare_desfire.c
[^10]: Credit command handler: https://github.com/nfc-tools/libfreefare/blob/c2b0cfa4b9fb0e4be88604f00b7a2405618d5abc/libfreefare/mifare_desfire.c#L2048
[^11]: NFCGate log parser *(only makes sense with MiFare DESFire cards)*: https://gist.github.com/nk521/60905729b5a70d83e1b94a6c33c174e0
[^12]: Example rule in my rewrite: https://github.com/nk521/nfcgate-server-go/blob/38f27e2559a3e4789efc257258d01339bac54fc0/server.go#L135
[^13]: Application and file definition of the travel card: https://gist.github.com/nk521/0e33ac83c6f526dcab74c6b97dc4c4dc
