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.
Narnia
An analysis on the exploitation of vulnerable binaries.
The source code of each program was given, however throughout this report each program will be treated as if we are not given this information. This approach is taken so as to replicate real-world environments in which an attacker most likely would not have knowledge on the source code of the binary he or she is trying to exploit.
This penetration test resulted in the successful exploitation of all nine out of nine binaries. Among the vulnerabilities were the following: passing unsanitized input into functions, failure to check boundaries, using insecure functions, and unnecessarily disabling the NX bit. Remediations are outlined in the Conclusion section where specific vulnerabilities were described more in detail. All users except root were compromised, and the password for each compromised user was retrieved:
Username
Password
narnia0
narnia0
narnia1
efeidiedae
narnia2
nairiepecu
narnia3
vaequeezee
narnia4
thaenohtai
narnia5
faimahchiy
narnia6
neezocaeng
narnia7
ahkiaziphu
narnia8
mohthuphog
narnia9
eiL5fealae
Attack Narrative
Each binary gets increasingly harder. For every challenge, I have downloaded each binary by copying its base64 or base32 data on my attacking box. This was done to allow a further analysis into the binary by allowing the usage of pwndbg[1], Ghidra[2], and other tools that are not present on the target machine.
Narnia 0
We are given the credentials for the narnia0 user, and with it we can ssh into the box.
Binary Analysis
Before trying to exploit the first binary by testing buffer overflows, we will check the security of the binary with the checksec command:
The “Arch” row shows that this binary is a 32 bit program and whose endianness is little-endian. Additionally, we can see that NX (non-execute), the bit responsible for not allowing writable memories to be executed, is enabled. This means that we cannot inject shellcode into the function. We can get a little more information about the binary by using the file command:
Note how this file is not stripped which means it will contain debug information regarding symbols and functions. This will give us a little bit more information as to what is going on with the binary when we try to reverse engineer it.
Running the program, we can see that it is asking for a certain value in the function to be changed.
Attempting to write the four letter word “test” to the buffer proves to be an inadequate length for overflowing the buffer as the value did not change.
Buffer Overflow
We can verify that this value can be modified by attempting to flood the buffer with a long string of characters:
Observe that the value has changed from 0x41414141 to 0x61616166 confirming that there is a buffer overflow vulnerability. To calculate the offset, the -l flag can be utilized in the pwn command:
Seeing as the offset is 20 bytes, it is possible to input up to 20 bytes into the buffer before the value gets changed. Thus the payload will incorporate a string of 20 bytes followed by 0xdeadbeef in little endian which is xefxbexadxde. Conducting this attack reveals the following:
Upon further thought into the reason for not receiving a shell, it came to mind that perhaps the shell is dying with the process of piping the python command into the narnia0 binary. It is possible that stdin is attached to this process and therefore the shell immediately dies. Appening ;cat - to the end of the command proves to work (this is because cat - outputs stdin).
Commands are successfully being executed inside the /bin/sh shell
Looking at the source code of the program, we can confirm that the aforementioned analysis of the binary was correct:
Narnia 1
Now with a shell as the narnia1 user, we have the necessary permissions to execute the next binary:
We can see that the binary is expecting an environment variable called EGG. The program states that it will execute this environment variable, hinting at the fact that this binary may be vulnerable to an environment variable buffer overflow[3]. Before attempting a buffer overflow, we can provide a simple string to the EGG environment variable to see how the binary is meant to behave:
Binary Analysis
After only providing one byte, the program experienced a segmentation fault. To further understand how this binary works, a long string of A’s can be exported to determine where the content of the environment variable is in the buffer (this was performed locally so as to have the ability to analyze with pwndbg):
We get a segmentation fault as expected, however the EIP register is not getting overwritten (an address of 0x41414141 was expected, but instead it is 0xffffddf3). It is possible that the program is using the getenv() function[4] without storing the environment variable in a buffer.
Exporting Shellcode into the Environment Variable
As can be seen from the segmentation fault error, the program is failing to validate the size and / or content of the environment variable. The program earlier stated that it will execute whatever is inside the EGG environment variable. The checksec command can be used to determine if the binary could execute shellcode:
Seeing as NX is disabled, the program might execute shellcode upon exporting shellcode to the EGG environment variable.
There are many different shellcodes to use, but for the purpose of this exercise I chose the /bin/sh shellcode from here[5]. However, exporting this shellcode into the EGG environment variable and executing the program proves to not work:
I do not know why this particular shellcode does not work. However, trying a shellcode[6] that executes /bin/bash does work:
Source Code
Note how the ret variable is not assigned a buffer. This is why the content of the environment variable was not seen in the ESP register during the analysis in pwndbg.
Narnia 2
Using the credentials obtained for the narnia2 user, we can execute the narnia2 binary.
Looking at the usage of the program, we see that it expects an argument. Inputting an argument of “A” just makes the program print out the same character. In essence, the program spits out whatever we put in. As usual, we will analyze the binary on a local attack box to understand it better:
This is a 32 bit binary. It is not stripped which means the debug symbols will still be present within the binary. Furthermore, NX is disabled so we might be able to inject shellcode into the buffer and have the binary execute it. To detect a buffer overflow vulnerability, a large string of bytes were sent:
Binary Analysis
We can see that the program errors out with a “Segmentation fault” error. It is essential to investigate further into what might be happening by using a debugger program such as gdb. There are other great debugger programs such as radare2, IDA, Ghidra, among many others, and each one of them has their strengths and weaknesses (gdb and radare2 tend to be very strong dynamic analysis debugger programs, while Ghidra and IDA are more useful for static analysis).
Calculating EIP Offset
After running the program in gdb and providing 1000 A’s as the argument, the EIP register was successfully overwritten to 0x41414141. To find the offset, the cyclic function can be used as follows:
Observe that the EIP register has changed in value causing the instruction pointer to return to an unexpected address and crash
Seeing that the EIP register is now 0x62616169, the offset can now be calculated with the -l flag:
Thus, 132 bytes can be passed before overwriting the EIP register. We can view what is inside the stack by accessing the ESP register. This register is responsible for pointing to the top of the stack.
How the Buffer Relates to the Stack
The buffer is where data is temporarily stored, and it is located in the RAM (random access memory) of the computer. When there is improper validation as to the content and size of the buffer, the program can experience an overflow in which inputted data floods the memory of the program.
[7] A visual image of how a buffer overflow attack can overwrite memory
As more data gets inputted into the buffer, the stored data of the program (located in the stack) gets overwritten in the following order:
Local variables
Saved registers
Return address
Function arguments (parameters)
[8] A simplified image on how the buffer relates to the stack
When a program allocates a fixed number of bytes into the buffer, the memory of the buffer will end up spilling into the EBP (base pointer), ESP (stack pointer), and EIP (instruction pointer) registers. The EIP register will hold the return address, while the ESP register contains the data of the program. The EBP register is typically reserved as a backup for the ESP in case the ESP is modified during execution of a function (note that the EBP register can be overflowed as well).
Constructing a Payload
Now with the knowledge of the EIP offset (132 bytes), we can construct a payload that will look like the following:
In regards to the payload, it is important to emphasize what is the purpose of a NOP sled and what it is. A NOP sled is a series of NOP (no operation) bytes, which is an instruction that occupies space in memory, but tells the program to not do anything. The purpose of a NOP sled in binary exploitation is to allow a greater leniency when determining the proper address to flood the EIP register with. When the shellcode is put after the NOP sled and the instruction pointer is pointing to somewhere within the bounds of the NOP sled, the program will essentially go through each NOP instruction until it executes the shellcode.
Binary Exploitation
POC
Before the attack was conducted on the target machine, the payload was first executed on the attacking box so as to get a better view as to how to correctly format the payload using pwndbg.
Note how the EIP register was successfully overwritten with 4 B’s
After executing this payload, we can view where in the EBP register lies the payload:
As we can see, the junk bytes lead all the way to 0xffffcf88, and the return address starts at 0xffffcf88 + 4 which is 0xffffcf8c. The NOP sled then begins at 0xffffcf90, and the shellcode starts at 0xffffcfac. Using this information, we can construct the payload to be the following:
The return address points to 0xffffcf98 which is an address within the boundary of the NOP sled. Therefore, this payload should go past each NOP bytes as it eventually gets to the shellcode.
Seeing as the program successfully executed the shellcode, we can now try this same payload ( with the modification of the return address) on the target machine.
Exploiting the Binary on the Target
After logging into the narnia2 user and running the same payload within gdb we see the following:
Here we can see that the junk bytes end at 0xffffd508 and the EIP register is overwritten at 0xffffd50C. The nop sled then begins at 0xffffd510 and the shellcode starts at 0xffffd52c. Therefore, we can modify the payload to point to 0xffffd518 which is within the bounds of the NOP sled and the shellcode will get executed.
Unfortunately, this payload did not work most likely due to a small shift in the memory address. It is important to note the fact that “Illegal instruction” was outputted instead of “Segmentation fault” which is a strong indicator that the payload is close to successful execution. It is the result of the overwritten EIP register pointing to an address with meaningless assembly code. After tweaking the address a little bit (changing x18xd5xffxff to x48xd5xffxff, we get a shell as narnia3:
Source Code
We can see that this program is vulnerable, as it only expects to receive up to 128 bytes for the buffer, and does not properly check the size of the user’s input.
Narnia 3
Executing the narnia3 binary we see the following:
Attempting to Read Passwords from the Stack Pointer
This means that the contents of the file it is reading from will most likely be in the esp register upon reading. We can verify this by first running the program in gdb and setting a breakpoint at the instruction right before the program terminates.
To determine where the contents of the inputted file will be located, the file a.txt was created (located in /tmp/test6/) whose contents is filled with 300 A’s.
Therefore, when the /etc/narnia_pass/narnia3 file is inputted, we can expect the contents of the file to be around 0xffffd560.
Looking at the output, we can see that the address of the contents of the file matches the expected location of 0xffffd560. The contents of the file are read from right to left in memory (as this is in little endian), and are stored using their respective ascii values in hex. Converting this to ascii reveals that this password is vaequeezee, which matches the password of narnia3. However, attempting this same methodology on /etc/narnia_pass/narnia4 does not work:
Security Behind SUID Debugging
The reason this does not work is due to the security risks involved with allowing a user to execute an SUID binary within a debugger. Essentially, if a user was allowed to execute a binary with permissions of another user, then they could easily modify a program to execute what they would like.
Debuggers have to execute the ptrace (process trace) function call to trace a function (this is how debugging programs work). This function prevents execve system calls from elevating privileges on the system, as the privilege elevations flags are ignored, effectively making the user have the same privileges as he or she did before debugging. The only way to execute an SUID binary with the permissions of the effective user, is to run the program as root.
Binary Analysis
Seeing as reading the narnia4’s password in the memory of the stack pointer was not successful, we can analyze the binary in Ghidra to see how it works and come up with a different methodology for exploitation:
We can see that the binary is providing 32 bytes to two different unidentified buffers defined as local_5c and local_3c. The program checks if an argument is sent. If not it will provide the usage, otherwise it will perform the strcpy function (a function used to copy strings). This is a dangerous function which can result in buffer overflows. Reading the man page of this function and going to the “Bugs” section, the following description can be read:
If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there’s enough space. This may be unnecessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.
Following the strcpy function are two if statements: one for checking if a file exists, and another for checking if we have valid permissions for opening the file. If the file exists and we have permissions for opening the file, then the read and write functions are executed.
Before figuring out how to exploit the binary, we should first understand how it behaves by doing what the program expects:
The program can read the narnia4 password file and copy it to /dev/null. However, due to this being the place in linux used for discarding data, we cannot recover the password.
Exploiting strcpy
Going back to Ghidra, we can see that the /dev/null device is set to the variable local_1c:
If there is a buffer overflow vulnerability we can possibly overwrite this local variable. When inputting many strings followed by the word “test”, we can see that the program returns an error for opening the program, however not all of the A’s that we sent are outputted.
We can try to create a file called AAAAAAAAAAAAtest and see how the binary responds.
Strangely, the binary now spits out all the A’s that we inputted. Recall that we found within Ghidra that 32 bytes are being allocated to two unknown buffers. It is possible that one of the buffers is meant for the name of the input file, while the other buffer is meant for the output. This means that upon creating a long-named directory and inputting the full path of a file located within this directory might successfully overwrite the variable allocated for the /dev/null device. This methodology was carried out as follows:
Note that a directory of 26 S’s was created because /tmp/ is 5 characters and the / at the end of the S directory is one character (6 + 26 = 32 which is the size allocated for the buffer)
Executing the full path of the “test” file within this directory proves to successfully overwrite the null device variable:
It follows that if we create a /tmp directory within the current working directory and create a file that is symbolically linked to narnia4’s password file, we can copy his credentials to wherever we specify.
This error was included to further help in understanding how the binary works
The error is missing a / at the beginning of the tmp directory. This is because /tmp/ is four characters, S is 26 characters, and the trailing / is one character (which completely fills the 32 bytes allocated for the buffer). Therefore, the string after the trailing / of the S directory is what overwrites the variable for the null device. Creating a directory with 27 S’s fixes this problem (note that the choice of S’s was arbitrary, and any sequence of 27 bytes within the /tmp directory would have worked):
The password for the narnia4 user was successfully copied to the /tmp directory under the filename of credentials.
Source Code
We can see from the source code that the program is not checking for the size of the user input before running the strcpy function. The usage of the strcpy function should be avoided as it can result in a buffer overflow vulnerability. By inputting over 32 bytes to the ifile, the ofile variable (initialized to /dev/null) was overwritten.
Narnia 4
As the narnia4 user, we can now running the narnia4 binary. However, when executing the binary, nothing happens:
Binary Analysis
Downloading this binary and opening it up on Ghidra shows the following code:
The program is allocating 256 bytes to some variable and performing some innocuous operation on it inside the while loop. After doing so, the program runs an if statement which uses the dangerous strcpy function (see Narnia 3).
Binary Exploitation
We can attempt to overflow the buffer by sending a large number of bytes in a pattern using pwndbg to determine where the eip offset is:
Therefore, we can input a maximum of 264 bytes before overwriting the eip register. Thus, we can do just as we did in Narnia 2, and create a payload that will fill overwrite the eip register with an address that points to shellcode[9]:
We can see that the NOP sled starts at 0xffffcec0, and the shellcode starts at 0xffffcf24. So the eip register can point to any address within the boundaries of these two address (the address of 0xffffcf08 was arbitrarily chosen; any address within the nop sled would work):
Expectedly, using this same methodology on the target machine results in successful exploitation:
Seeing as the NOP sled begins at 0xffffd440, and the shellcode begins at 0xffffd4a4, any address within the bounds of these two addresses will result in the execution of the shellcode. Using the same payload as the one on the attack box with the modification of the address surprisingly results in a “Segmentation fault”.
This was the same problem that occurred in Narnia 2. Just as we did in Narnia 2, tweaking the return address by slightly incrementing it results in the successful execution of the shellcode:
Source Code
The source code does not agree with what we saw in Ghidra. This is because Ghidra is converting the assembly instructions into c code, and for loops look similar to while loops. We can see from the source code that the program is setting 256 bytes to a buffer, and it is not performing any sort of boundary checks[10] (a detection of the size of the input before it is used).
Narnia 5
After exploiting the narnia4 binary, we now have the necessary permissions to execute the narnia5 binary,.
Binary Analysis
We can start by executing the narnia5 binary to see how it normally behaves:
We can see from the output that we are meant to change the value for the local variable called i. Furthermore, entering an input such as AAAA into the binary, we can see that the input gets reflected.
After fiddling around with the input, we can find that the buffer accepts a total of 63 bytes. We can analyze this binary further with Ghidra.
There is a local_c variable being set to 1 (this is the i) and stays unchanged. We can see that there is an if statement, and within it /bin/sh gets executed as the narnia6 user. However, due to the local_c variable staying unchanged, the if statement is never run. From the code, we can deduce that there is a vulnerability in the following line: snprintf(local_4c,0x40,*(char **)(param_2 + 4));. This may be surprising, as the manual page for snprintf encourages its usage:
BUGS
Because sprintf() and vsprintf() assume an arbitrarily long string, callers must be careful not to overflow the actual space; this is often impossible to assure. Note that the length of the strings produced is locale-de‐pendent and difficult to predict. Use snprintf() and vsnprintf() instead (or asprintf(3) and vasprintf(3)).
Code such as printf(foo); often indicates a bug, since foo may contain a % character. If foo comes from un‐trusted user input, it may contain %n, causing the printf() call to write to memory and creating a security hole.
The security hole within this function lies in the fact that it uses a buffer of a fixed length with no boundary checks[11].
(snprintf) is safe as you long as you provide the correct length for the buffer. snprintf does guarantee that the buffer won’t be overwritten, but it does not guarantee null-termination.
Format String Exploit
Therefore, upon providing a format character such as %x, the function will spit out addresses from the stack.
POC
narnia5@narnia:/narnia$ ./narnia5 %x
Change i’s value from 1 -> 500. No way…let me give you a hint!
buffer : [f7fc5000] (8)
i = 1 (0xffffd5f0)
Despite only providing %x as the input, we can see the buffer contains 8 bytes. The methodology behind a format string attack is finding the address of a local variable that we would like to overwrite (this is given to us as 0xffffd5f0). After discovering the address of the targeted variable, we need to determine where the input gets stored in memory. After finding this information, we can finally overwrite the variable by providing its address followed by the %n format specifier.
Providing the input string of AAAA followed by the %x format specifier, we can immediately see the position of the input in the stack:
Therefore, replacing AAAA with the address of the local i variable followed by the %n format specifier will successfully overwrite the variable.
Observe that the value for the variable is 4 which matches the amount of bytes in the buffer. Therefore, the amount of bytes inside the buffer corresponds to the overwriting value for the variable.
Controlling Variable Value
After verifying the ability for overwriting the local variable, we are left with the task of controlling its value. This can be done by padding the buffer using two different methods:
Method 1
We can use a specifier for the position of the input within the stack.
In the method above, the %1 specifies that the input is in position 1 within the stack. This method, however, is unstable in comparison to the second method. The payload used does not work when using single quotes around the input, rather only double quotes work.
Method 2
This method copies the address for the i variable twice before padding it with the necessary amount of bytes. Inputting the address twice was found to be necessary (after a lot of trial and error).
Source Code
Narnia 6
Binary Analysis
Behavior
Going onto analysing the narnia6 binary, we can see that it expects two arguments:
When providing two normal inputs as arguments to the program, nothing out of the ordinary seems to happen:
narnia6@narnia:/narnia$ ./narnia6 A B
A
Ghidra
Eight bytes are allocated to buffers local_18 and local_20 which most likely correspond to the two arguments that the program expects. The program then performs harmless operations within the while loops. Eventually, the strcpy function is run on the two arguments as can be seen in the following lines:
Within these two lines lie the vulnerability of the program. Eight bytes are being allocated to the local_18 and local_20 variables, which are then getting passed into the strcpy function with any kind of boundary checks being performed beforehand. The potential danger of this code is outlined within the “BUGS” subsection located in the manual page for the strcpy function:
If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there’s enough space. This may be unnecessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.
Additionally, it is important to check the security of the narnia6 binary with the checksec command to find binary security settings:
The NX bit is enabled, and therefore shellcode will be of no use for exploiting this program. However, this binary may be vulnerable to a ret2libc (return-to-lib-c) attack, as well as to Return Oriented Programming (ROP), though the latter is untested.
Ret2libc Attack
This kind of attack is useful when exploiting a binary whose NX bit is enabled, but has a buffer overflow vulnerability. The attack works by replacing pointing the return address of the binary to a subroutine / function that is already present within the binary.[12] Typically, the return address is replaced with an address pointing to the system function located within the stdlib library (as this is a function in c that executes system commands).
POC
To demonstrate the functionality of the system function, we can create a simple c program that runs the ls -la command:
Compiling and running this program, we see that it successfully executes the command:
Seeing as we can ssh into the target machine with credentials that we have received from the previous task, we can compile this same code on the target system to determine the location of the system function in memory (in other words, we do not have to leak the system function’s address). Using this address, we can point the address of the narnia6 binary to the system function and pass a command to it.
Determining System Address
First, we must compile the program in 32 bit format as follows:
After doing so, we can start debugging the program with gdb:
Now with a breakpoint at main, we can see all of the corresponding addresses to each assembly instruction. Most notably, system call is at 0x565555c5,so it follows that we should set a breakpoint there.
We can see that the ls -la command is in the edx register with an address of 0x565555c5. To get the address of the system function, we can simply type the following in the gdb console:
The system is located at 0xf7e4c850.
Exploit
Therefore, we can flood the buffer with 8 bytes before overwriting the eip. Accordingly, the exploit will look like the following:
COMMAND + JUNK + x50xc8xe4xf7 + ‘ ‘ + JUNK
Using this exploit template, we can run the narnia6 binary in gdb and pass this payload::
Note how the command + the junk (namely ‘ls’ + ‘A’ * 6) is equal to eight bytes
We can see that the sh command is trying to execute lsAAAAAAP, which is not a command. However, this can be easily resolved by adding a semicolon to the end of the ls command using one less ‘A’:
The ls command was successfully executed. Running the narnia6 binary outside of gdb and implementing the sh command instead, we get a shell as narnia7:
Source Code
Once again, Ghidra confused the for loop with a while loop. In any case, the operations within these loops were of no interest in regards to exploiting the binary. Note that the stdlib library was included in the binary which allowed us to use the system function.
Narnia 7
After grabbing the credentials of the narnia7 user, we can ssh into the box as the compromised user and access the narnia7 binary.
Binary Analysis
Behavior
When executing it, we are met with a prompt that expects an input as an argument:
Putting a simple input such as ‘A’, we can see that nothing out of the ordinary occurs:
Ghidra
The program exits after printing out the above text. We can use Ghidra to further analyse how the binary functions:
There are four functions of interest within the program: main, vuln, goodfunction, and hackedfunction. The main function takes an argument as input and passes it onto the vuln function. This vuln function allocates 128 bytes to the argument. Going further down this function, we can see that the local_84 variable is being assigned to the address of goodfunction.
Looking at the code for goofunction, we see that the function simply prints out a message and exits. Interestingly, toward the last line of the vuln function, the snprintf function is called and uses local_84 as an argument. Therefore, it can be deduced that this program is most likely vulnerable to a format string exploit. Seeing as hackedfunction calls /bin/sh with setuid privileges, if the local_88 variable is overwritten to point to the address of hackedfunction, then we will receive a shell as the narnia8 user.
Format String Exploit
The methodology to exploiting this binary is the same as the one outlined in Narnia 2. We can construct a payload that will look like the following:
(address to local_84) + %PADDINGx
The padding will correspond to the decimal value of the address for hackedfunction so as to overwrite the value of the local_84 with the appropriate address. Note that the program will convert this decimal value into hexadecimal, and the hackedfunction will therefore be executed.
From executing the binary, we saw that the hacked address is located at 0x8048724. Converting this hexadecimal value to decimal, we see that it is equivalent to 134514468. Furthermore, the binary printed out the value for local_84 at 0xffffd568. Therefore, a string comprised of the address to this variable in little endian format (as this binary is in little endian) followed by a padding of 134514468 will result in the execution of hackedfunction:
Source Code
Narnia 8
After exploiting a total of eight binaries, we are left with the task of exploiting the ninth and final binary: narnia8.
Binary Analysis
We can start by executing the binary to see how it behaves:
Similar to the previous binaries, this program expects an argument. Providing a normal input does not seem to do anything except print that same value back out:
Furthermore, when providing a large input such as 5000 ‘A’s, no segmentation fault occurred.
Ghidra
We can further analyse this binary using Ghidra to understand the inner workings of the program:
There are two interesting functions: main and func. The main function simply gets the argument and passes it into func. Within this function are 2 global variables: local_1c and local_8. Twenty bytes are allocated to the former, while the latter is set to the argument. The local_1c variable has all of its contents set to 0. Within the while loop appears to be a sort of operation that is setting local_1c equivalent to some index within local_8. Just as in the previous binaries, Ghidra may have mistook a for loop for a while loop. It is possible that this segment in the code actually looks like the following:
After performing this operation, the program prints the contents of local_1c. Looking at this for loop, we can see that the vulnerability lies within the fact that 20 bytes are allocated to the local_1c variable, but an argument of greater than 20 bytes can be inputted.
Additionally, running checksec on the binary reveals that the NX bit is also disabled:
Therefore, the return address of func could potentially be overwritten to point to shellcode.
Buffer Overflow
Passing a large input into the argument of the program did not result in a segmentation fault.
Gdb
The program can be analyzed in a dynamic environment using gdb. This will help in further understanding how the binary works. Before providing an input, we must first put a break point toward the end of func right before the program exits:
A breakpoint was then set at the nop operation, and an input of 5 A’s was passed (this amount was chosen arbitrarily):
Local_8 Address Behavior
The stack pointer can now be analyzed:
Note how there are 5 A’s starting at 0xffffd044 followed by 15 0 bytes. The 0’s are a result of the memset function. These 0’s are then followed by the 0xffffd2f9 address. Examining this address reveals that it is pointing to the local_8 buffer:
Interestingly, running the program again but inputting 6 A’s instead of 5 results in a decrement of 1 to the local_8 address:
Furthermore, when inputting more than 20 bytes, the address of local_8 gets overwritten by one byte:
However, when exactly 20 bytes are inputted followed by the address of local_8 and some junk, we are able to flood into other areas of memory:
Payload:
A20 + ADDRESS_TO_LOCAL_8 + ‘A’6
When we passed 6 A’s into the buffer, the address to local_8 was 0xfffd2f8. If we are to input 14 more A’s followed by the address to local_8 (which is four bytes) followed by another 6 A’s, then the resulting address to local_8 would be 0xffffd2f8 - (14+4+6).
Using this address, we can construct the payload as follows:
Now looking at the stack pointer, we see that we have successfully flooded memory past the local_8 address:
Incidentally, the reason why we were only able to overwrite other areas of memory only after including the address of the buffer in the payload, is because of the for loop within the program. When the address to local_8 is overwritten, the for loop is false and data stops getting written to local_1c.
Overwriting func Return Address
It is important to note that in the stack pointer, the return address of func is present shortly after the buffer:
More specifically, it is at 0x080484a7.
We can verify that this is the return address of func by disassembling the main function:
Note that main+23 comes right after the call to func. Therefore, if we overwrite this return address of func to the address of the shellcode (just as we did in Narnia 2 and Narnia 4), then the shellcode will be executed consequently giving a shell as the narnia9 user.
Shellcode
Now on the target machine, we can run the narnia8 binary with an input of 20 A’s and pipe it over to xxd to get the address of local_8:
Here we can see that local_8 is located at 0xffffd7d1. Subtracting this address by 4 bytes (local_8c address) + 4 bytes (junk) + 4 bytes (shellcode address) + 33 bytes (shellcode[13]), we get the local_8 address as 0xffffd7a4:
To calculate the address of the shellcode, we add 20 to the local_8 address to account for 20 A’s + 4 (address of local_8) + 4 (junk) + 4 (address of shellcode):
Therefore, the address of the shellcode is at 0xffffd7c4. Finally, the payload can be passed as the argument:
Source Code
Conclusion
Binaries with setuid permissions must be carefully examined before other users are given execute permissions. Every binary was vulnerable to exploitation using well-known techniques, among them being re2libc, format string exploitation, and shellcode injection. The following remediations will strengthen the security of every tested binary:
Perform boundary checks before passing user input into functions
Almost every binary outlined in this report was vulnerable due to failure of checking boundaries
Sensitive memory addresses were overwritten allowing ret2libc among other attacks
Unnecessary disabling NX bit
The NX bit was unnecessarily disabled for multiple binaries resulting in shellcode injection
Untrusted user input was directly passed to functions
Two out of nine binaries (namely Narnia 5 and Narnia 7) passed unsanitized user input directly to snprintf without boundary checks
SETUID permissions for every binary tested in this report should be removed immediately until the remediations outlined above are observed.
This challenge was about exploiting a binary via a return-to-libc attack (due to the enabled NX bit). The address of printf was provided to faciliate exploitation, however it was only given after passing in user input. This address could not be used for future execution of the binary due to the presence of ASLR. Nevertheless, despite the presence of the enabled NX bit and ASLR, the binary was vulnerable.
scanf() is a function that is widely used in C programs. This binary, which is seemingly secure, made subtle but dangerous programming mistakes that resulted in a security hole through which a user can manipulate memory. Since this binary is dynamically linked, overwriting the GOT entry subsequently forces the program to jump to memory of the attacker’s choice when the manipulated function pointer gets called.