BuckeyeCTF 2025 - befuddled challenge writeup
2025-11-22
Befuddled was one of the reverse engineering challenges that I got to solve during Buckeye’s 2025 CTF edition. I’ve decided to post a writeup on it, since it’s one of these funny examples of challenges that can take from 1 hour to 20 hours. Thankfully for me it was the former, which I can’t say about the second, harder version of it, befucked.
first up - static analysis
As it’s a reverse engineering challenge, I immediately put the binary into IDA. In the meanwhile I ran the program to see what it actually does:

Seems simple enough, it’s clear that we need to look for a flag in the program and verify it with it. Now let’s see how main looks in IDA:

We can see a clear control-flow obfuscation - which means thay traditional static analysis might not be enough. Imagine tracing each and single one of these calls (and they span for the next 5677 instructions). What’s more to do here is to examine the sub_4013CD function as it appears a lot (4013AD appears often only in the shown fragment of main).
This function pops an address from the stack, which per the x86_64 calling convention is the function’s return address (that is pushed to stack by the caller). Then it pops this address into %r14 register and manipulates it by adding a constant and something else that is stored at an 0x409018 + %r12*8 address. So %r12 stores an index for this structure. It’s a .data fragment which stores preinitialized data. However its contents dont say much as it’s mostly filled with 0xFF bytes and sometimes with something different. But that doesn’t matter that much - what matters more is that we found a “dispatcher” that controls the code’s flow - it takes the current return address, manipulates it and jumps to it through the instructions “push r14, retn”.
We can now each time see what function is actually called each time it calculates the address and jumps to it.
dynamic analysis - listing the states
The program mostly calls the dispatcher, which then calls other functions which end up in the dispatcher after their purpose. The rest of the functions are mostly small ones that manipulate %r12 or do some other minor tasks.
Let’s then write a small program in gdb that will print each address calculated by the dispatcher, to which then the programs jumps. First set a breakpoint at 0x4013DE - where the calculated address is already in %r14. Also lets enable a gdb workaround for stop events - that will not make the debugger die when we want a script to continue execution:
b *0x4013DE
set gdb-workaround-stop-event enabled
Then a simple script, that prints the address in %r14 and continues execution. The silent directive makes it so it doesn’t annouce that it has hit a bp each time:
commands 1
silent
printf "[DISPATCH] Jumping to: 0x%lx\n", $r14
continue
end
Then once we run the program in gdb a huge wall of those prints will be displayed and the program will hang - since it’s waiting for an input of the flag. This means that every single print before that doesn’t matter as it’s not possible that it was comparing our input with a flag. What’s important is this:

This is just a tiny fragment since there is a lot more. We can see some addresses repeat and can make an assumption - since it has to process our input, there must be some kind of a loop. That means that some “states” will repeat and then we will see some unique ones. This ctf’s flag format is bctf{}, so the first 5 characters are for sure correct. Also the states that appear after the binary starts printing “NOPE” can also be ommited.
So we can see the loop begins with address 0x402dd1. And the unique addresses start with 0x402449:

So let’s set a break point few addresses below the first unique one. I chose 0x402abb. The gdb view at this address looks like this:
It can be seen that the input flag is fully on the stack. From this spot I started following the program’s execution carefully:
It usually just kept going through dispatcher but at some point it pushed onto stack some hardcoded value and multiplied it by another hardcoded one. Also after multiplication it became the ‘}’ character. So it retrieves some constants and multplies them. However using this could be hard since an offset in the structure when they are held can be different each time. So i kept following until I found these spots:
First what happens is that the functions pops the value that was the result of previous multiplication into %rdi and our input ‘}’ (0x7d) into %rsi. Then it performs a substraction and stores the result on stack. At first there was no conditional jump, no comparison so I kept going until a snippet that’s present on the second screenshot popped up. Here the function retrieves the top value (after the return address) from the stack and checks if it’s zero with test rax, rax. If it’s zero it jumps. Sooo it seems like the true comparison happens here.
What can be done now? Well the easiest option would be to write a gdb script that prints what’s stored in %rdi and sets %rsi to value of %rdi before substraction. That would be ideal, however for me it didn’t work and gdb kept dying - it’s probably an issue on it’s end as it does tend to be broken. I tried wrestling with pwntools for some time but also I had trouble with them (i know skill issue). So I did it manually as I was defeated using some smarter tools:
break *0x40117b
commands 2
silent
printf "%c", (char)$rdi
exit
And once I retrieved one letter of the flag, I would pass it as the new flag to get another one and on and on…
In the end I got:
Where some characters had to be guessed probably due to malformed printing. For example in the word The, I thought of putting Th5 and it passed in the program, however the flag checker said it was incorrect and I had to change it to Th3.

In general it was a cool challenge, especially that it kept reminding me that I need to start scripting in pwntools.