social



PoliCTF 2017 - Ham writeup

oldzg@zhongtiao

I lay a finishing stroke, cast off my brush and take a step back to admire the sight. Neat symbols of the newborn poem stream from the wall, filling my mind with tranquility and my heart with joy; it is natural, after expressing yourself completely, to let quiet in and obtain balance. Perfect harmony! The dissolution of the Ego is truly a wonderful phenomenon, and I take a moment to contemplate the eternal miracle of chaos brought to order by The Way, permeating all existence and inhabiting every consciousness. Then, my kettle goes off.

Go with the flow, the ancients used to say, and I suppose I'm one of them after all. I splash some whitewash on the wall, pour myself a bowl of hot butter tea and return to the machine for my evening pleasures.

Tonight, it's a puzzle with some music in it! I don my headphones and spend a few minutes taking in the beat. It's not bad; should pass word to Jade Rabbit, he seems to love this stuff. But where's the challenge?

I examine the scroll, in hope to glean some hidden meaning:

(ctf) oldzg@zhongtiao:~/work/polictf17/ham$ strings ham.wav
...
LISTZ
INFOINAM
Free Software Song (CTF-edited)
IART
Bino
ICRD
2012
IGNR
FreeMusic
id3 r
hBz_TPE1
BinoTDRC
2012TCON
FreeMusicTIT2
Free Software Song (CTF-edited)

This looks significant! For what reason would the puzzlemaker mention that the song was edited if not to send me on a quest of finding the original? I eagerly intone my scrying spell:

Scrying spell

Ah! It's in a cloud; why, having spent much of my life chasing clouds, I immediately know what to do next! With some liberal scrying, I find a distant draining demon, who cordially agrees to help me the moment I tell him the way to the song. Soon, he hands me a scroll entitled Bino - Free Software Song.mp3, and, leaving him with my gratitude and some invaluable blessings and parables, I plan my next move.

Surely it would be beneficial to know how exactly two songs differ! I summon an Audacity and command him to open the challenge scroll, and then put its precursor next to it, using sacred words File -> Import -> Audio. He grumbles a bit but eventually obeys my iron will.

Both songs loaded

Luck seems to be on my side, for the songs line up exactly! Still, the original is much louder, so I try to bring them in some better agreement by ordering the sulky spirit to Effect - Normalize:

Tracks normalized

Much better! Now, to subtract the original from the puzzle; I point at the former and utter Effect - Invert, then, my hair standing on end from Audacity's boiling rage, I select both and quickly add Track - Mix and Render:

Mixed track

Interesting... I listen to the song, but it doesn't sound any different, except of course much quieter; still, what are these bumps? Throwing a quick glance at Audacity to estimate his remaining restraint, I ask to zoom in, both vertically and horizontally, and then move about some more:

Pattern

A pattern! It's so high in frequency that mere human ears cannot perceive it; but it is unquestionably a pattern of binary fashion, and it looks somehow familiar to me. It's very regular, series of longer unchanging parts intersected with those short nuggets, never two of them back to back except for the burst at the very start; I quickly establish that the common period seems to be 32 milliseconds, and those smooth transitions... Phase shifts, are they? Suddenly, it dawns on me: BPSK31! I think I heard about this "phase-shift keying" from the rabbit; he's a big aficionado of listening in to mortals' wireless transmissions, his usual whereabouts granting him much freedom in his pursuits of course. Well...

Trying to look as fierce as possible, I command the spirit to cut off everything before the very first clear hourglass shape and follow with File - Export Audio. As Audacity, snarling, delivers the new scroll with a bpsk31.wav title to me, I dismiss him without delay, letting out a small sigh of relief: those little newfangled devils could get troublesome if you catch them in a foul mood.

Trimming the track

Now, no reason to rush. Sipping the tea, I consider my advance.

First, I apparently ought to "demodulate" the song, producing a sequence of ones and zeroes from it. So how to go about it? Deep in thought, I fetch my brush and a fresh sheet of paper. Go with the flow!

Demodulating

Of course! I don't have a need to weave something intricate; true wisdom is simple. I begin my work on the new spell, taking care to leave notes to perhaps guide myself in the future:

from scipy.io import wavfile
import numpy as np

rate, data = wavfile.read("bpsk31.wav")

# select the second channel

data = data[:,1]

# figure out how many samples should go into
# a single symbol, trim the signal to be a
# multiple of the symbol size (to reshape later);
# as the signal is not very long, it should be
# possible to cheat a little and merely cast
# the symbol size to int without corrections

samples_per_symbol = int(rate / 1000.0 * 32)
data = data[:-(data.size % samples_per_symbol)]

# split the signal into symbols, take
# the absolute values (so averaging won't
# just yield zeroes), then get the means

symbols = np.mean(np.abs(data.reshape(-1, samples_per_symbol)), axis=1)

# the first symbol must be "zero"; discard
# everything less than a half of its value
# to get rid of the trailing noise

symbols = symbols[symbols > (symbols[0] / 2)]

# now, taking the max value would get "one";
# averaging "one" and "zero" will give
# the approximate cutoff value

cutoff = np.mean([max(symbols), symbols[0]])

# turn the means into ones and zeroes

print ''.join(["1" if x else "0" for x in symbols > cutoff])

A smile touches my lips as I observe the results of my experiment:

oldzg@zhongtiao:~/work/polictf17/ham$ python demodulate.py
/usr/lib/python2.7/dist-packages/scipy/io/wavfile.py:267: WavFileWarning: Chunk (non-data) not understood, skipping it.
  WavFileWarning)
00000000000000000000000000000001010101010010110011101100100101010010110010110100110100111001001011001110110010110010100110011011100101010010111001001011001010100110010010110110010101001011001011010011011100101100110110011011001011101001001101001111001001100110111110010100110100111100101111001010011010011100111100100111100111001101011001011001011010010110010111010010111001001111010100111110110010011010100111010111001001111010011011001011001011011001010110111001101011100111001110010111111001101001111001011011001101101100111001010100101011110010110011010011110010111110011100110101100101110011111110011110011011010010101100110011011110011111100110010111100101001010100110111001110110010111011001011001011010011001011101100110011101011001101100110100111100101101001010110101001111111111111111111111111111111111110

So, I redirect the binary string into a bpsk.data file and prepare to decode it, borrowing a Varicode dictionary from the gr-ham project after a little more scrying:

# snatched from https://github.com/argilo/gr-ham/blob/master/python/varicode_rx.py

varicode = {
    '1010101011' : '\x00',    '1011011011' : '\x01',
    '1011101101' : '\x02',    '1101110111' : '\x03',
    '1011101011' : '\x04',    '1101011111' : '\x05',
    '1011101111' : '\x06',    '1011111101' : '\x07',
    '1011111111' : '\x08',    '11101111'   : '\x09',
    '11101'      : '\x0A',    '1101101111' : '\x0B',
    '1011011101' : '\x0C',    '11111'      : '\x0D',
    '1101110101' : '\x0E',    '1110101011' : '\x0F',
    '1011110111' : '\x10',    '1011110101' : '\x11',
    '1110101101' : '\x12',    '1110101111' : '\x13',
    '1101011011' : '\x14',    '1101101011' : '\x15',
    '1101101101' : '\x16',    '1101010111' : '\x17',
    '1101111011' : '\x18',    '1101111101' : '\x19',
    '1110110111' : '\x1A',    '1101010101' : '\x1B',
    '1101011101' : '\x1C',    '1110111011' : '\x1D',
    '1011111011' : '\x1E',    '1101111111' : '\x1F',
    '1'          : ' ',       '111111111'  : '!',
    '101011111'  : '"',       '111110101'  : '#',
    '111011011'  : '$',       '1011010101' : '%',
    '1010111011' : '&',       '101111111'  : '\'',
    '11111011'   : '(',       '11110111'   : ')',
    '101101111'  : '*',       '111011111'  : '+',
    '1110101'    : ',',       '110101'     : '-',
    '1010111'    : '.',       '110101111'  : '/',
    '10110111'   : '0',       '10111101'   : '1',
    '11101101'   : '2',       '11111111'   : '3',
    '101110111'  : '4',       '101011011'  : '5',
    '101101011'  : '6',       '110101101'  : '7',
    '110101011'  : '8',       '110110111'  : '9',
    '11110101'   : ':',       '110111101'  : ';',
    '111101101'  : '<',       '1010101'    : '=',
    '111010111'  : '>',       '1010101111' : '?',
    '1010111101' : '@',       '1111101'    : 'A',
    '11101011'   : 'B',       '10101101'   : 'C',
    '10110101'   : 'D',       '1110111'    : 'E',
    '11011011'   : 'F',       '11111101'   : 'G',
    '101010101'  : 'H',       '1111111'    : 'I',
    '111111101'  : 'J',       '101111101'  : 'K',
    '11010111'   : 'L',       '10111011'   : 'M',
    '11011101'   : 'N',       '10101011'   : 'O',
    '11010101'   : 'P',       '111011101'  : 'Q',
    '10101111'   : 'R',       '1101111'    : 'S',
    '1101101'    : 'T',       '101010111'  : 'U',
    '110110101'  : 'V',       '101011101'  : 'W',
    '101110101'  : 'X',       '101111011'  : 'Y',
    '1010101101' : 'Z',       '111110111'  : '[',
    '111101111'  : '\\',      '111111011'  : ']',
    '1010111111' : '^',       '101101101'  : '_',
    '1011011111' : '`',       '1011'       : 'a',
    '1011111'    : 'b',       '101111'     : 'c',
    '101101'     : 'd',       '11'         : 'e',
    '111101'     : 'f',       '1011011'    : 'g',
    '101011'     : 'h',       '1101'       : 'i',
    '111101011'  : 'j',       '10111111'   : 'k',
    '11011'      : 'l',       '111011'     : 'm',
    '1111'       : 'n',       '111'        : 'o',
    '111111'     : 'p',       '110111111'  : 'q',
    '10101'      : 'r',       '10111'      : 's',
    '101'        : 't',       '110111'     : 'u',
    '1111011'    : 'v',       '1101011'    : 'w',
    '11011111'   : 'x',       '1011101'    : 'y',
    '111010101'  : 'z',       '1010110111' : '{',
    '110111011'  : '|',       '1010110101' : '}',
    '1011010111' : '~',       '1110110101' : '\x7F' }

data = open("bpsk.data").read().strip()

# remove the preamble and the trailing carrier

data = data.lstrip("0").rstrip("1")

# two consecutive zeroes are the Varicode separator

chars = data.split("00")

# decode the message 

print ''.join([varicode[c] if c in varicode else "?" for c in chars])

After putting the last bracket in place, I stretch and finish my tea. The puzzle is waiting serenely for me to take the final step. With pleasure, I oblige.

oldzg@zhongtiao:~/work/polictf17/ham$ python decode.py
Ham radio amateurs are gradually in extinction nowadays :( -> flag{LookingForRainbowsInTheSpectrumMadeMeBlind}?

What delight! I certainly must share this with the rabbit next time we meet. Perhaps this time he wouldn't scoff at my topical vocation! He and his dusty yesteryear radios. Who's ultramodern now?

Say, it would probably be virtuos to return the machine to its rightful proprietor the western barbarian and acquire one of my own though.

Well... Some day!

For more oldzg@zhongtiao writeups, proceed here!