Welcome to Russells-World

SDR Challenge Kit

PROLOGUE



Back in Defcon 21, Dragorn was asked by Druid to put together a wireless and SDR challenge for a party invite contest. What was born of this was a small used NVG pelican case that would be re-purposed to being the first SDR challenge kit I've seen. Time moved on, and at BSidesDE, Dragorn brought the kit to the talk that we were giving with Zero_Chaos on all things wireless. One of the subjects was SDR, and the kit provided a great example of some challenges that people attack for the class. Rick Mellendick reached out to us for building a SDR challenge at Shmoocon 2013. Some of us were skeptical about the level of interest, how hard we should make the flags, and others… but we moved forward.

Wow, simply wow. Not only were all the flags attempted, but the hidden message challenge of receiving the transmission of a NOAA weather satellite just like K2RNF had done was attempted (but ultimately not completed due to the Shmoocon party and good times). We were amazed by the level of interest. I felt overly ecstatic that many people tried the challenges, all the flags were identified in some capacity, and the over all feeling was positive. The next goal was a lofty one; develop a series of SDR challenges for Defcon 22 within the wireless village. We all threw back the red pills, and I started working on the design, build and implementation. And thus the nuclear football was built.

Core Components Needed



  1. HackRF One, BladeRF and multiple RTL-SDR's
  2. Intel NUC (D34010WYK)
  3. Multiple Raspberry Pi revision B's
  4. PiFM, GnuRadio Companion
  5. /dev/random bits…


    Let's get it on


Designing a challenge is hard, but if you follow the same principals that I did, I feel that you can easily construct one yourself. Simply enough, we need more am radio operators and the challenges that are built help generate interest in this field. By following the premise of teaching by doing, come up with a series of challenges that fit into the low, medium, high, OMG WHY and /dev/random categories.

Low
-Identify the center frequency
-Morse Code
Either done by playing a premade audio file with dit's and dah's, or by reading from a file and using on/off keying.

#!/bin/sh
while true ; do /home/pi/pifm /home/pi/dot2.wav 200.0 ; sleep 1 ;/home/pi/pifm /home/pi/dash.wav 200.0 ; sleep 1 ;done



-Voice Audio/Asshole Numbers Station

apt-get install festival
hints.txt
Alpha
Bravo
Charlie
Delta
...
Whiskey
X-ray
Yankee
Zulu
I'm a useless hint!
#!/usr/bin/perl

open(HINTS,"hints.txt");

my @hintlist;
my $i;

while(&ltHINTS&gt) {
  chomp;
  $hintlist[$i]=$_;
  $i++;
}

while (1) {
  my $sentence;
  my $i=0;
  while($i!=20) {
    my $rnum=int(rand($i));
    my $text = $hintlist[$rnum];
    $rnum=int(rand(500));
    $sentence = $sentence.". $rnum. ".$text;
    $i++;
  }
  #Say it;
  system("echo '$text' | text2wave -F 22050 - | /home/pi/pifm - 144 22050");
  #Shutoff xmit
  system("touch /tmp/empty && /home/pi/pifm /tmp/empty");
  sleep(3);
}


Medium
-AFSK

#!/bin/sh
while true ; do echo "knock knock... : `date +%c`" | minimodem --tx -f -8 1200 -f /home/pi/sentence.wav && /home/pi/pifm /home/pi/sentence.wav 146.0 48000 ; /home/pi/pi-shutdown.sh; sleep 10;done


-DVBT with Metadata

start.sh

#!/bin/sh
modprobe usb-it950x
while true ; do tsrfsend ~/SDR_Challenges/Defcon_2014/NUC/THE_MATRIX.TS 0 730000 6000 4 1/4 1/4 8 0 0 ; sleep 5; ~/SDR_Challenges/Defcon_2014/NUC/send-desktop.sh ; sleep 5; done

mkfifo ~/SDR_Challenges/Defcon_2014/NUC/desktop

send-desktop.sh

1
2
3
4
5
6
7
8
#!/bin/sh
echo "starting"
avconv -f x11grab -s 1024x768 -framerate 30 -i :0.0 -vcodec libx264 -s 720x576 -f mpegts -mpegts_original_network_id 1 -mpegts_transport_stream_id 1 -mpegts_service_id 1 -metadata service_provider="Just use the wireless keyboard" -metadata service_name="Research Travis Goodspeed's work on xor and keyboards" -muxrate 3732k -y ~/SDR_Challenges/Defcon_2014/NUC/desktop &
tsrfsend ~/SDR_Challenges/Defcon_2014/NUC/desktop 0 730000 6000 4 1/2 1/4 8 0 0&
sleep 3600
echo "stopping"
killall avconv
killall tsrfsend


High
-POCSAG with gr-mixalot

#!/usr/bin/env python
##################################################
# Gnuradio Python Flow Graph
# Title: Pocsagtx Hackrf
# Author: cstone@pobox.com
# Description: Example flowgraph for POCSAG transmitter
# Generated: Sun Aug 17 14:32:55 2014
##################################################

from gnuradio import analog
from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from gnuradio.filter import pfb
from optparse import OptionParser
import math
import mixalot
import osmosdr

class pocsagtx_hackrf(gr.top_block):

    def __init__(self):
        gr.top_block.__init__(self, "Pocsagtx Hackrf")

        ##################################################
        # Variables
        ##################################################
        self.symrate = symrate = 38400
        self.samp_rate = samp_rate = 8000000
        self.pagerfreq = pagerfreq = 938800000
        self.max_deviation = max_deviation = 4500.0

        ##################################################
        # Blocks
        ##################################################
        self.pfb_arb_resampler_xxx_0 = pfb.arb_resampler_ccf(
        	  float(samp_rate)/float(symrate),
                  taps=None,
        	  flt_size=16)
        	
        self.osmosdr_sink_0 = osmosdr.sink( args="numchan="   str(1)   " "   "hackrf=0" )
        self.osmosdr_sink_0.set_sample_rate(samp_rate)
        self.osmosdr_sink_0.set_center_freq(pagerfreq, 0)
        self.osmosdr_sink_0.set_freq_corr(0, 0)
        self.osmosdr_sink_0.set_gain(10, 0)
        self.osmosdr_sink_0.set_if_gain(20, 0)
        self.osmosdr_sink_0.set_bb_gain(20, 0)
        self.osmosdr_sink_0.set_antenna("", 0)
        self.osmosdr_sink_0.set_bandwidth(0, 0)
          
        self.mixalot_pocencode_0 = mixalot.pocencode(1, 512, 425321, "GRAND CENTRAL\x0a\x0aGuns. Lots of guns.", symrate)
        self.blocks_multiply_const_vxx_0 = blocks.multiply_const_vcc((0.5, ))
        self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_gr_complex*1, "/home/rhandorf/pocsag-5.9.raw", False)
        self.blocks_file_sink_0.set_unbuffered(False)
        self.blocks_char_to_float_0 = blocks.char_to_float(1, 1)
        self.analog_frequency_modulator_fc_0 = analog.frequency_modulator_fc(2.0 * math.pi * max_deviation / float(symrate))

        ##################################################
        # Connections
        ##################################################
        self.connect((self.mixalot_pocencode_0, 0), (self.blocks_char_to_float_0, 0))
        self.connect((self.blocks_char_to_float_0, 0), (self.analog_frequency_modulator_fc_0, 0))
        self.connect((self.blocks_multiply_const_vxx_0, 0), (self.pfb_arb_resampler_xxx_0, 0))
        self.connect((self.analog_frequency_modulator_fc_0, 0), (self.blocks_multiply_const_vxx_0, 0))
        self.connect((self.pfb_arb_resampler_xxx_0, 0), (self.osmosdr_sink_0, 0))
        self.connect((self.pfb_arb_resampler_xxx_0, 0), (self.blocks_file_sink_0, 0))


# QT sink close method reimplementation

    def get_symrate(self):
        return self.symrate

    def set_symrate(self, symrate):
        self.symrate = symrate
        self.analog_frequency_modulator_fc_0.set_sensitivity(2.0 * math.pi * self.max_deviation / float(self.symrate))
        self.pfb_arb_resampler_xxx_0.set_rate(float(self.samp_rate)/float(self.symrate))

    def get_samp_rate(self):
        return self.samp_rate

    def set_samp_rate(self, samp_rate):
        self.samp_rate = samp_rate
        self.pfb_arb_resampler_xxx_0.set_rate(float(self.samp_rate)/float(self.symrate))
        self.osmosdr_sink_0.set_sample_rate(self.samp_rate)

    def get_pagerfreq(self):
        return self.pagerfreq

    def set_pagerfreq(self, pagerfreq):
        self.pagerfreq = pagerfreq
        self.osmosdr_sink_0.set_center_freq(self.pagerfreq, 0)

    def get_max_deviation(self):
        return self.max_deviation

    def set_max_deviation(self, max_deviation):
        self.max_deviation = max_deviation
        self.analog_frequency_modulator_fc_0.set_sensitivity(2.0 * math.pi * self.max_deviation / float(self.symrate))

if __name__ == '__main__':
    parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
    (options, args) = parser.parse_args()
    tb = pocsagtx_hackrf()
    tb.start()
    raw_input('Press Enter to quit: ')
    tb.stop()
    tb.wait()


-ASK with GNURadio

#!/usr/bin/env python
##################################################
# Gnuradio Python Flow Graph
# Title: PowerRemote TX
# Generated: Thu Aug 28 22:16:25 2014
##################################################

from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from gnuradio.wxgui import forms
from grc_gnuradio import wxgui as grc_wxgui
from optparse import OptionParser
import osmosdr
import wx

class ceiling_fan_tx(grc_wxgui.top_block_gui):

    def __init__(self):
        grc_wxgui.top_block_gui.__init__(self, title="Ceiling Fan Tx")
        _icon_path = "/usr/share/icons/hicolor/32x32/apps/gnuradio-grc.png"
        self.SetIcon(wx.Icon(_icon_path, wx.BITMAP_TYPE_ANY))

        ##################################################
        # Variables
        ##################################################
        self.interp = interp = 600
        self.baud_rate = baud_rate = 300
        self.samp_rate = samp_rate = baud_rate*interp
        self.power = power = 1
        self.gain = gain = 3.5
        self.center_freq = center_freq = 314766000

        ##################################################
        # Blocks
        ##################################################
        _gain_sizer = wx.BoxSizer(wx.VERTICAL)
        self._gain_text_box = forms.text_box(
        	parent=self.GetWin(),
        	sizer=_gain_sizer,
        	value=self.gain,
        	callback=self.set_gain,
        	label='gain',
        	converter=forms.float_converter(),
        	proportion=0,
        )
        self._gain_slider = forms.slider(
        	parent=self.GetWin(),
        	sizer=_gain_sizer,
        	value=self.gain,
        	callback=self.set_gain,
        	minimum=0,
        	maximum=25,
        	num_steps=50,
        	style=wx.SL_HORIZONTAL,
        	cast=float,
        	proportion=1,
        )
        self.Add(_gain_sizer)
        _power_sizer = wx.BoxSizer(wx.VERTICAL)
        self._power_text_box = forms.text_box(
        	parent=self.GetWin(),
        	sizer=_power_sizer,
        	value=self.power,
        	callback=self.set_power,
        	label='power',
        	converter=forms.float_converter(),
        	proportion=0,
        )
        self._power_slider = forms.slider(
        	parent=self.GetWin(),
        	sizer=_power_sizer,
        	value=self.power,
        	callback=self.set_power,
        	minimum=0,
        	maximum=100,
        	num_steps=100,
        	style=wx.SL_HORIZONTAL,
        	cast=float,
        	proportion=1,
        )
        self.Add(_power_sizer)
        self.osmosdr_sink_0 = osmosdr.sink( args="numchan=" + str(1) + " " + "" )
        self.osmosdr_sink_0.set_sample_rate(2000000)
        self.osmosdr_sink_0.set_center_freq(center_freq, 0)
        self.osmosdr_sink_0.set_freq_corr(0, 0)
        self.osmosdr_sink_0.set_gain(gain, 0)
        self.osmosdr_sink_0.set_if_gain(20, 0)
        self.osmosdr_sink_0.set_bb_gain(20, 0)
        self.osmosdr_sink_0.set_antenna("", 0)
        self.osmosdr_sink_0.set_bandwidth(0, 0)
          
        self.blocks_vector_to_stream_0 = blocks.vector_to_stream(gr.sizeof_gr_complex*1, 1)
        self.blocks_vector_source_x_0 = blocks.vector_source_c([1,0,0,1,1,0,1,1,0,1,0,0,1,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1,0,1,1,0,1,0,0,1,0,] + [0]*3321, True, 1, [])
        self.blocks_repeat_0 = blocks.repeat(gr.sizeof_gr_complex*1, interp)
        self.blocks_moving_average_xx_0 = blocks.moving_average_cc(20, 0.9/20, 4000)

        ##################################################
        # Connections
        ##################################################
        self.connect((self.blocks_repeat_0, 0), (self.blocks_moving_average_xx_0, 0))
        self.connect((self.blocks_moving_average_xx_0, 0), (self.osmosdr_sink_0, 0))
        self.connect((self.blocks_vector_source_x_0, 0), (self.blocks_vector_to_stream_0, 0))
        self.connect((self.blocks_vector_to_stream_0, 0), (self.blocks_repeat_0, 0))



    def get_interp(self):
        return self.interp

    def set_interp(self, interp):
        self.interp = interp
        self.set_samp_rate(self.baud_rate*self.interp)

    def get_baud_rate(self):
        return self.baud_rate

    def set_baud_rate(self, baud_rate):
        self.baud_rate = baud_rate
        self.set_samp_rate(self.baud_rate*self.interp)

    def get_samp_rate(self):
        return self.samp_rate

    def set_samp_rate(self, samp_rate):
        self.samp_rate = samp_rate

    def get_power(self):
        return self.power

    def set_power(self, power):
        self.power = power
        self._power_slider.set_value(self.power)
        self._power_text_box.set_value(self.power)

    def get_gain(self):
        return self.gain

    def set_gain(self, gain):
        self.gain = gain
        self.osmosdr_sink_0.set_gain(self.gain, 0)
        self._gain_slider.set_value(self.gain)
        self._gain_text_box.set_value(self.gain)

    def get_center_freq(self):
        return self.center_freq

    def set_center_freq(self, center_freq):
        self.center_freq = center_freq
        self.osmosdr_sink_0.set_center_freq(self.center_freq, 0)

if __name__ == '__main__':
    import ctypes
    import sys
    if sys.platform.startswith('linux'):
        try:
            x11 = ctypes.cdll.LoadLibrary('libX11.so')
            x11.XInitThreads()
        except:
            print "Warning: failed to XInitThreads()"
    parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
    (options, args) = parser.parse_args()
    tb = ceiling_fan_tx()
    tb.Start(True)
    tb.Wait()

/dev/random - Participant transmissions
For the receive components where participants have to transmit back, you can use GnuRadio or your own variety of wrappers and decoders. I hacked together a perl script that would intercept the output of various programs that would force the pi to transmit another response.

mkfifo audiotx

start_rx.sh
#!/bin/sh
rtl_fm -f 146.0M -M wbfm -s 200000 -r 48000 -o 6 | sox -traw -r48k -es -b16 -c1 -V1 - -twav - | minimodem --rx -8 1200
 -f - > audiotx

knockknock.sh
#!/bin/sh
while true ; do echo "knock knock... : `date +%c`" | minimodem --tx -f -8 1200 -f /home/pi/sentence.wav && /home/pi/pifm /home/pi/sentence.wav 146.0 48000 ; /home/pi/pi-shutdown.sh; sleep 10;done

knock-knock.pl
#!/usr/bin/perl
use Time::HiRes qw(usleep nanosleep);

#`./start_rx.sh&`;

open (AUDIOFIFO, "audiotx");

print "opening fifo\n";

while (<AUDIOFIFO>) {
  chomp;
  print "RX: $_\n";
  if ((index(lc($_), "who's there") != -1) || (index(lc($_), "who is there") != -1)) {
    print "I RX: Who's there?\n";
    print "I TX: morpheus\n";
    usleep (500); 
    `echo 'morpheus.' | minimodem --tx -f -8 1200 -f /home/pi/knockknock1.wav && /home/pi/pifm /home/pi/knockknock1.wav 146.0 48000`;
    `/home/pi/pi-shutdown.sh`;
  } 
  if ((index(lc($_), "morpheus who") != -1)) {
    print "I RX: morpheus who?\n";
    print "I TX: the body cannot live without the mind\n";
    usleep(500);
    `echo 'the body cannot live without the mind.' | minimodem --tx -f -8 1200 -f /home/pi/knockknock2.wav && /home/pi/pifm /home/pi/knockknock2.wav 146.0 48000`;
    `/home/pi/pi-shutdown.sh`;
  }
}

pi-shutdown.sh
#!/bin/sh
touch /tmp/empty && /home/pi/pifm /tmp/empty

start.sh
#!/bin/sh
./knockknock.sh & ./start_rx.sh & ./knock-knock.pl&


Okay, so this is where it starts to get busy… you have to manage all this noise. Initially everything was executed by a series of shell scripts and heartbeat monitors. Next steps are writing a webUI that helps manage all the moving parts.

This is an example of one of the challenges that was run at Shmoocon 2013. Participants would have to intercept the audio transmission, replay it at 1200 baud through minimodem and were presented with the string that you see there. Following steps would be to base64 decode, and review the resulting file.

#!/bin/sh
while true ; do echo "MDAwMCAgIDAwIDIxIDU1IDNiIDc5IDY3IGQwIDY3IGU1IDUxIGU3IDE0IDA4IDAwIDQ1IDAwDQowMDEwICAgMDAgM2MgMGYgN2IgMDAgMDAgODAgMDEgM2QgYTggYzAgYTggMDEgNzggNGEgN2QNCjAwMjAgICBlMSAwMCAwOCAwMCA0ZCA0OSAwMCAwMSAwMCAxMiA1NiA2ZiA2YyA3NCA3MiA2Zg0KMDAzMCAgIDZlIDIwIDUzIDc1IDYzIDZiIDczIDAw : `date +%c`" | minimodem --tx -f -8 1200 -f /home/pi/ping.wav && /home/pi/pifm /home/pi/ping.wav 80.0 48000 ; sleep 4;done



Remember, keep all your transmissions legal. Get your Ham Radio ticket and you're mostly there.

How you put it together to travel is up to you. Just keep in mind that if it looks like the picture below and you fly a lot, you're going to have a bad time.



Thank You



I'd like to extend a warm, hearty and, eventually, beer supplemented thank you to Dragorn, Zero_Chaos, Rick Mellendick, DaKahuna, Justin Simon, Tara Miller, Mike Ossmann, Rob Ghilduta and Travis Goodspeed. Gents, you're great friends; thank you for you help, training and patience. It truly takes a village :)