Below is the file 'ENGINEERING' from this revision. You can also download the file.

Reverse Engineering Notes for MIFARE Card Reader

These notes refer to the MIFARE reader produced by StrongLink, model number
SL500L-0703.

Some general remarks on the communications protocol observed are made.

The API calls available from the DLL are listed, with corresponding byte streams
observed.

PROTOCOL BASICS
===============

At this stage, the basic communications protocol appears to be as follows:

Host to Reader (Command)
2-byte Header: 0xAA 0xBB
1-byte Length: Payload length in bytes
Payload: variable length, but containing:
 - 1-byte separator: 0x00
 - 2-byte target device ID, or 0x0000 for any available device
 - 1-byte command
 - 1-byte separator: 0x01, 0x02
 - Multiple-byte argument, depending on command.
1-byte Checksum: XOR of payload

Reader to Host (Response)
2-byte Header: 0xAA 0xBB
1-byte Length: Payload length in bytes
Payload: variable length, but containing:
 - 1-byte separator: 0x00
 - 2-byte device ID: can be set by command
 - 1-byte command received
 - 1-byte separator: 0x01, 0x02
 - Multiple-byte return value (0x00 for success when no data requested)
1-byte Checksum: XOR of payload

Thus a packet is always len(payload) + 4 bytes.

The device ID has been set to 0xDEAD for most of these tests.

SYSTEM FUNCTIONS
================
API Call: "lib_ver"
Parameters: none relevant
No communications observed.

--

API Call: "rf_init_com"
Parameters: serial port, baud rate.

Our SL500L speaks 19200 by default, but can change on command. This API call
sends the command to switch to a particular speed to the given port at various
speeds (9600 to 115200, in order) until it receives a response.

Commands:
1. '\xaa\xbb\x06\x00\x00\x00\x01\x01\x01\x01' Switch into 9600
2. '\xAA\xBB\x06\x00\x00\x00\x01\x01\x02\x02' Switch into 14400
3. '\xAA\xBB\x06\x00\x00\x00\x01\x01\x03\x03' Switch into 19200
4. '\xAA\xBB\x06\x00\x00\x00\x01\x01\x04\x04' Switch into 28800.
You get the idea. \x07 is 115200.

Responses:
1,2,3,4. '\xaa\xbb\x06\x00!\x02\x01\x01\x00#' (Note that 0x21 0x01 is the device
                                              ID)

--

API Call: "rf_ClosePort"
Parameters: none relevant
No communications observed.

--

API Call: "rf_get_device_number"
Parameters: none relevant

Commands:
1. '\xAA\xBB\x05\x00\x00\x00\x03\x01\x02'

Responses
1. '\xaa\xbb\x08\x00\x12\x01\x03\x01\x00\x12\x01\x02' Reported as 2102

--

API Call: "rf_init_device_number"
Parameters: two-byte device number to be written

Commands:
1. '\xAA\xBB\x07\x00\x00\x00\x02\x01\xBE\xEF\x52' Set device ID to '0xBEEF'

Responses:
1. '\xaa\xbb\x06\x00\xbe\xef\x02\x01\x00\x52' Success (verified by
                                              rf_get_device_number())

--

API Call: "rf_get_model"
Parameters: none relevant

Commands:
1. '\xAA\xBB\x05\x00\x00\x00\x04\x01\x05'

Responses:
1. '\xaa\xbb\x11\x00\xde\xad\x04\x01\x00SL500L-0703\x39' Reported as SL500L-0703

--

API Call: "rf_antenna_sta"
Parameters: desired antenna state (0 for off, 1 for on)

Commands:
1. '\xAA\xBB\x06\x00\x00\x00\x0C\x01\x00\x0D' Turn antenna off
2. '\xAA\xBB\x06\x00\x00\x00\x0C\x01\x01\x0C' Turn antenna on

Responses:
1,2. '\xaa\xbb\x06\x00\xde\xad\x0c\x01\x00\x7E' Reported as success

--

API Call: "rf_beep"
Parameters: beep sound duration in 10msec increments

Commands:
1. '\xAA\xBB\x06\x00\x00\x00\x06\x01\x01\x06' Beep for 10 msec
2. '\xAA\xBB\x06\x00\x00\x00\x06\x01\x10\x17' Beep for 160 msec
3. '\xAA\xBB\x06\x00\x00\x00\x06\x01\x64\x63' Beep for 1 sec (0x64 * 10msec)

Responses:

1,2,3. '\xaa\xbb\x06\x00\xde\xad\x06\x01\x00\x74' Reader beeps, success reported

--

API Call: "rf_light"
Parameters: desired LED state - 0 for off, 1 for red, 2 for green, 3 for both

Commands:
1. '\xAA\xBB\x06\x00\x00\x00\x07\x01\x00\x06' LED off
2. '\xAA\xBB\x06\x00\x00\x00\x07\x01\x01\x07' LED red
3. '\xAA\xBB\x06\x00\x00\x00\x07\x01\x02\x04' LED green
4. '\xAA\xBB\x06\x00\x00\x00\x07\x01\x03\x05' LED both ("yellow")

Responses:
1,2,3,4. '\xaa\xbb\x06\x00\xde\xad\x07\x01\x00\x75' Lights change appropriately

--

API Call: "rf_init_type"
Parameters: desired card type - A for ISO14443A, B for ISO14443B, r for
                                AT88RF020, 1 for ISO15693 mode

According to API documentation, untestable on SL500L (only one card type
supported).

Commands:
1. '\xaa\xbb\x06\x00\x00\x00\x08\x01A\x48' Select card type 14443A

Responses:
1. '\xaa\xbb\x06\x00\xDE\xAD\x08\x01\x00\x7A' Reported as success

--

ISO14443A (MIFARE) FUNCTIONS
============================

The API documentation suggests that rf_request be called to identify the type of
card available, then rf_anticoll, rf_select for MIFARE cards, or rf_ul_select
for MIFARE UltraLight cards.

API Call: "rf_request"
Parameters: card modes to select - 0x52 for all, 0x26 for non-halted cards.

Commands:
1. '\xAA\xBB\x06\x00\x00\x00\x01\x02\x52\x51' Select a card, ignore halted state

Responses:
1. '\xaa\xbb\x06\x00\xDE\xAD\x01\x02\x14\x64' Error 14 - no card present
1. '\xaa\xbb\x08\x00\xDE\xAD\x01\x02\x00\x04\x00\x74' Success - tag type 0x0400

Tag types returned as follows:
0x4400 = ultra_light
0x0400 = Mifare_One(S50)
0x0200 = Mifare_One(S70)
0x4403 = Mifare_DESFire
0x0800 = Mifare_Pro
0x0403 = Mifare_ProX
0x0033 = SHC1102

Of these, the SL500L can only identify the first three.

--

API Call: "rf_anticoll"
Parameters: API identifies a "bcnt - take count 4" parameter of unknown function
            All sample code uses 0x04 for this parameter.

Commands:
1. '\xaa\xbb\x06\x00\x00\x00\x02\x02\x04\x04'

Responses:
1. '\xaa\xbb\x0a\x00\xde\xad\x02\x02\x00\xDE\xAD\xBE\xEF\x51' Card DEADBEEF

--

API Call: "rf_select"
Parameters: card serial number

Commands:
1. '\xAA\xBB\x09\x00\x00\x00\x03\x02\xBA\xDC\x0D\xE0\xAC' Select card BADC0DE0

Responses:
1. '\xaa\xbb\x07\x00\xDE\xAD\x03\x02\x00\x08\x7A' Card capacity 8 blocks?
1. '\xaa\xbb\x07\x00\xDE\xAD\x03\x02\x00\x18\x6A' Card capacity 0x18 blocks?

After issuing this command, future commands will be sent to this card only.

--

API Call: "rf_m1_authentication2"
Parameters: key identifier - A 0x60, B 0x61, block number to authenticate on,
            encryption key (6 bytes)

The block number passed is the first block of the desired sector

Default transport keys: 0xFF FF FF FF FF FF, 0x00 00 00 00 00 00 (either A or B)
0xA0 A1 A2 A3 A4 A5 (key A)
0xB0 B1 B2 B3 B4 B5 (key B)

Commands:
1. '\xAA\xBB\x0D\x00\x00\x00\x07\x02\x60\x00\xFF\xFF\xFF\xFF\xFF\xFF\x65'
    Authenticate on sector 0 (block 0) with key A as '0xFF FF FF FF FF FF'
2. '\xAA\xBB\x0D\x00\x00\x00\x07\x02\x60\x04\xFF\xFF\xFF\xFF\xFF\xFF\x61'
    Authenticate on sector 1 (block 4) with key A as '0xFF FF FF FF FF FF'

Responses:
1,2. '\xAA\xBB\x06\x00\xDE\xAD\x07\x02\x00\x76' Successful authentication
1. '\xAA\xBB\x06\x00\xDE\xAD\x07\x02\x16\x60' Access denied (incorrect key)

--

API Call "rf_m1_read"
Parameters: 'absolute' block number

MIFARE cards are split into blocks of 4 16-byte sectors (where the fourth sector
contains the access control data). The SL500L requires an absolute offset, so
block 0 is sectors 0, 1, 2 and 3 (where sector 3 contains the keys and ACL).
Block 1 is sectors 4, 5, 6 and 7, and so on.

To make things a little more confusing, blocks 32 and above (sector >= 0x80) are
made up of 16 sectors, where the sixteenth is the key and ACL sector.

Commands:
1. '\xAA\xBB\x06\x00\x00\x00\x08\x02\x07\x0D' Read sector 0x07 (i.e. in block 1)

Responses:
1. '\xaa\xbb\x16\x00\xde\xad\x08\x02\x00\x00\x00\x00\x00\x00\x00\x00\xff\x07\x80\x69\xff\xff\xff\xff\xff\xff\x68'
   Success, returned data is 000000000000FF078069FFFFFFFFFFFF

--

API Call: "rf_m1_write"
Parameters: absolute block number, data to write

Commands
1. '\xAA\xBB\x16\x00\x00\x00\x09\x02\x01\xC0\xFF\xEE\x0B\xAD\xC0\xDE\xDE\xAD\xBE\xEF\x12'
    Write '0xc0ffee0badc0dedeadbeef1234567890' to sector 0x01 (i.e. in block 0)

Responses:
1. '\xaa\xbb\x06\x00\xde\xad\x09\x02\x00\x78' Reported as successful write.

--

API Call: "rf_halt"
Parameters: none

Commands:
1. '\xAA\xBB\x05\x00\x00\x00\x04\x02\x06' Halt current card

Responses:
1. '\xaa\xbb\x06\x00\xde\xad\x04\x02\x00\x75' Success
Always returns success, even if there is no card present.