KalmarCTF 2026 WU

just one challenge done

0racle [rev]

“Speak your truth and i shall convene with the gods on your behalf”

We need to put the right flag into that text box; nice theme, eh?

This challenge features a GUI and has a lot of functions for processing images and messages. My first instinct was to use x64dbg to find the input pipeline.

I put it in DiE (Detect It Easy) to search for message box APIs.
Asked Gemini anyway, and it said I should put a breakpoint on GetWindowTextW.

With ScyllaHide on, let’s try out some inputs

I guess they don’t like my hello

What about the wrapped hello?

Cool! They also give me another message

I captured 3 call stacks, here i go compare them and check out where they branch:

0x426A55, 0x4269c9 - 0x426658, 0x426a2b Let’s check them out with IDA, those addrs sit nicely inside sub_4268A0

What da sub_4268A0 doin

I commented on the lines with the corresponding messages

sub_426430 gets the message sub_426AF0 prints out the message box

Analyzing the logic of sub_426430, concluded that it just xor the unk_ with 0x57. I decoded the message of the case v9 = 1; it says “The gods are pleased and commend your insight.”, sounds good.

Let’s trace back the v9.

With x64dbg, i can confirm that v10 is our input. sub_4034C0 loads 0x4B7 bytes of sub_556260 into v11.

Blob blob blob sub_556260

The first part here using the kalmar{ to decrypt a blob

Then it performs a check. If it fails, it returns -1.

The decrypted blob

I saw the “heard” message (the address may vary each time, i put a bp around 0x4269AA and walked there)

After 2 jumps, it calls sub_0042660 to pop up the message, and then i encountered a weird ret far

(The addresses here are different because i ran it again, just focus on the asm)

Looking it up, this is called a “Heaven’s Gate” (nice theme fit). In short, it jumps from 32-bit into 64-bit mode

x32dbg crashes right after that, so I guess I have to dump the thing and read it

The validator inside the gate

I loaded the dumped bin to IDA. Luckily, thing wasn’t so tangled

First, jumps to loc_2AA

There, it calls sub_275. This walks the input, uppercases the characters and returns the length.

So here flag_len = 40

Next is loc_6D. Here it calls sub_2D1, this thing does some checking, and switches back to 32-bit mode. The main validation logic lives here

r11 holds the uppercased input r9 holds the original input

The while loops calling sub_124 checking through r11. The sequence of sub_F calculates and checks the hash of flag parts.

Finishing algos

v4 = Ax^3 + Bx^2 + Cx + D
A,B,C,D are derived from a2

v3 = rounded(v4)

But where is that second argument? Tracking back, it was poped from the stack.

We jumped from 0x6D, so the top of the stack is 0x72. There is the polynomial coefficients table

From here, we can dump out the table, replicate sub_124 and get the uppercased flag KALMAR{M15S_TH3_S1GN5_4ND_3NTER_TH3_M4Z3}

Then, we can replicate sub_F and brute force to get the correct casing of the characters.

FLag

kalmar{M15S_Th3_S1gN5_4Nd_3NteR_tH3_M4Z3}

p/s: this was fun, i wonder if there is a way to dynamic analyze when it switches to 64-bit mode to save time analyzing the polynomial thing.