吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7218|回复: 5
收起左侧

[Android 原创] 简单分析CVE-2015-1805漏洞

[复制链接]
penguin_wwy 发表于 2016-5-28 14:26
关于CVE-2015-1805漏洞的前世今生大家可以自行百度,总之就是一个牛逼的漏洞虽然发现的早但是玩脱的故事
吾爱关于这个漏洞的分析好像没有,我就先现个丑,简单分析一下。
参考文章http://bobao.360.cn/learning/detail/2810.html

漏洞介绍:在linux 内核3.16版本之前的fs/pipe.c当中,由于pipe_read和pipe_write没有考虑到拷贝过程中数据没有同步的一些临界情况,造成了堆数组拷贝越界的问题,因此有可能导致系统crash以及系统权限提升,这种漏洞又称之为” I/O vector array overrun”
出问题的函数为pipe_read
[C] 纯文本查看 复制代码
static ssize_t  
pipe_read(struct kiocb *iocb, const struct iovec *_iov,  
       unsigned long nr_segs, loff_t pos)  
{  
    struct file *filp = iocb->ki_filp;  
    struct inode *inode = filp->f_path.dentry->d_inode;  
    struct pipe_inode_info *pipe;  
    int do_wakeup;  
    ssize_t ret;  
    struct iovec *iov = (struct iovec *)_iov;  
    size_t total_len;  
  
    total_len = iov_length(iov, nr_segs);//这是拷贝数据的总长度,正是由于这个值没有能够即时更新所导致的漏洞  
    /* Null read succeeds. */  
    if (unlikely(total_len == 0))  
        return 0;  
  
    do_wakeup = 0;  
    ret = 0;  
    mutex_lock(&inode->i_mutex);  
    pipe = inode->i_pipe;  
    for (;;) {  
        int bufs = pipe->nrbufs;  
        if (bufs) {  
            int curbuf = pipe->curbuf;  
            struct pipe_buffer *buf = pipe->bufs + curbuf;  
            const struct pipe_buf_operations *ops = buf->ops;  
            void *addr;  
            size_t chars = buf->len;  
            int error, atomic;  
  
            if (chars > total_len)  
                chars = total_len;  
  
            error = ops->confirm(pipe, buf);  
            if (error) {  
                if (!ret)  
                    ret = error;  
                break;  
            }  
  
            atomic = !iov_fault_in_pages_write(iov, chars);//在此处判断iov->len是否大于0,且iov->base指向的地址是否可写且处于用户态,之后返回 
redo:  
            addr = ops->map(pipe, buf, atomic);  
            error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);//进行拷贝的关键函数  
            ops->unmap(pipe, buf, addr);  
            if (unlikely(error)) {  
                /* 
                 * Just retry with the slow path if we failed. 
                 */  
                if (atomic) {//当atomic为1,且拷贝中途失败时,进入该分支  
                    atomic = 0;  
                    goto redo;  
                }  
                if (!ret)  
                    ret = error;  
                break;  
            }  
            ret += chars;  
            buf->offset += chars;  
            buf->len -= chars;  
            if (!buf->len) {  
                buf->ops = NULL;  
                ops->release(pipe, buf);  
                curbuf = (curbuf + 1) & (pipe->buffers - 1);  
                pipe->curbuf = curbuf;  
                pipe->nrbufs = --bufs;  
                do_wakeup = 1;  
            }  
            total_len -= chars;//这里更新total_len的值  
            if (!total_len)  
                break;  /* common path: read succeeded */  
        }  
        if (bufs)   /* More to do? */  
            continue;  
        if (!pipe->writers)  
            break;  
        if (!pipe->waiting_writers) {  
            /* syscall merging: Usually we must not sleep 
             * if O_NONBLOCK is set, or if we got some data. 
             * But if a writer sleeps in kernel space, then 
             * we can wait for that data without violating POSIX. 
             */  
            if (ret)  
                break;  
            if (filp->f_flags & O_NONBLOCK) {  
                ret = -EAGAIN;  
                break;  
            }  
        }  
        if (signal_pending(current)) {  
            if (!ret)  
                ret = -ERESTARTSYS;  
            break;  
        }  
        if (do_wakeup) {  
            wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);  
            kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);  
        }  
        pipe_wait(pipe);  
    }  
    mutex_unlock(&inode->i_mutex);  
  
    /* Signal writers asynchronously that there is more room. */  
    if (do_wakeup) {  
        wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);  
        kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);  
    }  
    if (ret > 0)  
        file_accessed(filp);  
    return ret;  
}

可以看到在if(bufs){...}
分支内进行拷贝,而且在分支内当出错后可以以goto的方式跳转。而total_len的更新在分支之外。这就有可能导致一种情况:拷贝中途发生错误,goto跳转,但是由于total_len值并未更新,所以在重新开始拷贝的时候,虽然已经拷贝了大小为n的内容(指针已经向前前进了n),还是会从当前位置向后拷贝total_len大小的数据,导致数组越界,多拷贝了大小为n的内容。
[C] 纯文本查看 复制代码
static int  
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,  
              int atomic)  
{  
    unsigned long copy;  
  
    while (len > 0) {  
        while (!iov->iov_len)  
            iov++;  
        copy = min_t(unsigned long, len, iov->iov_len);  
  
        if (atomic) {  
            if (__copy_to_user_inatomic(iov->iov_base, from, copy))  
                return -EFAULT;  
        } else {  
            if (copy_to_user(iov->iov_base, from, copy))  
                return -EFAULT;  
        }  
        from += copy;  
        len -= copy;  
        iov->iov_base += copy;//指针向前前进已拷贝的大小  
        iov->iov_len -= copy;//将长度减去已拷贝的大小  
    }  
    return 0;  
} 

落实到具体代码,如果atomic=1,则pipe_iov_copy_to_user -> __copy_to_user_inatomic;如果atomic=0,则pipe_iov_copy_to_user -> copy_to_user 。成功拷贝,就会将当前iov数组内元素的ivo_base和ivo_len进行相应的变化。这个变化在函数内,也就是前文所说的分支内进行,与total_len的更新不同步。

下面来构造一个例子

首先构造一个struct iovec iov[5]的数组。如下赋值

ivo[0]->ivo_base = 0x00004000

ivo[0]->ivo_len = 0x100

ivo[1]->ivo_base = 0x00004200

ivo[1]->ivo_len = 0x100

ivo[2]->ivo_base = 0x00004400

ivo[2]->ivo_len = 0x100

ivo[3]->ivo_base = 0x00004600

ivo[3]->ivo_len = 0x100

ivo[4]->ivo_base = 0x00004800

ivo[4]->ivo_len = 0x100

total_len = 0x500

chars假设为0x200

先将ivo[1]的ivo_base设置为不可访问。

以atomic=1状态进入拷贝

首先ivo[0]拷贝成功,

ivo[0]->ivo_len = 0x0

然后ivo[1]拷贝失败进入分支

if (atomic)

goto跳转重新开始拷贝

再将ivo[1]->ivo_base的地址设为可访问,每次成功拷贝0x200,两次分别将ivo[1]、ivo[2]和ivo[3]、ivo[4]的数据拷走

之后

total_len -= chars

这里更新total_len的值 total_len两次0x200,最后会多出0x100,就是因为第一次的错误导致ivo[0]的长度没有减去  

由于第一次的错误导致此处的total_len不为0再次进行拷贝,此次拷贝的内容超过数组范围,产生越界。

只要通过喷堆或者ROP之类的方式,控制越界的内容为我们想要的代码,就可以利用漏洞,进行提权。


免费评分

参与人数 2威望 +1 热心值 +2 收起 理由
Hmily + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
TDY-heaven + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Catshark 发表于 2016-5-28 21:44
支持楼主。

免费评分

参与人数 1热心值 +1 收起 理由
随风飘 + 1 我很赞同!

查看全部评分

qtfreet00 发表于 2016-5-29 13:57
Ericky 发表于 2016-5-30 20:19
Ericky 发表于 2016-5-30 20:20
Ericky 发表于 2016-5-30 20:21

pediy 硬编码的exp都有啦,这个简单分析就先不加了。楼主可以分析研究个没人研究的,例如CVE-2016-1503等
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 13:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表