WTFJ-CTF: Capturing the 16th flag

xmas edition. fuck xmas.

–WTFJ Operator Awoo (the author)


The mission (translated)

MISSION 016                goo.gl/DAVduj            DIFFICULTY: █████████░ [9╱10]
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
                December of 3540. Zumzum Planet. The Bureau of Space Surveying.

Unit 1337 Agents,

Our transgalactic Striker craft 0xDEADBEEF has captured a suspicious message
coming from the direction of the "colorful planet". Our bureau of cryptography
has managed to decode the transmission. Unfortunately the image is undecipherable
and contains suspicious artifacts.

We're suspicious that there's a real message buried deeper inside. We're
asking you to help. Your task is to find the real message.

Our hopes are in you!

    Captured stream: goo.gl/VNhhTW

Good luck!

---------------------------------------------------------------------------------
Hint1: Files are saved in sequence and are not fragmented.
Hint2: The flag is marked very distinctly. 🚩
Hint3: It's worth paying attention to the settings, they can change a lot.
Hint4: We're suspecting that the password might be really strong. Don't brute it.
We're almost certain that it's also contained in the message, represented in a
very oldschool way.
Hint5: The settings are really really important.


Post the recovered message in a comment under this video.
Links to code / writeups are also welcome!

P.S. The mission was created entirely by marbel82 - japrogramista.net

Let’s get to it

Mission read, in wget we trust, fresh new zip file in my work folder. Since apparently the source of the zip is some internal agency I’ve blindly assumed that it’s just going to unpack and contains no surprises. And i got a stream.bmp file. Neat.

stream.bmp file. i hate xmas.Hey, it even opened. Some of it seems oddly familiar.

Now, let’s play a “Spot the Clues” game:

  • There’s some hex stuff, which seems to be the reason for Hint1,
  • The thing below hex data looks like a notepad window,
  • The other thing on the side vaguely reminds me of some Audacity dialog window?

We’re off to a scavenger hunt

Since opening a file in a hex editor is simple enough, I’m making it my first step in doing stuff. After a bunch of scrolling I’ve found some magic numbers and interesting data:

  • The bitmap we’re seeing,
  • A PK magic, indicating a ZIP file:
    • File contains 4 files,
    • Files are named Decoder, Encoder, Helper and Tester,
    • The ZIP is encrypted, so we’re taking Hint4.
  • A RIFF WAVEfmt magic, indicating a WAV file:
    • Listening to it made my ears ring roughly 10dB louder,
    • Take a hard look at the dialog window hint, the mission is adamant about using it.

Three paths, one way

After splitting the files we’re left with one option: looking for stuff inside the WAV file. Since the initial image contains a clue, let’s rummage through Audacity to find the dialog it’s based off of.

spectrogram settings. gotcha.Yup, seems to match.

Since we’ve got most of the settings given on a (highly tarnished) silver plate, let’s load up the WAV file and apply them. Problem solved, new clue uncovered:

this explains most of the ear rapeI’m never loading Gyn’s files into Foobar2k ever again.

So what’s next? Let’s try consulting Hint4 and go looking for some oldschool formats. The weird dots and dashes in the sky are clearly not stars, but what are they?

Well, they’re certainly not Braille or the Dice Cipher either. There’s this thing called Morse Code though.

I too have a red nose, and it’s the kind of magic we call vodka

We’ve established that there’s some hidden message written in Morse. Let’s try deciphering it:

.-. -.. ..- --- ..-. .-..   .-. . ..- .- --- ... -> RDUOFL REUAOS

I smell a very distinct smell, and it’s the smell of frantic housecleaning and gingerbread. The clue seems off, given the theme of the mission, so let’s try inverting the image:

`.-. ..- -.. --- .-.. ..-.   .-. . -.. -. --- ... -> RUDOLF REDNOS`

That seems better. Now, what can I do with this string? Heck, let’s try using it as the password for the ZIP file.

Programming and stuff

Password accepted, sesame.open(), substitute for own pun if necessary. The end effect is that we have all four of the files now. I’ve compacted them into a single code listing for clarity. With some help of fellow WTFJ Operator Fox the programming language was recognised as C#.

The code

void Decode(byte[] wav, int off, byte[] LUT) {
    byte[] iLUT = InvertLUT(LUT);

    for (int i = off, p = 0; i < wav.Length - off; i++, p++) {
        if ((p & 2) != 0) {
            wav[i] = iLUT[#$%@!@];
            if ((p & 0x80) != 0)
                wav[i] = iLUT[#$%@!@];
        }
    }
}

void Encode(byte[] wav, int off, byte[] LUT) {
    for (int i = off, p = 0; i < wav.Length - off; i++, p++) {
        if ((p & 2) != 0) {
            wav[i] = LUT[wav[i]];
            if ((p & 0x80) != 0)
                wav[i] = LUT[wav[i]];
        }
    }
}

// Inverting Look Up Table for decoding
byte[] InvertLUT(byte[] LUT) {
    byte[] iLUT = new byte[256];
    for (int i = 0; i < 256; i++) {
        byte f = 0;
        while (LUT[f] != i)
            f++;
        iLUT[i] = f;
    }
    return iLUT;
}

void TestEncodeDecode() {
    byte[] PalletteLUT = LoadLUTFromPalletteAlphaChannel("stream.bmp");
    byte[] wav = new byte[16000];

    for (int i = 0; i < 16000; i++)
        wav[i] = (byte)i;

    byte[] wav2 = (byte[])wav.Clone();

    Encode(wav2, 40, PalletteLUT);
    Decode(wav2, 40, PalletteLUT);

    Debug.Assert(wav.SequenceEqual(wav2), "TestEncodeDecode Failed!");
}

Simple enough

Unfortunately we’re not even close to being done yet:

  • The Decode method has some bits corrupted,
  • LoadLUTFromPalletteAlphaChannel is missing. Nice name though!

Dealing with code obviously means we’re close to finding the solution, and it’s hidden right there in the right WAV channel that looks like paint drips.

What are our clues here? LUT probably stands for “lookup table” and we’re using alpha channels to store data. Let’s take a look at the bitmap again:

42 4D C6 D8 13 00 00 00 00 00 36 04 00 00 28 00
00 00 B0 04 00 00 3B 04 00 00 01 00 08 00 00 00
00 00 00 00 00 00 C4 0E 00 00 C4 0E 00 00 00 01
00 00 00 01 00 00 FF FF FF 1B FF FF FF 64 FF FF
FF 74 FF FF FF 19 FF FF FF 78 FF FF FF E8 FF FF
FF 4C FF FF FF 0F FF FF FF 27 FF FF FF 6F FF FF
FF 67 FF FF FF 79 FF FF FF 96 FF FF FF 61 FF FF
FF 70 FF FF FF 8D FF FF FF 14 FF FF FF 4B FF FF
FF 1A FF FF FF 5B FF FF FF 08 FF FF FF E1 FF FF
FF BB FF FF FF 7A FF FF FF 44 FF FF FF B5 FF FF
FF 5F FF FF FF 0A FF FF FF 85 FF FF FF A7 FF FF
FF 35 FF FF FF B7 FF FF FF 99 FF FF FF E7 FF FF
FF E5 FF FF FF BE FF FF FF 34 FF FF FF 90 FF FF
FF 3B FF FF FF F2 FF FF FF 9B FF FF FF 58 FF FF
FF BF FF FF FF 59 FF FF FF 39 FF FF FF B3 FF FF
FF 52 FF FF FF C7 FF FF FF 18 FF FF FF DA FF FF

Clear as mud.

What we see here are the BMP header and some data. We’re looking for a lookup table. Let’s get rid of the headers and take a peek at the whatever-this-is-that-looks-like-pixels just past the header:

FF FF FF 64
FF FF FF 74
FF FF FF 19
FF FF FF 78

Note: Section redacted and highlighted for expository purposes


Hey look, now it looks like RGBA color notation! We’re looking for things inside the alpha channel, so let’s dump the whole block and get the alpha values in code. We need to write us some C# anyway.

public static void Main(string[] args) {
    // You get it. Lots of bytes there.
    byte[] BMPBlob = { 0xFF, 0xFF, 0xFF, 0x1B [...] 0xFF, 0xFF, 0xFF, 0x17 };
    byte[] LUT = new byte[256];

    int j = 0;

    for (int i = 0; i < BMPBlob.Length; i++) {
        if (i % 4 == 3) {
            LUT[j++] = BMPBlob[i];
        }
    }

    byte[] CT = File.ReadAllBytes("./audio.wav");
    Decode(CT, 40, LUT);
    File.WriteAllBytes("./decoded.wav", CT);
}

One badly written function later we’ve got the LLFPAC code more or less done. After some debugging and adjustments (several OutOfBounds exceptions and infinite loops were hit on the way) the program.. refused to compile.

Wait, what

Well, remember the corruption in Decode method that we’ve discovered? Turns out it’s not a magic macro or anything, it’s just garbled data. and my continuity fell apart mid-paragraph It seems to be deliberately placed there as the last bump on the road to victory, but my bytemangling mind was too sidetracked to not be an idiot (no excuses here, lookup tables are not rocket science). Trying to come up with some code that spews data back out I’ve tried several idiosyncracies, including:

  • wav[i] = iLUT[i];
  • wav[i] = iLUT[i % p];
  • wav[i] = iLUT[p];
  • wav[i] = iLUT[p % i];
  • wav[i] = iLUT[i * p];

..you get it, right? Lots of random flailing around with no clue to find. That’s where the totally awesome WTFJ Operator Fox hits me with a tree branch wrapped in obvious, saving the day again:

[22:07] [WTFJ] Fox: so uh
[22:07] [WTFJ] Fox: decoding is the opposite of encoding
[22:07] [WTFJ] Fox: and we’re already using wav[i] as the index

Well fuck me sideways, I lost all of my bearings for a moment there. Let’s fix that blunder and not speak of this again.

Program recompiled and exited with code 0. A fresh decoded.wav file waiting for me to doubleclick it drag it into Audacity.

before and after decoding

Hey look, it’s the 🚩 distinctively 🚩 marked 🚩 flag 🚩 Note: not the actual decoded.wav file - instead it’s a before-and-after shot.

Merry Fooken Xmas.

–WTFJ Operators Awoo and Fox.

excercising bragging rights.png