This report can be read both on this site, and as its original report form. It is highly recommended that you read the original report form instead because it is better formatted.
The binary in question was provided within a zip file. The source code of the program was not given, and analysis was performed using Ghidra for static analysis and GDB for dynamic analysis. Due to the usage of the vulnerable gets() function which fails to perform boundary checks, the program is vulnerable to buffer overflow exploits.
Attack Narrative
The IP and port on which the vulnerable binary runs is given:
IP
Port
159.65.54.50
31449
Other than this information, no other data is provided.
Binary Analysis
Before attempting to execute the binary, is it essential to first analyze how it works.
Behavior
Upon executing the binary, the user is prompted with an input:
Whatever string the user inputs, the same input gets printed back out. To analyze how this binary works, tools such as GDB[1] (for dynamic analysis) and Ghidra[2] (for static analysis) are used throughout this report.
Ghidra
Many different programs can be used for static analysis, however Ghidra, a tool created by the NSA, is utilized throughout this report because of its capability to translate assembly code into C code for easier analysis. Looking at the output of Ghidra, the following three functions are found:
Within main() the string You know who are 0xDiablos: is printed out before the vuln() function is executed. This function allocates 180 bytes to the buffer local_bc before the vulnerable gets() function is executed with local_bc as the argument. The gets() function is a deprecated function within C due to its inability to perform boundary checks on the user input. The manual for the function states to “Never use this function”[3].
The third function of the binary, namely flag(), was not called by either main() or vuln(). The flag() function checks if a file flag.txt exists. If it does, then it performs an if statement in which it compares the param_1 and param_2 to certain hex values. On condition that this if statement is true, the contents of flag.txt are read out.
GDB
Analysing this function through GDB helps in dissecting what the program is doing on the assembly level:
The aforementioned if statement compares the value of the base pointer + 8 to 0xdeadbeef and the base pointer + 12 to 0xc0ded00d. Therefore, a successful exploit will constitute the control of the foregoing base pointer addresses along with the overwriting of the EIP register to point to the flag() function.
Constructing Exploit
EIP Offset
The offset of the EIP register overwrite must first be determined. Within GDB, in order to provide an input to a program which prompts the user for a string, the desired string must first be echoed into a file. The contents of this file can then be run within the debugger. Hence, using the cyclic function, a pattern of 200 bytes was echoed into a file called eip_overwrite as follows:
The contents of this file are then piped into the program with r < eip_overwrite:
Thus, 188 bytes can be passed into the buffer before the EIP register is overwritten.
Flag() Debug
With the EIP register successfully overwritten, the next step is to control it such that it points to the flag() function. Before determining where this function lies in memory, it is imperative to first establish that this binary is in little endian format:
After finding out that this binary is an LSB executable, the next step is to discover where flag() is in memory. This can be done with the info functions command within GDB:
Note the functions of interest which are in red
Flag() is at 0x080491e2 which in little endian byte format is xe2x91x04x08. Therefore, upon inputting a string of 188 bytes followed by the address of the flag, the program should call the function:
Before running this malicious string, recall that the program exits if the file flag.txt does not exist. This file was simply created using the touch command as follows:
The comparison within the function in question starts at flag+100 (or 0x08049246). This can be found using the disass (short for disassemble) command within GDB:
As expected, the breakpoint at flag+100 was hit. Looking at ebp+0x8, it can be observed that it was not overwritten:
Upon looking at the first 16 bytes of the EBP register, an interesting circumstance can be noticed:
At exactly $ebp, the junk bytes that are present in the malicious string can be seen. Following that is a succession of eight zeroes followed by the value of $ebp+0x8 and $ebp+0xc. This succession of zeroes is particularly interesting as it is not clear what it relates to. Modifying the malicious string by adding four B’s to the end of it and piping it into the program ,reveals an interesting behavior within the binary:
Now, looking at the EBP register, observe the value at $ebp+0x4:
Thus, $ebp+0x8 and $ebp+0xc can now successfully be controlled by appending 0xdeadbeef and 0xc0ded00d in little endian byte format (xefxbexadxde and x0dxd0xdexc0 respectively).
Exploit
Therefore, the final exploit will take the following form:
Piping the contents of eip_flag into the binary and checking the EBP register, it can be seen that ebp+0x8 and ebp+0xc were successfully controlled.
Consequently, the if statement discussed earlier in the Ghidra section will run true. After piping the malicious string into netcat, the flag.txt file located within the server is printed out.
Conclusion
The binary in question was vulnerable to a buffer overflow attack due to the lack of boundary checks performed on user input. The deprecated gets() function was used within the binary despite the security warnings that are associated with it. As a result, memory could be overwritten resulting in behavior that the binary was not written to perform. The following remediations should be strongly considered:
Never use the deprecated gets() function
Usage of this function creates the possibility for security risks that could allow malicious actors to run arbitrary code
Use the secure fgets() function
This function reads user input until a newline character is found or until the buffer gets filled
The aforementioned remediations should be observed as soon as possible. Until this binary is patched, the service running on port 31449 should be disabled.
This challenge was about binary exploitation. There were a total of nine binaries which increased in difficulty after each exploit. Common binary exploitation techniques are discussed in this report including ret2libc, shellcode injection, format string exploitation, among others.
Behemoth is the sequel to Narnia and is rated to be slightly harder. In comparison to Narnia, it involved more reverse engineering exercises and required more knowledge of C. Each binary contained a different vulnerability ranging from PATH environment variable privilege escalation to buffer overflows, format string exploits, and bypassing shellcode filtering.