Decoding a BPSK Signal
Binary phase shift keyring or BPSK is the simplest form of digital phase modulation. Depending on the phase of the signal, the hardware or software decodes a zero or one. In this exercise dervied from the DoD Hack-A-Sat challenge, Phasor, you will decode a BPSK signal using GNU Radio to reveal a flag.
This project assumes access to a Unix or Linux Desktop with GNU Radio installed.
Download and unzip the wav file. It contains the BPSK encoded data.
Examine The Wav File
Open the wav file in Audacity. Notice how the waveform constantly changes or maintains its current phase. However, the frequency and amplitude of the waveform maintains constant. Changing and maintaining phases is how BPSK modulation encodes digital data.
Setting Up a New GNU Radio Project
Open GNU Radio Companion and start a new project.
- Save the project as bpsk.grc in the same directory as bpsk.wav.
- Double click on the Options block to open the project properties.
- Set the project’s Id and Title to “bpsk” (no quotes). Click OK.
- Drag the File Operators -> Wav File Sink block onto the workspace.
- Double click on the Wav File Source block to open the properties.
- Set File to “bpsk.wav”. Set Repeat to “No”. Set N Channels to “1”.
- Press OK.
Converting Float to Complex
The blocks needed to demodulate a BPSK waveform expect I/Q (in-phase / quadrature) data. However, we only really need I for the calculations. We’ll create a complex data type and set I to the output of the Wav File Source block and Q to a constant zero.
- Drag a Waveform Generators -> Constant Source block onto the canvas.
- Open the Constant Source block properties and set Output Type to float and Constant to 0. Press OK.
- Drag a Type Converters -> Float To Complex block onto the canvas.
- Connect the Wav File Source block to the “re” input to the Float to Complex block.
- Connect the Constant Source block to the “im” input of the Float to Complex block.
The Polyphase Clock Sync
Before we can begin decrypting BPSK data, we need to sync our input to a clock. When demodulating digital data, we first need to sync the incoming signal with something like a polyphase clock or a phase lock loop.
- Drag a Variables -> Variable block onto the workspace.
- Double clock on the Variable block to open the properties.
- Set the Id to “taps” and the value to “firdes.root_raised_cosine(32,32,1.0, 0.35, 32)”
firdes.root_raised_cosine is a python function which will define the taps used in the Polyphase Clock Sync block. We set the gain to 32, the sampling frequency to 32, the symbol rate to 1.0, the excess bandwith factor to 0.35, and the number of taps to 32. These are all fairly default values. Don’t worry if root raised cosines are a new concept. All you need to know right now is that this root raised cosine will help us decrease intersymbol interference or ISI.
- Drag the Synchronizers -> Polyphase Clock Sync onto the workspace.
- Double click the Polyphase Clock Sync to open the properties.
- Set Smaples/Symbols to “4”. Set the Loop Bandwidth to “0.062831853” (This is 2pi/100). Set Taps to “taps”.
- Press OK.
- Connect the output of the “Float To Complex” block to the input of the “Polyphase Clock Sync block.
Adding a Costas Loop
A Costas Loop will help us lock onto a signal, particularly when that signal might undergo dopler shift as is the case with satellite transmissions.
- Drag the Synchronizers -> Costas Loop block onto the workspace.
- Double click on the Costas Loop block to open the properties.
- Set Loop Bandwidth to “0.062831853” (2pi/100). Set Order to “2” (This is the value for BPSK). Set Use SNR to “Yes”. Press OK.
- Connect the output of the Polyphase Clock Sync to the input of the Costas Loop.
BPSK, QPSK, and 8PSK all use constellations to decode data. A constellation is a plot of I/Q data on a graph where Q is up/down and I and left/right. When we take readings of the signal based on the syncing loops we already set up, we can turn I/Q data into binary data. A good example of a constellation is for QPSK which uses four points. This is what a QPSK constellation looks like (source wikipedia):
So in QPSK, if we read our I/Q data in the upper-right quadrant, we decode the two bits, “11”. BPSK is even simpler. We’ll ignore height (Q) all together and focus on whether our points are on the left or right of the quadrant. A BPSK constellation looks like this (source wikipedia):
So let’s define our BPSK constellation.
- Drag the Modulators -> Constellation Object block onto the workspace.
- Double click on the Constellation Object block to open the properties.
- Set the id to “bpsk_constellation”. Set the Constellation Type to “BPSK”.
- Drag the Symbol Coding -> Constellation Decoder block on the workspace.
- Double click on the Constellation Decoder block and set the Constellation Object to “bpsk_constellation”. Press OK.
- Connect the output of the Costas Loop block to the input of the Constellation Decoder block.
Output to File
At this point, the Constellation Decoder block will decode our data. We just need to write it to a file.
- Drag the File Operators -> File Sink block onto the workspace.
- Double click on the File Sink block to open the properties.
- Set file to “bpsk.bin”. Set Input Type to “byte”. Set Unbuffered to “On”. Press OK
- Connect the output of the Constellation Decoder block to the input of the File Sink block.
It also might be nice to get some visual feedback.
- Drag the Instrumentation -> QT -> QT GUI Constellation Sink onto the workspace.
- Drag the QT GUI Time Sink onto the workspace.
- Connect the output of the Costal Loop block to the input of both QT GUI Sinks.
Run the Decoder
It’s time to run the decoder and turn the waveform into data the computer can use.
- Save the GNU Radio Companion workspace. Menu: File -> Save.
- Press F5 or select Run -> Generate to generate the Python program.
- Open a terminal.
- Navigate to the folder where you saved the workspace, bpsk.grc.
- Run the command ./bpsk.py.
A window should pop up where you can see the constellation graph and waveform. A file will be written in your directory called bpsk.bin.
Run the “file” command on bpsk.bin. The flag is under the green box in the screenshot below.
Run “hexdump bpsk.bin” or open bpsk.bin in your favorite hex editor. Notice that every byte is a zero or a one.
We need to combine these zeros and ones into meaningful bytes.
Python 3 Script to Fix Our Data
We’re going to use Python 3 to create a script to turn the bits we extracted into bytes. We are going to use the bitstring library to help us in this task, so we’ll have to install that dependency first.
- In the command prompt, type the following:
pip3 install bitstring
- Now, open your favorite editor and copy and paste the following Python code:
#!/usr/bin/env python3 import bitstring bits = bitstring.BitArray() # Read in the bits into the BitArray. with open("bpsk.bin", "rb") as bitstream_file: bit = bitstream_file.read(1) while bit != b'': if bit == b'x00': bits.append('0b0') else: bits.append('0b1') bit = bitstream_file.read(1) # Print the bytes at different offsets since our data may start in the middle of a byte. bitslen = len(bits) bitslen_bytes = int(bitslen / 8) * 8 for bitoffset in range(0,8): if(bitslen_bytes + bitoffset > bitslen): print(bits[bitoffset:bitslen_bytes + bitoffset - 8].bytes) else: print(bits[bitoffset:bitslen_bytes + bitoffset].bytes)
- Save the python file as decode.py.
- Exit the text editor.
Run the following command to execute the python script we just created:
chmod a+x decode.py ./decode.py | grep FLAG
The flag was placed in the data stream several times. Print the flag which is covered in green.