Introduction

FLARE-On is a month-long annual CTF event by Mandiant. It focuses on Windows oriented reverse engineering and malware analysis.

It is one of my most favorite CTF events throughout the year. And it’s really, really hard. This year, I managed to solve 4 out of 11 challenges.

This blog post focuses on my writeups for the first two of these challenges and attempts to trace my train of thoughts.

Flaredle - Challenge 1

This is pretty much the easiest warm-up challenge.

We’re given a 7z archive which contains the files necessary for this challenge. The 7z contains a typical collection of files used to run a website. It contains index.html, style.css and two javascript files - script.js and words.js.

In a setup like this, I prefer setting up a local python server and playing around with the website first. We can setup a server in the same folder like this:

$ python -m http.server

This should start a server at the default port 8080. Cool!

Wordle and code review

The HTML page shows us a game setup. Flaredle

Many readers might recognize this setup to be the same of Wordle’s! Now the challenge’s name makes sense. But this version looks much harder. This being a CTF challenge, we’re not going to play the game. If we take a look at the source, you’ll find that the HTML sources the script.js file. Let’s take a look inside.

script.js

The file contains multiple functions that are typical to a Wordle-like game implementation (Bonus question: Do you know how Wordle works?). The opening lines look like this:

import { WORDS } from "./words.js";

const NUMBER_OF_GUESSES = 6; const WORD_LENGTH = 21;

const CORRECT_GUESS = 57;

let guessesRemaining = NUMBER_OF_GUESSES; let currentGuess = []; let nextLetter = 0; let rightGuessString = WORDS[CORRECT_GUESS];

We presumably import a set of words from the words.js file and set some constants. It looks like we get 6 guesses at this. What’s interesting to us is that the ‘correct’ guess is hardcoded and is the 57th word in the wordlist.

Once we enter a word and hit enter, we invoke the checkGuess function due to this code:

document.addEventListener("keyup", (e) => {
<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">guessesRemaining</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">0</span>) {
    <span style="color:#66d9ef">return</span>
}


<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">pressedKey</span> <span style="color:#f92672">=</span> String(<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">key</span>)
<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pressedKey</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;Backspace&#34;</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">nextLetter</span> <span style="color:#f92672">!==</span> <span style="color:#ae81ff">0</span>) {

    <span style="color:#a6e22e">deleteLetter</span>()
    <span style="color:#66d9ef">return</span>
}

<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">pressedKey</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;Enter&#34;</span>) {
    <span style="color:#a6e22e">checkGuess</span>()
    <span style="color:#66d9ef">return</span>
}


<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">found</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">pressedKey</span>.<span style="color:#a6e22e">match</span>(<span style="color:#e6db74">/[a-z]/gi</span>)
<span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">found</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">found</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">1</span>) {
    <span style="color:#66d9ef">return</span>
} <span style="color:#66d9ef">else</span> {
    <span style="color:#a6e22e">insertLetter</span>(<span style="color:#a6e22e">pressedKey</span>)
}

})

The checkGuess function

if (guessString === rightGuessString) {
		let flag = rightGuessString + '@flare-on.com';
		toastr.options.timeOut = 0;
		toastr.options.onclick = function() {alert(flag);}
        toastr.success('You guessed right! The flag is ' + flag);
		guessesRemaining = 0
		return
}

So we obtain the flag for this challenge if we guess the word correctly. The words.js file contains a long list of words.

export const WORDS = ['acetylphenylhydrazine',
	'aerobacteriologically',
	'alkylbenzenesulfonate',
	'aminoacetophenetidine',
	'anatomicopathological',
	'anemometrographically',
	...

We check this file to locate the 58th word. It turns out to be flareonisallaboutcats. Let’s try entering this word into Flaredle.

Flaredle flag

We get the flag!

$$ \boxed{\text{Flag: flareonisallaboutcats@flare-on.com}} $$

Again, this was a really easy challenge that involves taking a look into source code.

PixelPoker - Challenge 2

The difficulty of challenges grows rapidly from this challenge. Here, we encounter a proper 32-bit PE that we have to reverse. Let’s see.

Challenge: I said you wouldn’t win that last one. I lied. The last challenge was basically a captcha. Now the real work begins. Shall we play another game?

The challenge offers us a 7z again. Once we extract it, we’re left with 32-bit executable named PixelPoker.exe and a readme file. The readme file tells us that our goal is to click a correct pixel. Running this executable, we are greeted with this screen.

PixelPoker

The main canvas seems to be filled an array of random pixels, reminiscent of a TV’s static noise. The title bar holds two interesting things.

  1. Position of the cursor in x and y co-ordinates.
  2. Some sort of counter that keeps a count of how many clicks have been made on the canvas.

e.g. In the screenshot visible above, the cursor was positioned at $x=743$ and $y=198$.

Exhausting the attempts leaves us with a dialog. PixelPoker Attempt

Now to make some guesses based on the observations and my intuition:

  1. It’s certain that we have to click the correct the pixel out of all of the possible combination of pixels on the canvas. The maximum size of canvas seems to be (741, 641). This means that there are 474981 unique pixels to click. No way we’re going to find that manually.
  2. It’s possible that the array of pixels is either completely random, created at runtime. Or, it’s loaded from a Bitmap that’s present inside the binary. Such bitmaps are typically loaded using Win32 API’s1 BitBlt and friend functions.
  3. To find out the correct pixel, there’s probably some sort of calculation that happens at runtime. We’ll need to reverse engineer the application to find it out.

Let’s get our hands dirty. I love Ghidra, so I’ll use that. I very much miss the expensive licensing costs of IDA these days.

Diving into the pixel soup

Whenever I analyze a binary, I like to check the strings first, as they give me a good initial impression of what I’m analyzing (or not). Looking into the strings, we get some indicators right off the bat.

PixelPoker Strings

We see some typical functions such as BitBlt (aha!) and MessageBoxA. These functions definitely tell me that I’m dealing with Bitmaps and Win32 GUI functions. Tracing these functions into the imports of the application and examining their cross references lands us in the main function.

In the main function, we see a bunch of match statements matching different values. I was hoping to find something similar to this, since I had a hunch that the application would listen for mouse button down event somewhere. Our search is simplified by Ghidra’s decompilation and some very helpful strings. After all of the boilerplate code that manages menus and Bitmap loading, we have the stuff we really care about. PixelPoker Main

The first thing that immediately stands out is the Please play again message. Before that string, we even see a variable being compared to decimal value 10. This is probably our click counter!

The next thing that stands out is the comparisons of param_2 against different hardcoded constants. These are probably some kind of macro constants. If we look up those constants in the context of WIN32 API and events, it doesn’t take us very long to find out that 0x200 is a WM_MOUSEMOVE constant and 0x201 is a WM_LBUTTONDOWN (Mouse left button down) event. Just like we predicted! [Reference]

Further down else branch we see our click counter getting incremented. Then, we do some kind of check against some values. Let’s understand what these values are.

uVar8 and uVar6 are the input X and Y co-ordinates that we clicked. (Exercise to the reader: How did I arrive at that conclusion?).

The first argument to the modulo (%) operator is seems to be a constant. You’ll see a Ghidra exclusive notation where, for example, the variable name ends with ._0_4_. This just means that only the first four bytes of that data source are considered. This roughly translates to $\text{MOV EAX, [s_FLARE-On_00412004]}$ in assembly (Add offset for the next 4 bytes). The entire modulo operation looks something like this in assembly.

        00401472 a3 98 32        MOV        [DAT_00413298],EAX                               = ??
                 41 00
        00401477 a1 04 20        MOV        EAX,[s_FLARE-On_00412004]                        = "FLARE-On"
                 41 00
        0040147c 8b 35 80        MOV        ESI,dword ptr [DAT_00413280]                     = ??
                 32 41 00
        00401482 f7 f6           DIV        ESI

It turns out s_FLARE-On_00412004 is just a 8 byte string "FLARE-On". But since these are the first 4 bytes LE, it translates to the constant 0x52414c46. Further down, we see s_E-On_00412004+4 getting referenced. These are probably the next 4 bytes LE which are 0x6e4f2d45 (LE: Little Endian).

Next, we care about what DAT_00413280 and DAT_00413284 is. I went into the cross references of these data pointers to find out these are also used for creating window and are provided as dimensions to CreateWindowExW which has a signature as described here.

PixelPoker CreateWindow

So these are the window height and width, which we know to be 741 and 641! This is also probably the check to find out the correct pixel.

It’s a common practice in reverse engineering to rename everything in the decompilation as we go along. The entire code, after renaming constants and variables looks something like this:

PixelPoker CreateWindow

Much better!

Wrapping up

So we know that the actual check to see if clicked the correct pixel happens where the modulo operation happens. The two checks actually look like this.

if (INPUT_X_PIXEL == 0x52414c46 % 741) and (INPUT_Y_PIXEL == 0x6e4f2d45 % 641):
    # Success

We can actually calculate these values in a calculator.

>>> 0x52414C46 % 741
95
>>> 0x6E4F2D45 % 641
313

Once we click the pixel located at (95, 313), we succesfully get the flag.

PixelPoker flag

$$ \boxed{\text{Flag: w!nN3r_W!NneR_cHick3n_d1nNer@flare-on.com}} $$

$$ \blacksquare \blacksquare \blacksquare $$