From proof-of-concept to exploitable
Exploitability assessment of vulnerabilities is important for both defenders and attackers. The ultimate way to assess the exploitability is crafting a working exploit. However, it usually takes tremendous hours and significant manual efforts. To address this issue, automated techniques can be adopted. Existing solutions usually explore in depth the crashing paths, i.e., paths taken by proof-of-concept (PoC) inputs triggering vulnerabilities, and assess exploitability by finding exploitable states along the paths. However, exploitable states do not always exist in crashing paths. Moreover, existing solutions heavily rely on symbolic execution and are not scalable in path exploration and exploit generation.
In this paper, we propose a novel solution to generate exploit for userspace programs or facilitate the process of crafting a kernel UAF exploit. Technically, we utilize oriented fuzzing to explore diverging paths from vulnerability point. For userspace programs, we adopt a control-flow stitching solution to stitch crashing paths and diverging paths together to generate exploit. For kernel UAF, we leverage a lightweight symbolic execution to identify, analyze and evaluate the system calls valuable and useful for exploiting vulnerabilities.
We have developed a prototype system and evaluated it on a set of 19 CTF (capture the flag) programs and 15 realworld Linux kernel UAF vulnerabilities. Experiment results showed it could generate exploit for most of the userspace test set, and it could also facilitate security mitigation bypassing and exploitability evaluation for kernel test set.
Due to the success of automated vulnerability discovery solutions (e.g., fuzzing), more and more vulnerabilities are found in real world applications, together with proof-of-concept (PoC) inputs. As a result, more and more human resources are spent on assessing vulnerabilities, e.g., identifying root causes and fixing them. It thus calls for solutions to automatically assess the severity and priority of vulnerabilities.
Vulnerability assessment, especially exploitability assessment, is important for both defenders and attackers. Attackers could isolate exploitable vulnerabilities and write exploits to launch attacks. On the other hand, defenders could prioritize exploitable vulnerabilities to fix first, and allocate resources accordingly. Moreover, defenders could learn from the exploits to generate IDS (Intrusion Detection System) signatures, to block future attacks.
A straightforward way to assess a vulnerability is analyzing the program state at the crashing point, i.e., the instruction leading to program crashes or security violations, which could be caught by a sanitizer . For example, Microsoft’s !exploitable tool inspects all instructions in the crashing point’s basic block, and searches for known exploitable patterns, e.g., control transfer instructions with tainted targets. HCSIFTER takes an extra step to recover the data corrupted by heap overflow, enabling the program to execute more code after the crashing point, and thus provides more reliable assessments. However, these solutions rely on heuristics to determine the exploitability of vulnerabilities, and thus are inaccurate sometimes. Moreover, they could not provide exploit inputs to prove the exploitability.
The ultimate way to assess the exploitability of a vulnerability is generating a working exploit. But crafting an exploit is typically regarded as a time-consuming manual process requiring security knowledge.
Several prototype approaches to automatically generating exploits have been proposed. Sean Heelan proposed a prototype in his thesis, using dynamic analysis and symbolic execution to generate exploits for classic buffer overflow vulnerabilities. AEG and Mayhem provide end-to-end systems to discover vulnerabilities and automatically generate exploits when possible, for source code and binary respectively. Q (Schwartz et al. 2011) and CRAX could generate exploits for binaries given PoC inputs. However, these solutions are insufficient and could only solve a small number of problems. For example, machines developed in CGC could only solve in total 26 out of 82 challenge programs in the Final Event. Most solutions could not exploit heap-based vulnerabilities.
For OS kernel which has higher complexity and scalability, it is not suitable for fully-automated exploit generation. This is mainly due to the fact that state-of-the-art program analysis techniques have many limitations. However, we can still use semi-automated techniques to facilitate exploitability evaluation by easing the process of exploit crafting.
There are several challenges need to be addressed for both fully-automated and semi-automated exploit generation:
Challenge 1: Exploit derivability issue As pointed in , once memory corruption vulnerabilities are triggered, the victim program’s state machine turns into a weird (state) machine. Exploitation is actually a process of programming the weird machine to perform unintended behavior. It is extremely important to set up the initial state of this weird machine in order to exploit it.
However, PoC inputs (e.g., provided by fuzzers) could corrupt some data and lead weird machines to non-exploitable initial states. For example, the program may exit soon after the crashing point due to some sanity checks. So, AEG solutions have to search for exploitable states not only in crashing paths taken by PoC inputs, but also in alternative diverging paths. In OS kernel, the diverging paths cause different kernel panic. Generating an exploit for a kernel UAF vulnerability also needs to vary the context of a kernel panic and explore exploitability in them.
This is known as exploit derivability, one of the core challenges of exploitation
Challenge 2: Symbolic execution bottleneck Existing solutions heavily rely on symbolic execution to explore program paths (e.g., for vulnerability discovery), or perform reasoning (e.g., for test case and exploit generation). AEG and Mayhem utilize symbolic execution to explore paths reachable from the vulnerability point and search for exploitable states, able to mitigate the aforementioned exploit derivability issue. However, symbolic execution has scalability issues and performs poorly in exploit generation.
First, it faces the path explosion issue when exploring paths, and consumes too many resources even when analyzing only one path. Second, it gets blind to certain exploitable states after concretizing some values. For example, it has to concretize symbolic arguments of memory allocations and symbolic indexes of memory access operations in a path, in order to model the memory states and enable exploring following sub-paths. But the concretized values could lead to non-exploitable memory states.
To solve the exploit derivability issue, we must search exploitable states in diverging paths not only crashing paths. However, symbolic execution which is heavily used in existing solutions has several severe challenges, and is not suitable for path exploration or exploitable state searching, especially for heap-based vulnerability or UAF in OS kernel So instead of symbolic execution, we use fuzzing to explore diverging paths.
First, we use dynamic analysis to analyze the vulnerabilities and collect some runtime information in the crashing path. In addition, we inspect corrupted memory objects (denoted as exceptional objects), and objects that can be used to locate the exceptional objects. Then we use oriented fuzzing to search alternative diverging paths for exploitable states based on the information collected before. Finally, we try to synthesize new EXP inputs to trigger both the exploitable states in diverging paths and vulnerabilities in crashing paths. In certain cases, we can directly generate working exploits. But it is not guaranteed. The complexity of OS kernel is far beyond the ability of current constraint solver. For OS kernel, it is not for the purpose of fully automating exploit generation. Rather, we leverage a lightweight symbolic execution to explore exploitability under different contexts.
Results We have build a framework Revery, able to generate working control-flow hijacking exploits for userspace programs. We also build a framework FUZE, able to evaluate the exploitability of kernel Use-After-Free vulnerabilities.
We evaluated Revery it on 19 CTF (Capture The Flag) programs. It demonstrated that Revery is effective in triggering exploitable states, and could generate working exploits for a big portion of them. More specifically, Revery could generate exploits for 9 (47%) out of 19 programs, while existing open source AEG solutions could not solve any of them. Furthermore, it could trigger exploitable states for another 5 (26%) of them.
We implement FUZE on a 64-bit Linux system by extending a binary analysis framework and a kernel fuzzer. Using 15 real-world kernel UAF vulnerabilities on Linux systems, we then demonstrate FUZE could not only escalate kernel UAF exploitability but also diversify working exploits from various kernel panics. In addition, we demonstrate FUZE could even help security analysts to craft exploits with the ability to bypass broadly-deployed security mitigation such as SMEP and SMAP.