Few times ago I have published an article about two RFID locks that I encountered while traveling and a rough blackbox analysis of these two technologies. Unfortunately, back then, I only had few samples of key cards regarding Vingcard’s locks and that led me to take false assumptions.

But I was lucky enough very recently as to meet this lock once more. And because it was a three weeks stay, it was pretty easy to purposely tell the reception that my card was not working anymore, a couple of times, in order to have them reprogram it (yay, I’m a bad guy!). The purpose here was, first, to check what values can change over time (they usually encode the duration of the stay instead of the checkout timestamp) and secondly, to ensure that there is not a kind of timestamp-dependant key.

Just as a quick recap, last time I unveiled that Vingcard’s locks rely on Mifare Ultralight which is a simple 64-byte memory storage without any access control or cryptography embedded. Part of the memory was simply XORed with a 1 byte key that I supposed to be derived from at least the serial of the tag. At the end of the XORed block, we found a 4-byte block that might be a checksum of the protected zone but unfortunately, I wasn’t able to find the algorithm nor the data involved in its computation.

Right now, my sample collection is worth 24 cards, covering 3 hotels around the world with at least 2 rooms in each of them and different stays going from 1 day up to 3 weeks.

First thing you want to do when you are at least as lazy as me, is to write some scripts that will help you narrowing your analysis. The first script I wrote replaced the protected block with its unXORed version so I only get cleartext data.

Then, my second step was to write another Python script that will take a bunch of files as input and will output a sort of byte mask to unveil which bytes remain constant through the given samples. I also added an option to print this mask with a given amount of bytes per line. This may ease the analysis as Mifare Ultralight works on 4-bytes blocks.

Here is the output when this script is ran against the full sample set I have (a question mark means that the value is changing and this script does the comparison against nibbles):