Skip navigation
← Back to Index

TEMPEST@Home - Finding Radio Frequency Side Channels

by Mikhail Davidov and Baron Oldenburg

01. Introduction

Have you ever listened to a photocopier or a car engine to infer what it’s doing? If so, you already have all the fundamentals you need to study emission security. Be it the audible click of a relay, a whine of a capacitor, or the flickering of the lights when the heat comes on, these behaviors all have one thing in common: they leak information about some internal state and reveal what is happening inside to an outside observer. When viewed through the lens of information security, these types of electrical and mechanical side-effects form the field of emission security. The field was formalized around the end of the Second World War when, after being told to put up or shut up, Bell Labs technicians scared the living daylights out of the United States Signal Corps. Over the years, defensive requirements and certifications have been codified under the standards titled, “Telecommunications Electronics Materials Protected from Emanating Spurious Transmissions,” or more simply put, TEMPEST.

As the test procedures in the TEMPEST standards are rudely made unavailable to us as they are considered “classified” we have to do the next best thing and make up our own. This article aims to make barely acceptable analogies about how radios work and show that you really don’t need that much in terms of know-how and equipment to find and take advantage of leaky radio signals. Towards the end, we will apply what we have learned to find a signal that can exfiltrate data out of a radio-less and air-gapped desktop workstation through a wall and 50ft away.

02. Electromagnetic Radiation

The easiest way to think about electromagnetics is to just not. Instead, let's consider sound: a quiet sound is only audible from nearby and doesn’t take much energy to produce, while a loud sound can be heard from a greater distance and requires more energy to produce. The pitch of a sound can also help determine how far away or through what materials you can hear that sound. For instance, consider the club hits your upstairs neighbor loves to play at 3 in the morning. All you can make out are the low pitch wobbles of an absolutely filthy bass line because the higher pitched melodic components end up reflecting off of their floor.

The exact same principles apply to radio-frequency (RF) radiation traveling through an imaginary electromagnetic aether instead of vibrating air. Lower frequency signals have better penetration vs higher frequencies and louder signals are easier to pick up.

To listen to this radiation, we can use a receiving antenna. Antennas can be thought of as devices that amplify signals and impart directionality to the receiver based on the signal’s frequency. Back before radar existed this was how incoming planes were acoustically detected.

03. The Frequency Domain

Sounds (and radio waves) are rarely made up of a single uniform tone but instead are a mix of frequencies that change over time. If we were to take a snapshot of a complex signal and break it apart into all the frequencies that combine to create it and plot them based on their relative intensity, we’ve done something really interesting. We’ve left the time domain and entered the frequency domain through what’s called a Fourier transform:

This frequency domain snapshot allows us to see what frequencies make up a given signal and see how much of a given frequency goes in to making the signal per unit time division. Now, if we get really crazy and plot how these frequency snapshots change over time, we end up with a 3D plot:

But these 3D plots can be difficult to clearly interpret. If instead of using height to denote intensity, we used a color gradient going from blue to yellow to red, we can encode all the data in a scrolling 2D image called a waterfall.

These waterfall plots are a fantastic way to look at the RF spectrum and we’ll be using them a lot. In the above image we can see six distinct FM radio stations which use frequency modulation to encode an audio signal. Two of the stations (100.3Mhz and 99.5Mhz) also have side-bands on either side of the analog signal that encode the digital data more commonly known as HD Radio. The range of frequencies that a given station occupies in the RF spectrum is known as the signal’s bandwidth and is correlated to the amount of raw information that a given signal can encode per unit of time.

04. The Federal Communications Commission

Just as it's very hard to find a perfectly quiet place, there is a near constant cacophony of radio signals surrounding us. The organization responsible for making sure devices play nice and don’t shout over each other and is the Federal Communications Commision. The FCC allocates frequency ranges dictating what can be used for what applications. Additionally, they define transmission power limits to ensure that your microwave doesn’t knock out cell service for the entire block.

05. Electromagnetic Interference & Mitigations

Be it an inductive load like a power transformer, a long wire or trace, or even just a fast oscillating clock, spurious accidental transmitters are everywhere. There are many sources of electromagnetic interference (EMI) out there, but the type we really care about radiate energy on the radio spectrum. EMI can wreak havoc on electronics as it can make signals appear on neighbouring signal lines. It is very hard to design something that does not leak EMI. Usually, the mitigating approach designers end up taking is to “decouple” components from each other and encase a device in a metal box so that the EMI that is produced is attenuated enough not to exceed federal limits, but those tests are conducted under normal operating conditions.

Digital electronics typically have a component called an oscillator that provides synchronization of various internal operations. For instance, when you buy a CPU it will have a given clock rate associated with it, such as 2.3Ghz. This means that internally, it is performing a basic action every 500 picoseconds. Those actions consume power and have a chance to leak some EMI in the process at that very frequency which can lead to exceeding the FCC EMI limits.

To limit this type of EMI a technique known as spread-spectrum clocking can be employed. Instead of having a large peak at the oscillator's center frequency, the oscillator's output frequency is wiggled a little bit to spread out the energy across a wider range of the spectrum leading to a measured reduction in EMI emission ostensibly making shielding easier.

06. Software Defined Radios

Now that we have a better understanding of electromagnetism, the frequency domain, and EMI, we can finally talk about the tools and methodology. The very first thing we need is a way to actually pick up radio signals. Software Defined Radios (SDRs) are peripheral devices that can tune to specific frequency ranges. There are many different types of SDRs these days that vary in functionality, the most basic of which is the RTL2832U based USB dongle. These have an interesting history as they were initially hacked up USB Digital TV tuners. They are receive-only and can tune to frequencies between 0.5 to 1700Mhz but only have around 2.5 Mhz of bandwidth which means you can only look at 2.5 Mhz worth of spectrum at a time. The main benefit of these is the price point. You can get one of these for less than $100. Just about every piece of radio software supports these nowadays.

The preferred class of SDR for doing this work typically costs around $300 to $600. The jump in cost buys you not only transmit capability but also a much wider range of frequencies that can be tuned to and a much larger bandwidth meaning you can look at a much wider (10-60Mhz) slice of spectrum. Recommended SDRs in this category are the LimeSDR, bladeRF 2.0, and the now slightly dated but very capable HackRF One.

Beyond this class you can start adding zeros to the price tag for diminishing functional returns.

07. Antennas

We’ll need to pair our SDRs with an antenna, but the “best” antenna depends on the frequency and distance of a signal. As our first goal is going to be to identify the presence of a side channel and not maximize range, we don’t have to worry much about very sensitive purpose-built antennas and can focus on doing our testing up close and personal. I recommend a couple standard antennas to get a good range of frequency coverage.

A standard omnidirectional “Ultra High Frequency” (UHF) whip or rubber ducky antenna. UHF is somewhat a misnomer as it covers frequencies from 300Mhz - 1000Mhz. Since we’ll be doing signal identification at a distance measured in inches at first, you could get away with even using a paperclip to do this as long as you never transmit. To complement the UHF antenna, I like to use a directional ultra-wideband antenna which supports frequencies from 800Mhz to 12Ghz range.

Once a target frequency is identified and you want to maximize your recieve range you’ll rarely find the perfect match online and will likely want to make your own antenna.

08. Software

Once we have an SDR and antenna set up, we’ll want some software to actually drive it. On macOS and Linux setup is fairly straightforward. My graphical tuner of choice is GQRX and packages are readily available for it that will bring in the needed dependencies. If you are on Windows, options are available such as SDR# and HDSDR but if you really want to dive deeper, things start to get finicky. I highly recommend setting up a Linux virtual machine and passing the SDR’s USB interface to the VM instead.

The first thing you see when you start GQRX is the device configuration window. Here you can select which SDR to use as well as configure the input rate. Setting this rate will define how much of the spectrum you can see at a time. This is where different SDRs will have different defined limits. For instance, an RTL2832U-based SDR will only be able to capture around 3 mega samples per second (denoted as 3200000) giving you a view of 3.2 Mhz of spectrum while a bladeRF device can effectively support up to up to 28 Mhz of instantaneous bandwidth.

For doing side-channel analysis I prefer to have a 15Mhz (input rate of 15000000) view of the spectrum. If your SDR does not support a sample rate high enough to support that you won’t be prevented from doing this type of analysis but it will just take longer to find the signal as you’ll be looking at a narrower range of frequencies at a time. Once you hit OK, you will be in the main GQRX interface. Press the play button in the upper left to begin sampling and scrolling a waterfall graph.

Under the Input Controls tab, you will see a checkbox called “Hardware AGC” and a bunch of sliders. AGC in this context stands for Automatic Gain Control which will automatically adjust internal amplifiers based upon what is being received to help normalize received signals. If you can, check this box; if your particular SDR does not support this, then just leave the sliders be for now. Optionally, you can head over to the Receiver Options tab and set the Mode to “Off” unless you want to hear a lot of static.

The value displayed above the waterfall graph (310Mhz in this case) is referred to as the center frequency. As we set up our SDR to have a bandwidth of 15Mhz via the sample rate, we should see all the signals from around 302.5Mhz on the left to 317.5Mhz on the right with some signal attenuation toward the limits.

To tune the SDR you roll your mouse over the frequency display and either manually type in a frequency or pick a digit and use the mouse wheel to scroll it to change the tuning. You will likely notice that no matter where you tune the SDR to, at the center frequency there will always be a strong spike. This is called a DC spike and it is caused by the way signals are captured by the SDR itself and is present in all SDRs. When trying to tune to a specific frequency its best to not directly tune the SDR to it but instead near it as otherwise it may be masked by the DC spike. GQRX makes this offsetting process very simple. You can click and drag the red tuning needle off of the center frequency and the display as well as subsequent tuning will take that offset into account.

09. Identifying Simple Side Channels

Now that we know how to use an SDR and how to browse the radio spectrum with GQRX, we can get to identifying some side-channels. Ultimately, we are looking for spurious transmissions or changes in an existing signal that are a result of some action we do. We need some way to differentiate that signal from the existing radio traffic that surrounds us. For side-channels which give us the ability to encode data based on the presence or absence of a signal (known as on-off keying) my preferred methodology for identifying them revolves around performing some action at a consistent rate.

This way, as I’m browsing the spectrum, if I see a pattern that changes with the same rate, I can take note of it. Viable candidates really do jump out at you.

To actually locate the signals on the spectrum I tune my SDR as low as it can go and start stepping in 10Mhz increments until I reach some upper frequency. On each step I’d stop, wait a couple of seconds, and see if I spot a repeating pattern that matches my driving rate. This is something that could be automated but I prefer to manually scrub through as there might be something unexpected that catches the eye.

010. Triggers to Explore

Now that we know how to spot them, how do we go about actually triggering them? Almost any action could potentially leak EMI but some actions are more likely than others. Typically actions that draw or shift around large amounts of power tend to be the most detectable when oscillated. Any actions that can shift a sub-components’ clock speed or suddenly have to drive a lot of data around can also be fruitful.

Let's look at a real target and see what we can find in terms of side channels that are useful for exfiltrating data. For the target, we ordered a run of the mill Dell Precision 3430 workstation without a wireless chipset, 8Gb of 2666MHz DDR4 and a Radeon Pro WX 3100 running Ubuntu 18.04 LTS with a 5.1.11 kernel.

Let’s try a simple test, writing lots of things to memory:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>

inline void clflush(void* ptr) {
    asm volatile ("clflush (%0)" :: "r"(ptr));

inline uint64_t qpc() {
    unsigned long a, d;
    asm volatile ("rdtsc" : "=a" (a), "=d" (d));
    return a | ((uint64_t)d << 32);

uint64_t approx_100ms() {

    uint64_t sum = 0;
    for(uint64_t i = 0; i < 10; i++) {
        uint64_t start = qpc();
        usleep(100 * 1000); // microseconds
        sum += qpc() - start;
    return sum/10;

int main(void) {

    uint64_t ticks_per_100ms = approx_100ms();
    volatile uint64_t *addr = (volatile uint64_t*)malloc(sizeof(uint64_t));

    while(1) {

        //on time
        uint64_t target_end_time = qpc() + (ticks_per_100ms * 5); // end 500ms from now
        while(qpc() < target_end_time) {
            *addr += 1 ^ *addr; // write to memory
            clflush((void*)addr); // flush cache lines

        // off time
        target_end_time = qpc() + (ticks_per_100ms * 3); // end 300ms from now
        while(qpc() < target_end_time);

This program will constantly write to the same memory address and flush the cache lines at a fixed rate. If we look through the spectrum with our antenna we’ll see various areas where we can see signs of that pattern, especially around the cellular ranges.

This signal itself is very quiet and at a frequency of over 1000 Mhz so it has limited penetration potential. You won’t be exfiltrating data more than a few feet with this one. However, it has been demonstrated that when combined with wide vector instructions it can be turned into a useful tool.

Let’s take a look at a more interesting example. Graphics cards these days can suck up a lot of power but they aim to be efficient in doing so by scaling power draw with performance requirements. This behavior is typically completely invisible to the end user outside of maybe the sound of a fan spinning up. Facilities vary from vendor to vendor as to how to adjust relevant power mode thresholds and as the machine we ordered has an ATI based GPU, we will be focusing on that.

The amdgpu driver exposes its power management interfaces conveniently through sysfs. For me as there is also an embedded Intel GPU, the card comes in as /sys/class/drm/card1 and the device subfolder contains all the power management related control files.

Precision-Tower-3430:/sys/class/drm/card1$ ls device/pp* -l
-r--r--r-- 1 root root 4096 Feb 24 09:17 device/pp_cur_state
-rw-r--r-- 1 root root 4096 Jan 29 13:08 device/pp_dpm_mclk
-rw-r--r-- 1 root root 4096 Feb 24 09:17 device/pp_dpm_pcie
-rw-r--r-- 1 root root 4096 Feb 24 09:52 device/pp_dpm_sclk
-rw-r--r-- 1 root root 4096 Feb 24 09:17 device/pp_force_state
-rw-r--r-- 1 root root 4096 Feb 24 09:17 device/pp_mclk_od
-r--r--r-- 1 root root 4096 Feb 24 09:17 device/pp_num_states
-rw-r--r-- 1 root root 4096 Feb 24 09:17 device/pp_od_clk_voltage
-rw-r--r-- 1 root root 4096 Jan 29 12:30 device/pp_power_profile_mode
-rw-r--r-- 1 root root 4096 Feb 24 09:17 device/pp_sclk_od
-rw-r--r-- 1 root root 4096 Feb 24 09:17 device/pp_table

The pp_dpm_sclk file defines what the currently available shader clocks are available to the power management system and which are currently enabled.

$ cat device/pp_dpm_sclk
0: 214Mhz
1: 734Mhz
2: 921Mhz
3: 1018Mhz
4: 1098Mhz
5: 1147Mhz
6: 1183Mhz
7: 1219Mhz

Lets try a test where we put the GPU under some load with glmark2 and shift between the lowest two power states which would correspond to the 734Mhz and 214Mhz clocks.


glmark2 -b :duration=10000& # Create some GPU load.

# set performance control to manual.

echo "manual" > $DEVICE/power_dpm_force_performance_level
while true
    echo "HIGH"
    echo "1" > $DEVICE/pp_dpm_sclk
    sleep 0.5

    echo "LOW"
    echo "0" > $DEVICE/pp_dpm_sclk
    sleep 0.5

When the 214Mhz clock is enabled, we can absolutely pick it up at multiples of 214Mhz with 428Mhz being the loudest for our configuration.

This is a great carrier! It is very loud over background and is a nice low frequency of 428Mhz which allows for great signal penetration. In my tests I was able to pick this particular signal up from over 50ft away through a wall. This gives us the ability to on-off key messages one bit at a time, but that is quite slow and we can do much better. The amdgpu driver also lets you configure the actual clock values themselves in 1Mhz increments. So let’s write a script to do just that and step through the lowest 5 frequencies dwelling on each one for half a second:


glmark2 -b :duration=10000& # Create some GPU load.

echo "manual" > $DEVICE/power_dpm_force_performance_level
while true
        echo "s 0 214 700" > $DEVICE/pp_od_clk_voltage
        echo c > $DEVICE/pp_od_clk_voltage
        echo 0 > $DEVICE/pp_dpm_sclk
        sleep 0.5

        echo "s 0 215 700" > $DEVICE/pp_od_clk_voltage
        echo c > $DEVICE/pp_od_clk_voltage
        echo 0 > $DEVICE/pp_dpm_sclk
        sleep 0.5

        echo "s 0 216 700" > $DEVICE/pp_od_clk_voltage
        echo c > $DEVICE/pp_od_clk_voltage
        echo 0 > $DEVICE/pp_dpm_sclk
        sleep 0.5

        echo "s 0 217 700" > $DEVICE/pp_od_clk_voltage
        echo c >  $DEVICE/pp_od_clk_voltage
        echo 0 > $DEVICE/pp_dpm_sclk
        sleep 0.5

        echo "s 0 218 700" > $DEVICE/pp_od_clk_voltage
        echo c > $DEVICE/pp_od_clk_voltage
        echo 0 > $DEVICE/pp_dpm_sclk
        sleep 0.5

By shifting this shader clock 1Mhz at a time at the lower limit we can see the side channel also start to jump around in the frequency domain:

Not only can we control the duration of a transmission to encode data but now we can start to form an alphabet using a technique called sequential multiple frequency shift keying to encode a lot more data per transmission! We can even vary the rate at which we shift from frequency to frequency to further pack additional data.

What we’ve covered so far is only looking at the presence of a signal at a given frequency and not necessarily at what data if any is encoded in the narrow signal. If we further demodulate this signal using the familiar amplitude modulation (AM) and frequency modulation (FM) techniques we can gain even more information.

For instance, let’s pin the transmission to a fixed frequency and cycle through some different GPU workloads:

You can clearly hear a correlation between different scenes and this may be the highest throughput way to encode data onto this particular side channel. If we look at the frequency domain of some of these transmissions we can clearly see a delineation:

You may think that decoding this type of modulation by hand is difficult, and indeed it is. Luckily, computers are more than capable of solving those hard problems for you by applying machine learning models to decode those types of signals into a more usable form. Performing this type of demodulation requires relatively good signal strength. While I was able to pick up the presence of the carrier from over 50 feet away, demodulating it with AM and FM left me with mostly static.

011. Conclusion

As we’ve seen here, modern hardware products produce various side channels that can be picked up with relative ease. In the last example we covered where the signal conveyed information about what the workload the GPU was processing. This is particularly interesting as it is similar to the type of side channel leakage that can be leveraged to extract cryptographic material only on a much larger scale. I hope you enjoyed this adventure into discovering side channels with an eye toward data exfiltration. What’s covered here only barely scratches the surface of side channels and I hope you are inspired to use this knowledge to go and explore all the devices that surround us.