This tool allows you to modify DESFire NFC tags via a simple command line user interface. The command line language is simply an interactive Lua shell. DESFire operations are mapped to Lua function calls.
- pkgconfig
- GNU Readline (https://tiswww.case.edu/php/chet/readline/rltop.html)
- Lua 5.1 or newer (http://www.lua.org/)
- OpenSSL 1.1.1 or 3 (https://www.openssl.org/)
- zlib (https://www.zlib.net/)
- libnfc (https://github.com/nfc-tools/libnfc)
- libfreefare (https://github.com/nfc-tools/libfreefare)
Use CMake.
$ mkdir build $ cd build $ camake .. $ make $ make install See https://cmake.org/cmake/help/latest/guide/user-interaction/index.html for details.
Simply calling desfsh list the available card reader devices. In the following example all readers are virtually the same, just the drivers to access the reader differ.
3 devices found: 0: acr122_usb:001:097 1: acr122_pcsc:ACS ACR122U PICC Interface 01 00 ** Unable to open device. ** 2: acr122_pcsc:ACS ACR122U PICC Interface 02 00 With a NFC tag present, each reader lists detected tags. Here two NFC tags are present in the radio field of the reader.
3 devices found: 0: acr122_usb:001:097 0: Mifare DESFire --> 044f6bf2893180 1: Mifare DESFire --> 04257a020c5180 1: acr122_pcsc:ACS ACR122U PICC Interface 01 00 ** Unable to open device. ** 2: acr122_pcsc:ACS ACR122U PICC Interface 02 00 0: Mifare DESFire --> 044f6bf2893180 1: Mifare DESFire --> 04257a020c5180 To access a distinct tag, you have to provide the reader and tag identifier as provided by the above-mentioned output. You may either specify the index ...
./desfsh -D acr122_usb:001:097 -T 044f6bf2893180 ... or the textual identifier ...
./desfsh -d 0 -t 0 ... or a combination of both.
./desfsh -d 0 -T 044f6bf2893180 You will get an interactive Lua shell which lets you execute commands against the tag.
> result, errmsg, aids = cmd.appids() > print(result, errmsg) 0 OK > for idx, aid in ipairs(aids) do >> print(idx, aid) >> end 1 0x000001 You can specifiy a command via the -c-option. The program exits after the command string has been executed.
In the following example the script examples/clt21.lua is executed.
CAUTION: This script will delete all applications irremediably on the card. It will try to authenticated with a zero DES or AES key respectively and change the PICC Master Key to the zero AES key. Afterwards a sample application with AID 1 and a couple of sample files is created.
./desfsh -d 0 -t 0 -c 'dofile("examples/clt21.lua")' Using the -i-option, the program will enter the interactive mode after executing the provided command string instead of exiting.
Using the -o-option enters offline mode. In offline mode no card reader is required. The features of the program are limited to the functions which don't depend on a connection to cards. This is mainly useful to perform cryptographic calculations.
You can inspect all input and out parameters as well as status codes by setting the appropriate debug flags. The debug level is a logical or combination of serveral flags. The following flags are defined.
0x01Show status codes of executed commands. Command names are printed in purple. Successful commands are printed in green, failed commands in red.0x02Show input parameters. Input parameters are printed in blue.0x04Show returned parameters. Returned parameters are printed in cyan.0x08Show additions debug information.
> debugset(15) > cmd.appids() >>> GetApplicationIDs <<< STAT <=> 0: OK AID <= 0x000001 > cmd.select(1) >>> SelectApplication <<< AID => 0x000001 STAT <=> 0: OK In the example all debug flags are set. Then all defined applications are queried. The command executes successful (STAT <=> 0: OK). There is one applications ID returned (AID <= 0x000001). In case multiple applications would be available on the tag, further AID-lines would be printed out. Lastly the application with ID 1 is selected. The AID 1 is transferred to the tag (AID => 0x000001). The command executes successfully, too.
The help-command shows an online help for all commands. Without an command name, all available commands are listed.
> help() buf.concat Concatenate buffers buf.fromascii Read Buffer from ASCII String buf.fromhexstr Read Buffer from HEX String buf.fromtable Read Buffer from Table buf.hexdump Convert Buffer to HEX Dump buf.toascii Convert Buffer to ASCII String buf.tohexstr Convert Buffer to HEX String buf.totable Convert Buffer to Table cmd.abort Abort Transaction cmd.appids Get Application List cmd.auth Authenticate to PICC cmd.carduid Get Real Card UID cmd.cbdf Create Backup Data File cmd.ccrf Create Cyclic Record File cmd.cfs Change File Settings cmd.ck Change Key cmd.cks Change Master Key Configuration cmd.clrf Create Linear Record File cmd.commit Commit Transaction cmd.createapp Create Application cmd.crec Clear Record File cmd.credit Increase a Files Value cmd.csdf Create Standard Data File cmd.cvf Create Value File cmd.debit Decrease a Files Value cmd.deleteapp Delete Application cmd.delf Delete File cmd.fileids Get File List cmd.format Format PICC cmd.freemem Get Size of remaining Memory cmd.getval Get Value of File cmd.getver Get PICC Information cmd.gfs Get File Settings cmd.gks Get Master Key Configuration cmd.gkv Get Key Version cmd.lcredit Increase a Files Value by a Limited Amount cmd.read Read from File cmd.rrec Read from Record cmd.selapp Select Application cmd.wrec Write to Record cmd.write Write to File crc.crc32 Calculate a CRC-32 checksum crypto.cmac Calculate CMAC crypto.hmac Calculate HMAC debugset Set Debug Flags help Show Help Text key.create Create key object key.diversify Calculate diversified Key show.apps Show Application Information show.files Show Files of an Application show.picc Show PICC Information When you specify a command, a brief description is printed out including the input and output parameters of the command. Commands may have aliases. For example cmd.appids, cmd.aids and cmd.GetApplicationIDs are all the same command.
> help(cmd.appids) Get Application List code, err, [aids] = cmd.appids() Aliasses: appids, aids, GetApplicationIDs code Return Code err Error String aids List of AIDs > help(cmd.select) Select Application code, err = cmd.selapp(aid) Aliasses: selapp, select, SelectApplication aid AID code Return Code err Error String Commands are grouped into namespaces.
| Namespace | Description |
|---|---|
cmd | Commands executed against DESFire tags |
buf | Creation and manipulation of byte sequence buffers |
key | Creation of DESFire key objects |
crc | Checksum functions |
crypto | Cryptographic functions |
show | Compound functions for analyzing tags |
The cmd-namespace contains all commands which are supported by DESFire tags. All commands receive an provide the same parameters as specified in the DESFire standard. A detailed explantion of the DESFire standard is beyond the scope of this document.
The remainder of this section shows a sample session. The tag used is based on the example script examples/clt21.lua. You can prepare you own card by executing this script inside the DESFire shell.
Activate debugging.
> debugset(255) Show all present DESFire applications.
> cmd.appids() >>> GetApplicationIDs <<< STAT <=> 0: OK AID <= 0x000001 Change current application to AID 1.
> cmd.select(1) >>> SelectApplication <<< AID => 0x000001 STAT <=> 0: OK Show all files of this application.
> cmd.fileids() >>> GetFileIDs <<< STAT <=> 0: OK FID <= 0 FID <= 1 FID <= 2 FID <= 3 FID <= 4 There are five files defined numbered from 0 through 4.
Show settings of file 0.
> cmd.gfs(0) -- get file settings >>> GetFileSettings <<< FID => 0 STAT <=> 0: OK COMM <= 0x03 (CRYPT) ACL <= RD:01 WR:02 RW:03 CA:00 FSET <= [SDF] SIZE: 32 It is a standard data file of 32 bytes length. File access operations are encrypted. Key 1 is allowed to read that file, key 2 is allowed to write, key 3 is allowd to read and write. Key 0 is allowed to change these access settings.
Try to to read the file 0.
> cmd.read(0, 0, 32) >>> ReadData <<< FID => 0 OFF => 0 LEN => 32 *I* Executing GetFileSettings() to determine file type and size. STAT <=> 174: AUTHENTICATION_ERROR This operations fails as we are unauthenticated.
Authenticate with key 1.
> cmd.auth(1, AES("11111111111111111111111111111111")) >>> Authenticate <<< KNO => 1 KEY => AES:11111111111111111111111111111111 (V:000) STAT <=> 0: OK Now try to read file 0 again.
> cmd.read(0, 0, 32) >>> ReadData <<< FID => 0 OFF => 0 LEN => 32 *I* Executing GetFileSettings() to determine file type and size. STAT <=> 0: OK BUF <= 00000000 43 68 65 6d 6e 69 74 7a |Chemnitz| BUF <= 00000008 65 72 00 00 00 00 00 00 |er......| BUF <= 00000010 00 00 00 00 00 00 00 00 |........| BUF <= 00000018 00 00 00 00 00 00 00 00 |........| As we gained read permission, the command succeeds.
Writing to the file is disallowed in the current authentication status. To write to the file, we authenticate using key number 3.
> cmd.write(0, 11, buf.fromascii("Linux-Tage")) >>> WriteData <<< FID => 0 OFF => 11 BUF => 0000000b 4c 69 6e 75 78 2d 54 61 |Linux-Ta| BUF => 00000013 67 65 |ge | STAT <=> 174: AUTHENTICATION_ERROR > cmd.auth(3, AES("33333333333333333333333333333333")) >>> Authenticate <<< KNO => 3 KEY => AES:33333333333333333333333333333333 (V:000) STAT <=> 0: OK > cmd.write(0, 11, buf.fromascii("Linux-Tage")) >>> WriteData <<< FID => 0 OFF => 11 BUF => 0000000b 4c 69 6e 75 78 2d 54 61 |Linux-Ta| BUF => 00000013 67 65 |ge | STAT <=> 0: OK As we are authenticated using key number 3, reading the file is allowed, too.
> cmd.read(0, 0, 32) >>> ReadData <<< FID => 0 OFF => 0 LEN => 32 *I* Executing GetFileSettings() to determine file type and size. STAT <=> 0: OK BUF <= 00000000 43 68 65 6d 6e 69 74 7a |Chemnitz| BUF <= 00000008 65 72 00 4c 69 6e 75 78 |er.Linux| BUF <= 00000010 2d 54 61 67 65 00 00 00 |-Tage...| BUF <= 00000018 00 00 00 00 00 00 00 00 |........| Finally we inspect the security settings of the currently selected application.
> cmd.gks() >>> GetKeySettings <<< STAT <=> 0: OK KEYSET <= 0x0f = AKC:AMK CONF:M CA/F:* DA/F:M/* LIST:* MKC:M MAXKEYS <= 4 The application has four keys. The key settings are encoded in the key settings byte 0x0f and interpreted as following.
AKC:AMK: Each application key (1 through 3) can be changed by the application master key (key number 0).CONF:M: The security settings can by changed by the application master key.CA/F:*: Files can be created unauthenticaed.DA/F:M/*: Files can be deleted unauthenticated. The application can be deleted by the applications master key.LIST:*: File listing is permitted without authentication.MKC:M: The application master key (key number 0) can be changed by itself.
For a detailed description of the security settings, refer to the DESFire specification.
Access to files is byte oriented. The file content is stored in buffer objects which can be transformed to different representations:
- UTF-8 text strings
- Hex strings
- Lua tables
This expression creates a buffer object x from the text string Hello World!.
> x = buf.fromascii("Hello World!") This buffer can be converted into a hex string ...
> print(x:tohexstr()) 48656c6c6f20576f726c6421 ... or into a Lua table ...
> for i, v in ipairs(x:totable()) do >> print(i, v) >> end 1 72 2 101 3 108 4 108 5 111 6 32 7 87 8 111 9 114 10 108 11 100 12 33 ... or into a hex dump.
> print(x:hexdump()) 00000000 48 65 6c 6c 6f 20 57 6f |Hello Wo| 00000008 72 6c 64 21 |rld! | Cryptographic key values are stored in lua tables. These key objects are manipulated by functions of the key-namespace. Each key has a type, a key value and a key version number. The following code creates the AES-key 00112233445566778899aabbccddeeff with key version 0.
k = key.create({ t = "AES", k = "00112233445566778899aabbccddeeff", v = 0 }) The shortcut function AES() simplifies this expression considerably.
k = AES("00112233445566778899aabbccddeeff", 0) If you skip the key version number, it will default to 0.
k = AES("00112233445566778899aabbccddeeff") If you skip the key value, the key value will be set to zero. This is the default key.
k = AES()