Mathew McBride's website

Poking around on a Mifare card: LibNFC crash course

technologylibnfcrfidSun 07 Feb 2010 09:02:11No comments

Poking around on a MiFare card: LibNFC crash course

Background

libnfc is a library for communicating with ISO14443 RFID tags. You might know these things for their use in smart card ticketing systems such as Oyster, Octopus, Snapper and myki. But they are also present in other forms such as photocopy cards, student ID's, building access controls* and passports.  
Two forms of ISO14443 dominate: Felica, which debuted in Hong Kong's Octopus RFID ticket and spread across Asia soon after, and Mifare, which dominates just about everything else.
 
* It should be noted many building security systems use a lower frequency, non-ISO14443 system. 

Hardware

Our weapon

libnfc works with NXP PN53x series chipsets, which feature USB connectivity, giving rise to their use in smartcard reload gadgets such as the Snapper USB 'Feeder'. They can even emulate a RFID card!

Snapper were kind enough to ship a USB feeder over to me in Australia. Thanks guys!

The test subject

The best way to experiment with RFID cards would be to source a blank one. But I didn't have a blank card handy at the time of writing, so I will examine an existing one. My previous research indicated that I would be able to modify any unused sectors on the card, but not modify used sectors that I do not have the keys for.


In this article I will be examining a common RFID tag, a photocopy card. I've replaced the artwork on the card with an identical image to avoid naming the vendor in question.


This card is a Mifare Classic 1k. It has 1024 bytes of memory, but stores only 768 bytes after the configuration and authentication data. 

Memory on the Mifare card is split into 16 sectors of 4 blocks, three 16 byte blocks of data, one 16 byte block for security configuration.
Heres a diagram which I have stolen from this PDF

Sector 1 Block 0 - read only block reserved for card UID Block 1 Block 2

Block 3 - sector trailer and security configuration


Sector 2 Block 4 Block 5 Block 6 Block 7 - sector trailer and security configuration
Sector n Block (n*4)-4 ... ... Block (n*4)-1 - sector trailer and security configuration
Sector 15 Block 60 Block 61 Block 62 Block 63 - sector trailer and end of card

Mifare Classic has been cracked several times over the years, not because of a paltry 48 bit key length, but because of severe weaknesses of its Crypto-1 ("Crapto1") encryption algorithm. See Wikipedia for a history of Mifare Classic hacking.

Newer systems, such as Myki in Melbourne, use the Mifare DESfire platform, which is a lot "smarter" than Classic in that its a full microprocessor and memory system.

First steps: install libnfc

The following sections assume you are familiar with installing Unix programs from source code. If not, read the INSTALL file in the source code distribution first.

You can download libnfc from its Google code site. At the time of writing the current version is 1.3.2.

You will need libusb. The best version to use is libusb-1.0 (new API) combined with the compatibility module for older applications.

If you are using a PN53x USB key, add --disable-pcsc-lite to get out of the libpcsclite requirement, needed for another type of hardware.

I used OS X 10.6.2 as my development platform, and ran into some compile troubles. To fix this, just add:
"#define _DARWIN_C_SOURCE 1" to config.h. By the time you read this article, the issue might be fixed.

After you have installed libnfc in the usual fashion, plug in the reader and run the command "nfc-list". You should get something like this:

$ nfc-list
nfc-list use libnfc 1.3.2 (r294)
INFO: Sorry, serial auto-probing have been disabled at compile time.

Connected to NFC reader: Philips / USB TAMA - PN531 v3.4

The following (NFC) ISO14443A tag was found:

    ATQA (SENS_RES): 00  04  
       UID (NFCID1): 5a  12  04  dd  
      SAK (SEL_RES): 08  
Everything working? Good, lets move on.

Your first libnfc application

We all learn best by example, don't we? So lets start with the libnfc example provided in its documentation.

Save this example to a C file and use the following command to compile it:
gcc libnfc_example.c -o libnfc_example -l nfc

If you are using your own IDE's or Makefiles, like I was using XCode, add -lnfc to LDFLAGS and any relevant paths to -I for CFLAGS.

One you run your compiled example, the output should be the same as nfc-list. 

Aside: Mifare security model

Each sector on a Mifare card is secured by two 48-bit keys: A and B. The last block in the sector (the trailer) contains these keys, as well as a security configuration that details what each key can do with each block, i.e block 0 could be configured so that key A could read and write, but if a reader authenticates with key B, the reader would only be able to read that block.

Fresh, empty Mifare cards have all their sectors configured with a pair default keys, usually some trivial configuration, like FFFFFFFFFFFF or 000000000000. 

Here is a list from nfc-mfclassic.c in libnfc's example tools dir:

static byte_t keys[] = {
        0xff,0xff,0xff,0xff,0xff,0xff,
        0xd3,0xf7,0xd3,0xf7,0xd3,0xf7,
        0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,
        0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,
        0x4d,0x3a,0x99,0xc3,0x51,0xdd,
        0x1a,0x98,0x2c,0x7e,0x45,0x9a,
        0xaa,0xbb,0xcc,0xdd,0xee,0xff,
        0x00,0x00,0x00,0x00,0x00,0x00
};

'Breaking in'

With that in mind, take the block of default keys above and add it to your source code before the main() function. With that in mind, we also need to move and define some structs outside the main function. 

Delete the first two lines of main() and then paste in this:

The top of your file should now look like this:
#include <nfc/nfc.h>
#include <string.h>

static mifare_param mp;
nfc_device_t* pnd;
nfc_target_info_t nti;

static byte_t keys[] = {
        0xff,0xff,0xff,0xff,0xff,0xff,
        0xd3,0xf7,0xd3,0xf7,0xd3,0xf7,
        0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,
        0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,
        0x4d,0x3a,0x99,0xc3,0x51,0xdd,
        0x1a,0x98,0x2c,0x7e,0x45,0x9a,
        0xaa,0xbb,0xcc,0xdd,0xee,0xff,
        0x00,0x00,0x00,0x00,0x00,0x00
};

Now, lets make an authentication function:
bool authenticate(int block, int keynum, bool keyB) {
        memcpy(mp.mpa.abtUid,nti.nai.abtUid,4);
        memcpy(mp.mpa.abtKey, &keys[keynum], 6);
        
        bool res = nfc_initiator_mifare_cmd(pnd, (keyB ? MC_AUTH_B : MC_AUTH_A), block, &mp);
        if (res) {
                printf("Authentication succcessful on block %d\n",block);
        } else {
                printf("Authentication failed on block %d\n",block);
        }
        return res;
}


And something to read a few blocks at once:
void readblocks(int start, int end) {
        int block;
        for (block=start; block<(end+1); block++) {
                bool res = nfc_initiator_mifare_cmd(pnd, MC_READ, block, &mp);
                if (res) {
                        printf("Block %d data: ",block);
                        print_hex(mp.mpd.abtData,16);
                } else {
                        printf("Reading block %d failed\n",block);
                }
        }
}

I advise only reading one sector (4 blocks) at a time using the above function, remember that you have to authenticate to the sector first.

You should also add an #include <string.h> for memcpy.

With our necessary functions in, add this after the initial polling block:

......
print_hex(nti.nai.abtAts,nti.nai.szAtsLen);
                }
        } else {
                goto DISCONNECT;
        }
        // Try to login to sector 0. Use the default key FFFF, for key A
        bool authenticated = authenticate(3,0,false);
        
        if (!authenticated) {
                goto DISCONNECT;
        }
        
        readblocks(0,3);
        
        
DISCONNECT: 
        // Disconnect from NFC device
        nfc_disconnect(pnd);
        return EXIT_SUCCESS;
What we have done is authenticate to sector 0, using block 3 (the sector trailer). You can authenticate to any block in a sector to be able to perform operations, I have authenticated to the trailer block as a matter of convention.

Once you compile and run the code, you should get something like this

./nfcdemo use libnfc 1.3.2 (r294)
Connected to NFC reader: Philips / USB TAMA - PN531 v3.4
The following (NFC) ISO14443A tag was found:
    ATQA (SENS_RES): 00  04  
       UID (NFCID1): 5a  12  04  dd  
      SAK (SEL_RES): 08  

Authentication succcessful on block 3
Block 0 data: 5a  12  04  dd  91  88  04  00  46  51  75  52  4d  10  13  08  
Block 1 data: ca  fe  ba  be  de  ad  be  ef  de  ad  de  ed  fa  ce  fe  ed  
Block 2 data: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  
Block 3 data: 00  00  00  00  00  00  ff  07  80  69  ff  ff  ff  ff  ff  ff 
Congratulations! You have successfully read from a Mifare card!


Block 0 is a read only block containing the cards UID among other things. Block 3, as said before, is the sector trailer. 

Writing data

Lets add a function to write data:

void writeblock(int block, byte_t *data) {
        memcpy(mp.mpv.abtValue,data,16);
        bool res = nfc_initiator_mifare_cmd(pnd, MC_WRITE, block, &mp);
        if (res) {
                printf("Wrote to block %d successfully\n", block);
        } else {
                printf("Could not write to block %d\n",block);
        }
}

This function will write an array of data to the specified block. The byte array should be 16 bytes, the size of the block.

And here is some code to write data using that function:
 byte_t data[16] = {0xCA,0xFE,0xBA,0xBE,
                        0xDE,0xAD,0xBE,0xEF,
                        0xDE,0xAD,0xDE,0xED,
                        0xFA,0xCE,0xFE,0xED};
  writeblock(1,&data);

Compile and run the program twice. The first time will write the new data to block 1, and hopefully, on the second invocation, you see something like this:

./nfcdemo use libnfc 1.3.2 (r294)
Connected to NFC reader: Philips / USB TAMA - PN531 v3.4
The following (NFC) ISO14443A tag was found:
    ATQA (SENS_RES): 00  04  
       UID (NFCID1): 5a  12  04  dd  
      SAK (SEL_RES): 08  
Authentication succcessful on block 3
Block 0 data: 5a  12  04  dd  91  88  04  00  46  51  75  52  4d  10  13  08  
Block 1 data: ca  fe  ba  be  de  ad  be  ef  de  ad  de  ed  fa  ce  fe  ed  
Block 2 data: 00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  
Block 3 data: 00  00  00  00  00  00  ff  07  80  69  ff  ff  ff  ff  ff  ff  
Wrote to block 1 successfully

Cool, huh?

What is on the card, anyway?

Now that we know what is on one sector of the card, it would be nice to see what other sectors we can access and use as well.

All we need to do is write a loop.

 int sector;
 for (sector=1; sector<64; sector++) {
        printf("Getting sector %d\n",sector);
        int authBlock = (sector*4)-1;
        int startBlock = authBlock-3;
        bool auth = authenticate(authBlock, 0, false);
        if (auth) {
                readblocks(startBlock,authBlock);
        }
}

On my card, it showed that 31 of the 63 sectors were available, meaning I could only use 384 out of the 768 bytes of data space myself. Oh well, not bad for a card I got out a vending machine. Guess I'll order some blanks now.

Complete source

Your finished application should resemble something like this.

Topics not covered

  • Changing the keys required to access a sector
    • Question for MiFare experts: Given the sector trailer above, why doesn't key 000000000000 work?
  • Value blocks
  • Playing with newer cards, i.e DESfire 

The micmd project implements a "shell" to manipulate MiFare cards and has the ability to analyse trailer sectors and manipulate value blocks. 

Applications

Now that I have a RFID reader, and the ability to read and write a card, what could I use it for? Some sort of digital wallet, or key store, say start an ssh session, and "tap your card" to authenticate.

Acknowledgements





Your email address will not be published

Please retry reCAPTCHA

Welcome to my site

Mathew McBride, telecoms hardware access engineer, programmer, gamer and all round nerd

Warning: contents of blog may not make any sense whatsoever.

ipv6 ready

You are accessing this page over IPv6!

(C) Mathew McBride, 2006-2017
Creative Commons License
Unless specified, the content on this website is licensed under a Creative Commons Attribution-ShareAlike 3.0 Australia License.