️🏴 Dialed Up CTF

Hear all the tunes; hack all the things.


#ctf #file-format

Dialed Up CTF

I originally noticed this on Twitter, the weekend before DEF CON 30: a "mini-CTF all embedded in an .mp3". Who could resist?

The details of the CTF are located at https://ctf.projectmammoth.com/ with the MP3 itself found here.

Metadata FTW! (10)

My metadata brings all the hackers to the yard. Find a key in the contest mp3 file.

Hint: Running "strings" is usually a good way to start a CTF!

Hint: exiftool FTW

I didn't reach for the hints on this one or any MP3-specific tool, instead going straight for mediainfo:

mediainfo "Dialed Up CTF.mp3"

Among the not-insubstantial output there's this:

METADATA-FTW                             : Thanks for playing Dialed Up CTF! Here’s the first key! {DialedUpCTF}7a317a4a827269e5  

All your base64 (25)

There’s gotta be some base64 in there somewhere… it wouldn’t be a CTF without it!

As per the previous flag, there's a lot of metadata in the output. In there somewhere, there's some obvious Base64 in the Comment field. Using base64 to decode that:

echo CgpHb29kIHdvcmshICBIZXJl4oCZcyB0a… | base64 -d -
Good work!  Here’s the key for “All your base64”:  {DialedUpCTF}beeb066f00617283

Hello World! (50)

Find and extract the contest data from the mp3 file. You should end up with an unpacked directory with some challenge folders in it.

I'm not sure if this is a format hack and didn't reach for the hints. I tried unzip without success but thankfully 7z comes to the rescue (presumably it's exploiting the prepended space allotted to ID3v2 to store the compressed data but that's one for another day…):

7z x "Dialed Up CTF.mp3"

This creates a dialed-up-ctf-data directory:

$ tree dialed-up-ctf-data
├── all-your-base64
   └── README.md
├── dot-dot-dash
   ├── dot-dot-dash.mp3
   └── README.md
├── hello-world
   └── README.md
├── lazy-admin
   └── README.md
├── metadata-ftw
   └── README.md
├── README.md
├── simon-says
   └── README.md
├── spectralicious
   ├── Dialed Up - Spectralicious.mp3
   └── README.md
├── stego-saurus
   ├── Prodigy-VoodooPeople-hidden-data.mid
   └── README.md
├── thats-completely-notes
   ├── HackersTheMusical-ThatsCompletelyNotes.png
   ├── README.md
   └── ThatsCompletelyNotes-XOR-key.mp3
└── what-the-dtmf
    └── README.md

10 directories, 16 files

Among the new files, there's a subdirectory for this challenge:

$ cat dialed-up-ctf-data/hello-world/README.md 
## Hello World!

Thanks for playing Dialed Up CTF!  Here’s another key!  

What the DTMF? (100)

Find it. Decode it.


Note: There is no “{DialedUpCTF}” prefix for this key. Key submissions will work with or without it. Don't forget what the format/length of the keys are (16 hex chars/nibbles)!

I know nothing about DTMF so after some searching I found and tried this: https://github.com/ribt/dtmf-decoder.

It only works on WAV files so ffmpeg to the rescue:

ffmpeg -i "Dialed Up CTF.mp3" "Dialed Up CTF.wav"

After which:

$ ./dtmf.py "Dialed Up CTF.wav"

That first burst appears to be a match but contains a # character. This stumped me for a while so I ended up trying each of the sequences in turn (I somewhat stupidly hadn't even listened to the song at this point.) In the end, I had to take a hint and, as it turns out:


With that replacement, the first sequence is the flag: 08462DB937A4F6BD.

Lazy Admin (150)

Looks like the Plague was too busy watching Crash Override, Acid Burn and friends hack the Gibson to change his default password for his admin account. Can you recover the key? 1-415-727-9620


Note: This is not a brute-forcing challenge, and you should not need to do that to solve this (please don’t :))

Hint: Password length is 5.

As this involved actually dialling a number, I chickened out for the better part of a week. However, I couldn't leave this incomplete and finally called the number, during which a surprisingly British-sounding voice recited menu options. After selecting the one for the admin. menu and being prompted to enter the admin. password, I hung up.

However, after a while, inspiration struck and I called back: 23646.

Or A-D-M-I-N on a keypad.

The synthesised voice then kindly read out: {DialedUpCtf}b3d6cb616f0dd41d.

Simon Says (150)

Let’s play a game of Simon Says. 1-415-727-9620

Note: If you can’t hear the tones, maybe try a different provider or phone as some seem to filter things out.

Initially, as per the previous challenge, I wasn't brave enough to complete this. After completing Lazy Admin, however, and selecting the Simon Says option from the same menu, I received as series of DTMF tones to be repeated back, much like the old Simon game.

Automating this was not easy (given this and my general phobia around using phones, I'd have made a lousy phreak.) Largely this was a combination of my mobile phone's speaker, Audacity, some fast fingering of the record/play buttons, trial, error and guess-work. Eventually, after 8 numbers, the helpful synthetic voice gave the flag: {DialedUpCtf}7e21e63b0609fbad.

Dot dot dash (150)

ACE: Now you've done it, how are we gonna send smoke signals to the rescue party across the valley now that you pissed out the fire?

RACER: We won't need smoke signals anymore now that I set up your drum kit on top of that hillside. Now play the whole valley a truly phat beat, something worth decoding.


This time around, the target appears to be another MP3 file located in the directory we extracted earlier: dialed-up-ctf-data/dot-dot-dash/dot-dot-dash.mp3. Based on the challenge's title and the audio, one would assume this is a Morse Code challenge. However, I couldn't quite discern what qualified as a dot, dash or break.


kick == dot
snare == dash

Hint: If you’re having a hard time decoding it, try looking at the waveform.

That largely describes what I had to do to find this one: opening the file in Audacity, slowing the tempo significantly and tapping out the dots/dashes in part based on the sound and in part "Yeah, that looks snare-ish.":

- .... .- .-.. . - .. ..- .--. -.-. - ..-. .---- ----. .- -... ..--- -.... -... -.-. ..-. .- --... ....- .---- ..--- ...-- .....

Pasting that into Morse Code Translator gives:


Assuming I'd fat-fingered that first part and adding the usual prefix gives {DialedUpCTF}19AB26BCFA741235 which—thank goodness—was correct.

Spectralicous (200)

This version of the track is visibly spectralicous!

Note: The key for this challenge has one less digit than the other keys. Oops!

Note: File is in the contest data from the .mp3

Hint: Notice anything different about the audio in this version?

Based on the challenge title and a recent conversation at work related to Aphex Twins' infamous spectrogram trick, I assumed we were dealing with an image hidden in the audio.

It seems the ever-impressive ffmpeg can actually render spectrograms. However, I couldn't discern anything in the resulting image. A little Googling later and I'm using this: https://www.sonicvisualiser.org/:


However, there appeared to be only 15 hands after the final }. Thankfully, there's a note (which I'll admit I missed entirely to begin with) on the CTF's site:

"Note: The key for this challenge has one less digit than the other keys. Oops!"

I immediately assumed this was a finger-binary representation and set about decoding it as such:

hands = """01110

    f"{''.join(hex(int(h, 2))[2:].upper() for h in hands)}"

This gives us {DialedUpCTF}E1EE1E071E1CE0E1FE06 which is too long.

This is because, of course, it isn't finger-binary: it's American Sign Language. Or, more accurately (apparently), it's the American Manual Alphabet.

After some confusion between the exact translation (both B and 4, for instance, differ only in whether the extended fingers are splayed), I translated the above to: {DialedUpCTF}6B6BE3B96E656E2.

Stego-saurus (300)

We had to move Joey’s Garbage file to a safer hiding place. It’s no longer in that place where I put that thing that time. We did find this MIDI file though and think there could be a key in there, could you take a look and see if you can find it?


Hint: We're pretty sure Joey knows a little steganography, so hopefully he can still find the key.

Come for the CTF, stay for the Hackers references.

This is another one where the target is an extracted file, this time a MIDI file: dialed-up-ctf-data/stego-saurus/Prodigy-VoodooPeople-hidden-data.mid.

There are a bunch of steganography-/MIDI-related tools on GitHub but, after trying several, no dice (though kudos to this repo. for porting the old, Python 2 python-midi library to Python 3.)

Some hasty Googling later, I was scanning this paper: Steganography via MIDI Files by Adjusting Velocities of Musical Note Sequences With Monotonically Non-Increasing or Non-Decreasing Pitches which states:

"Miyata et al.…proposed a method to hide secret information by using pitch bend events twice in succession…"

Does that happen?!

import python3_midi as midi

track = pattern[0]
prev = None
for event in track:
    if isinstance(event, midi.events.PitchWheelEvent) and isinstance(prev, midi.events.PitchWheelEvent):
    prev = event

No, that doesn't happen. However, how many pitch-events do we have?

len([event for event in track if isinstance(event, midi.events.PitchWheelEvent)])

240? 240 ÷ 8 = 30 which is around the 29 characters contained in a flag…

for event in track:
    if isinstance(event, midi.events.PitchWheelEvent):

That looks something like:

midi.PitchWheelEvent(tick=0, channel=0, data=[0, 64])
midi.PitchWheelEvent(tick=0, channel=0, data=[1, 64])
midi.PitchWheelEvent(tick=0, channel=0, data=[1, 64])

data always has 2 items and that first one is always 0 or 1—is this binary?

binary_string = "".join(str(event.data[0]) for event in track if isinstance(event, midi.events.PitchWheelEvent))

Dividing our binary-string into byte-length chunks and then converting those to characters:

print("".join(chr(int(binary_string[i:i + 8], 2)) for i in range(0, len(bin), 8)))

That yields: {DialedUpCTF}c65d85379486d5cd (the 30th character is a \n for reference.)

That’s completely notes! (300)

Phantom Phreak got arrested and had to find an encoded way to give Cereal Killer the key to the garbage file, so he tapped out the key with his foot while being detained. For extra protection he also secretly gave an XOR key while using a payphone. Here is a recording of the call and the music for the song for this scene from “Hackers the musical”. Can you find the key?


Hint: https://en.wikipedia.org/wiki/Red_box_(phreaking)

Hint: How much was that call again?

We have another MP3 in our extract: dialed-up-ctf-data/thats-completely-notes/ThatsCompletelyNotes-XOR-key.mp3.

The project music sheet seems to be just a series of two notes: assuming the lower note is 0 and the higher 1, we get:

     0010 0001 0001 1110 0011 0011 0011
1011 0011 0110 0011 1111 0011 1110 0000
1111 0010 1010 0001 1001 0000 1110 0001
1100 0010 0111 0110 1001 0110 1111 0011
1000 0110 1000 0011 1000 0110 1100 0011 1011
0110 1100 0110 1100 0110 1111 0011 1110 0110
1000 0110 1000 0110 1110 0011 1011 0011 1000

This seemed an odd pattern, with the increase in numbers of bars and those numbers on the left: I actually turned to my wife (a classically trained singer to whom this is a little more familiar) for help decoding these arcane symbols. Turns out they're unrelated and perfectly normal.

The 10 point hint here is "How much was that call again?" Is that the call referenced in the linked YouTube video ("…and drop in five bucks in quarters.")? And would that be $5? 500¢? 20 quarters? Or the one in the recording?

Taking the hint related to phreaking and reviewing the Wikipedia page:

One 66 ms tone represents a nickel. A set of two 66 ms tones separated by 66 ms intervals represent a dime, and a quarter is represented by a set of five 33 ms tones with 33 ms pauses."

So that's 3 quarters, a dime and a nickel. 90¢!

Turning the earlier music/binary into a single binary string, before chunking it into byte-size pieces and using our XOR key of 90 gives:

import re

key = re.sub(
     0010 0001 0001 1110 0011 0011 0011
1011 0011 0110 0011 1111 0011 1110 0000
1111 0010 1010 0001 1001 0000 1110 0001
1100 0010 0111 0110 1001 0110 1111 0011
1000 0110 1000 0011 1000 0110 1100 0011 1011
0110 1100 0110 1100 0110 1111 0011 1110 0110
1000 0110 1000 0110 1110 0011 1011 0011 1000

"".join(chr(int(key[i:i + 8], 2) ^ 90) for i in range(0, len(key), 8))

That yields {DialedUpCTF}35b2b6a665d224ab.