FLARE-On 12 Writeups (Part 2)
1303 words | 7 minutes
You can read Part 1 of these writeups, which covers Levels 1-6, here
Introduction
I wanted to give these next three challenges the attention they deserve, especially since I had a particularly fun journey solving each of them. You’ll be seeing lots of bad code, questionable decisions and odd tangents, but I want these writeups to be a more honest reflection of the solving process rather than the clear cut tale I could spin with the blessing of hindsight.
7 - The Boss Needs Help
Initial Analysis
We’re given two files, hopeanddreams.exe and a PCAP file, packets.pcapng. Let’s look at the PCAP first, shall we?
![]() |
|---|
| Fig: Plaintext HTTP in the big ‘25 |
We’re presented with communication in pure, insecure HTTP/1.1 with a call to the endpoint /good under the hostname twelve.flare-on.com:8000, and several calls to the endpoints /, /get and /re under the hostname… theannualtraditionofstaringatdisassemblyforweeks.torealizetheflagwasjustxoredwiththefilenamethewholetime.com:8080.
The initial call to /good has the server responding with a big JSON block with the value d, after which we switch over to the annualtradition.com (I am NOT putting it in full every single time) host, making a POST request with some value d and "msg":"sysi". Subsequently, we have GET requests made to /get occasionally paired with POST requests to /re.
We have the vague idea of a C2 here — it seems like we first have some sort of initialization through the call to twelve.flare-on.com:8000/good followed up by receiving commands from annualtradition.com/good and POSTing the command results to annualtradition.com/re. For now, these shall remain as educated guesses.
Trying to run hopeanddreams.exe, we get a… whole lotta nothing. So let’s jump into decompilation!
Decompilation
Let’s toss this binary into Ghidra and see what we get. We eyeball the main function to be FUN_14020ba00, step into it and… we get blasted.
![]() |
|---|
| Fig: AND SO I STARTED BLASTING! |
We just see these massive chunks of what sort of looks like Mixed Boolean Arithmetic? And amidst all of the crap we can spot a few function calls here and there:
1...
2 local_3f4 & local_358 - local_318 ^ local_394 & local_3d4 & local_2e8 & local_3e0 - local_370
3 | local_31c ^ local_28c + ((local_39c - local_398) - local_2fc) |
4 local_380 ^
5 local_2f8 - local_324 ^
6 local_328 + local_2b4 ^
7 local_350 - local_368 ^ -local_348 ^ local_3a0 - local_344 & local_3c0 - local_364 |
8 local_460 ^
9 local_340 & local_458 ^
10 local_42c & local_33c - local_34c ^
11 local_3e4 & local_3bc + ((local_414 - local_3b8) - local_2f4) ^
12 -local_430 & local_448 - local_36c);
13 local_3fc = (int)local_3fc >> 0x1f;
14 FUN_1402268c0(local_168,0x140);
15 FUN_14002e020(local_168,&DAT_1404780a0);
16 cVar2 = FUN_140081590(local_168);
17 if (cVar2 == '\0') {
18 local_2ac = (DAT_14047a3b4 ^ 0x18e17bee) - (DAT_14047a3b4 + 0xf8442ef2 | 0xa3f68b8e);
19 local_410 = local_2ac ^ 0x2a11d61a;
20 local_328 = (local_410 | local_410 + 0x2b62c1e2) & 0xa802062 | 0x101e8c11;
21 uVar3 = local_410 + 0x847b15c3;
22 local_40c = -0x7918f400;
23 local_418 = uVar3 | 0xbe7285bc;
24 local_308 = local_328 + (-0x2625ba52 - (local_2ac ^ 0xa9f45832));
25 local_3fc = local_410 & 0xdaf04d8f;
26...
So, how do we even get started? The ideal situation here is that all of this is actually deadcode, and fortunately Ghidra has a handy feature to let you “follow” a variable by highlighting it with a middle click, then subsequently you can step backwards and forwards to see its references by using Ctrl+, and Ctrl+. respectively.
| Fig: Wheee we go up and down |
At least by eyeballing, we can see that the local variable referenced by these function calls seems to be untouched by all the arithmetic in between! This doesn’t just appear in the main function, in fact we see these humongous chunks of boolean arithmetic in almost every function… We can be more rigorous with this by performing something like taint analysis to properly trace the data dependencies that these function calls are relying on, but semi-counterintuitively, I (while solving) chose to apply that to the deadcode instead.
Taint Analysis: Silly Style
Something caught my eye among what I am now confidently assuming is deadcode.
local_438 = ~(DAT_14047a3b0 ^ 0xfcffff7f | 0x7cf8ef7f);
uVar3 = (local_438 ^ 0xbe147238) + 0x6e038e76;
local_468 = ~((uVar3 ^ 0x2361f848) + 0xde8f09e4 ^ 0x3f321ae2 - (uVar3 ^ 0xa42b8509));
uVar4 = ~(uVar3 ^ 0xfcf7eb7d) & 0x4b0c35a2;
local_460 = uVar4 + 0x6b82a3a2;
...
local_430 = ~((local_460 & 0x8a664086) + ((uVar3 ^ 0xb84b1441) & (uVar3 ^ 0xe8319112)));
uVar43 = 0x503b2565 - ((DAT_14047a3b0 ^ 0xb00088d) & 0xbf0c8def);
local_434 = ~((0x70342f7c - (uVar6 ^ 0xd6ef279f)) - (local_468 ^ 0x5abffef9));
uVar11 = uVar8 | 0xfb7ffb72;
...
DAT_14047a3b0 = DAT_14047a3b0 + 1;
local_404 = DAT_14047a3ac ^ 0xc3f7dbaf;
uVar4 = (local_404 | 0x1e0c19ae) & 0x8af3cfab;
uVar5 = uVar4 ^ 0xf7a6aaba;
Do you see it? With the syntax highlighting in Ghidra it’s a touch more obvious: these expressions all (this is an assumption) seem to somehow involve DAT_14047a3ac, DAT_14047a3b0 and DAT_14047a3b4 in some form! In fact, preceeding other function calls (which we are also assuming here to be the meaningful part of the program), there seems to usually be an assignment of a gigantic expression to one of these globals. For example:
DAT_14047a3ac =
local_460 |
local_2fc |
local_45c |
local_3ec |
local_3d4 |
local_3b4 |
local_2e0 |
local_2f8 - local_394 |
local_2dc & local_39c |
local_44c + local_410 |
local_3e0 - local_468 |
local_3a4 ^ local_3a8 |
local_3f0 & local_458 |
local_350 + local_3e4 |
local_418 ^ local_434 + local_428 |
local_2d8 ^ local_3c4 ^ local_2e4 |
local_3ac ^ local_2f4 ^ local_2ec + local_304 + local_300 |
local_348 + local_3fc + ((local_344 - local_3c0) - local_438) |
local_41c ^ local_308 + local_42c + ((local_3e8 - local_440) - local_43c) |
local_30c ^ local_388 ^ local_2c8 & local_340 & local_384 + local_2d4 |
local_398 ^ local_33c + local_328 & local_31c + local_3a0 |
local_34c ^ local_368 ^ -local_2a0 ^ local_3f8 & local_320 |
local_2c4 + local_430 ^ -local_364 ^ local_36c - local_334 |
local_424 + local_448 + local_3f4 ^ local_3bc & local_40c + local_3cc |
(local_3c8 - local_330) - local_38c & local_32c + ((local_414 - local_3b0) - local_3b8) |
local_3d8 ^
local_360 ^
local_2d0 ^
local_378 ^
local_444 - local_290 ^
local_3dc + local_29c ^
local_454 & local_2c0 - local_2bc ^
local_404 & local_35c & -local_2b8 - 0x52ff ^
local_298 & (local_420 - local_354) - local_310 ^
local_400 + local_28c + (local_370 - local_2cc) ^
local_294 & local_2b0 & local_380 + ((local_2a8 - local_358) - local_2ac) ^
local_450 + local_464 &
local_314 + local_2f0 + (((local_2a4 - local_318) - local_408) - local_2e8);
FUN_140004080(local_198,8);
These globals all also have over a thousand XREFs each, so they are pretty prevalent in this binary. So if we continue with our current line of assumptions, we could possibly clean up the decompilation by a lot simply by treating those globals as taint sinks and all the locals it touches to be tainted, then blasting all of those instructions! Sounds like a plan. That is, if I had any clue how to perform taint analysis.
The internet tells me Triton exists (and it will make a comeback later!) but for now, I thought I could just whack this challenge with a simple Ghidra script.
Attempt 1: Deadcode Delimiters
My first idea to deobfuscate this binary was simple: A lot of these deadcode chunks appear to begin with the assignment of one of these globals to a local variable, and terminates with the assignment of a local to one of these globals. So perhaps we could use those as “markers” of the deadcode chunks and just place a JMP across the entire section or NOP it out if we see that! Just watch for mov reg, global and mov global, reg.
Apparently this idea is not wrong and actually works but somehow when I tried it… Well, I don’t know? It

