Investigating a tricky network problem with Python, Scapy and other network tools

We’ve had a fairly long-term issue at work with connectivity to one of our application servers. Every now and then you can’t login or connect and it has seemed fairly random. This finally annoyed myself and a customer enough that I had to look into it.

The connection is made to the server on port 1494 – Citrix ICA. Initially we suspected that the .ica file downloaded and opened by the Citrix Receiver software was incorrect or corrupt, but inspection and testing of this showed that it was OK. It really did look like the connection was just being randomly rejected.

It seemed that myself and a single customer were having far more frequent issues that other users. Of course it could just be my tolerance for whinging is lower than my colleagues.

Note that nearly all of the below was done on OS X – syntax of some of these commands differs under Linux and Windows. I have changed the host IP for security reasons.


Most applications that listen on a TCP port will respond to telnet, even if they don’t send anything back. Telnet is almost raw TCP – it has some control and escape sequences layered on top, but it is very simple at a protocol level.

ICA responds when connecting by sending back “ICA” every 5s:

But every now and then I was getting nothing back:

Oddly, whenever the Citrix Receiver failed to launch, I wasn’t always having problems with telnet, and vice versa. This is good – we’ve replicated the issue with a very simple utility using raw TCP rather than having to look into the intricate details of Citrix and whatever protocol it uses.


So let’s fire up tcpdump to see what happens when the connection is working. tcpdump is a command line packet analyser. It’s not as versatile or as easy to use as Wireshark, but it is quick and easy. You can use tcpdump to generate a .pcap file which can then be opened in Wireshark at a later date – this is good for when you are working on systems with no UI.

I filtered the tcpdump output to only show traffic where one of the two IPs was the server.

This all looks fairly normal – my laptop is sending a SYN to the server, which responds with SYN-ACK, and then I respond with an ACK. You can see this in the “Flags” part of the capture. S, S., . (. means ACK in tcpdump). Everything then progresses normally until I close the connection.

However, when the connection fails:

I get nothing back at all – it’s just telnet trying the connection again and again by sending SYNs. I was expecting part of the connection to succeed, but this looked like the host just wasn’t responding at all. This might indicate a firewall or network issue rather than a problem with Citrix.

I used Wireshark on the server side to confirm that no traffic was getting through. I could see the successful connections progressing fine, but I could see nothing of the failing connections. I wanted to check both sides because there were a number of potential scenarios where a client could send a SYN and not get a SYN-ACK back:

  1. Client sends SYN, server never sees SYN.
  2. Client sends SYN, server sees SYN, server sends SYN-ACK back which is lost.
  3. Client send SYN, server sees SYN, choses not to respond.

It seemed that 1 was happening here.

So what was causing this? Sometimes it worked, sometimes it didn’t. Did it depend on what time I did it? Was there another variable involved?


Let’s check for outright packet loss. ping and traceroute are useful for diagnosing packet loss on a link, but it can be hard work working out which step is causing problems. Step in mtr, or my trace route. This provides a tabular, updating output which combines ping and traceroute with a lot of useful information.

I let this run for a while and observed virtually no packet loss. It’s important to note that it is using ICMP pings – not TCP as Citrix uses. ICMP messages can be dealt with differently to TCP. mtr does support TCP pings but I can’t get it to work under OS X.

Python and telnetlib

So wrote a small Python program using the telnetlib module to periodically connect to the port using telnet and indicate when there were problems. The output was simple graphical representation so that I could spot any timing patterns.

So this prints a . for a successful connection and * for unsuccessful. After every 16 packets, the number of failures/total is printed.

What can we note?

  • There is some vague pattern there, often repeating every 8 packets.
  • The rate of failed to successful connections is nearly always 25%.
  • Varying the WAITTIME (the time between connections) had some interesting effects. With short times, the patterns were regular. With longer times they seemed less regular.
  • Using the laptop for other things would disrupt the pattern but packet loss stayed at 25%. Even with very little other traffic the loss was 25%.

What varies over time, following a pattern, but would show behaviour like this?

The source port.

Every TCP connection not only has a destination port, but a source port – typically in the range of 1025 to 65535. The source port is incremented for each connection made. So the first time I telnet it would be 43523, the next time 45324, then 45325 and so on. Other applications share the same series of source ports and increment it as they make connections.

When I run the test program with a short wait time, there is very little chance for other applications to increment the source port. When I run it with a longer wait time (30s or so), many other applications will increment the source port, causing the pattern to be inconsistent.

It really looked like certain source ports were failing to get through to the server.


I had to test this theory. You can’t control the source port with telnet, but you can with the excellent netcat (or nc, as the command is named). “-p” controls the source port:

As you can see – connections from 1025 and 1027 always succeed and 1026 always fail. I tested many other ports as well. We have our culprit!

Python and Scapy

Now, can we spot a pastern with the ports that are failing and those that are succeeding? Maybe. I needed something to craft some TCP/IP packets to test this out. I could use netcat and a bash script, but I’ve recently learnt about Scapy, a packet manipulation module for Python. It’s incredibly flexible but also very quick and easy. I learnt about it after reading the book Violent Python, which I would recommend if you want to quickly get using Python for some real world security testing.

The script needs to connect to the same destination port from a range of source ports and record the results. With Scapy, half an hour later, I have this (note, I did have some issues with Scapy and OS X that I will need to go over in another post):

This produced really helpful output. The failed packets are highlighted in the excerpt below:

At this point in the port range it appears that packets ending in 001 or 110 are failing.

Move further down the port range and packets ending 000 and 111 are failing.

In fact, at any given point it seems that the packets failing are either 000/111, 001/110, 010/101, 011/100 – complements of one another. Higher order bits seem to determine which pair is going to fail.


What makes this even stranger is that changing the destination port (say, from 1494 to 80) gives you a different series of working/non-working source ports. 1025 works for 1494, but not 80. 1026 works for both. 1027 works only for 80.

All of my tests above have been done on my laptop over an internet connection. I wanted to test a local machine as well to narrow down the area the problem could be in – is it the perimeter firewall or the switch the server is connected to?


The local test machine is a Linux box which is missing Python but has hping3 on it. This is another useful tool that allows packets to be created with a great degree of flexibility. In many respects it feels like a combination of netcat and ping.

What does all this mean?

  • First parameter is the IP to connect to.
  • -s is the start of the source port range – hping3 will increment this by 1 each time unless -k is passed
  • -S means set the SYN flag (similar to the Scapy test above)
  • -i u100000 means wait 100000us between each ping
  • -c 20 means send 20 pings
  • -p 1494 is the offending port to connect to

And what do we get back?

The same sort of packet loss we were seeing before. Oddly, the source ports that work differ from this Linux box to my laptop.

Here’s where it gets even stranger. I then decided to try padding the SYN out with some data (which I think is valid for TCP, though I’ve never seen a real app do it and mtr’s man page says it isn’t). You use -d 1024 to append 1024 bytes of data. I first tried 1024 bytes and had 20% packet loss as before. They I tried 2048 bytes:

Wait? All the packets are now getting through?

Through a process of trial and error I found that anything with more than 1460 bytes of data was getting through fine. 1460 bytes of data + 20 bytes TCP header + 20 bytes IP header = 1500 bytes – this is the Ethernet MTU (Maximum Transmit Unit). Anything smaller than this can be sent in a single Ethernet frame, anthing bigger needs to be chopped up into multiple frames (although some Ethernet networks allow jumbo frames which are much bigger – this one doesn’t).

I then ran the hping3 test from my laptop and found that altering the data size had no impact on packet loss. I strongly suspect that this is because a router or firewall along the way is somehow modifying or reassembling the fragmented frames to inspect them, and then reassembling them in a different way.

At this point I installed the Broadcom Advanced Control Suite (BACS) on the server to see if I could see any further diagnostics or statistics to help. One thing quickly stood out – a counter labelled “Out of recv. buffer” was counting up almost in proportion to the number of SYN packets getting lost:


This doesn’t sound like a desirable thing. It turns out the driver is quite out of date – maybe I should have started here!


I’m still not sure what is going on here. The packets being rejected do seem to follow some kind of pattern, but it’s certainly not regular enough to blame it on the intentional behaviour of a firewall.

At this point we are going to try upgrading the drivers for the network card on the sever and see if this fixes the issue.

The point of all of the above is to show how quick and easy it can be to use easily available tools to investigate network issues.

Reverse engineering Megamos Crypto?

Some of you might have read the stories going around a few weeks ago – “Scientist banned from revealing codes used to start luxury cars“. The short of it is that a security researcher has had a injunction imposed on him, preventing him from publishing a paper. The paper reveals security problems in the Megamos Crypto system used in the immobiliser system of many cars. Volkswagen are not happy – it really seems they want this shut down.

(As an aside, I hate the way that mainstream media refers to “codes” – it can mean source code, executables, an algorithm, or even a secret key. Often used interchangeably in the same article)

Details were a little scant, but last night the EFF passed comment, based on the court’s decision.

I am not a lawyer – I’m not going to pass judgement on the legal side. But what is interesting is how the researchers got hold of the Megamos Crypto algorithm. It wasn’t by decapping the chips in the transponders, it wasn’t from observing them black-box, it wasn’t from looking at an embedded software implementation – they took a Windows program used to clone car key transponders and reverse engineered that.

In terms of working out how Megamos was implemented, someone else had already done the hard work. This left the researchers to perform detailed cryptanalysis of the algorithm and – rumour has it – find some serious problems.

The piece of software is called “Tango Programmer“, a third party tool (software and hardware) used to make transponders. This has been available since at least 2009.

Tango Programmer is readily available, but it appears that it needs to be bought alongside a physical programmer. I strongly suspect that the software would be available on file sharing sites illegally, or possibly even legitimately on another site if you look hard enough.

Another company, Bicotech, produce a similar tool called RwProg. The software is downloadable from their website. The executable is packed, but I am sure it would be perfectly possible to reverse engineer the algorithm from the binary.

The court decision itself contains valuable information on Megamos as well, notably from paragraphs 4 and 5:

In detail the way this works is as follows: both the car computer and the transponder know a secret number. The number is unique to that car. It is called the “secret key”. Both the car computer and the transponder also know a secret algorithm. That is a complex mathematical formula. Given two numbers it will produce a third number. The algorithm is the same for all cars which use the Megamos Crypto chip. Carrying out that calculation is what the Megamos Crypto chip does.

When the process starts the car generates a random number. It is sent to the transponder. Now both computers perform the complex mathematical operation using two numbers they both should know, the random number and the secret key. They each produce a third number. The number is split into two parts called F and G. Both computers now know F and G. The car sends its F to the transponder. The transponder can check that the car has correctly calculated F. That proves to the transponder that the car knows both the secret key and the Megamos Crypto algorithm. The transponder can now be satisfied that the car is genuinely the car it is supposed to be. If the transponder is happy, the transponder sends G to the car. The car checks that G is correct. If it is correct then the car is happy that the transponder also knows the secret key and the Megamos Crypto algorithm. Thus the car can be satisfied that the transponder is genuine. So both devices have confirmed the identity of the other without actually revealing the secret key or the secret algorithm. The car can safely start. The verification of identity in this process depends on the shared secret knowledge. For the process to be secure, both pieces of information need to remain secret – the key and the algorithm.

In standard cryptography terminology:

A car \text{C} and a transponder \text{T} share a secret key K. A pseudo-random function family \textsf{PRF} is keyed using key K i.e. \textsf{PRF}_K. The output from this PRF is split into two parts F and G.

  1. \text{C} generates a random number r.
  2. \text{C} calculates (F,G) = \textsf{PRF}_K(r)
  3. \text{C} \to \text{T}: r, F
  4. \text{T} calculates (F',G') = \textsf{PRF}_K(r)
  5. \text{T} checks that F = F'
  6. \text{T} \to \text{C}: r, G
  7. \text{C} checks that G = G'

This process means that the transponder believes the car knows the key and PRF, and the car believes the transponder knows the key and PRF. They should have authenticated themselves with each other.

What is a PRF? A pseudo-random function is similar in many respects to a psuedo-random number generator (PRNG), except instead of sequentially generating output, you can randomly access any of the outputs using an index (r in the example above). The key is analogous to the seed of the PRNG. Using a certain key, a given input will map to a determined output.

Importantly, the output of a PRF should be indistinguisable to an observer from a random function, and by extension you should not be able to derive the key even if inputs, outputs, or free access to the function is given. You should also not be able to tell which PRF is in use even if you can control the inputs and read the outputs.

So – if this is a secure, solid, verified PRF, the protocol should be secure, even if we know what the PRF is. The only thing that needs to be kept secret is the key.

But the court decision says:

The verification of identity in this process depends on the shared secret knowledge. For the process to be secure, both pieces of information need to remain secret – the key and the algorithm.

This suggests a few things:

  1. The PRF used is not secure
  2. They don’t know what they are talking about

Both are entirely possible, but I would strongly suspect that the PRF has issues and they want to keep it secret. This would be a clear example of “security through obscurity”.

How could a PRF be insecure?

  • Using one or more input/output pairs, it might be possible to derive the key.
  • You might not need a key to derive the output given the input.
  • The key length might not be long enough to prevent bruteforcing.
  • F and G might not depend on the whole key i.e. you might be able to calculate G given part of the key.

The protocol itself might suffer from further issues:

  • There does not appear to be any protection from replay attacks (prevented from being used as a direct vulnerability because the authentication is bidirectional).
  • Is the random nummber actually random? Does it matter if it isn’t? If they are re-used (i.e. it’s not a nonce), it probably does matter.
  • The transponder can bypass the check for F = F’ – it can be a “yes” key. If we don’t need the entire key to compute G, this matters.
  • The key might be constant across an entire line or make of cars. Recover the key from one transponder and there would be no secrets left.
  • The key might be derived from an open piece of information like the car VIN number
  • The key might be derived from something like the manufacture date/time of the car, massively reducing keyspace
  • Probably a million more things

Let’s look at the attacks described in the court decision.

Firstly, note:

The attacks are not, themselves, trivial things to do. However, they allow someone, especially a sophisticated criminal gang with the right tools, to break the security and steal a car.

This makes it sound like some of these attacks are practical i.e. it won’t take 2 weeks of effort after decapping and reading the key from EEPROM.

Attack 1:

One attack relies on weaknesses in the secret keys that are used in certain cars. That “weak key” weakness arises because certain car makers have used weak secret keys which are easier to guess than they need to be. In effect, it is a bit like using the word “password” for a password.

As I mentioned above, there are a number of situations where the keys chosen might be poor. It might be the case that the researchers need 2 weeks to work out the key given a car and transponder, but then if the same key is used across all cars, it doesn’t really matter.

Attack 2:

Another is concerned with key updates. The details do not matter.

This is very vague.  Maybe you can alter or add keys easily if you already have access to the car?

Attack 3:

The third attack relates to weaknesses in the Megamos Crypto algorithm itself. The academics explain this attack in the paper, and, as I say, the paper also sets out the whole of the algorithm. It is these two elements that the claimants seek to prevent publication of. The claimants wish to remove the Megamos Crypto algorithm and information about the attack based on the weakness in it from the paper.

This is where we get to the point that it sounds like the PRF is not secure. It sounds like this attack may take days of work with access to both the car and transponder.

This could be like the insecurities found in Keeloq. The first step was determining the details of the algorithm. The first few papers detailed weaknesses that meant the protocol was insecure, but the weakness could not practically be exploited. After this, papers were released that detailed faster, more effective attacks, until finally we are at the stage where Keeloq can be called “broken”.

A quick look at some of the software

I haven’t got hold of Tango Programmer, but I do have RwProg up and running. Here is a screenshot:2013-08-07 22_37_06-RwProg   v2.17.0002

What can we tell from this? Well, the crypto key looks to be 96bits long – too long to bruteforce.

There are a few videos as well:

Nothing really groundbreaking. I can’t see how the software reads and then writes the crypto key.


Regardless of the court decision, it looks like there is enough information out there for other people to start work on this. Download the software, maybe buy Tango Programmer, reverse the algorithm and then let the world loose on it!


We need an antidote to the anti-code

In the last post, I briefly went over the process of reverse engineering the algorithm behind an anti-code generator for an alarm system.

It turned out that the algorithm was very simple indeed. For a given 5-digit numeric quote code, we can derive a 5-digit reset code using a “secret” 8-bit (256 possibilities) version number as a key. This has a lot in common with a keyed hash function or a message authentication code.

There are some pretty serious security implications with this mechanism.

5 digit numeric codes are never going to be strong

Even if I had to enter a pin at random, a 5-digit numeric code only has 100,000 options – I have a 1/100,000 chance of getting it right.

If we made this into a 5-digit hexadecimal code, we would now have a 1/1,048,576 chance – a factor of over 10 times less likely.

Up this to a 6-digit alphanumeric code, and it is now 1/2,176,782,336 – a factor of over 20,000 times less likely we could guess the code.

It doesn’t take many alterations to the limits on codes to make them much more secure.

For this reason it surprises me that alarms are still using 4-digit pins, but most internet forums insist on 8-character passwords with alphanumeric characters and punctuation.

The algorithm isn’t going to stay secret

There is no way to reliably protect a computer application from reverse engineering. If you can run it, at all, it is highly likely the operation can be observed and reversed. Relying on the secrecy of an algorithm or a key hidden within the software is not going to afford any level of security.

Once we know the algorithm, the odds massively improve for an attacker

The algorithm takes a version number from 0-255. For a given quote code, I can try each version number, giving me a list of up to 256 potentially valid reset codes (sometimes, two version numbers will generate the same reset code).

If I enter a code from this list, I now have a 1/256 chance of getting it right. Not great compared to 1/100,000 for a purely random guess.

This is entirely due to the short version number used.

Given a quote/reset code, most of the time we can infer the version

It quickly became apparent that for most quote/reset pairs, there was only a single version number than could produce this pair. I’m awful at probability and decision maths, so I thought running a simulation would be better.

I like running simulations – generally when the number of simulations becomes large enough, the results tend towards the correct value. So I tried the following:

1. Generate a genuine quote/reset pair using a random quote.

2. Use a brute force method to see which version numbers can produce this pair

3. Record if more than one version number can produce this quote/reset pair.

I started doing this exhaustively. This would take a long time though… someone on the Crypto stack exchange answered my question with a neater, random simulation.


I ran this test over 20 million times. From this it turns out that 99.75% of quote/reset code pairs will directly tell me the version number. Most of the remaining 0.25% require yield two version numbers. A tiny number (<0.001%) yield more than four version numbers. You are almost certain to know the version number after two quote/reset pairs as a result.

What does this mean in the real world?

The version number is treated as the secret, and I am informed that this secret is often constant across an entire alarm company. All ADT alarms or all Modern Security Systems alarms may use the same version number to generate reset codes.

This means I could get hold of any quote/reset pair, infer the version number, and then use that later to generate my own anti-codes for any ADT alarm. I could get hold of these quote/reset pairs by going to an accomplice’s house with a ADT alarm system, or by eavesdropping on communications.

With that anti-code I could either reset a system presenting a quote code, or impersonate an alarm receiving centre (there are other speech based challenge-response requirements here to prove the caller is genuine, which are easily gamed I would imagine).


A 5-digit reset code using an 8-bit key is never going to be secure.

When computer passwords are 8 characters and 128-bit keys are the norm, this anti-code mechanism seems woefully inadequate.

Reversing an anti-code

A contact in the alarm industry recently asked if I could take a look at a quick reverse engineering job. I’m trying to gain some credibility with these guys, so I naturally accepted the challenge.

Many alarms have the concept of an “anti-code”. Your alarm will go off and you will find it saying something like this on the display:


QUOTE 12345

The idea is then that you call the alarm receiving centre, quote 12345, they will input this into a PC application, get a reset code back, tell the customer, and then they can use this to reset the alarm. This means that you need to communicate with the alarm receiving centre to reset the alarm.

Alarm manufacturers provide their own applications to generate these codes. This particular manufacturer provides a 16-bit MS-DOS command line executable, which will refuse to run on modern PCs. This is a pain – it’s not easy to run (you need to use a DOS emulator like DOS-BOX) and it doesn’t allow for automation (it would be convenient to call a DLL from a web-based system, for example).

So I was asked if I could work out the algorithm for generating the unlock codes. x86 reverse engineering is not my forté, especially older stuff, but I thought i would have a quick go at it.

Turns out it was easier than expected! I find documenting reverse engineering incredibly difficult in a blog format, so I’ll just cover some of the key points.

Step 1: Observe the program

First things first, let’s get the program up and running. DOS-BOX is perfect for this kind of thing.

The program takes a 5 digit input and produces a 5 digit output. There is also a version number which can be input which varies from 0-255.

I spent a while playing around with the inputs. Sometimes things like this are so basic you can infer the operation (say, if it is XORing with a fixed key, flipping the order of some bits or similar). It didn’t look trivial, but it was plain to see that there were only two inputs – the input code and version. There was no concept of time or a sequence counter.

At this stage, I’m thinking it might be easiest to just create a lookup for every single pin and version. It would only be 2,560,000 entries (10,000 * 256). That’s a bit boring though, and I don’t have any idea how to simulate user input with DOS-BOX.

Step 2: Disassemble the program

To disassemble a program is to take the machine code and transform it into assembly language, which is marginally more readable.

There are some very powerful disassemblers out there these days – the most famous being IDA. The free version is a bit dated and limited, but it allowed me to quickly locate a few things.

An area of code that listens out for Q (quit) and V (version), along with limiting input characters from 0-9. Hex values in the normal ASCII range along with getch() calls are a giveaway.

Keyboard input
Another area of code appears to have two nested loops that go from 0-4. That would really strongly indicate that it is looping through the digits of the code.

Other areas of code add and subtract 0x30 from keyboard values – this is nearly always converting ASCII text numbers to integers (0x30 is 0, 0x31 is 1 etc. so 0x34 – 0x30 = 4)


A block of data, 256 items long from 0-9. Links in with the maximum value of the “version” above. Might just be an offset for indexing this data?

IDA’s real power is displaying the structure of the code – this can be a lot more telling than what the code does, especially for initial investigations.

Code structure
It’s still assembly language though, and I’m lazy…

Step 3: Decompile the program

Decompiling is converting machine code into a higher level language like C. It can’t recover things like variable names and data structures, but it does tend to give helpful results.

I used the free decompiler dcc to look at this program. I think because they are both quite old, and because dcc has signatures for the specific compiler used, it actually worked really well.

One procedure stood out – proc2, specifically this area of code:
DCC outputIt’s a bit meaningless at the moment, but it looks like it is two nested while loops, moving through some kind of data structure, summing the results and storing them. This is almost certainly the algorithm to generate the reset code.

Now, again, I could work through this all and find out what all the auto named variables are (i.e. change loc4 to “i” and loc5 to “ptrVector”. Or I could step through the code in a debugger and not have to bother…

Step 4: Run the code in a debugger

A debugger allows you to interrupt execution of code and step through the instructions being carried out. It’s generally of more use when you have the source code, but it is still a helpful tool. DOS-BOX can be run in debug mode and a text file generated containing the sequence of assembly instructions along with the current registers and what is being written and read from them. It’s heavy going, but combined with IDA and the output from DCC, it’s actually quite easy to work out what is going on!

Step 5: Write code to emulate the behaviour

Shortly after, I had an idea how the algorithm worked. Rather than work it through by hand, I knocked up a quick Python program to emulate the behaviour.The first cut didn’t quite work, but a few debug statements and a couple of tweaks later, and I was mirroring the operation of the original program.

Overall, it was only a few hours work, and I’m not really up on x86 at all.

I’m not releasing the algorithm or the software, as it could be perceived as a threat. In the next post, I am going to discuss some of my security concerns around the idea of an anti-code and this specific implementation.

Reverse engineering a wireless burglar alarm, part 8

Last time we worked out that we could drive the CC1150 daughterboard using the Seeeduino Mega Arduino board easily.

The Seeeduino Mega uses a ATmega2560 microcontroller, which has a number of features which will help us do this:

  • Hardware SPI interface – this makes high speed SPI connections almost effortless. No nasty bit-banging here.
  • Pin change interrupts – this means we can quickly and efficiently respond to the clock signal coming into the microcontroller. 6.25kHz isn’t so fast that it precludes the use of a polling technique, but polling is wasteful and limits what we can do with the rest of our time.
  • Lots of memory – we don’t need to care about creating wasteful buffers. The 8KB in the 2560 is far more flexible that 2KB in the 328.

Connecting the hardware is easy – we need to use PB0 (CS), PB1 (CLK), PB2 (MOSI) and PB3 (MISO). These map through to pins 53, 52, 51 and 50. We are going to use the adjacent PL0 port (pin 49) to clock out the data to GDO0. VCC is supplied from the 3.3V supply on the Seeeduino Mega (it is switchable from 3.3 to 5V operation – a valuable addition).

It was at this time I found out that if you connect the CC1150 incorrectly it gets very hot and starts letting out the magic smoke. It also still works after this..

Now, as for the software – I’m not really sure where to begin. It’s quite hard to explain bit-by-bit. I hope it is self-explanatory, to a large extent.

The code is available on github. There isn’t really much fancy going on here – I am using some CC11xx headers from previous projects to help identify the settings registers. All this aims to do is take the 48 bit (6 bytes) of data we have sniffed from a door contact, and replay this signal. It configures the CC1150 exactly as per the sniffed traffic, and then we use a pin change interrupt to clock the data out.

Oooo 2-FSK!

Oooo 2-FSK!

Once all of this is wired up, I can immediately see that my spectrum analyser is showing bursts of 2-FSK. That must mean things are approximately correct. I used to hate working with RF – I could never tell if it was the transmitter, receiver or just idiocy that was preventing things from working. A spectrum analyser gives you reassurance that you are doing at least something right.

So now I need to see if the bell box can learn the door contact from this signal. I connect up the bellbox, press and hold learn, and… nothing. I try the door contact, and it is learnt immediately. I must be doing something wrong.

(when working with alarm sirens, you need to be careful. If you leave the siren connected, they can be loud enough to actually damage your hearing. I never trust the soft setting to turn off the siren for testing, so always disconnect it physically. This then presents another danger – siren drivers can produce large voltages when the siren is open circuit – sometimes hundreds of volts, enough to give you a fair belt.)

The trusty Saleae Logic Analyser comes out again, and I connect it up to my cloned alarm sensor. It’s apparent that the signal isn’t quite right – a few extra bits here and there. A really quick and easy debug method is to use extra IO ports – PL1 and PL2 in this case – to  observe timing and code flow. Serial is just too slow for this kind of stuff – it might help me, but normally it slows down execution enough to cause problems. ATmega chips do have on-chip debug, but it is always more trouble than it is worth – these are very simple microcontrollers!

He's learning, we're learning.

He’s learning, we’re learning.

It turns out it is a simple bug – I’d used an incorrect comparison to signify the end of a packet. The code is fixed, and I try again. This time the panel learns the remote!

So where to from here? I need to start experimenting with the data in the packet, and seeing what the panel will and won’t accept. Next time.

Reverse engineering a wireless burglar alarm, part 7

So, where were we? We’ve worked out how several components of the alarm use the CC1150 to send out signals, and now I want to emulate the signal and then play with it.

By far the easiest way to do this is to use the same CC1150 chip as in the authentic transmitters. I can be absolutely sure the 2-FSK modulation with ~50kHz deviation will be reproduced exactly. There are many other RF transmitter chips out there that can do this, but we know that these work and we already have the correct settings.

So, the first challenge is – where do I get a board with a CC1150 on it? Well, we have plenty of alarm sensors with neat little CC1150 daughterboards on them!

Door contact RF board

Door contact RF board

I chose a door contact as my victim – they are the cheapest and simplest sensors.

The daughterboard has an 8-pin interface, with a 0.1″ pitch pin header connecting the two. I need to desolder the board to use it. Desoldering pin headers is always a pain – you can’t desolder all 8 pins at a time. My technique now is:

  1. Chop the pin header along it’s length, separating the two boards. I have a nice pair of snips for this, but a Dremel with cut-off wheel can do it if they don’t fit.
  2. Chop the plastic spacer away from the pins, leaving you with just half the pins stuck in the board.
  3. Desolder the pins one at a time.

The daughterboard

Now I have a small CC1150 board. I soldered a new pin header onto it, and a pin socket onto the rest of the door contact. This allows me to easily reconnect it to the door contact if I need to.

You’ll notice in the picture above that there is a small bridge wire. I desoldered and resoldered the CC1150 QLP package (just to see if I could) and burned a track in the process.


Testing testing 1-2

Now we need to drive this board using something. There are a plethora of options available to me:

  • An Arduino board
  • A mbed board
  • A Bus Pirate
  • A GoodFET
  • A CC1110 development board (this would skip using the CC1150 board, as it has a built-in transceiver)
  • One of the Ciseco CC11xx boards (again, this would skip the CC1150 board)
  • Any of the other development boards I have lying around…

I decided I would use an Arduino board – specifically the Seeeduino Mega.

Why the Arduino? In reality, I’ve stopped using the Arduino environment and now use AVRGCC, so this is really just a nice little AVR development platform.

It won out over the mbed as I have had SPI issues with the mbed before.

The Bus Pirate and GoodFET are very similar, but require a host PC. I would like this standalone.

It seemed silly to implement this on a CC1110 chip, and I have had problems in implementing proprietary encoding schemes on it before.

Why the Mega? I find myself often using the extra pins and RAM as compared to the ATmega328p used in the Uno

So, now I have a RF board and something to control it. Next, I need to connect the two and write some code!

Reverse engineering a wireless burglar alarm, part 6

Last time, we looked at the data the door contact was sending out over the airwaves. We arrived at a simple encoding system where 0 = 100 and 1 = 110. This might not be entirely correct, but we are finding 48 bit long packets containing the same data each time.

Now we need to try and work out what these bits represent.

As we worked out in part 4, all of the automated packet control in the CC1150 has been bypassed. This means that the microprocessor must deal with everything. What is everything? What does a normal packet look like in a simple RF system?

There can be a number of components sent. In approximate order, from start to finish:

  • Pre-amble – generally a series of 10101010. The purpose of this is to make the receiver aware that a transmission is happening, adjust the gain as required, and synchronise clocks. The average value of this signal is partway between 1 and 0, which will mean the gain is appropriately set to receive both 1s and 0s.
  • Sync-word – sometimes a sync word is sent immediately after the pre-amble. This is similar to an address or ID, but often it is system wide.
  • Packet length – if the packet length is variable, then the receiving system needs to know how long the packet should be. Even if packet length appears fixed, the protocol may accommodate for variable length packets.
  • ID – each device must be uniquely identifiable. We already know this is 20 bits long i.e. just over 1 million unique IDs. Sometimes other data is encoded in the ID, such as sensor type or manufacturer. This effectively reduces the keyspace, and makes guessing IDs easier.
  • Data – this is the data such as triggered/not-triggered, sensor type, battery state, tamper state and so on.
  • Checksum – often a checksum is calculated on the other other bits, so that the receiver can detect errors (and sometimes even correct them). On simple uni-directional RF systems used for safety that retransmit huge numbers of times, checksums become less useful.

Working out which piece of the data is which involves careful prodding of multiple components and observation of changes. Let’s get going!

It’s quite time consuming running a logic trace, exporting the data, running it through my parser, and interpreting the results. I should really learn to write analyser plugins for the Saleae Logic software, but my last attempt never quite worked right.


First thing I notice – there is no variation from packet to packet. There is no rolling code used here.

Door contact 1

The contact only transmits RF when the button is released (tamper) or the magnet removed (door open). There is therefore no indication to the rest of the system that a fault condition has been removed or a door closed. There appears to be no low-battery signal sent either.

These are the two transmissions

Notice only a couple of bits vary (shown in red)

Door contact 2

Again, only a couple of bits vary – the same bits as with door contact 1.

Let’s compare door contact 1 and 2:

The first 4 bits are the same between the two sensors. Is this the pre-amble and sync?

We then have a few bits that differ. This could be part of the ID.

The last bit also differs (green). Notice how 10 bits are high in the first packet, and only 9 in the second. This could mean that the last bit is a parity bit, a very simple checksum.


14 bits are high in the PIR packet, supporting the last bit being a parity bit.

Let’s compare this with the door contacts

It now looks like the pre-amble is shorter and the ID a bit more certain.


Let’s compare this to the other PIR

Wow – only a couple of bits differ. They are almost sequential!


This component is much more complex, so let’s just look at the simplest trigger – the tamper switch.

Notice how 6 bits are high, and the last bit is 0. It really does look like parity.

Let’s compare with the others:

Now we have a lot more information. The pre-amble has got shorter. Somewhere in the middle it looks like we have data indicating the sensor type.


A typical packet:

We have :

  • Packet start (not shown). The encoded bits 111000 are sent. This is on the short side for a pre-amble.
  • Yellow – this could be a continuation of the pre-amble, which would now be 111000100100 – still really quite short!
  • Red – 18 bits of ID. Not sure which end the other two bits are at. Or are they using the 2 bits of sensor type to make it up to 20 bits?
  • Blue – likely sensor type; 00 – PIR, 01 – door contact, 11 – panel.
  • Purple – status of various switches and alarms.
  • Green – a simple odd-parity checksum bit

I’ve now run out of components to test. We’ve gleaned a fair bit of information about the format of the packet, most importantly that no rolling code is in use.

Where next? I know the format of genuine packets, so I should be able to build a system to transmit these sniffed packets. We’ll test to see if the receiver can pick these packets up. Then we can start modifying the packets bit-by-bit to see how the receiver responds.

Reverse engineering a wireless burglar alarm, part 5

Last time we worked out that data was being transferred from the microprocessor to the CC1150 chip using synchronous serial mode at 6.5kBaud. This time, we are going to look at that data using the logic analyser and try to interpret it.

This is what our data looks like – channel 1 is data, channel 3 is a clock. The Saleae Logic software can help to interpret this as plain synchronous serial.


You might notice that the software doesn’t have an option for synchronous serial – only asynchronous. It does however have “Simple Parallel”. This takes a number of channels and a clock. Serial is just parallel with a single channel, so that is what we will  use.

Another plus of the Saleae software is that you can use multiple analysers against the same channels i.e. on our MISO/GDO1 line we can have an SPI and simple parallel decoder running.

Now we have successfully decoded a series of pulses, and the software has conveniently added little dots to show the data bits:


You might quickly notice that the pulses are either 1, 2 or 3 bits long. Never 4 or more. This is the case along the entire stream of data. 1 and 2 bit pulses are common, and 3 bit pulses are at the beginning, end and at regular intervals. We could guess that 1 bit = 0, 2 bits = 1, and 3 bits signifies the start and end of a packet? Seems sensible to me.

As an aside, it is fairly common for RF systems to encode data so that there are frequent transitions between 1 and 0. This is for a few reasons:

  • To try and keep the average value of the signal midway between 1 and 0, allowing automatic gain controls and DC filters to work correctly.
  • To make sure any clock recovery or synchronisation has frequent edges to work on.
  • Representing 1 bit of data as multiple bits can provide redundancy and error checking.

Manchester encoding is very common, but there are a lot of proprietary and ad-hoc schemes in use. I have seen infrared protocols used on AM OOK systems before now – they kind of meet the requirements and allow the use of existing chips and code.

The entire data burst lasts about 1.20s. At a data rate of 6.5kBaud, that is 7.8k symbols. I don’t fancy manually transcribing or interpreting that much data. There is a high probability that the data repeats to a certain extent, but it can be hard to see the boundaries on a logic trace. Automating this process makes more sense.

Looking back at the data, and my initial observations, I can see:

  • 111 is always followed by 000, and I can see this happens relatively infrequently but regularly
  • 1 is always followed by 00
  • 11 is always followed by 0

So, let’s make some arbitrary choices:

  • 111000 is the start/end of a packet
  • 100 is a 0
  • 110 is a 1

Where to from here then? Python! Let’s read the data in, and try to process it using these simple rules.

Why am I using Python? In recent years I have found it is by far the quickest way to get simple data processing tasks like this done. It seems naturally suited to arrays of data and is tolerant of idiocy. I use Pycharm as a cross-platform IDE – it’s really useful.

I’ve knocked up a very simple program to read in a Saleae logic file, and then interpret the data as above.


It’s getting like a Daily Mail article on hacking in here.

With the input from my trace, I get the following repeating pattern:

No change at all from packet to packet, 48 bits long. This encodes to 150 bits long, and re-transmits about 50 times in the 1.2s burst (retransmission is a common technique to ensure the receiver hears the transmission, even in noisy environments).

We know from the documentation the system uses 20 bit IDs. Just a gut feeling, but I would expect the address to have a lot of entropy, so might assume that the first 20 bits are the address – 001100011101111101 – and the rest is data. There may be a pre-amble and sync word before the ID, meaning it might be shifted a bit further on. To confirm this, I will need to put the sensor into different states (triggered/not triggered,tamper/safe,battery low etc.) and compare to other sensors.

Is the system rolling code? We can’t really say at this stage – we need to grab more traffic. I’d expect the code to roll in 50 retransmits though. It’s not looking good.

What is interesting is that to see this data, I didn’t actually need to look at any RF at all. In fact, I don’t even need the comparatively expensive Saleae Logic – a Bus Pirate acting as a logic analyser would be able to do this.

Where to from here? Let’s start sniffing data from the other door contact and start changing their states. For next time 🙂

Reverse engineering a wireless burglar alarm, part 4

In the last part, we looked at sniffing the SPI traffic between the Elan microprocessor and CC1150 RF chip in a door contact from a Friedland Response alarm system. We worked out which pin was which, and interpreted some of the SPI traffic to check our assumptions were correct.

Part of my general philosophy when reverse engineering products is to just get a good idea of what is going on, rather than rigorously explore each and every avenue. Generally this will give you an overall picture of the system far quicker, and allow you to identify weak points to look at in more detail. So, on the whole, I never have a 100% confidence level in my assumptions.

Let’s look at some other aspects of the sniffed traffic.

First, let’s look right at the beginning of the signals immediately after the sensor is triggered.


We have worked out what channel 0, 3, 4 and 5 do. So what is going on with channel 1? It has an approximate 135kHz clock signal for a short period. Why is this?

Let’s go and look at the datasheet once again. Page 37 – we can see that general purpose I/O pin GDO0 defaults to output a 125-146kHz clock output – the main oscillator frequency over 192. We have a 26MHz crystal, giving us 135.4kHz – exactly as observed.

Why then does it stop suddenly. Maybe one of the registers we set just prior to it turning off – 0x01 or 0x02 – control this pin?

IOCFG0, 0x02, controls GDO0. The lowest 6 bits select what GDO0 should output. What does 0x0C correspond to? Page 38, table 24, shows 0x0C as “Reserved – defined on the transceiver version” – what’s going on here then? Is it a mistake? We’ll come back to this later.

Again, this is something that is really key in reverse engineering – read the datasheets, and read the manuals. They are full of useful information.

Moving further through the SPI packets sent, we get to the end:

Time [s] Packet ID MOSI MISO
0.0041055 28 0x2D 0x0F
0.00417875 28 0x35 0x0F
0.00424825 29 0x2E 0x0F
0.0043215 29 0x09 0x0F
0.00438775 30 0x7E 0x0F
0.004463 30 0x00 0x0F
0.00451425 30 0x0B 0x0F
0.00456675 30 0x25 0x0F
0.0046195 30 0x68 0x0F
0.00467225 30 0x60 0x0F
0.004725 30 0x86 0x0F
0.0047775 30 0xCC 0x0F
0.00483075 30 0xC3 0x0F
0.0049125 31 0x35 0x0F
0.01802275 32 0x36 0x2F

Why is packet 30 so long? 9 bytes instead of 1 or 2 as per the previous strobe commands and register writes.

Address 0x7E doesn’t seem to come up directly in the datasheet. If we refer to Table 28 though, we see that if we want to write to 0x3E in burst mode, we need to add 0x40 onto the address. Burst mode allows us to write multiple values in one go without incrementing the address. 0x3E is the PATABLE or power table, allowing the power of the radio signal to be modulated.


Let’s zoom right back out on the logic trace:


The small white blocks are bursts of SPI traffic as we have looked at already. What about the huge white blocks – let’s zoom in to take a look at them.


That looks like synchronous serial data to me with a clock on channel 3 and data on channel 1. Let’s scroll backwards to examine the SPI transfer immediately prior to this to see how the CC1150 is being configured.

We’ll start from the beginning and manually interpret the commands (I might skip or gloss a few of the really unimportant ones)

  • Strobe command SRES – reset the chip
  • IOCFG1 – GDO1 output pin configuration – set to 0x0B – Set GDO1 to “Serial Clock. Synchronous to the data in synchronous serial mode.
  • IOCFG0 – GDO0 output pin configuration – set to 0x0C – Reserved in the datasheet as it is for the transceiver, CC1101, only. If we look there it means “Serial Synchronous Data Output. Used for synchronous serial mode” So far it looks like these are being setup for some kind of synchronous serial data transfer.
  • SYNC1, SYNC0 – sync word – set to 0xD391. The sync word is a bit pattern that receivers can look out for, in a way people listen out for their name. Oddly, 0xD391 is the default used in the TI software (SmartRF Studio) and code examples. Keeping it as default would likely cause problems in a busy environment (these chips are becoming very common), so this might indicate they are not using the packet control built into the chip.
  • PKTLEN – packet length – set to 0xFF. This is the maximum – 255 packets. It can mean one of three things – fixed packets 255 long, variable length packets up to 255 long, or don’t use the packet control at all. Another sign they might not be using packet control!
  • PKTCTRL0 – Packet automation control – set to 0x12. this is made up of a number of smaller settings:
    • WHITE_DATA – data whitening – set to off.
    • PKT_FORMAT – set to 0b01 –   “Serial Synchronous mode, data in on GDO0“.
    • CRC_EN – enable CRC – set to off.
    • LENGTH_CONFIG – set to 0b10 –  “Infinite packet length packets” – again, bypassing the built in packet control
  • ADDR – address – set to 0x00. This can be used to filter packets down.
  • CHANNR – channel number – set to 0x00. A neat feature of the CC11xx chips is being able to use a number of channels – offsets from the frequency. This can be used for frequency hopping spread spectrum. Not used here though.
  • FREQ2, FREQ1, FREQ0 – frequency control – set to 0x21656a. This equates to 868,299,865kHz. As the channel is set to 0x00, this will be the frequency used.
  • MDMCFG4, MDMCFG3 – modulator configuration, data rate – set to 0x87, 0xF8. This is calculated in a similar way to the frequency using an exponent and mantissa, and gives us 6.24847kBaud. If we look up to MISO/GDO1 in the screenshot above – the clock is showing as 6.25kHz. It’s all starting to come together.
  • MDMCFG2, MDMCFG1, MDMCFG0 – modulator configuration, various. We can see:
    • MOD_FORMAT – modulation – set to 0b000 – 2-FSK.
    • SYNC_MODE – synchronisation mode – set to 0b000 –  “No preamble/sync word”. This means that the system is not using any of the inbuilt packet control. The microprocessor must deal with pre-amble, sync word and so on.
    • NUM_PREAMBLE – number of preamble bytes sent. This is set to 4, but because the SYNC_MODE is set to use no preamble, it is ignored.
    • CHANSPC – channel spacing. If the system was using multiple channels, this decides how far apart they are. This system isn’t so we’ll ignore this.
  • MCSM1, MCSM0 – main radio control state machine config. I’m not going to look in here as it’s unlikely to change how the data is handled.
  • FREND0 – front end TX config. Going to skip this for now.
  • FSCAL4, FSCAL3, FSCAL2, FSCAL1, FSCAL0 – again, RF related and we can skip.
  • FSTEST, PTEST, TEST2, TEST1, TEST0 – various test settings. These are usually defaulted, not explained or shouldn’t be set. Let’s keep it that way.

Immediately after this, the STX strobe command is issued, along with setting IOCFG1 and IOCFG0 again. The SPI stops, GDO0 becomes a clock at 6.5kBaud, and MOSI starts showing data. I don’t know which direction this data and clock are going in, but we can have a good guess.

It looks like the CC1150 is in a mode where it will accept synchronous serial data, generating a clock for the microprocessor to use. Pre-amble, sync, packet length and other features are implemented in the microprocessor. This is an interesting revelation – a huge advantage of the CC11xx chips is the automated packet control, and it has been thrown aside.

Why would this be done? We can only speculate. Asynchronous serial mode is often used by devices when the CC1150 is interfaced to legacy microprocessors – you can just send it raw data and it only does the RF side of things. Ancient 434MHz systems using AM OOK modulation can be changed into narrowband 868MHz products using 2-FSK easily.

Synchronous serial is a bit different – the microprocessor needs to listen to a clock and clock data out. Whilst this is trivial to implement afresh, it doesn’t really fit in with the idea of supporting a legacy application. Maybe Friedland used to use another module that used a similar data exchange.

Decoding an RF packet in a microprocessor isn’t hugely challenging (VirtualWire on the Arduino is a nice example, well implemented), but I have seen numerous systems with quirks and bugs in the packet handling. It’s sometimes even possible to crash receivers using malformed packets.

Now we know how the CC1150 is accepting data – so now we need to try and interpret that data. Next time!

Reverse engineering a wireless burglar alarm, part 3

In part 1, we used some simple tools available to us to look at the modulation and frequency the Response alarms use (868MHz, 2-FSK)

In part 2, we opened the alarm up and looked at the main components in the system (TI CC11xx RF chips with various microprocessors).

Now we are going to try and look at the communication between the microprocessors and the TI CC1150 chips. They have an SPI serial interface, allowing the user to set various registers to control the rich and complex feature set.

I have a number of tools available to me that can sniff SPI. The cheapest is the Bus Pirate. This has an SPI sniffer interface along with a basic logic analyzer. It works well, 90% of the time. If timings are curious or the protocol deviates slightly from normal, it tends to fall over.

I also have an oscilloscope. It is hard work decoding serial though – it doesn’t have any built in protocol analysers.

So I go to my good old friend, the Saleae Logic 8-channel logic analyser. I have had this a number of years and it has rarely let me down. It’s simply brilliant for dealing with embedded systems, and the software is very easy to use and cross-platform.

I’m going to work with the door sensor initially – they are the cheapest and simplest sensors. I was planning on selling unopened sensors, and the PIRs fetch decent prices on ebay (as an aside, the provenance of alarm detectors on eBay is questionable – could this be an attack vector?).

The RF daughterboard has a 8 pin header. We need to work out which is ground, and then we can safely connect the Saleae Logic. We do this using a continuity tester between the battery connector and pins. It looks like pin 1 is ground.


Another pin will be the supply, but at this stage, it doesn’t really matter which. I just need to confirm it is <5V to avoid damaging the logic analyser. I connect the board to the power supply and confirm that no voltages are higher than 3V, regulated down from the two CR2032 batteries.

Small wires are soldered to the pin header to make connecting the logic analyser easier.


We then start the logic analyser with no triggering and press the test button on the contact.


Excellent – this looks very much like we have data!

I could at this stage buzz out the pins to the CC1150 chip to work out what the pins do, but the daughterboard is soldered down so the chip is hard to access. I’d rather just have a guess at the pins – we know it is SPI after all.

SPI requires the following pins:

  • CS – chip select, generally active low.
  • SCLK – the clock for the data.
  • MOSI – master out, slave in data.
  • MISO – master in, slave out data.

CS is nearly always going to be the slowest changing pin – let’s zoom in to the beginning of the trace and see what we have:

Chip Select

From this, I’d wager that CS is channel 0.

Now, to look at this trace, it would be easy to assume that channel 3 is a clock – look how regular it is! However, in SPI, because we are transferring at least 8-bits of data, we need at least 8 transitions each time CS is asserted. Channel 3 isn’t nearly fast enough.

What about channel 4 being the clock? The pulses look to short (and on some traces, they are very irregular as well!):


But if we look a little closer, we can clearly see 8 pulses per CS. There is no requirement for the clock pulses to be regular and they also don’t need to have a 50% duty cycle.

The two other channels with changes are 3 and 5. One will be MOSI, the other MISO – we aren’t really bothered which right now as it won’t have any impact on decoding the data.

I’ll now set up the Saleae Logic’s protocol decoder for SPI on these channels. There are a number of configuration settings, let’s leave them on the defaults and see what happens – it will throw errors or produce obviously squiffy data if incorrect.


The little orange and green boxes represent decoded data – we just need to zoom in to see what that data is now. I can then export this as CSV to examine in an external program. We have no errors, so we can check if the decoding has worked by examining the packets against the datasheet.

Time [s] Packet ID MOSI MISO
0 0 0x30 0xFF
0.0001085 1 0x01 0xFF
0.00018025 1 0x0B 0xFF
0.00024925 2 0x02 0x0F
0.00032075 2 0x0C 0x0F
0.00038925 3 0x03 0x0F
0.0004615 3 0x07 0x0F
0.0005305 4 0x04 0x0F
0.0006025 4 0xD3 0x0F
0.00067225 5 0x05 0x0F
0.00074475 5 0x91 0x0F
0.00081325 6 0x06 0x0F
0.000886 6 0xFF 0x0F
0.00096775 7 0x08 0x0F
0.00103925 7 0x12 0x0F
0.00110775 8 0x09 0x0F
0.00118 8 0x00 0x0F
0.00124725 9 0x0A 0x0F
0.0013195 9 0x00 0x0F
0.001408 10 0x0D 0x0F
0.00148075 10 0x21 0x0F
0.00154925 11 0x0E 0x0F
0.001622 11 0x65 0x0F
0.0016915 12 0x0F 0x0F
0.00176475 12 0x6A 0x0F
0.00183425 13 0x10 0x0F
0.00190625 13 0x87 0x0F
0.00197525 14 0x11 0x0F
0.002048 14 0xF8 0x0F
0.0021175 15 0x12 0x0F
0.00218975 15 0x00 0x0F
0.00225725 16 0x13 0x0F
0.00232975 16 0x22 0x0F
0.00239825 17 0x14 0x0F
0.002471 17 0xF8 0x0F
0.0025405 18 0x15 0x0F
0.00261325 18 0x50 0x0F
0.00269225 19 0x17 0x0F
0.0027655 19 0x30 0x0F
0.002834 20 0x18 0x0F
0.002906 20 0x18 0x0F
0.00306925 21 0x22 0x0F
0.0031415 21 0x15 0x0F
0.0032105 22 0x23 0x0F
0.00328375 22 0xE9 0x0F
0.00335325 23 0x24 0x0F
0.0034255 23 0x2A 0x0F
0.0034945 24 0x25 0x0F
0.00356725 24 0x00 0x0F
0.0036345 25 0x26 0x0F
0.00370725 25 0x1F 0x0F
0.00379825 26 0x29 0x0F
0.003871 26 0x59 0x0F
0.0039405 27 0x2A 0x0F
0.00401325 27 0x7F 0x0F
0.0041055 28 0x2D 0x0F
0.00417875 28 0x35 0x0F
0.00424825 29 0x2E 0x0F
0.0043215 29 0x09 0x0F
0.00438775 30 0x7E 0x0F
0.004463 30 0x00 0x0F
0.00451425 30 0x0B 0x0F
0.00456675 30 0x25 0x0F
0.0046195 30 0x68 0x0F
0.00467225 30 0x60 0x0F
0.004725 30 0x86 0x0F
0.0047775 30 0xCC 0x0F
0.00483075 30 0xC3 0x0F
0.0049125 31 0x35 0x0F
0.01802275 32 0x36 0x2F

This is exactly what I would expect to see when a micro is controlling a CC1150. Let’s work out what is going on.

We are just going to lock at MOSI – the packets the microcontroller is sending to the CC1150.

The first packet is an 1 byte packet, where 0x30 is sent. If we search for 0x30 in the datasheet, we find out that there are a series of 1 byte packets called “strobe commands” on page 42, table 25 . They all trigger changes in the internal state machine. 0x30, SRES, reset – fairly obvious meaning!


Next we have a series of 2 byte packets. You can see the first byte of each packet is generally just incrementing one by one. Again, we look through the datasheet and on page 43, table 26, we have a list of the configuration registers. Notice how we skip some numbers – 0x0C, 0x0D – this is because they are only used for reception, not transmission, and the CC1150 is a transmitter only.

I could go through these one by one, explaining how they are configuring the radio. But let’s just look at the most important ones for now, referring to the datasheet.

FREQ2, FREQ1, FREQ0 are stored in 0x0D, 0x0E, 0x0F and are set to 0x21, 0x65, 0x6A respectively. From page 47 of the data sheet, we need to concatenate these (0x21656a) and then use a simple formula to work out the frequency. This gives a result of 868,299,865Hz – almost exactly as we measured.


MDMCFG2, 0x12, contains modulator settings. Bits 6-4 are the modulation format – so let’s look at them. The value written to 0x12 is 0x00,  so bits 6-4 are 0b000. This corresponds to 2-FSK, as we observed before.

DEVIATN, 0x15, tells us the deviation settings (i.e. how far apart the two frequencies will be in 2-FSK). A simple formula is used. Bits 6-4 give us the exponent, and 2-0 give us the mantissa. 0x15 is set to 0x50, so the exponent is 0b101 and the mantissa 0b000. From this we get 50781.25Hz – again, this corresponds to our ~100kHz gap between the two peaks from part 1.

We are now confident that we the SPI traffic we are looking at is correct. Now we need to work out how data is being sent between the two. There are so many different ways this can be done using the CC1150, so we will need to drill into the settings and sniffed traffic in more detail – which we will do in the next part.