Bait and Switch
Writeup for Bait and Switch (Rev) - K17 CTF (2025) π
Description
This store promised me a free gift if I downloaded their app, but it's not working >:(
Solution
We'll follow the usual reversing methodology; basic file checks, static analysis, dynamic analysis.
Basic File Checks
It's 64-bit linux binary, not stripped (easier to reverse).
file bait-and-switch
bait-and-switch: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=38f8c17f0aa216ee86c7f2d76c5f6737250e7cae, for GNU/Linux 3.2.0, with debug_info, not strippedI run the program, expecting the usual terminal UI but what's this? π²
OK, this looks like a cool reversing challenge! π
Testing the basic functionality, I add an item to the cart.
Clicking the β button on the navbar takes us to a "redeem a gift" screen, but since the 1337 value is outside the permitted range, we can't verify we are human π€
Static Analysis
Opening the binary in ghidra reveals a lot of functions, and references to golang. There's so many that it took ghidra forever to do the auto analysis.
Luckily, we did some basic file checks and we already know what functionality to investigate first. I tried searching for strings "human", "verif", "1337" etc, but no luck. I also see mentions of crypto - maybe the flag is encrypted, but there's no network element (I already ran wireshark).
We can throw the main() function at ChatGPT and ask if there's a better way to reverse go binaries (I suspect there is).
OK, now we have some functions to search for (I guess we could have searched "main" π). I also notice that PIE is disabled, maybe we can break at these functions in GDB?
Returning to ghidra, I notice a function that sounds interesting; main.reveal π Checking the references to that function, it is called by main.makeGiftPage.func3 but we probably can't jump directly to it, as there's some setup (main.genBytes()).
Dynamic Analysis
Let's open pwndbg and set a breakpoint at main.reveal
I thought/hoped the code might be executed during setup, but not actually revealed/presented on screen. However, setting a breakpoint at the reveal call did not trigger anything. Trying to work backwards, I spot an interesting snippet in makeGiftPage.func3
It checks if a value equals 4, and another value equal 0x37333331. What's 0x37333331 converted to ASCII? It's 7331, aka our target verification code (endianess revered). So this is the condition responsible for checking that our code is 4 digits long and equals 1337! We want to break at each of these conditions (CMP instructions):
Set those breakpoints in GDB and run the application.
We hit the first breakpoint at 0xb43770, let's check the condition.
It's comparing [r8 + 0x78] to 4 - what does that address currently hold?
It's 3 (the max allowed digits), let's change it to 4
Verify it's looking good β
We also need the code to actually be 4 digits (1337) so let's continue to our next breakpoint at 0xb4377f
It's comparing [r10] to 0x37333331 - what does that address currently hold?
It's 0x333331 because we are one digit short. Let's do another live modification.
Verify it's also looking good β
Hit continue and boom - we did it! π₯³
It's not possible to copy and paste the flag, so here's a shortcut to find it in memory.
TLDR; we used some static analysis and live debugging to bypass the 3 digit limit and submit the correct 4 digit code, receiving the flag. I'm not sure if this is the most efficient, or intended path to solution - let me know if you solved it differently!
Flag: K17{i_d0n't_th1nk_fi$h_lik3_w@sabi}
Last updated