One of the cool new hardware feature of recent Android handsets is the inclusion of an NFC (Near-Field Communication) chip.

In Android, its main standalone usage is Beam which, roughly speaking, allows to propagate Intent objects to another phone by just touching it.

In practice, the framework gives us access to a full NFC stack and even supply a couple of other existing protocols, formats in addition to NDEF (on which Beam is based).

What that means is that a world of existing contact-less gadgets and cards is now available for your hacking pleasure.

You would actually be surprised just how much of your world is NFC-based already. Take your city tranportation pass for instance, that’s one of the main use of it (stored value cards).

Recent credit cards are also NFC-enabled to allow contact-less payments (look for a signal symbol near the chip).

In Dublin, the transportation system has been standardized to use a single system named the Leap card which is essentially a stored-value card that you can use in buses and tram (called Luas here) and top-up in shops and at tram stations.

Using an application like TagRead (made by NXP which produces a lot of the chips of these contact-less cards including the Leap card), you can already learn a good deal of information from your NFC tags:

The interesting bit given by these tools is the supported Android Tech that tells you how you can read the NFC tag inside your app.

There are a couple of them but generally recent chips support the IsoDep tech (ISO 14443 standard).

With this tech, you connect to the card (as if it was a normal network endpoint) and then use the Transceive method that allows you to send a byte[] payload and receive a similar response in a synchronous manner (read blocking).

Obviously, blocking isn’t optimum for an app so care will be taken to spice up our code examples with async/await as recently released in alpha.

Anyhow, after you are “connected” with the card, communication is done in the specific card protocol (which is left for you to implement).

With the Leap card we are going to use the native Desfire command set as described in the following datasheet.

Given below is the code to our asynchronous RPC-like wrapper around Transceive:

public static class IsoDepExtensions
{
	static readonly byte[] ContCommand = new byte[] { 0xaf };

	public static Task<byte[]> SendCommandAsync (this IsoDep iso, DesfireCommand cmd, params byte[] parameter)
	{
		return Task.Run (() => {
			byte[] response = null;
			byte[] command = new byte[] { (byte)cmd };
			if (parameter != null)
				command = command.Concat (parameter).ToArray ();
			List<byte> aggregatedResponse = new List<byte> ();

			do {
				response = iso.Transceive (command);
				aggregatedResponse.AddRange (response.Skip (1));
				command = ContCommand;
			} while (response.Length > 1 && response.First () == ContCommand[0]);

			return aggregatedResponse.ToArray ();
		});
	}

	public static void DumpResponse (this byte[] array)
	{
		Console.WriteLine (string.Join (", ", array.Select (b => b.ToString ("x2"))));
	}
}

public enum DesfireCommand : byte {
	Version = 0x60,
	GetAppIds = 0x6a,
	SelectApp = 0x5a,
	GetKeysDetails = 0x45,
	GetFileIds = 0x6f,
	GetFileSettings = 0xf5,
	ReadData = 0xbd
}

Since Desfire response can be split in chunks (identified by the 0xAF sequence), the method takes care of reconstructing the whole response. No other status code are handled.

You can look at the above datasheet for other error code, especially since you are probably going to run into at least 0x9D (Permission denied) and 0x1C (Illegal command).

In our Xamarin.Android application (correctly set up to use NFC as described in NFC Basics), we create a PendingIntent instance to retrieve the card data when it’s put in contact with the phone:

NfcAdapter nfcAdapter;

protected override void OnCreate (Bundle bundle)
{
	base.OnCreate (bundle);
	SetContentView (Resource.Layout.Main);
	
	nfcAdapter = NfcAdapter.GetDefaultAdapter (this);
}

protected override void OnPause ()
{
	base.OnPause ();
	nfcAdapter.DisableForegroundDispatch (this);
}

protected override void OnResume ()
{
	base.OnResume ();
	var pendingIntent = PendingIntent.GetActivity (this, 0, new Intent (this, Class).AddFlags (ActivityFlags.SingleTop),
	                                               (PendingIntentFlags)0);
	var filter = new IntentFilter (NfcAdapter.ActionTagDiscovered);
	var techList = new string[] {
		"android.nfc.tech.NfcA",
		"android.nfc.tech.IsoDep",
	};
	
	nfcAdapter.EnableForegroundDispatch (this, pendingIntent, new[] { filter }, new[] { techList });
}

When the tag is acknowledged by Android, it will be forwarded to your app in the OnNewIntent method:

protected override void OnNewIntent (Intent intent)
{
	base.OnNewIntent (intent);

	var tag = (Tag)intent.GetParcelableExtra (NfcAdapter.ExtraTag);
	Console.WriteLine ("Supported techlist:");
	foreach (var tech in tag.GetTechList ())
		Console.WriteLine ("\t" + tech);

	ReadIsoDep (tag);
}

Once we are at that point, the only thing to do is to have fun. Following is my commented investigation code for the Leap card:

async void ReadIsoDep (Tag tag)
{
	var iso = IsoDep.Get (tag);
	Console.WriteLine ("Connecting...");
	iso.Connect ();

	// Get and print version information (hardware/software revision, fabrication date, ...).
	var result = await iso.SendCommandAsync (DesfireCommand.Version);
	result.DumpResponse ();

	// Get the list of application on the card, on the Leap card there is only one.
	// An App ID is a 3-bytes value.
	result = await iso.SendCommandAsync (DesfireCommand.GetAppIds);
	result.DumpResponse ();

	// Select the Leap card single app by input'ing its App ID.
	result = await iso.SendCommandAsync (DesfireCommand.SelectApp, result);

	// Get keys details to see what we can do with this app
	// With the Leap card, we have a tiny amount of room for plain transmission (i.e. no key involved).
	result = await iso.SendCommandAsync (DesfireCommand.GetKeysDetails);
	result.DumpResponse ();

	// We get the list of files registered in the app (a good few on the Leap card,
	// I wonder what they could be used for). A file ID is a 1-byte value.
	var files = await iso.SendCommandAsync (DesfireCommand.GetFileIds);
	Console.Write ("File list: ");
	files.DumpResponse ();

	foreach (var fileId in files) {
		// Output the permissions for the file
		var perm = await iso.SendCommandAsync (DesfireCommand.GetFileSettings, fileId);
		// If the file can be read in plain communication mode (remember, we aren't authenticated)
		// and it can be read freely (i.e one nibble equals to 0xE), read it.
		// That amount to 1 file only on the Leap card.
		if (((perm [1] & 1) == 0 || (perm [1] & 0x3) == 1)
		    && perm.Skip (2).Take (2).SelectMany (b => new int[] { b >> 4, b & 0xF }).Any (b => b == 0xE)) {
			// On the Leap card, there are only standard-files which is kind of weird considering
			// there is a specific value-file type for stored amount which has a couple of handy
			// bank account-like operations.
			// Here we read the whole content of the current file.
			var rawData = await iso.SendCommandAsync (DesfireCommand.ReadData, fileId, 0, 0, 0, 0, 0, 0);
			Console.Write ("File {0:x2} content: ", fileId);
			rawData.DumpResponse ();
		}
	}

	iso.Close ();
}

To save you the pain of running the code for those only interested in the content of that file, here is the data of 3 different Leap cards I had:

  • 03 CF FF FE 81
  • 03 CF FF FB 78
  • 03 CF FF FB 7A

Interpretation of these values will be left to another motivation surge.

PS: If you want to look more into Desfire, here is another blog post with hands-on explanation of the protocol communication as a good companion to the specification.