Compass Security Blog

Offensive Defense

There is such thing as a free lunch

Usually you need to pay for lunches with cash or using your credit card. But in some places employees can pay for a lunch using their access badge. And this is the payment method that will be covered in this blogpost.

The process of getting a lunch consists of two steps. First you go with your badge to a top-up machine where you put cash inside the machine to load money onto your badge. Then, you can pay for your lunch at the cash register by placing your badge on a reader, similarly as if you used a contactless credit card. However, in contrary to a credit card payment system, badge readers do not seem to be connected to any network, and neither are the top-up machines. This indicates that the amount of money you have on your badge is literally stored on the badge.

Usually employees’ badges are just RFID cards with the employee’s photo printed on it. There are a few commonly used types of such RFID cards. Whereas some of them store a private key that cannot be extracted and use cryptographic mechanisms to secure the communication with a reader, other types are relatively easily readable or even writable. In order to be able to communicate with a RFID card, or at least to figure out what type it is, a device like Proxmark3 can be helpful.

Proxmark3 – a tool for communicating with RFID cards.

Proxmark3 offers a possibility to automatically detect the most popular types of RFID tags. Unfortunately, the examined badge could not be identified in this way. As not all types of cards are currently supported in the automatic detection, it is reasonable to try to connect to the badge as if it was one of the remaining types. After issuing the command hf legic reader, Proxmark3 confirmed that the badge is actually a Legic Prime RFID card. Moreover, this type of card has already been broken (actually decoded as no encryption is used) and Proxmark3 can be used to read all data stored on the card and overwrite most of them.

Displaying the content of the badge can be accomplished with two commands: hf legic reader and then data hexsamples 1024. As the badge stores 1kB of data, the easiest way to find where exactly the possessed amount of money is saved is to read the entire card, buy something or put more money on it and check which bytes have been altered.

After putting 10 Francs on the empty badge and subsequently buying a coffee for 1.80, it was observed that a few bytes had changed (see below).

0x90: 2d aa 8c c3 41 2c 2c 2c
0x98: 32 2c 2c 05 33 2c 2c 2c
0xA0: 32 2c 2c 01 26 8d 69 6c

Balance: 0.00 CHF

0x90: 2d aa 8c c3 41 2c 2f c4
0x98: 32 2c 2c 17 16 2c 2f c4
0xA0: 32 2c 2c 02 1c c7 69 6c

Balance: 10.00 CHF

0x90: 2d aa 8c c3 41 2c 2f 18
0x98: 32 2c 2c 44 5b 2c 2f 18
0xA0: 32 2c 2c 03 4e a0 69 6c

Balance: 8.20 CHF

Hex dumps of data stored on the badge.

The good news is that the balance of the badge really seems to be stored in its data which facilitates manipulation of the balance. The bad news is that each transaction modified as many as nine bytes so finding out the meaning behind the stored data may be impeded. However, a simple trick that can be performed without understanding what is written on the badge is to overwrite each changed byte with its previous value such that the previous state of the card will be restored. Indeed, carefully modifying bytes one after the other using hf legic fill <offset> 0x01 <byte> Proxmark3 command made the top-up machine think we still have 10 Francs on the badge.

10 CHF restored on the badge
Restored balance of the badge.

Double-spending money is already cool, but it would be better to have the possibility to put an arbitrary amount of money on the badge without having to give any cash to the top-up machine.

After taking a closer look at the data on the card, it can be noticed that bytes at 0x96-0x97 and 0x9E-0x9F have always the same value and are only slightly changed when the amount of money is modified. This suggests that those bytes represent the actual balance. Additionally, byte at 0xA3 seems to be a counter as its value is incremented with each transaction. Unfortunately, the remaining four bytes seem to take random values.

It turns out that data stored on Legic Prime RFID cards is obfuscated by XOR operation with the checksum of card’s UID (UID is the unique serial number of each Legic Prime card written in its first four bytes). The checksum itself is stored at 0x04, its value in the examined badge is: 0x2c. After XORing each byte with 0x2c again, the real data can be revealed and indeed the balance, as a hexadecimal number, is stored exactly where it was suspected.

0x90: 01 86 a0 ef 6d 00 03 e8
0x98: 1e 00 00 3b 3a 00 03 e8
0xA0: 1e 00 00 2e 30 eb 45 40

Balance: 10.00 CHF

Data on the badge after decoding.

Still, the bytes at 0x9B-0x9C and 0xA4-0xA5 remain unexplained. But maybe they are not needed? Unfortunately, changing only the balance renders the card invalid. Similarly, when all four bytes are manually modified, the badge is no longer recognized as a mean of payment. Interestingly, a card with only one pair of bytes untouched is still considered valid and the reader restores the changed bytes to their original value . One option would be to set the balance to any chosen high value and brute-force the two bytes until the badge is considered as correct. However, approaching the top-up machine a few thousand times before getting the free lunch is not very convenient, and may arouse suspicion among other employees.

Although no official documentation of Legic Prime standard is publicly available, attempts has been done to reverse engineer it. For example, a fork of Proxmark3 software tries to explain the meaning of bytes representing cash on Legic Prime, it also hints at CRC-16 checksum to be the algorithm used to calculate the pairs of enigmatic bytes. However, CRC-16 is not a single function, it is rather a parametrized algorithm. Calculation of the checksum with commonly used parameters can be performed online, but none of the functions output the same value as stored on the badge. Nonetheless, having enough samples of valid CRC-16 checksums and knowing (or at least suspecting based on online resources) over which bytes the checksums are calculated, the correct parameters of CRC-16 can be brute-forced. Actually, brute-forcing is not the most precise description here since not all parameter combinations need to be tried and the process takes only a few seconds. So, after buying a few more coffees using the badge to obtain more valid checksums, a set of CRC-16 parameters that always resulted in the correct results has been found.

Poly: 0x002d
Init: 0x2c2c
RefIn: True
RefOut: False
XorOut: 0x0000

Parameters of CRC-16 used to derive checksums on the examined badge.

Now it is time to check whether changing the balance of the badge, together with adjusting the checksums will trick the top-up machine into accepting the card.

555 CHF put on the badge
Money put on the badge using Proxmark3.

It did! Generating digital money out of thin air became possible. But putting money into a colleague’s badge failed because the checksums were invalid. Investigating further why it failed revealed that the parameters used in the CRC-16 differ. For his card the used “Init” value was 0x8383, other parameters remained unchanged. Yet another badge used 0xa0a0. It seems that one byte is always being repeated to get a two-byte value, but where does the byte come from? After a closer look, an interesting regularity was found. In the badge examined from the beginning the “Init” parameter of the CRC-16 was 0x2c2c and its checksum of UID was 0x2c, similarly, the checksum of UID of the other two cards was 0x83 and 0xa0 respectively. Thereby all mysteries of payments with a Legic Prime badge has been solved.
Now everyone can enjoy free lunches.

Disclaimer: Activities described in this blogpost did not cause any financial damage to the provider of coffee and lunches.

1 Comment

  1. Captn


    Thanks a lot for this very interesting article, I really enjoyed reading it ;)
    Take it easy!

Leave a Reply

Your email address will not be published. Required fields are marked *