RII recently had the privilege of sponsoring the Capture The Flag (CTF) event of one of our local BSides chapters. We want to thank the HackUCF club for its wonderfully run event (SunshineCTF) and all of the organizers and volunteers for a great conference that was moved to a purely virtual arena. In this post, we'll describe a little bit about the challenge we donated to SunshineCTF, named Florida Forecaster. This post emphasizes the challenge creation, but does assume some previous knowledge of binary analysis topics.
As an additional part of our sponsorship, we hosted a random drawing to give away two personal licenses of Binary Ninja. This is an excellent tool from our friends at Vector35 and we hope our winners get excellent usage out of it. The winners are:
- Joab Kose
Florida Forecaster Story
This is less of a technical walkthrough of the solution, and rather an overview of the challenge creation and high-level concepts. A more complete technical writeup has already been published at https://bannsecurity.github.io/writeups/2020/11/09/sunshinectf-2020-pwn-florida-forecaster/ if you are interested. Some partial images re-used here.
The theme of this year's CTF was "Floridian Vacations", and as an employee of our Melbourne, FL office, I knew exactly who a good mascot might be: Florida Man. Combining a meme with a hollow reference to cutting edge technology (here, machine learning) seems to satisfy the story portion of the challenge.
We see some basic functionality, including some supposedly "automated" tests, as well as the ability to enter some "forecast parameters" and get a Florida Man forecast (fun fact: all the forecasts are based on actual news stories). In addition to the help string, there is a challenge description is provided on the scoreboard that gives a little more context.
While the story is not really a relevant part of the challenge, there are components of it that are important to the challenge. YouTuber LiveOverflow has a good video on CTF challenge authoring that covers "guessing" vs "not knowing". Certain hints in the story of this challenge should point us in the right direction:
"It's in high demand, so don't spend too much time on it!" This message in the challenge description is included specifically as a tip that time is a relevant portion of the technical solution.
"Hey Rob, you automated those tests, right?" This is a callout to the testing functionality of the program, and likely means this is where we should probably focus.
"Hey, you're taking too long. I'm only going to warn you once..." This string is visible in the binary, and is output when an alarm is raised. We will discuss this in the next section, but "only going to warn you once" is meant to be another hint that we'd like this alarm to happen more than once.
This challenge includes all available mitigations, including ASLR, a non-executable stack, the stack canary, and full RELRO. While this can be common for heap challenges, there is no heap usage in this challenge. The unique portion of this challenge is the signal handler for the SIGALRM - specifically, that the previous signal handler function is stored on the stack, and it is restored in the custom signal handler that prints the "only going to warn you once" message.
A common exploitation scenario is arbitrarily overwriting a function pointer - whether that pointer is in an object, a stack frame, or a global section such as .data or .bss. One function pointer I have never seen used as an overwrite target before is the sighandler_t returned by the signal() function. That seedling of a thought was really the inspiration behind this challenge - and some pretty contrived code had to be written to use the stack value that could be overflown.
Because of the delay between when the stack canary is overwritten and the return of the function (where the stack canary is actually checked), we can wait for the SIGALRM to be raised after the canary has been corrupted. By overflowing into the previous stack frame and corrupting the stored signal handler on the stack, we can wait for the signal to be raised a second time and then usurp control flow of the program to the provided win() function.
Side note: there is also a infoleak in the binary, but it only leaks a static value - this is to bypass PIE and locate the win() function. It is not arbitrary, and thus can't be used to leak the canary value as is often done in other challenges.
While this challenge was not meant to be particularly evil, there was an incidental troll - it is very common for participants to patch the binary to remove alarms so they can debug or analyze the binary uninterrupted. While this is normally a non-issue, it will completely derail anyone trying to solve this particular challenge - where that alarm is integral to the solution. This is another reason why the hints in the story were provided - so the intended solution became the path of least resistance.
Till Next Time!
Events like BSides and CTF competitions are a core part of our community, and we're grateful for the opportunity we had to be involved. This has been a challenging year for many of us, and we appreciate the hard work of everyone involved in these events and adapting to virtual engagements. Hope to see you at the next one!