Overview

Each year the Mandiant FLARE team puts together a month long CTF focused on reverse engineering. This CTF is over a month long which gives me a chance to work on the challenges without destroying my vibrant social life on the weekends. I made it little further this year than last which I’m pretty happy about, I’m hoping that applies next year as well if the Google + Mandiant team puts on Flare-On 10.

Disclaimer: I really dislike using Windows, reverse engineering Windows apps, and most Windows concepts which is most of FlareOn.

The Challenges

01 - Flaredle

Welcome to Flare-On 9!

You probably won’t win. Maybe you’re like us and spent the year playing Wordle. We made our own version that is too hard to beat without cheating.

Play it live at: http://flare-on.com/flaredle/

7-zip password: flare

Loading that site we can see a big and empty FLARE take on the popular Wordle game. We likely need to input some word/phrase to get the flag, let’s read the source provided in the 7z bundle to understand the how the correct input is chosen.

flaredle-start

The index.html for the page will lazily load script.js which creates the game board and had the logic we want. The script first gets it’s dictionary of valid words by importing words.js , and from reading the checkGuess function we can see that the rightGuess string used to compare our input against populated as WORDS[CORRECT_GUESS] where CORRECT_GUESS is statically defined as 57. Let’s dump this element of the array via the REPL and try that in the website UI. Because this JavaScript is written with ES module syntax with modern versions of node we can run it with a .mjs extension and adding a print at the end for out value:

$ cp words.js words.mjs
$ echo 'console.log(WORDS[57])' >> words.mjs
$ node words.mjs
flareonisallaboutcats

Seems like an answer the challenge authors would choose! Throwing this in the UI we see a success message that our “guess” was good!

flaredle-success

02 - Pixel Poker

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?

7-zip password: flare

After extracting the 7z bundle we get a readme.txt file explaining the game, and a Windows executable.

Welcome to PixelPoker ^_^, the pixel game that’s sweeping the nation!

Your goal is simple: find the correct pixel and click it

Good luck!

Checking the executable’s filetype using DetectItEasy we can see that it’s a 32-bit PE, built with Microsoft Visual C/C++ built last month.

Starting the game, we have a menu with an option to quit and explanations of how the game is played. These menus and playing to a loss can show us some strings for key parts of the gameplay that when reverse engineering we can search for like: Womp womp... :( (loss of the game), Let's go!! (dismiss help), I Give Up (quit game), and PixelPoker (X,Y) - #N/10 (game state) which hopefully aren’t obfuscated in the binary.

pixel-poker-start pixel-poker-failure

Checking the cross-references from the game loss, and game state strings we find lots of the game logic is implemented at 0x004012c0. We can see that when the handler get’s a WM_LBUTTONDOWN message (a click), it will check for if the user is out of turns, and then compare with clicks coordinate values with FLARE-On modulo the size of the game.

pixel-poker-click-handler

To solve we need to click the correct pixel one time, the correct pixel can be calculated with the following script:

key = 'FLARE-On'
endian_type = 'little'
xMax, yMax = 741, 641
xVal = int.from_bytes(key[:4].encode('utf-8'), byteorder=endian_type) % xMax
yVal = int.from_bytes(key[4:].encode('utf-8'), byteorder=endian_type) % yMax
print(f'Click at ({xVal}, {yVal}) to win')

Expected: 95, 313

pixel-poker-success

Flag: [email protected]

03 - Magic 8 Ball

You got a question? Ask the 8 ball!

7-zip password: flare

After extracting the 7z bundle we are presented with a 32-bit Windows executable, 8 DLL and a few assets.

$ tree
.
├── Magic8Ball.exe
├── SDL2.dll
├── SDL2_image.dll
├── SDL2_ttf.dll
├── assets
│   ├── DroidSans.ttf
│   ├── LICENSE.txt
│   ├── NotoSans_Regular.ttf
│   ├── OFL.txt
│   ├── OpenSans_regular.ttf
│   └── ball_paint.png
├── libjpeg-9.dll
├── libpng16-16.dll
├── libtiff-5.dll
├── libwebp-7.dll
└── zlib1.dll

magic-8-ball-die

magic-8-ball-start

Written using the SDL C++ Library for interacting with the keyboard input, loading font libraries, images, and rendering the application windows.

Some key functions in the disassembly are: - 0x00401e50: seems to be the main event loop, drawing the board and polling for keyboard input. - 0x00401a10: RC4 decryption of embedded ciphertext with key parameter - 0x004024e0: Compares the 2 inputs (textual + directional) with the values: - Textual: gimme flag pls? - Directional:LLURULDUL`

magic-8-ball-x32dbg magic-8-ball-directional-input

After submitting this we get our flag of: [email protected]

magic-8-ball-success

04 - Darn Mice 1

“If it crashes its user error.” -Flare Team

7-zip password: flare

After extracting the 7z bundle we are presented with a 32-bit Windows executable.

darn-mice-1-die

Starting with emulating the program to see it’s behavior, and the program exits right away. Finding the programs main function (0x004011d0), we can see reason it for the exit is because it expects a CLI parameter.

$ wine darn_mice.exe

darn-mice-1-main

$ wine darn_mice.exe AAA
On your plate, you see four olives.
You leave the room, and a mouse EATS one!
wine: Unhandled page fault on write access to 00000091 at address 00EE0001 (thread 0024), starting debugger...

After running the program with an argument we can see that it crashes like suggestion in the prompt for this challenge. Looking at the function called from main (0x00401000), the crash is caused by executing the first character of our input (‘A’) 0x41 + 0x50 which result in an invalid instruction.

darn-mice-1-main

Before trying to build complicated shellcode that will successfully execute, let’s try building input that will result in a NOP sled so forcing all of the instructions executed to be 0x90. I tried building the input with the following in Python but it fails when c is a value greater than 0x90 so a simple NOP sled won’t work. Variations of single byte instructions that can be executed are here.

input = [ chr(0x90 - c) for c in arr]

I started analyzing the bytes as “close-to-functioning” shellcode to see if I could make smaller edits get it to run, but ultimately I didn’t make it past challenge #3 this year.

its-over