Quick note FlareOn 2025

This year, I participated in FlareOn and was fortunate enough to solve all the challenges. Overall, many of this year’s challenges leaned heavily toward math or cryptography, which I found less appealing since they didn’t focus as much on reverse engineering like in previous years. Below are my notes on how I tackled each challenge.
Challenge 1
The first challenge is always the easiest. I simply brute-forced the sum variable and checked if the resulting string contained @flare-on.com.
condition = True
sum = 0
while condition:
key = sum >> 8
encoded = "\xd0\xc7\xdf\xdb\xd4\xd0\xd4\xdc\xe3\xdb\xd1\xcd\x9f\xb5\xa7\xa7\xa0\xac\xa3\xb4\x88\xaf\xa6\xaa\xbe\xa8\xe3\xa0\xbe\xff\xb1\xbc\xb9"
plaintext = []
for i in range(0, len(encoded)):
plaintext.append(chr(ord(encoded[i]) ^ (key+i)))
flag = ''.join(plaintext)
if '@flare-on.com' in flag:
print(flag, i)
condition = False
sum += 1
Challenge 2
Although this challenge was relatively easy, it took me a few hours to solve because I initially used the wrong Python version. The original file was built with Python 3.12. To decompile it, I used PyLingual. Since the provided code was a marshalled object loaded into memory for execution, I had to convert it to a .pyc format for decompilation. This involved adding a 16-byte header, with the first two bytes being the magic header for Python 3.12. Once successful, I obtained the following code, which made the task much easier:
import os, sys
import emoji
import random
import asyncio
import cowsay
import pyjokes
import art
from arc4 import ARC4
async def activate_catalyst():
LEAD_RESEARCHER_SIGNATURE = b'm\x1b@I\x1dAoe@\x07ZF[BL\rN\n\x0cS'
ENCRYPTED_CHIMERA_FORMULA = b'r2b-\r\x9e\xf2\x1fp\x185\x82\xcf\xfc\x90\x14\xf1O\xad#]\xf3\xe2\xc0L\xd0\xc1e\x0c\xea\xec\xae\x11b\xa7\x8c\xaa!\xa1\x9d\xc2\x90'
print('--- Catalyst Serum Injected ---')
print("Verifying Lead Researcher's credentials via biometric scan...")
current_user = os.getlogin().encode()
user_signature = bytes((c ^ i + 42 for i, c in enumerate(current_user)))
await asyncio.sleep(0.01)
status = 'pending'
if status == 'pending':
if True:
art.tprint('AUTHENTICATION SUCCESS', font='small')
print('Biometric scan MATCH. Identity confirmed as Lead Researcher.')
print('Finalizing Project Chimera...')
arc4_decipher = ARC4('G0ld3n_Tr4nsmut4t10n'.encode())
decrypted_formula = arc4_decipher.decrypt(ENCRYPTED_CHIMERA_FORMULA).decode()
print(decrypted_formula)
cowsay.cow('I am alive! The secret formula is:\n' + decrypted_formula)
else:
art.tprint('AUTHENTICATION FAILED', font='small')
print('Impostor detected, my genius cannot be replicated!')
print('The resulting specimen has developed an unexpected, and frankly useless, sense of humor.')
joke = pyjokes.get_joke(language='en', category='all')
animals = cowsay.char_names[1:]
print(cowsay.get_output_string(random.choice(animals), pyjokes.get_joke()))
sys.exit(1)
else:
if False:
pass
print('System error: Unknown experimental state.')
asyncio.run(activate_catalyst())
Challenge 3
This felt like the least reverse-engineering-focused challenge this year, as it leaned more toward steganography. However, it taught me how to use qpdf to parse PDFs, which was a valuable lesson.
Challenge 4
The author used Visual Basic to compile this challenge into a native executable, making reverse engineering much trickier. I used API Monitor alongside IDA to debug and understand the program’s flow. The flag was hidden under overlapping windows, which I initially overlooked.
Challenge 5
This was one of my favorite challenges this year. As an avid IDA user, I was intrigued when the author exploited IDA’s tendency to crash on large decompiled functions. I switched to tools like Cutter and Binary Ninja, which successfully decompiled the code. The challenge involved building a DFS graph based on the provided data. Each input traversed pre-constructed branches, and the correct input (a branch with 17 nodes) served as the key to decrypt the flag. The solution was iqg0nSeCHnOMPm2Q.
Challenge 6
This challenge combined Web3 and cryptography. The goal was to attack a random number generator to recover its parameters. I reused code from the decompiler to decrypt the flag. Note that the Web3 bytecode included some unusual opcodes, rendering most online decompilers ineffective. I used AI to assist with decompilation, which yielded good results. The Web3 deployment bytecode followed a similar approach.
Challenge 7
This was my second-favorite challenge. It focused on decoding network traffic to retrieve the flag. The challenge was tough due to obfuscation, C++ templates, and third-party libraries.
First, I used capa to get an overview and noticed the program used AES for encryption. The obfuscation involved Mixed Boolean Arithmetic (MBA) with three global variables, which slowed down IDA’s decompiler. Unsure if the MBA was part of the main execution flow, I created a PinTool to dump the values of the global variables at runtime and patched them directly into the binary. Using IDA’s decompiler, I cleaned up the patched MBA sections and realized the MBA was just junk code. I then NOP’d it out based on patterns, resulting in clean code.
For the C++ templates, IDA’s signature recognition failed, so I used Lumia (only compatible with IDA < 8.0) and BinDiff. To identify the third-party library, I dumped strings from the program, searched them online, and used AI to generate sample code based on the library to build signatures for IDA. The library handled JSON processing.
Finally, I debugged the original binary with IDA and ret-sync to synchronize with x64dbg. The key insight was the XOR operation to derive the key for decrypting the first C2 response. By analyzing Wireshark data and the program’s flow, I determined how the data was parsed to obtain the XOR key. From the second request onward, AES encrypted the traffic.
Challenge 8
This challenge was built on the Qt framework and was also obfuscated. I identified two types of obfuscation: call rax and jmp rax. The call rax instructions invoked functions, while jmp rax jumped to the next instruction. I wrote an IDAPython script to deobfuscate by hardcoding values for call rax and NOP-ing jmp rax. Next, I analyzed the program’s flow. The challenge required entering 25 numbers, each hashed and summed. Clicking "OK" compared the sum to a constant, revealing the flag if matched. This was a Subset Sum Problem. To identify slot functions for event handling, I traced imported APIs. Existing scripts ran for hours (one took 8 hours and crashed due to RAM issues), so I optimized the input into a 2D array with constraints, solving it in 0.1 seconds.
After solving, I realize that the obfuscation can be cleaned by Ghidra automatically :))
Challenge 9
This was the most disappointing challenge for me, as it was heavily math-focused. I used Grok to figure out how to recover 32 bytes per library, which provided a hint to obtain the flag.

