Abstract

Stack buffer overflow vulnerability is a common software vulnerability that can overwrite function return addresses and hijack program control flow, causing serious system problems. Existing automated exploit generation (AEG) solutions cannot bypass position-independent executable (PIE) exploit mitigation and cannot cope with the situation where the standard output function is not introduced into the program. In this paper, we propose a solution to alleviate the above difficulties: BofAEG, which is based on symbolic execution and dynamic analysis to automatically detect stack buffer overflow vulnerability and generate exploit. We used to capture the flag (CTF) and common vulnerabilities and exposures (CVE) programs for experiments. Results show that BofAEG can not only detect and generate exploits effectively but also implement more exploit techniques and is faster than existing AEG solutions.

1. Introduction

As a pervasive vulnerability in various applications and operating systems, buffer overflow is easily exploited by attackers because languages such as C/C++ do not have instructions to automatically detect buffer overflow, and the time cost of writing code to detect whether a buffer overflow will occur in real time is too great. Stack is one of the most common buffers. First described in detail in 1996, Aleph One [1] described the architecture of the Linux stack and proposed how to use stack-based buffer overflow to implant a piece of code into a process to obtain a shell. Based on this idea, an attacker can create malicious code anywhere in the program’s memory, causing the operating system to crash and reject the service, or even control the program’s entire execution process, thus obtaining complete control of the target host machine. There were 188 CVEs related to stack buffer overflow in the last 5 years [2].

Since the return address of a function is stored in the stack, an attacker can modify the return address to hijack the control flow of the program by exploiting the stack buffer overflow vulnerability. Therefore, program control flow hijacking is the characteristic of stack overflow vulnerability. CANARY [3] is a stack buffer overflow attack mitigation. When it first enters the function, it places a random number canary on the stack and then determines whether the random number has been changed at the end of the function. If it has been changed, it indicates that an attack has occurred. CANARY effectively prevents attacks that only depend on the stack buffer overflow vulnerability.

Traditional stack buffer overflow attack first arranges malicious code in the controllable area and then hijacks the program control flow to the area to execute malicious code. However, with the implementation of the NX (Non-Executable Memory) mitigation [4], the method is invalid. Because NX makes these controllable areas unexecutable. However, ROP (Return-oriented Programming) [5] attack can effectively bypass NX. It is a new type of attack based on code reuse technology, in which attackers extract instruction fragments (gadgets) from existing libraries or executable files to build malicious code. These instructions themselves are located in executable text segments, and they end with ret instructions to realize the convergence of instruction segment execution flows. However, the ASLR (Address Space Layout Randomization) [6] system mitigation can effectively prevent the attacker from getting the address of these instruction segments by randomizing the layout of linear areas such as heap, stack, and shared library mapping. Further, the PIE (position-independent executable) mitigation makes the program change the load base address every time it is loaded so that the gadgets located in the program itself are also invalid. For programs without PIE protection, the base address of each load is fixed, usually 0 × 400000 on 64 bits. On the contrary, the base address of programs protected by PIE is different every time they are loaded.

The existing automated vulnerability detection methods, such as fuzzing [711], can detect a large number of software vulnerabilities, but cannot verify the exploitability of the generated vulnerabilities. Therefore, automated exploit generation of software vulnerabilities has become a research hotspot in the field of software security. In the field of automated exploit generation (AEG), the accuracy and reliability of symbolic execution techniques make it an important tool for automated program analysis. Symbol execution [12] is a program analysis technology, which can get the input for specific code areas to be executed by analyzing programs. When using symbolic execution to analyze a program, the program will use symbolic values as input, instead of the specific values used in general program execution. When the target code is reached, the analyzer can get the corresponding path constraints, and then get the specific value that can trigger the target code through the constraint solver. Through this method, we can get the input that triggers the stack buffer overflow vulnerability.

In this paper, we propose an effective method BofAEG to automatically detect and generate exploit of ELF x64 binaries with stack buffer overflow vulnerabilities. Because the stack buffer overflow vulnerability is characterized by overwriting the return address and hijacking the program control flow, BofAEG first checks whether there is a win function (backdoor) in the program. Then if the program is protected by PIE, BofAEG attempts to trigger the win function directly or look for address leakage. Next BofAEG reaches the exact location of the stack buffer overflow vulnerability through symbolic execution and obtains all controllable symbolic addresses. Finally, BofAEG applies different exploit techniques according to whether the program contains win functions and is protected by PIE. Because there is no real memory information in symbol execution, dynamic analysis is needed to determine whether the program memory state meets the conditions when applying the exploit technology.

The contributions of this paper are summarized as follows:(1)We study and summarize the characteristics and difficulties of automated exploit generation of stack buffer overflow vulnerability, and propose a solution, BofAEG.(2)We use CTF and CVE programs for experiments, and the experiments show that BofAEG can effectively perform automated detection and exploit generation for stack buffer overflow vulnerability. Compared with the existing solutions, BofAEG can deal with more situations and generate exploits more quickly.(3)We implement six exploit techniques for the stack buffer overflow vulnerability and prove that the stack buffer overflow vulnerability is highly harmful and can bypass most of the existing exploit mitigations.

The remainder of this paper is structured as follows. Section 2 covers related work. Section 3 describes methods for automated stack buffer overflow vulnerability detection and exploit generation, including stack buffer overflow vulnerability and its characteristics, two ways of ROP attack, and the method flow of BofAEG. Section 4 provides comparative experiments between BofAEG and existing AEG solutions. Section 5 discusses the limitations of BofAEG and our future work. Section 6 concludes the paper.

In recent years, various AEG solutions for different objectives have emerged. The solutions related to heap vulnerability are [1317]. And [1821] are solutions for format string vulnerability. In addition to the software level, [2225] go deep into the Linux kernel. Similarly, AEG solutions exist not only in Linux system, but also in Android system [2629].

In this paper, we take ELF x64 binary file under Linux system as the target to automatically detect and generate exploit for stack buffer overflow vulnerability. Heelan et al. [30] use binary instrumentation to do taint propagation and gather runtime information and generate exploits by checking whether the EIP register is affected by the taint. AEG [31] first preprocesses the source code to generate bytecode. Based on bytecode, AEG uses conditional symbol execution to find vulnerable functions, objects covered by overflow, and paths that trigger bugs. At the same time, dynamic binary analysis is used to extract runtime information, and finally, exploits are generated. They extended this method to Mayhem [32] to support binaries. CRAX [33] also starts at the crash point, symbolically executing the program to find exploitable states and generate exploits. Padaryan et al. [34] overcome ASLR on this basis. Xu et al. [35] then extended the method to overcome NX. Although the solution considers NX, it cannot solve the problem that ASLR and NX are turned on at the same time and relies on the program to contain the “jmp esp” instruction to complete the exploit. Under the condition that ASLR and NX are turned on at the same time, Zeratool [36] achieves the purpose of hijacking the program control flow to the win function and using ROP attacks to execute commands. However, it does not consider that the program is protected by PIE and relies on the existence of standard output functions (puts, printf, etc.) in the program when using ROP attacks.

3. Automated Stack Buffer Overflow Vulnerability Detection and Exploit Generation

3.1. Stack Buffer Overflow Vulnerability

Stack buffer overflow means that the number of bytes written by the program to a variable in the stack exceeds the number of bytes applied by the variable itself, resulting in the change of variables in its adjacent stack. For the ELF x64 program compiled by C/C++ language, the execution process of the program is the calling process of the function. There is a nested call relationship between functions, and each unfinished function occupies a section of stack space, which is called a stack frame. The stack frame is inserted and popped out of the stack with the call of the function. Local variables and return addresses are stored in the stack frame. A typical stack buffer overflow vulnerability is shown in Figure 1. The local variable s only applies for the size of 0 × 40 bytes, but the program calls fgets to read it up to 0 × 100 bytes, resulting in a stack buffer overflow. Its stack frame is shown in Figure 2. Reading 0 × 100 bytes from s will overwrite the local variable v2, the caller’s stack frame address, and the return address of vuln. This makes the program control flow that should have returned the main function hijacked.

3.2. Ret to Libc

Ret to libc is a exploit technique mainly aimed at dynamically linked programs. Because the program is dynamically linked, it will load libc.so at runtime. Libc.so is a dynamically linked version of the runtime glibc in the C language library under Linux. And it contains a large number of functions that can be used, including system, execve. Therefore, the attacker can gain control of the target program by finding the addresses of these functions in memory and using the stack buffer vulnerability to overwrite the return addresse to these functions.

With ASLR and NX turned on, the load base address of libc.so is random, and the attacker cannot directly execute malicious code by using the controllable memory address in the program. Take tamilctf2021_name as an example, the memory address mapping of its runtime is shown in Figure 3. The address selected by the blue box (0x7f11eb94f000) is the load base address of libc.so, which is random every time the program runs. Therefore, like the system function and “/bin/sh” string in libc.so, their addresses are also random. The key to ret to libc is to first obtain the load base address, and then hijack the control flow to the system function located in libc.so.

Therefore, the stack frame layout of applying ret to libc to tamilctf2021_name is shown in Figure 4. The left is the first time to exploit the stack buffer overflow vulnerability. It sets plt_puts as the parameter and jumps to got_puts for execution through pop rdi; ret, a gadget located in the program text segment. The address of the libc.so function introduced by the program is stored in the GOT table, and the PLT table transfers the program control flow to the actual function by referencing the function address in the GOT table. The right shows that after leaking the address, it places the “/bin/sh” string and the address of system in the stack.

The control flow graph of ret to libc is shown in Figure 5. Red addresses represent known program code addresses, and purple addresses represent random addresses in libc.so. It first leaks the address of puts in libc.so by using the puts function introduced in the program, so that the address of libc.so loaded in the program can be obtained. Then it transfers control flow back to vuln_func to trigger the stack buffer overflow vulnerability again. Finally, after getting the load address of libc.so, it sets the function parameter to the address of “/bin/sh”, and jumps to the system for execution, where “/bin/sh” and system are located in libc.so. That is, the program control flow is hijacked from the program code segment to libc.so for execution.

3.3. Ret to Dl-Resolve

In the case that the standard output function is not introduced into the program, how can the attacker obtain the load address of libc.so? The answer is that there is no need to obtain it. Under dynamic linking, there are a lot of function references between program modules, and it will take a lot of time to dynamically link all functions before the program starts to execute. Therefore, Linux adopts the delayed binding mechanism, the basic idea of which is that the function is bound (symbol search, relocation, etc.) when it is first used, and it is not bound if it is not used. The address in the GOT table is obtained by relocation through _dl_runtime_resolve when the function is used for the first time. Therefore, the main idea of ret to dl-resolve is to forge the relocation structure of the target function (including function name, relocation table) in the readable and writable memory. Then it uses _dl_runtime_resolve to resolve the target function (such as system) and hijack the program control flow to the target function. This technique is closely related to the ELF file structure [37]. Fortunately, the ret2dl-resolve module of pwntools [38], a well-known exploit framework, makes it easy for us to generate such payloads. In summary, while this technique does not rely on the standard output function, it relies on the standard input function to store fake data into readable and writable memory.

3.4. BofAEG

We use angr [39] for symbolic execution and getting input that triggers the stack buffer overflow vulnerability, and radare2 [40] for dynamic execution and analysis of binary programs. The flow chart of the entire method is shown in Figure 6. First, BofAEG takes a binary program as input and then calls the find_win function to check if there is a win function in the program. The win function refers to a hacker method that bypasses the security control of software and obtains access rights to a program or system from a secret channel. For CTF programs, there are mainly two types of win functions. One is to read the contents of the “flag” file into memory and print it through standard output, as shown in Figure 7; the other is to execute the system function and return an interactive shell, such as Figure 8.

Secondly, BofAEG determines whether the binary is protected by PIE, and makes a choice based on whether it contains the win function. If a win function is included then explore_to_win is called to use symbolic execution to get the input to the win function. Figure 9 shows a program protected by PIE with a flag win function. The local variable s1 has a stack buffer overflow vulnerability, but due to PIE, the attacker cannot obtain the program load address and modify the return address to the flag win function. Fortunately, the flag win function can be executed when the value of the local variable v6 is 0xDEADBEEF. Finally, explore_to_win can get the payload that uses the s1 buffer overflow to modify the v6. However, if the program is protected by PIE and there is no win function, BofAEG calls find_leak to try to obtain the program load address (text_addr) and libc.so load address (libc_addr). Figure 10 shows the leakage of text_addr and libc_addr in the program. Find_leak uses dynamic analysis to identify the features of text_addr and libc_addr in the standard output stream of the program (text_addr: 0 × 555555, libc_addr: 0x7fff). Then, it is judged whether the identified address is in the program or libc.so loading memory address range. With this method, find_leak can get the loading address of the program or libc.so to bypass PIE/ASLR.

Thirdly, BofAEG calls the find_stack_bof function to detect stack buffer overflow vulnerability. find_stack_bof uses symbolic execution to explore paths, and because the shorter the path that triggers the vulnerability, the easier it is to analyze, it uses a breadth-first search strategy during exploration. Since the stack buffer overflow vulnerability is characterized by hijacking the program control flow (that is, the rip register is modified), find_stack_bof checks whether the rip is symbolized after each symbolic state transition. If rip is symbolicated, the program triggers a stack buffer overflow vulnerability. At the same time, find_stack_bof records the controllable symbolic memory address, in preparation for the final use of symbolic constraints to generate exploit.

Finally, the goal of binary exploit is to get shell interaction and execute arbitrary commands (usually to make the program execute system (”/bin/sh”)). Therefore, the get_shell function implements six exploit techniques based on the stack buffer overflow vulnerability characteristics:(1)Explore to win. This exploit technique uses symbol execution technology to try to get the input that triggers the win function when the program is protected by PIE and there is a win function.(2)Ret to win. This exploit technique achieves the purpose of hijacking the program control flow to the win function by overwriting the return address with the address of the win function when the program has a win function and the address of the win function is known.(3)Ret to system. This exploit technique is implemented when the program is protected by PIE and there is a libc.so address leak (that is, libc_addr is obtained). Because the program loading address is unknown, the functions introduced in the program cannot be used, so it is necessary to directly use the gadgets and functions in libc.so. Because libc.so contains the “/bin/sh” string and the system function, ret to system can directly use the ROP attack to execute system (”/bin/sh”).(4)Ret to one_gadget. one_gadget [41] is a tool used to find the address in libc.so that can lead to execve (”/bin/sh”, NULL, NULL) when the program registers and memory state meet certain conditions. Figure 11 shows one_gadget. Ret to one_gadget uses dynamic analysis to obtain the register and real memory information of the program when the stack buffer overflow vulnerability is triggered, and then obtains the applicable one_gadget address according to this information. Similar to ret to win, ret to one_gadget modifies the return address to the address of the applicable one_gadget.(5)Ret to libc. As described in 3.2, this technique is one of the most common ways of ROP attacks. When the program load address is known (not protected by PIE or text_addr is obtained) and the standard output function is introduced, this technique first calls the standard output function to leak the libc.so load address. It then triggers the stack buffer overflow vulnerability again by hijacking the control flow to the vulnerable function. Finally, the program control flow is hijacked to the system function in libc.so.(6)Ret to dl-resolve. As described in 3.3, this technique does not rely on the standard output function, but on the standard input function to store fake data into readable and writable memory. It uses _dl_runtime_resolve to resolve the fake target function into the program and call the target function.

4. Evaluation

Based on the main idea of Section 3, we implemented this method in 700 lines of python, specifically using angr v9.1.11752 for symbolic execution and radare2 v5.6.4 for dynamic analysis. The experiments were carried out on an Ubuntu 18.04 64 bit machine with Intel(R) Core(TM) i7-8700 CPU @ 3.20 GHz, 16G RAM, and 5.4.0 kernel version.

We use CTF and CVE programs with stack buffer overflow vulnerabilities to perform experiments, most of them can be found in CTFTIME [42]. A CTF program can be thought of as a simplified version of a real-world program, used to more concisely and demonstrate the principles of the vulnerability. The difference is that the CTF program is used to test the relevant abilities of the players in the competition, so the vulnerability can be exploited, while the real-world program has higher complexity, and even if there is a vulnerability, it may not be exploitable.

According to the method introduced in Figure 6, we use whether the program is protected by PIE and whether it contains win functions as the selection criteria to demonstrate the effectiveness of BofAEG as much as possible, which is divided into the following four types: 1. Not protected by PIE, but containing win functions. 2. Not protected by PIE and does not contain win functions. 3. It is protected by PIE but does not contain win functions. 4. It is protected by PIE and has win functions. Besides, We assume that the system has ASLR turned on and these programs are protected by NX. Table 1 shows the results of Zeratool [36] and BofAEG on 24 CTF programs and 5 CVE program. BofAEG can automatically detect stack buffer overflow vulnerabilities and generate exploits for 22 of them, while Zeratool can only successfully exploit 7 of them. And the total time for BofAEG to complete detection and exploitation is less than Zeratool. Among the 7 programs that BofAEG cannot exploit, 2 programs (blue) successfully detect stack buffer overflow vulnerability, and the remaining 5 program (red) cannot detect the vulnerability.

For programs containing win functions, Zeratool only supports function-level win function calls, while BofAEG supports function-level and block-level win function calls. Figure 8 is a function-level win function, while Figure 12 is a block-level win function. If the program control flow is hijacked directly to the main function containing win function, the conditional check of local variable v5 will not be automatically bypassed. Therefore, BofAEG chooses a more fine-grained block-level win function call and directly hijacks the program control flow to address 0 × 4011EF to bypass the condition check.

For programs that do not contain win functions but introduce standard output functions, both Zeratool and BofAEG can better implement the ret to libc exploit technique. However, BofAEG can implement the ret to dl-resolve exploit technique even if the program does not introduce a standard output function. In addition, for programs protected by PIE, BofAEG can effectively find address leaks and apply exploit techniques.

5. Discussion

BofAEG overcomes some of the difficulties faced by current research on automated detection and exploit generation of stack buffer overflow vulnerability. However, BofAEG still has some limitations and issues to overcome, so we discuss these limitations and future work.

5.1. Limitations

It can be known from the experiments that BofAEG still has programs that cannot be successfully detected and exploited, and these programs illustrate the limitations of BofAEG.(1)Limitations of symbolic execution. Since BofAEG uses symbolic execution to automatically detect stack buffer overflow vulnerability, obtain input that accurately reaches the vulnerability point, and generate the final exploit, it is impossible to avoid path explosion and constraint solving problems faced by symbolic execution. For example, because the input in lexingtonctf2021_madlibs is formatted and spliced by multiple “%s” in sprintf, the symbol constraints cannot be expressed correctly; the symbolic execution fails to explore the stack buffer overflow vulnerability in the more complex cve-2004-1257_abc2mtex due to the path explosion problem. Therefore, it is difficult for BofAEG to detect stack buffer overflow vulnerabilities for CVE programs with high complexity.(2)Limitations of a single vulnerability type. Although the stack buffer overflow vulnerability is highly harmful, CANARY can effectively mitigate the harmfulness of this vulnerability. However, since CANARY has been randomized and stored in the program memory when the program is started if the value of CANARY can be leaked through other vulnerabilities, the stack buffer overflow exploits can also be completed. cyberctf2021_harvester contains both stack buffer overflow and format string vulnerabilities. The attacker needs to leak CANARY through the format string vulnerability, and then use the stack buffer overflow vulnerability to complete the exploitation.

5.2. Future Work

In the future, we will conduct further research on the above limitations to try to overcome them.(1)Optimize symbolic execution. Use static analysis techniques to guide symbolic execution to mitigate the path explosion problem, while trying to optimize the symbolic processing function so that it can correctly generate symbolic constraints.(2)Combine multiple vulnerability types. Attempt to exploit in combination with other vulnerability types, such as format string vulnerability. This requires detecting all of these vulnerabilities and using some technology to manipulate them, which is not simple.

6. Conclusion

In this paper, we introduced the stack buffer overflow vulnerability, which is caused by a program writing more bytes to the buffer variable on the stack than it requested for the buffer size. The stack buffer overflow vulnerability can overwrite the return address of the function to achieve the purpose of hijacking the control flow of the program. Based on this feature, we implemented BofAEG, which uses symbolic execution and dynamic analysis to automatically detect stack buffer overflow vulnerability and generate exploit. The results show that BofAEG can not only detect and generate exploits effectively but also implements more exploit techniques and is faster than existing AEG solutions. The source code of BofAEG and the test cases used in the experiments are available on github [43].

Data Availability

The source code of BofAEG and the test cases used in the experiments are available on github (https://github.com/Kirito0/bof_aeg).

Conflicts of Interest

The authors declare that they have no conflicts of interest.

Acknowledgments

This work was supported by National Key Research and Development Program of China (no. 2018YFB0204301) and the National Natural Science Foundation of China (no. 61472439).