Sunshine CTF 2020 - Florida Forecaster
This challenge was an interesting use case of bypassing a canary. I don’t recall seeing this specific trick in any other CTFs. In short, you ended up getting control over an exception handler by writing past the stack canary, which would normally stop it. You then simply wait for the exception to happen to get the flag.
Giving this a quick run, we see it’s a menu based pwn challenge.
If you’re like me and left the program open at the prompt for over 30 seconds, you get the following:
The Print Flag Function
Since this binary has all protections turned on, we might need to break ASLR. When I started looking through the program I had forgotten to do a strings run first (whoops). Instead, I opened the binary in ghidra and discovered a function that didn’t seem to have any references to it, but looked like part of the solution:
If I had run strings first, seeing “flag.txt” would have been a giveaway for something i needed to look at. In any case, since this function exists, it’s clear we should likely be using it as part of our solution. We still have the fact that this is a PIE binary and thus we need to figure out how to either leak it’s address or perform a partial overwrite.
Looking at the forecast function I discovered a code path I had not taken:
Code like this should generally raise alarm bells since it’s overly complicated. Obviously they’re doing something here they shouldn’t be doing and the output of the forecast will change based on very constrained inputs. Since most of the inputs place us in the first path starting with “A Florida man”, I’m interested in the second.
The second path prints out a pointer to a function in memory! This is what we need to be able to break ASLR. If we can get that pointer, all we need to do is subtract it’s relative offset from the start of the binary to get where the binary’s base load address is. From the looks of it, the main check is that the two numbers (one positive, one negative) need to be xored together to return 0xc0c0c0c0. You can find many of these, but here’s one example:
Now that we have ASLR destroyed, we’re only missing the ability to someone gain control of execution…
We’ve looked at forecast already and found the leak. Maybe the execution is in the test function? Let’s try a simple stack overflow:
That stack smashing warning shows that we are indeed overwriting the stack pointer (or at least the canary). Looking at the code, the following is responsible for this overwrite:
The data is being read in with a “%s” format string into a buffer on the stack. This is actually something that can be easily caught dynamically or and even statically in this case. This has the same effect as the notorious gets function that you know not to use.
But wait, can’t we use this to incrementally write one more byte at a time, then have the “Received test data” echo it back to us, thus leaking the stack canary? Unfortunately no. This format string will null terminate whatever we give it, and so we won’t be able to read the canary out.
Well we’re part way there… We have an overwrite. But we have no way to leak the canary.. At this point I stalled out for a bit trying to figure out how to leak the canary.
That Darn Alarm
Remember there was an alarm? It was easy to pass up because many pwn challenges in CTFs have alarms. And usually that’s simply to help the organizers not get DoS’d due to hanging connections. However, when looking at the handler I noticed something strange:
It’s not uncommon to post something about the alarm in the handler. However, it is uncommon to register a new handler dynamically and re-register your alarm. So where is that new handler pointer sitting? Let’s look at the cross references to it:
Looks like that address is being written to in main. After renaming this address, I can see what I glanced over in the main function:
The new alarm handler pointer is getting the address of main’s stack! That means, since I have a stack overflow in the test function, I could continue to write until I eventually overwrite the pointer storing the exception handler.
But wait, wouldn’t the canary catch me? In this case the answer is actually no. The canary is only checked at the function epilogue. Since there’s a validation prompt after my input and prior to returning from the function, the canary is actually never checked.
One subtle gotcha in the overwrite of the pointer is that, normally you would overwrite with all 8 bytes (a pointer in 64-bit is 8 bytes). However, in this case we’re reading the input with the “%s” format string. This format string adds an extra null at the end of the input to ensure it’s a properly null terminated string. The good thing here is that in normal user-space 64-bit addresses, the top two bytes will actually be nulls. This has to do with normal memory layouts and saving the upper space for the kernel. The details are not important, but the upshot is that you only have to write the first 6 bytes of the address, and let the format string add your null at the end. If you don’t do this you will overwrite the timeout field, which in this case is sitting right next to the pointer in memory.
The Final Exploit
The final exploit looks like this:
- Leak the address of the binary with the forecast function
- Overwrite the signal handler with a stack overflow in the test function
- Do NOT enter anything at the validation prompt.
- Wait upwards of 30 seconds for the alarm handler to trigger, then 5 more seconds for the second alarm to cause the print flag function to trigger.
- Enjoy your flag.
Let me know what you think of this article on twitter @bannsec!